@mutmutco/cli 3.135.0 → 3.137.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.cjs +956 -460
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -3416,7 +3416,7 @@ var program = new Command();
|
|
|
3416
3416
|
|
|
3417
3417
|
// src/index.ts
|
|
3418
3418
|
var import_promises12 = require("node:fs/promises");
|
|
3419
|
-
var
|
|
3419
|
+
var import_node_fs48 = require("node:fs");
|
|
3420
3420
|
var import_node_child_process19 = require("node:child_process");
|
|
3421
3421
|
|
|
3422
3422
|
// src/cli-shared.ts
|
|
@@ -5502,7 +5502,9 @@ function parseWorktreeOwners(text) {
|
|
|
5502
5502
|
return parsed.entries.filter((e) => {
|
|
5503
5503
|
if (!e || typeof e !== "object") return false;
|
|
5504
5504
|
const entry = e;
|
|
5505
|
-
|
|
5505
|
+
const provenance = entry.provenance;
|
|
5506
|
+
const provenanceValid = provenance === void 0 || typeof provenance === "object" && typeof provenance.creationBaseOid === "string" && /^[0-9a-f]{40,64}$/i.test(provenance.creationBaseOid) && typeof provenance.workerBranch === "string" && provenance.workerBranch.length > 0;
|
|
5507
|
+
return typeof entry.path === "string" && entry.path.length > 0 && typeof entry.branch === "string" && typeof entry.createdAt === "string" && typeof entry.lastSeenAt === "string" && isActor(entry.actor) && provenanceValid;
|
|
5506
5508
|
});
|
|
5507
5509
|
}
|
|
5508
5510
|
function serializeWorktreeOwners(entries) {
|
|
@@ -5847,6 +5849,21 @@ async function sweepDeferredWorktrees(store, deps, removalContext) {
|
|
|
5847
5849
|
continue;
|
|
5848
5850
|
}
|
|
5849
5851
|
const owner = removalContext ? lookupWorktreeOwner(removalContext.primaryRoot, entry.path) : void 0;
|
|
5852
|
+
if (!current && deps.pathExists?.(entry.path) === false) {
|
|
5853
|
+
removed.push(entry.path);
|
|
5854
|
+
if (removalContext) {
|
|
5855
|
+
recordWorktreeRemoval(removalContext.primaryRoot, {
|
|
5856
|
+
action: "removed",
|
|
5857
|
+
command: removalContext.command,
|
|
5858
|
+
target: entry.path,
|
|
5859
|
+
branch: entry.branch,
|
|
5860
|
+
actor: removalContext.actor,
|
|
5861
|
+
owner,
|
|
5862
|
+
reason: "deferred worktree already absent from Git registration and filesystem"
|
|
5863
|
+
});
|
|
5864
|
+
}
|
|
5865
|
+
continue;
|
|
5866
|
+
}
|
|
5850
5867
|
if (removalContext) {
|
|
5851
5868
|
const activeRoot = removalContext.activeWorkspaceRoot ?? resolveActiveWorkspaceRoot();
|
|
5852
5869
|
const activeGuard = decideActiveWorkspaceGuard(entry.path, activeRoot, process.platform, {
|
|
@@ -6400,6 +6417,8 @@ function buildGcPlan(inputs) {
|
|
|
6400
6417
|
const preservedBranches2 = new Set(inputs.preservedBranches ?? []);
|
|
6401
6418
|
const preservedWorktrees = new Set((inputs.worktrees ?? []).filter((w) => w.preserved).map((w) => w.branch));
|
|
6402
6419
|
const mergedIntoBase = new Set((inputs.mergedIntoBase ?? []).map((b) => b.trim()).filter(Boolean));
|
|
6420
|
+
const exactTreeLanded = new Map((inputs.exactTreeLanded ?? []).map((p) => [p.branch.trim(), p]));
|
|
6421
|
+
const exactTreeRefusals = new Map((inputs.exactTreeRefusals ?? []).map((p) => [p.branch.trim(), p.detail]));
|
|
6403
6422
|
const prLookupFailures = new Map((inputs.prLookupFailures ?? []).map((f) => [f.branch.trim(), f.detail]));
|
|
6404
6423
|
const skipped = [];
|
|
6405
6424
|
const branches = [];
|
|
@@ -6426,8 +6445,10 @@ function buildGcPlan(inputs) {
|
|
|
6426
6445
|
skipped.push({ branch, reason: "open-pr" });
|
|
6427
6446
|
continue;
|
|
6428
6447
|
}
|
|
6429
|
-
const
|
|
6430
|
-
|
|
6448
|
+
const exactDelivery = exactTreeLanded.get(branch);
|
|
6449
|
+
const exactRefusal = exactTreeRefusals.get(branch);
|
|
6450
|
+
const state = closedState(prSet) ?? (exactDelivery ? { state: "EXACT_TREE", numbers: [], headOids: [] } : null);
|
|
6451
|
+
if (!state && !exactRefusal) continue;
|
|
6431
6452
|
if (branch === inputs.currentBranch) {
|
|
6432
6453
|
skipped.push({ branch, reason: "current-branch" });
|
|
6433
6454
|
skipTrackingBranches.add(branch);
|
|
@@ -6444,8 +6465,14 @@ function buildGcPlan(inputs) {
|
|
|
6444
6465
|
skipTrackingBranches.add(branch);
|
|
6445
6466
|
continue;
|
|
6446
6467
|
}
|
|
6468
|
+
if (!state) {
|
|
6469
|
+
skipped.push({ branch, reason: "exact-tree-proof-refused", detail: exactRefusal });
|
|
6470
|
+
skipTrackingBranches.add(branch);
|
|
6471
|
+
continue;
|
|
6472
|
+
}
|
|
6447
6473
|
const localHead = branchHeads?.get(branch);
|
|
6448
|
-
const
|
|
6474
|
+
const exactHeadStillMatches = Boolean(exactDelivery && localHead === exactDelivery.headOid);
|
|
6475
|
+
const containedInBase = mergedIntoBase.has(branch) || exactHeadStillMatches;
|
|
6449
6476
|
if (!containedInBase && (worktree2?.unpushed || branchHeads && (!localHead || !state.headOids.length || !state.headOids.includes(localHead)))) {
|
|
6450
6477
|
const detail = worktree2?.unpushed ? worktree2.path : localHead && state.headOids.length ? `${localHead} != ${state.headOids.join("|")}` : "local branch head could not be verified against the PR head";
|
|
6451
6478
|
skipped.push({ branch, reason: "unpushed-branch", detail });
|
|
@@ -6458,7 +6485,8 @@ function buildGcPlan(inputs) {
|
|
|
6458
6485
|
prNumbers: state.numbers,
|
|
6459
6486
|
worktreePath: worktree2?.path,
|
|
6460
6487
|
...state.headOids.length ? { reviewedHeadOids: state.headOids } : {},
|
|
6461
|
-
...containedInBase ? { containedInBase: true, ...localHead ? { plannedHeadOid: localHead } : {} } : {}
|
|
6488
|
+
...containedInBase ? { containedInBase: true, ...localHead ? { plannedHeadOid: localHead } : {} } : {},
|
|
6489
|
+
...exactHeadStillMatches ? { deliveredBy: exactDelivery.candidateOid } : {}
|
|
6462
6490
|
});
|
|
6463
6491
|
}
|
|
6464
6492
|
const trackingRefs = [...new Set(inputs.staleTrackingRefs ?? [])].map((ref) => {
|
|
@@ -7060,7 +7088,8 @@ function formatGcPlan(plan, apply, auditClone) {
|
|
|
7060
7088
|
for (const b of plan.branches) {
|
|
7061
7089
|
const prs = b.prNumbers.length ? ` #${b.prNumbers.join(",#")}` : "";
|
|
7062
7090
|
const wt = b.worktreePath ? ` (worktree: ${b.worktreePath})` : "";
|
|
7063
|
-
|
|
7091
|
+
const authority = b.deliveredBy ? `${b.prState}; deliveredBy ${b.deliveredBy}` : `${b.prState}${prs}`;
|
|
7092
|
+
lines.push(` - ${b.branch} (${authority})${wt}`);
|
|
7064
7093
|
}
|
|
7065
7094
|
}
|
|
7066
7095
|
const remoteBranches = plan.branches.filter((b) => (b.reviewedHeadOids?.length ?? 0) > 0);
|
|
@@ -7869,7 +7898,7 @@ function commandLadderHint() {
|
|
|
7869
7898
|
}
|
|
7870
7899
|
|
|
7871
7900
|
// src/index.ts
|
|
7872
|
-
var
|
|
7901
|
+
var import_node_path46 = require("node:path");
|
|
7873
7902
|
|
|
7874
7903
|
// src/merge-ci-policy.ts
|
|
7875
7904
|
function resolveMergeCiPolicy(input) {
|
|
@@ -8384,6 +8413,12 @@ function loadBootstrapSeeds(manifestJson) {
|
|
|
8384
8413
|
if (s.ownership !== "org" && s.ownership !== "repo") {
|
|
8385
8414
|
throw new Error(`invalid seed ownership '${s.ownership}' for ${s.target} (must be 'org' or 'repo')`);
|
|
8386
8415
|
}
|
|
8416
|
+
if (s.managedBlock) {
|
|
8417
|
+
const block = s.managedBlock;
|
|
8418
|
+
if (!block.source || !block.begin || !block.end || block.begin === block.end || /[\r\n]/.test(block.begin + block.end)) {
|
|
8419
|
+
throw new Error(`invalid managedBlock for ${s.target} (needs source and distinct single-line begin/end markers)`);
|
|
8420
|
+
}
|
|
8421
|
+
}
|
|
8387
8422
|
}
|
|
8388
8423
|
return {
|
|
8389
8424
|
seeds,
|
|
@@ -8399,6 +8434,68 @@ function missingPlaceholders(rendered) {
|
|
|
8399
8434
|
for (const m of rendered.matchAll(PLACEHOLDER_RE)) out.add(m[1]);
|
|
8400
8435
|
return [...out];
|
|
8401
8436
|
}
|
|
8437
|
+
function isPropagatableSeed(seed) {
|
|
8438
|
+
return seed.ownership === "org" && seed.source === "self" || seed.managedBlock != null;
|
|
8439
|
+
}
|
|
8440
|
+
function sourceLines(content) {
|
|
8441
|
+
const lines = [];
|
|
8442
|
+
const newline = /\r\n|\n|\r/g;
|
|
8443
|
+
let start = 0;
|
|
8444
|
+
for (const match of content.matchAll(newline)) {
|
|
8445
|
+
const index = match.index;
|
|
8446
|
+
lines.push({ start, textEnd: index, end: index + match[0].length, text: content.slice(start, index) });
|
|
8447
|
+
start = index + match[0].length;
|
|
8448
|
+
}
|
|
8449
|
+
lines.push({ start, textEnd: content.length, end: content.length, text: content.slice(start) });
|
|
8450
|
+
return lines;
|
|
8451
|
+
}
|
|
8452
|
+
function normalizeEol(content) {
|
|
8453
|
+
return content.replace(/\r\n|\r|\n/g, "\n");
|
|
8454
|
+
}
|
|
8455
|
+
function upsertManagedSeedBlock(current, desired, begin, end) {
|
|
8456
|
+
const desiredLines = sourceLines(desired);
|
|
8457
|
+
const desiredBegins = desiredLines.filter((line) => line.text === begin);
|
|
8458
|
+
const desiredEnds = desiredLines.filter((line) => line.text === end);
|
|
8459
|
+
if (desiredBegins.length !== 1 || desiredEnds.length !== 1 || desiredEnds[0].start <= desiredBegins[0].start) {
|
|
8460
|
+
return { ok: false, reason: "managed block source must contain exactly one well-ordered marker pair" };
|
|
8461
|
+
}
|
|
8462
|
+
const desiredBegin = desiredBegins[0];
|
|
8463
|
+
const desiredEnd = desiredEnds[0];
|
|
8464
|
+
if (desired.slice(0, desiredBegin.start).trim() || desired.slice(desiredEnd.end).trim()) {
|
|
8465
|
+
return { ok: false, reason: "managed block source must contain only the bounded block" };
|
|
8466
|
+
}
|
|
8467
|
+
const canonical = desired.slice(desiredBegin.start, desiredEnd.textEnd);
|
|
8468
|
+
const currentLines = sourceLines(current);
|
|
8469
|
+
const begins = currentLines.filter((line) => line.text === begin);
|
|
8470
|
+
const ends = currentLines.filter((line) => line.text === end);
|
|
8471
|
+
if (begins.length === 0 && ends.length === 0) {
|
|
8472
|
+
const eol2 = current.match(/\r\n|\n|\r/)?.[0] ?? "\n";
|
|
8473
|
+
const block2 = normalizeEol(canonical).replace(/\n/g, eol2);
|
|
8474
|
+
if (current.length === 0) return { ok: true, content: `${block2}${eol2}`, changed: true, action: "insert" };
|
|
8475
|
+
const separator = /(?:\r\n|\n|\r)$/.test(current) ? eol2 : `${eol2}${eol2}`;
|
|
8476
|
+
return { ok: true, content: `${current}${separator}${block2}${eol2}`, changed: true, action: "insert" };
|
|
8477
|
+
}
|
|
8478
|
+
if (begins.length !== 1 || ends.length !== 1) {
|
|
8479
|
+
return { ok: false, reason: `malformed managed block markers (begin=${begins.length}, end=${ends.length})` };
|
|
8480
|
+
}
|
|
8481
|
+
const currentBegin = begins[0];
|
|
8482
|
+
const currentEnd = ends[0];
|
|
8483
|
+
if (currentEnd.start <= currentBegin.start) {
|
|
8484
|
+
return { ok: false, reason: "malformed managed block markers (end precedes begin)" };
|
|
8485
|
+
}
|
|
8486
|
+
const existing = current.slice(currentBegin.start, currentEnd.textEnd);
|
|
8487
|
+
if (normalizeEol(existing) === normalizeEol(canonical)) {
|
|
8488
|
+
return { ok: true, content: current, changed: false, action: "current" };
|
|
8489
|
+
}
|
|
8490
|
+
const eol = current.match(/\r\n|\n|\r/)?.[0] ?? "\n";
|
|
8491
|
+
const block = normalizeEol(canonical).replace(/\n/g, eol);
|
|
8492
|
+
return {
|
|
8493
|
+
ok: true,
|
|
8494
|
+
content: `${current.slice(0, currentBegin.start)}${block}${current.slice(currentEnd.textEnd)}`,
|
|
8495
|
+
changed: true,
|
|
8496
|
+
action: "replace"
|
|
8497
|
+
};
|
|
8498
|
+
}
|
|
8402
8499
|
async function resolveHubSeedSource(execGit, defaultBranch = "development") {
|
|
8403
8500
|
let status;
|
|
8404
8501
|
try {
|
|
@@ -9463,6 +9560,9 @@ function seedMatchesProjectType(seed, projectType) {
|
|
|
9463
9560
|
return projectType != null && seed.projectTypes.includes(projectType);
|
|
9464
9561
|
}
|
|
9465
9562
|
function planSeedAction(seed, exists) {
|
|
9563
|
+
if (seed.managedBlock) {
|
|
9564
|
+
return exists ? { target: seed.target, action: "update", ownership: "repo", reason: "repo-owned file; Hub-managed block reconciled in place" } : { target: seed.target, action: "create", ownership: "repo", reason: "repo-owned, missing; full template created with Hub-managed block" };
|
|
9565
|
+
}
|
|
9466
9566
|
if (seed.source === "managed-block") {
|
|
9467
9567
|
return exists ? { target: seed.target, action: "update", ownership: "org", reason: "org-managed block merged in-place (repo-owned lines preserved)" } : { target: seed.target, action: "create", ownership: "org", reason: "org-managed block; .gitignore absent, created" };
|
|
9468
9568
|
}
|
|
@@ -9521,6 +9621,18 @@ function resolveSeedContent(seed, vars, readFile9) {
|
|
|
9521
9621
|
}
|
|
9522
9622
|
return null;
|
|
9523
9623
|
}
|
|
9624
|
+
function resolveSeedWriteContent(seed, vars, readFile9, remoteContent) {
|
|
9625
|
+
if (!seed.managedBlock) {
|
|
9626
|
+
return { ok: true, content: resolveSeedContent(seed, vars, readFile9), managed: seed.source === "managed-block" };
|
|
9627
|
+
}
|
|
9628
|
+
const base = remoteContent ?? resolveSeedContent(seed, vars, readFile9);
|
|
9629
|
+
if (base == null) return { ok: true, content: null, managed: true };
|
|
9630
|
+
const blockSeed = { ...seed, source: seed.managedBlock.source, managedBlock: void 0 };
|
|
9631
|
+
const desired = resolveSeedContent(blockSeed, vars, readFile9);
|
|
9632
|
+
if (desired == null) return { ok: true, content: null, managed: true };
|
|
9633
|
+
const result = upsertManagedSeedBlock(base, desired, seed.managedBlock.begin, seed.managedBlock.end);
|
|
9634
|
+
return result.ok ? { ok: true, content: result.content, managed: true } : { ok: false, reason: result.reason, managed: true };
|
|
9635
|
+
}
|
|
9524
9636
|
function buildRegisterPayload(repo, cls, vars, options = {}) {
|
|
9525
9637
|
const parsedRepo = parseOwnerRepo(repo);
|
|
9526
9638
|
const slug = parsedRepo.slug;
|
|
@@ -9752,9 +9864,9 @@ function parseVerifyBroker(stdout) {
|
|
|
9752
9864
|
}
|
|
9753
9865
|
|
|
9754
9866
|
// src/train-apply.ts
|
|
9755
|
-
var
|
|
9867
|
+
var import_node_fs23 = require("node:fs");
|
|
9756
9868
|
var import_promises4 = require("node:fs/promises");
|
|
9757
|
-
var
|
|
9869
|
+
var import_node_path23 = require("node:path");
|
|
9758
9870
|
|
|
9759
9871
|
// src/plugin-guard-io.ts
|
|
9760
9872
|
var import_node_fs19 = require("node:fs");
|
|
@@ -11186,9 +11298,9 @@ function renderAccessReport(report) {
|
|
|
11186
11298
|
}
|
|
11187
11299
|
|
|
11188
11300
|
// src/cli-doctor-shared.ts
|
|
11189
|
-
var import_node_fs20 = require("node:fs");
|
|
11190
|
-
var import_node_path21 = require("node:path");
|
|
11191
11301
|
var import_node_fs21 = require("node:fs");
|
|
11302
|
+
var import_node_path22 = require("node:path");
|
|
11303
|
+
var import_node_fs22 = require("node:fs");
|
|
11192
11304
|
|
|
11193
11305
|
// ../infra/registry-endpoints.mjs
|
|
11194
11306
|
var PROJECTS_LIST_PATH = "/projects/list";
|
|
@@ -13630,6 +13742,142 @@ async function fileReport(deps, req) {
|
|
|
13630
13742
|
return { ok: true, body };
|
|
13631
13743
|
}
|
|
13632
13744
|
|
|
13745
|
+
// src/worktree-delivery-proof.ts
|
|
13746
|
+
var import_node_fs20 = require("node:fs");
|
|
13747
|
+
var import_node_os9 = require("node:os");
|
|
13748
|
+
var import_node_path21 = require("node:path");
|
|
13749
|
+
var OID_RE = /^[0-9a-f]{40,64}$/i;
|
|
13750
|
+
var EXACT_TREE_CANDIDATE_LIMIT = 32;
|
|
13751
|
+
async function repointDeliveredWorktreeTransactionally(input) {
|
|
13752
|
+
const symbolic = (await input.git(["symbolic-ref", "--quiet", "--short", "HEAD"]).catch(() => "")).trim();
|
|
13753
|
+
if (symbolic !== input.branch) return { action: "refuse", reason: "wrong-branch", detail: symbolic || "detached HEAD" };
|
|
13754
|
+
const current = (await input.git(["rev-parse", "--verify", `refs/heads/${input.branch}`]).catch(() => "")).trim();
|
|
13755
|
+
if (current !== input.expectedWorkerOid) {
|
|
13756
|
+
return { action: "refuse", reason: "worker-moved", detail: `${current || "unreadable"} != ${input.expectedWorkerOid}` };
|
|
13757
|
+
}
|
|
13758
|
+
const status = await input.git(["status", "--porcelain"]).catch(() => "unreadable");
|
|
13759
|
+
if (status.trim()) return { action: "refuse", reason: "dirty-worktree", detail: "tracked or untracked files present" };
|
|
13760
|
+
try {
|
|
13761
|
+
await input.git(["checkout", "-B", input.branch, input.landedTipOid]);
|
|
13762
|
+
return { action: "repointed" };
|
|
13763
|
+
} catch (error) {
|
|
13764
|
+
return { action: "refuse", reason: "checkout-failed", detail: error instanceof Error ? error.message : String(error) };
|
|
13765
|
+
}
|
|
13766
|
+
}
|
|
13767
|
+
function supportsExactTreeProofGit(versionOutput) {
|
|
13768
|
+
const match = /\bgit version (\d+)\.(\d+)(?:\.\d+)?/i.exec(versionOutput.trim());
|
|
13769
|
+
if (!match) return false;
|
|
13770
|
+
const major = Number(match[1]);
|
|
13771
|
+
const minor = Number(match[2]);
|
|
13772
|
+
return major > 2 || major === 2 && minor >= 32;
|
|
13773
|
+
}
|
|
13774
|
+
function describeExactTreeRefusal(verdict) {
|
|
13775
|
+
return `${verdict.reason}${verdict.detail ? `: ${verdict.detail}` : ""}`;
|
|
13776
|
+
}
|
|
13777
|
+
function sameChangedPathSet(a, b) {
|
|
13778
|
+
const sorted = (value) => value.split("\0").filter(Boolean).sort().join("\0");
|
|
13779
|
+
return sorted(a) === sorted(b);
|
|
13780
|
+
}
|
|
13781
|
+
async function proveExactTreeDelivery(input, deps = {}) {
|
|
13782
|
+
const base = input.creationBaseOid?.trim();
|
|
13783
|
+
if (!base || !OID_RE.test(base)) return { action: "refuse", reason: "missing-provenance" };
|
|
13784
|
+
if (!input.recordedWorkerBranch || input.recordedWorkerBranch !== input.branch) {
|
|
13785
|
+
return { action: "refuse", reason: "worker-identity-mismatch" };
|
|
13786
|
+
}
|
|
13787
|
+
if (!OID_RE.test(input.workerOid)) return { action: "refuse", reason: "proof-failed", detail: "worker OID is invalid" };
|
|
13788
|
+
const run = deps.git ?? (async (args, env) => (await execFileP2("git", ["-C", input.repoRoot, ...args], {
|
|
13789
|
+
timeout: GIT_TIMEOUT_MS,
|
|
13790
|
+
...env ? { env: { ...process.env, ...env } } : {}
|
|
13791
|
+
})).stdout);
|
|
13792
|
+
const isAncestor = (ancestor, descendant) => run(["merge-base", "--is-ancestor", ancestor, descendant]).then(() => true).catch(() => false);
|
|
13793
|
+
let version;
|
|
13794
|
+
try {
|
|
13795
|
+
version = await run(["version"]);
|
|
13796
|
+
} catch {
|
|
13797
|
+
return { action: "refuse", reason: "proof-failed", detail: "Git capability probe failed" };
|
|
13798
|
+
}
|
|
13799
|
+
if (!supportsExactTreeProofGit(version)) {
|
|
13800
|
+
return { action: "refuse", reason: "unsupported-git", detail: `requires Git >=2.32; found ${version.trim() || "unparseable version"}` };
|
|
13801
|
+
}
|
|
13802
|
+
try {
|
|
13803
|
+
const [baseType, workerType] = await Promise.all([
|
|
13804
|
+
run(["cat-file", "-t", base]).then((v) => v.trim()).catch(() => ""),
|
|
13805
|
+
run(["cat-file", "-t", input.workerOid]).then((v) => v.trim()).catch(() => "")
|
|
13806
|
+
]);
|
|
13807
|
+
if (baseType !== "commit" || workerType !== "commit" || !await isAncestor(base, input.workerOid)) {
|
|
13808
|
+
return { action: "refuse", reason: "unverified-base" };
|
|
13809
|
+
}
|
|
13810
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
13811
|
+
for (const tip of [...new Set(input.landedTips.map((v) => v.trim()).filter(Boolean))]) {
|
|
13812
|
+
if (!OID_RE.test(tip) || !await isAncestor(base, tip)) continue;
|
|
13813
|
+
const listed = await run([
|
|
13814
|
+
"rev-list",
|
|
13815
|
+
"--first-parent",
|
|
13816
|
+
"--parents",
|
|
13817
|
+
`--max-count=${EXACT_TREE_CANDIDATE_LIMIT + 1}`,
|
|
13818
|
+
`${base}..${tip}`
|
|
13819
|
+
]);
|
|
13820
|
+
const rows = listed.split(/\r?\n/).map((v) => v.trim()).filter(Boolean);
|
|
13821
|
+
if (rows.length > EXACT_TREE_CANDIDATE_LIMIT) {
|
|
13822
|
+
return {
|
|
13823
|
+
action: "refuse",
|
|
13824
|
+
reason: "candidate-limit-exceeded",
|
|
13825
|
+
detail: `more than ${EXACT_TREE_CANDIDATE_LIMIT} first-parent candidates since stored base`
|
|
13826
|
+
};
|
|
13827
|
+
}
|
|
13828
|
+
for (const row of rows) {
|
|
13829
|
+
const [oid, parent] = row.split(/\s+/);
|
|
13830
|
+
if (oid && parent && OID_RE.test(oid) && OID_RE.test(parent)) candidates.set(oid, parent);
|
|
13831
|
+
}
|
|
13832
|
+
if (candidates.size > EXACT_TREE_CANDIDATE_LIMIT) {
|
|
13833
|
+
return {
|
|
13834
|
+
action: "refuse",
|
|
13835
|
+
reason: "candidate-limit-exceeded",
|
|
13836
|
+
detail: `more than ${EXACT_TREE_CANDIDATE_LIMIT} unique first-parent candidates across landed tips`
|
|
13837
|
+
};
|
|
13838
|
+
}
|
|
13839
|
+
}
|
|
13840
|
+
if (!candidates.size) return { action: "refuse", reason: "no-candidate" };
|
|
13841
|
+
const workerPaths = await run(["diff", "--name-only", "-z", "--no-renames", base, input.workerOid]);
|
|
13842
|
+
const pathMatches = [];
|
|
13843
|
+
for (const [candidate, parent] of candidates) {
|
|
13844
|
+
const candidatePaths = await run(["diff", "--name-only", "-z", "--no-renames", parent, candidate]);
|
|
13845
|
+
if (sameChangedPathSet(workerPaths, candidatePaths)) pathMatches.push([candidate, parent]);
|
|
13846
|
+
}
|
|
13847
|
+
if (!pathMatches.length) return { action: "refuse", reason: "tree-mismatch" };
|
|
13848
|
+
const makeTempDir = deps.makeTempDir ?? (() => (0, import_node_fs20.mkdtempSync)((0, import_node_path21.join)((0, import_node_os9.tmpdir)(), "mmi-tree-proof-")));
|
|
13849
|
+
const writeTextFile = deps.writeTextFile ?? ((path2, text) => (0, import_node_fs20.writeFileSync)(path2, text, "utf8"));
|
|
13850
|
+
const removeTempDir = deps.removeTempDir ?? ((path2) => (0, import_node_fs20.rmSync)(path2, { recursive: true, force: true }));
|
|
13851
|
+
const root = makeTempDir();
|
|
13852
|
+
try {
|
|
13853
|
+
const synthesize = async (name, from, to) => {
|
|
13854
|
+
const index = (0, import_node_path21.join)(root, `${name}.index`);
|
|
13855
|
+
const patch = (0, import_node_path21.join)(root, `${name}.patch`);
|
|
13856
|
+
const env = { GIT_INDEX_FILE: index };
|
|
13857
|
+
await run(["read-tree", base], env);
|
|
13858
|
+
const delta = await run(["diff-tree", "-p", "--binary", "--full-index", "--no-renames", "--no-ext-diff", from, to]);
|
|
13859
|
+
writeTextFile(patch, delta);
|
|
13860
|
+
if (delta.length) await run(["apply", "--cached", "--binary", "--3way", patch], env);
|
|
13861
|
+
return (await run(["write-tree"], env)).trim();
|
|
13862
|
+
};
|
|
13863
|
+
const workerTreeOid = await synthesize("worker", base, input.workerOid);
|
|
13864
|
+
const matches = [];
|
|
13865
|
+
let candidateNumber = 0;
|
|
13866
|
+
for (const [candidate, parent] of pathMatches) {
|
|
13867
|
+
const tree = await synthesize(`candidate-${candidateNumber++}`, parent, candidate).catch(() => void 0);
|
|
13868
|
+
if (tree === workerTreeOid) matches.push(candidate);
|
|
13869
|
+
}
|
|
13870
|
+
if (matches.length === 1) return { action: "settle", workerTreeOid, candidateOid: matches[0] };
|
|
13871
|
+
if (matches.length > 1) return { action: "refuse", reason: "ambiguous-candidate" };
|
|
13872
|
+
return { action: "refuse", reason: "tree-mismatch" };
|
|
13873
|
+
} finally {
|
|
13874
|
+
removeTempDir(root);
|
|
13875
|
+
}
|
|
13876
|
+
} catch (error) {
|
|
13877
|
+
return { action: "refuse", reason: "proof-failed", detail: error instanceof Error ? error.message : String(error) };
|
|
13878
|
+
}
|
|
13879
|
+
}
|
|
13880
|
+
|
|
13633
13881
|
// src/cli-doctor-shared.ts
|
|
13634
13882
|
var GC_GH_TIMEOUT_MS = 2e4;
|
|
13635
13883
|
var RUN_LIST_TIMEOUT_MS = 2e4;
|
|
@@ -13732,7 +13980,7 @@ async function localBranchHeads() {
|
|
|
13732
13980
|
}
|
|
13733
13981
|
async function currentRepoWorktreeGitRoot(repoRoot2) {
|
|
13734
13982
|
const gitCommonDir = (await execFileP2("git", ["rev-parse", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim();
|
|
13735
|
-
return gitCommonDir ? (0,
|
|
13983
|
+
return gitCommonDir ? (0, import_node_path22.resolve)(repoRoot2, gitCommonDir, "worktrees") : "";
|
|
13736
13984
|
}
|
|
13737
13985
|
async function worktreeBranches() {
|
|
13738
13986
|
const { stdout } = await execFileP2("git", ["worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS });
|
|
@@ -13752,18 +14000,18 @@ function resolveGitdirForWorktreeFile(worktreePath, content) {
|
|
|
13752
14000
|
const match = /^gitdir:\s*(.+)\s*$/im.exec(content);
|
|
13753
14001
|
if (!match?.[1]) return void 0;
|
|
13754
14002
|
const raw = match[1].trim();
|
|
13755
|
-
return (0,
|
|
14003
|
+
return (0, import_node_path22.isAbsolute)(raw) ? raw : (0, import_node_path22.resolve)(worktreePath, raw);
|
|
13756
14004
|
}
|
|
13757
14005
|
function metadataOwnsMissingWorktreeDir(worktreePath, worktreeGitRoot) {
|
|
13758
14006
|
if (!worktreeGitRoot) return false;
|
|
13759
14007
|
try {
|
|
13760
|
-
const entries = (0,
|
|
14008
|
+
const entries = (0, import_node_fs22.readdirSync)(worktreeGitRoot, { withFileTypes: true });
|
|
13761
14009
|
for (const ent of entries) {
|
|
13762
14010
|
if (!ent.isDirectory()) continue;
|
|
13763
14011
|
try {
|
|
13764
|
-
const gitdirPath = (0,
|
|
13765
|
-
const resolvedGitdir = (0,
|
|
13766
|
-
if (sameWorktreeMetadataPath((0,
|
|
14012
|
+
const gitdirPath = (0, import_node_fs21.readFileSync)((0, import_node_path22.join)(worktreeGitRoot, ent.name, "gitdir"), "utf8").trim();
|
|
14013
|
+
const resolvedGitdir = (0, import_node_path22.isAbsolute)(gitdirPath) ? gitdirPath : (0, import_node_path22.resolve)(worktreeGitRoot, ent.name, gitdirPath);
|
|
14014
|
+
if (sameWorktreeMetadataPath((0, import_node_path22.dirname)(resolvedGitdir), worktreePath)) return true;
|
|
13767
14015
|
} catch {
|
|
13768
14016
|
}
|
|
13769
14017
|
}
|
|
@@ -13773,7 +14021,7 @@ function metadataOwnsMissingWorktreeDir(worktreePath, worktreeGitRoot) {
|
|
|
13773
14021
|
}
|
|
13774
14022
|
function pathExistsKnown(path2) {
|
|
13775
14023
|
try {
|
|
13776
|
-
(0,
|
|
14024
|
+
(0, import_node_fs22.statSync)(path2);
|
|
13777
14025
|
return true;
|
|
13778
14026
|
} catch (e) {
|
|
13779
14027
|
const code = typeof e === "object" && e && "code" in e ? String(e.code ?? "") : "";
|
|
@@ -13782,10 +14030,10 @@ function pathExistsKnown(path2) {
|
|
|
13782
14030
|
}
|
|
13783
14031
|
}
|
|
13784
14032
|
function inspectSiblingWorktreeDir(path2, worktreeGitRoot) {
|
|
13785
|
-
const gitPath = (0,
|
|
14033
|
+
const gitPath = (0, import_node_path22.join)(path2, ".git");
|
|
13786
14034
|
let st;
|
|
13787
14035
|
try {
|
|
13788
|
-
st = (0,
|
|
14036
|
+
st = (0, import_node_fs22.lstatSync)(gitPath);
|
|
13789
14037
|
} catch (e) {
|
|
13790
14038
|
const code = typeof e === "object" && e && "code" in e ? String(e.code ?? "") : "";
|
|
13791
14039
|
if (code === "ENOENT" || code === "ENOTDIR") {
|
|
@@ -13802,7 +14050,7 @@ function inspectSiblingWorktreeDir(path2, worktreeGitRoot) {
|
|
|
13802
14050
|
if (st.isDirectory()) return { path: path2, gitType: "dir" };
|
|
13803
14051
|
if (!st.isFile()) return { path: path2, gitType: "other" };
|
|
13804
14052
|
try {
|
|
13805
|
-
const gitFileContent = (0,
|
|
14053
|
+
const gitFileContent = (0, import_node_fs21.readFileSync)(gitPath, "utf8");
|
|
13806
14054
|
const gitdir = resolveGitdirForWorktreeFile(path2, gitFileContent);
|
|
13807
14055
|
const gitDirExists = gitdir ? pathExistsKnown(gitdir) : false;
|
|
13808
14056
|
return {
|
|
@@ -13819,7 +14067,7 @@ function inspectSiblingWorktreeDir(path2, worktreeGitRoot) {
|
|
|
13819
14067
|
}
|
|
13820
14068
|
function inspectDeadWorktreeDirContent(path2) {
|
|
13821
14069
|
try {
|
|
13822
|
-
return { entries: (0,
|
|
14070
|
+
return { entries: (0, import_node_fs22.readdirSync)(path2) };
|
|
13823
14071
|
} catch (e) {
|
|
13824
14072
|
const code = typeof e === "object" && e && "code" in e ? String(e.code ?? "") : "";
|
|
13825
14073
|
return { error: code ? `unable to inspect directory contents (${code})` : "unable to inspect directory contents" };
|
|
@@ -13836,11 +14084,11 @@ async function preservedBranches() {
|
|
|
13836
14084
|
async function siblingWorktreeDirs(explicitRoot) {
|
|
13837
14085
|
const repoRoot2 = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim() || process.cwd();
|
|
13838
14086
|
const worktreeGitRoot = await currentRepoWorktreeGitRoot(repoRoot2);
|
|
13839
|
-
const primaryRepoRoot = worktreeGitRoot ? (0,
|
|
14087
|
+
const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path22.dirname)((0, import_node_path22.dirname)(worktreeGitRoot)) : repoRoot2;
|
|
13840
14088
|
try {
|
|
13841
14089
|
const dirs = explicitRoot ? listDirsIn(resolveExplicitScanRoot(explicitRoot, primaryRepoRoot)) : worktreeScanDirs(siblingMmiWorktreesRoot(primaryRepoRoot), primaryRepoRoot, listDirsIn, isRepoCheckoutDir);
|
|
13842
14090
|
const agentDirs = listDirsIn(agentWorktreesRoot(primaryRepoRoot));
|
|
13843
|
-
const repoLocalWorktrees = listDirsIn((0,
|
|
14091
|
+
const repoLocalWorktrees = listDirsIn((0, import_node_path22.join)(primaryRepoRoot, ".worktrees"));
|
|
13844
14092
|
return [...dirs, ...agentDirs, ...repoLocalWorktrees].map((dir) => inspectSiblingWorktreeDir(dir, worktreeGitRoot)).filter((entry) => Boolean(entry));
|
|
13845
14093
|
} catch {
|
|
13846
14094
|
return [];
|
|
@@ -13848,18 +14096,18 @@ async function siblingWorktreeDirs(explicitRoot) {
|
|
|
13848
14096
|
}
|
|
13849
14097
|
function listDirsIn(dir) {
|
|
13850
14098
|
try {
|
|
13851
|
-
return (0,
|
|
14099
|
+
return (0, import_node_fs22.readdirSync)(dir, { withFileTypes: true }).filter((ent) => ent.isDirectory()).map((ent) => (0, import_node_path22.join)(dir, ent.name));
|
|
13852
14100
|
} catch {
|
|
13853
14101
|
return [];
|
|
13854
14102
|
}
|
|
13855
14103
|
}
|
|
13856
14104
|
function isRepoCheckoutDir(dir) {
|
|
13857
|
-
return (0,
|
|
14105
|
+
return (0, import_node_fs22.existsSync)((0, import_node_path22.join)(dir, ".git"));
|
|
13858
14106
|
}
|
|
13859
14107
|
function resolveExplicitScanRoot(explicitRoot, repoRoot2) {
|
|
13860
14108
|
let rootDirs;
|
|
13861
14109
|
try {
|
|
13862
|
-
rootDirs = (0,
|
|
14110
|
+
rootDirs = (0, import_node_fs22.readdirSync)(explicitRoot, { withFileTypes: true }).filter((ent) => ent.isDirectory()).map((ent) => ent.name);
|
|
13863
14111
|
} catch {
|
|
13864
14112
|
return explicitRoot;
|
|
13865
14113
|
}
|
|
@@ -13890,6 +14138,38 @@ async function gcPlan(remote, limit, opts = {}) {
|
|
|
13890
14138
|
])].filter((b) => !isProtectedBranch(b)),
|
|
13891
14139
|
limit
|
|
13892
14140
|
);
|
|
14141
|
+
const primaryRoot = await primaryCheckoutRootOf(gitOut);
|
|
14142
|
+
const owners = primaryRoot ? readWorktreeOwners(primaryRoot) : [];
|
|
14143
|
+
const mergedSet = new Set(mergedIntoBase);
|
|
14144
|
+
const preservedSet = new Set(preserved);
|
|
14145
|
+
const failedLookups = new Set(failures.map((failure) => failure.branch));
|
|
14146
|
+
const openBranches = new Set(prs.filter((pr2) => pr2.state === "OPEN").map((pr2) => pr2.headRefName));
|
|
14147
|
+
const proofWorktrees = worktrees.filter((wt) => !wt.dirty && wt.branch !== current.trim() && !isProtectedBranch(wt.branch) && !preservedSet.has(wt.branch) && !mergedSet.has(wt.branch) && !failedLookups.has(wt.branch) && !openBranches.has(wt.branch));
|
|
14148
|
+
const trainTips = [];
|
|
14149
|
+
if (proofWorktrees.length) {
|
|
14150
|
+
for (const name of ["development", "main", "master"]) {
|
|
14151
|
+
const oid = (await gitOut(["rev-parse", "--verify", `${remote}/${name}`]).catch(() => "")).trim();
|
|
14152
|
+
if (oid) trainTips.push(oid);
|
|
14153
|
+
}
|
|
14154
|
+
}
|
|
14155
|
+
const headByBranch = new Map(heads.map((h) => [h.branch, h.oid]));
|
|
14156
|
+
const exactTreeLanded = [];
|
|
14157
|
+
const exactTreeRefusals = [];
|
|
14158
|
+
for (const wt of proofWorktrees) {
|
|
14159
|
+
const headOid = headByBranch.get(wt.branch);
|
|
14160
|
+
const owner = owners.find((entry) => sameWorktreeMetadataPath(entry.path, wt.path));
|
|
14161
|
+
if (!headOid || !owner || !primaryRoot) continue;
|
|
14162
|
+
const proof = await proveExactTreeDelivery({
|
|
14163
|
+
repoRoot: primaryRoot,
|
|
14164
|
+
branch: wt.branch,
|
|
14165
|
+
workerOid: headOid,
|
|
14166
|
+
creationBaseOid: owner.provenance?.creationBaseOid,
|
|
14167
|
+
recordedWorkerBranch: owner.provenance?.workerBranch,
|
|
14168
|
+
landedTips: trainTips
|
|
14169
|
+
});
|
|
14170
|
+
if (proof.action === "settle") exactTreeLanded.push({ branch: wt.branch, headOid, candidateOid: proof.candidateOid });
|
|
14171
|
+
else exactTreeRefusals.push({ branch: wt.branch, detail: describeExactTreeRefusal(proof) });
|
|
14172
|
+
}
|
|
13893
14173
|
return buildGcPlan({
|
|
13894
14174
|
localBranches,
|
|
13895
14175
|
prLookupFailures: failures,
|
|
@@ -13902,6 +14182,8 @@ async function gcPlan(remote, limit, opts = {}) {
|
|
|
13902
14182
|
remote,
|
|
13903
14183
|
preservedBranches: preserved,
|
|
13904
14184
|
mergedIntoBase,
|
|
14185
|
+
exactTreeLanded,
|
|
14186
|
+
exactTreeRefusals,
|
|
13905
14187
|
originBranches,
|
|
13906
14188
|
trainOnly: opts.trainOnly
|
|
13907
14189
|
});
|
|
@@ -13938,6 +14220,11 @@ function semverRelation(a, b) {
|
|
|
13938
14220
|
function classifyRow(row) {
|
|
13939
14221
|
const findings = [];
|
|
13940
14222
|
const reasons = /* @__PURE__ */ new Set();
|
|
14223
|
+
if (row.onboarding.status === "drift") {
|
|
14224
|
+
findings.push(`canonical onboarding link drift: ${row.onboarding.detail}`);
|
|
14225
|
+
} else if (row.onboarding.status === "unknown") {
|
|
14226
|
+
reasons.add(`canonical onboarding link evidence unknown: ${row.onboarding.detail}`);
|
|
14227
|
+
}
|
|
13941
14228
|
const releaseRequired = row.track !== "trunk" && !row.classifications.includes("no-deploy-surface");
|
|
13942
14229
|
const tagIsVersion = Boolean(row.release.version) && row.unknowns.some((u) => u.startsWith(NO_VERSION_LOCK_PREFIX));
|
|
13943
14230
|
for (const unknown of row.unknowns) {
|
|
@@ -14200,10 +14487,10 @@ var rollout_plan_default = {
|
|
|
14200
14487
|
note: "The v4.0.0 stamp happens at cut time (D6e #4463); until then the candidate is the origin/development head artifacts (built cli/dist + npm pack), identity proven by dist content hash (D6a)."
|
|
14201
14488
|
},
|
|
14202
14489
|
baseline: {
|
|
14203
|
-
version: "3.
|
|
14204
|
-
tag: "v3.
|
|
14205
|
-
commit: "
|
|
14206
|
-
npm: "@mutmutco/cli@3.
|
|
14490
|
+
version: "3.137.0",
|
|
14491
|
+
tag: "v3.137.0",
|
|
14492
|
+
commit: "48d8161cd121",
|
|
14493
|
+
npm: "@mutmutco/cli@3.137.0"
|
|
14207
14494
|
},
|
|
14208
14495
|
exitCriterion: "fleet-n-of-n",
|
|
14209
14496
|
hubOnlyShortcut: "forbidden",
|
|
@@ -14220,14 +14507,14 @@ var rollout_plan_default = {
|
|
|
14220
14507
|
repo: "mutmutco/mmi-hub",
|
|
14221
14508
|
role: "canary",
|
|
14222
14509
|
schedule: "train",
|
|
14223
|
-
v3Target: "v3.
|
|
14510
|
+
v3Target: "v3.137.0"
|
|
14224
14511
|
}
|
|
14225
14512
|
],
|
|
14226
14513
|
rollbackTrigger: "Any red inside the post-cut soak window: `devops train gate` FAIL attributable to the v4 doors, Hub endpoint health probe failure, a v3 client refused while the compat window must still admit it (SUPPORTED_MINOR_WINDOW=2, MIN_CLIENT_VERSION 0.0.0 \u2014 D6a), or npm consumer install/doctor failure on the v4 dist.",
|
|
14227
14514
|
rollback: {
|
|
14228
14515
|
independent: true,
|
|
14229
|
-
mechanism: "npm dist-tag latest -> 3.
|
|
14230
|
-
v3Target: "v3.
|
|
14516
|
+
mechanism: "npm dist-tag latest -> 3.137.0 and redeploy the Hub Lambda from tag v3.137.0 (48d8161cd121); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
14517
|
+
v3Target: "v3.137.0 (@mutmutco/cli@3.137.0, tag commit 48d8161cd121 \u2014 the preserved latest-v3 distribution, D6b)"
|
|
14231
14518
|
}
|
|
14232
14519
|
},
|
|
14233
14520
|
{
|
|
@@ -15435,6 +15722,7 @@ function registerQueryCommands(program3) {
|
|
|
15435
15722
|
|
|
15436
15723
|
// src/fleet-track-inventory.ts
|
|
15437
15724
|
var NO_DEPLOY_SURFACE_MODELS = /* @__PURE__ */ new Set(["none", "content"]);
|
|
15725
|
+
var CANONICAL_ONBOARDING_URL = "https://github.com/mutmutco/MMI-Hub/blob/main/docs/Architecture/agentic-dev-environment.md";
|
|
15438
15726
|
function ghErrorText(e) {
|
|
15439
15727
|
const err = e;
|
|
15440
15728
|
return String(err?.stderr || err?.message || e).trim().replace(/\s+/g, " ");
|
|
@@ -15465,6 +15753,23 @@ async function readFleetPluginProbe(deps, repo, branch) {
|
|
|
15465
15753
|
return isGhNotFound2(e) ? { repo, state: "absent" } : { repo, state: "unknown", unknown: `plugin surface read failed: ${ghErrorText(e)}` };
|
|
15466
15754
|
}
|
|
15467
15755
|
}
|
|
15756
|
+
async function readFleetOnboardingProbe(deps, repo, branch) {
|
|
15757
|
+
const [owner, name] = repo.split("/");
|
|
15758
|
+
const ref = branch ? `?ref=${encodeURIComponent(branch)}` : "";
|
|
15759
|
+
try {
|
|
15760
|
+
const raw = await deps.ghJson([
|
|
15761
|
+
"api",
|
|
15762
|
+
`repos/${encodeURIComponent(owner ?? "")}/${encodeURIComponent(name ?? "")}/contents/README.md${ref}`
|
|
15763
|
+
]);
|
|
15764
|
+
if (raw.encoding !== "base64" || typeof raw.content !== "string") {
|
|
15765
|
+
return { repo, status: "unknown", detail: "README contents read returned no base64 content" };
|
|
15766
|
+
}
|
|
15767
|
+
const readme = Buffer.from(raw.content.replace(/\s/g, ""), "base64").toString("utf8");
|
|
15768
|
+
return readme.includes(CANONICAL_ONBOARDING_URL) ? { repo, status: "canonical", detail: CANONICAL_ONBOARDING_URL } : { repo, status: "drift", detail: `README.md does not link ${CANONICAL_ONBOARDING_URL}` };
|
|
15769
|
+
} catch (e) {
|
|
15770
|
+
return isGhNotFound2(e) ? { repo, status: "drift", detail: `README.md is absent; expected ${CANONICAL_ONBOARDING_URL}` } : { repo, status: "unknown", detail: `onboarding README read failed: ${ghErrorText(e)}` };
|
|
15771
|
+
}
|
|
15772
|
+
}
|
|
15468
15773
|
async function mapBounded2(values, limit, read) {
|
|
15469
15774
|
const results = new Array(values.length);
|
|
15470
15775
|
let next = 0;
|
|
@@ -15495,9 +15800,10 @@ function classificationsOf(surfaces) {
|
|
|
15495
15800
|
if (surfaces.deploy === "absent") classes.push("no-deploy-surface");
|
|
15496
15801
|
return classes;
|
|
15497
15802
|
}
|
|
15498
|
-
function buildFleetTrackInventory(projects, releaseProbes, pluginProbes, generatedAt) {
|
|
15803
|
+
function buildFleetTrackInventory(projects, releaseProbes, pluginProbes, generatedAt, onboardingProbes = []) {
|
|
15499
15804
|
const releaseByRepo = new Map(releaseProbes.map((p) => [p.repo.toLowerCase(), p]));
|
|
15500
15805
|
const pluginByRepo = new Map(pluginProbes.map((p) => [p.repo.toLowerCase(), p]));
|
|
15806
|
+
const onboardingByRepo = new Map(onboardingProbes.map((p) => [p.repo.toLowerCase(), p]));
|
|
15501
15807
|
const projectByRepo = /* @__PURE__ */ new Map();
|
|
15502
15808
|
const anomalies = [];
|
|
15503
15809
|
for (const project2 of projects) {
|
|
@@ -15519,11 +15825,13 @@ function buildFleetTrackInventory(projects, releaseProbes, pluginProbes, generat
|
|
|
15519
15825
|
const project2 = projectByRepo.get(repo.toLowerCase());
|
|
15520
15826
|
const release = releaseByRepo.get(repo.toLowerCase());
|
|
15521
15827
|
const plugin = pluginByRepo.get(repo.toLowerCase());
|
|
15828
|
+
const onboarding = onboardingByRepo.get(repo.toLowerCase()) ?? { repo, status: "unknown", detail: "onboarding README was not probed" };
|
|
15522
15829
|
const surfaces = classifySurfaces(project2, plugin);
|
|
15523
15830
|
const unknowns = [...release?.unknowns ?? []];
|
|
15524
15831
|
if (!release) unknowns.push("GitHub release evidence was not collected");
|
|
15525
15832
|
if (plugin?.unknown) unknowns.push(plugin.unknown);
|
|
15526
15833
|
if (!plugin) unknowns.push("plugin surface was not probed");
|
|
15834
|
+
if (onboarding.status === "unknown") unknowns.push(onboarding.detail);
|
|
15527
15835
|
return {
|
|
15528
15836
|
repo,
|
|
15529
15837
|
slug: String(project2?.slug ?? repo.split("/")[1] ?? repo).toLowerCase(),
|
|
@@ -15535,6 +15843,7 @@ function buildFleetTrackInventory(projects, releaseProbes, pluginProbes, generat
|
|
|
15535
15843
|
surfaces,
|
|
15536
15844
|
classifications: classificationsOf(surfaces),
|
|
15537
15845
|
pluginVersion: plugin?.version ?? null,
|
|
15846
|
+
onboarding,
|
|
15538
15847
|
release: {
|
|
15539
15848
|
tag: release?.releaseTag ?? null,
|
|
15540
15849
|
version: release?.releasedVersion ?? null,
|
|
@@ -15547,7 +15856,7 @@ function buildFleetTrackInventory(projects, releaseProbes, pluginProbes, generat
|
|
|
15547
15856
|
requiredChecks: Array.isArray(project2?.requiredChecks) ? project2.requiredChecks : null,
|
|
15548
15857
|
exemptReason: typeof project2?.ciExemptReason === "string" ? project2.ciExemptReason : null
|
|
15549
15858
|
},
|
|
15550
|
-
source: { registry: "hub-registry-live", release: "github-releases-live", plugin: "github-contents-live" },
|
|
15859
|
+
source: { registry: "hub-registry-live", release: "github-releases-live", plugin: "github-contents-live", onboarding: "github-readme-live" },
|
|
15551
15860
|
freshness: { readAt: generatedAt, releasedAt: release?.releasedAt ?? null },
|
|
15552
15861
|
unknowns
|
|
15553
15862
|
};
|
|
@@ -15560,6 +15869,7 @@ function buildFleetTrackInventory(projects, releaseProbes, pluginProbes, generat
|
|
|
15560
15869
|
roster: "hub-registry-live",
|
|
15561
15870
|
releases: "github-releases-live",
|
|
15562
15871
|
plugin: "github-contents-live",
|
|
15872
|
+
onboarding: "github-readme-live",
|
|
15563
15873
|
committedInventory: "never"
|
|
15564
15874
|
},
|
|
15565
15875
|
counts: {
|
|
@@ -15570,6 +15880,7 @@ function buildFleetTrackInventory(projects, releaseProbes, pluginProbes, generat
|
|
|
15570
15880
|
noVault: repos.filter((r) => r.classifications.includes("no-vault")).length,
|
|
15571
15881
|
noPlugin: repos.filter((r) => r.classifications.includes("no-plugin")).length,
|
|
15572
15882
|
noDeploySurface: repos.filter((r) => r.classifications.includes("no-deploy-surface")).length,
|
|
15883
|
+
onboardingDrift: repos.filter((r) => r.onboarding.status === "drift").length,
|
|
15573
15884
|
unknownRows: repos.filter((r) => r.unknowns.length > 0).length,
|
|
15574
15885
|
anomalies: anomalies.length
|
|
15575
15886
|
},
|
|
@@ -15601,11 +15912,12 @@ async function readFleetTrackInventory(deps = defaultFleetTrackInventoryDeps())
|
|
|
15601
15912
|
}
|
|
15602
15913
|
}
|
|
15603
15914
|
}
|
|
15604
|
-
const [releaseProbes, pluginProbes] = await Promise.all([
|
|
15915
|
+
const [releaseProbes, pluginProbes, onboardingProbes] = await Promise.all([
|
|
15605
15916
|
mapBounded2(repos, 6, (repo) => readOrgVersionProbe(deps.query, repo)),
|
|
15606
|
-
mapBounded2(repos, 6, (repo) => readFleetPluginProbe(deps.query, repo, branchByRepo.get(repo.toLowerCase())))
|
|
15917
|
+
mapBounded2(repos, 6, (repo) => readFleetPluginProbe(deps.query, repo, branchByRepo.get(repo.toLowerCase()))),
|
|
15918
|
+
mapBounded2(repos, 6, (repo) => readFleetOnboardingProbe(deps.query, repo, branchByRepo.get(repo.toLowerCase())))
|
|
15607
15919
|
]);
|
|
15608
|
-
return buildFleetTrackInventory(projects, releaseProbes, pluginProbes, deps.now().toISOString());
|
|
15920
|
+
return buildFleetTrackInventory(projects, releaseProbes, pluginProbes, deps.now().toISOString(), onboardingProbes);
|
|
15609
15921
|
}
|
|
15610
15922
|
function formatFleetTrackInventory(report) {
|
|
15611
15923
|
const { counts } = report;
|
|
@@ -15613,13 +15925,13 @@ function formatFleetTrackInventory(report) {
|
|
|
15613
15925
|
`fleet version-track inventory \u2014 ${counts.registeredRepos} registered repositories (${counts.projects} registry projects)`,
|
|
15614
15926
|
`source: live Hub registry + live GitHub releases/contents \xB7 read ${report.generatedAt} \xB7 committed inventory: never`,
|
|
15615
15927
|
`tracks: ${counts.tracks.full} full \xB7 ${counts.tracks.direct} direct \xB7 ${counts.tracks.trunk} trunk`,
|
|
15616
|
-
`classified: ${counts.noBoard} no-board \xB7 ${counts.noVault} no-vault \xB7 ${counts.noPlugin} no-plugin \xB7 ${counts.noDeploySurface} no-deploy-surface \xB7 ${counts.unknownRows} rows with unknowns`,
|
|
15928
|
+
`classified: ${counts.noBoard} no-board \xB7 ${counts.noVault} no-vault \xB7 ${counts.noPlugin} no-plugin \xB7 ${counts.noDeploySurface} no-deploy-surface \xB7 ${counts.onboardingDrift} onboarding-link drift \xB7 ${counts.unknownRows} rows with unknowns`,
|
|
15617
15929
|
"",
|
|
15618
|
-
"repository | track | deploy model | released | plugin | classifications"
|
|
15930
|
+
"repository | track | deploy model | released | plugin | onboarding | classifications"
|
|
15619
15931
|
];
|
|
15620
15932
|
for (const row of report.repos) {
|
|
15621
15933
|
lines.push(
|
|
15622
|
-
`${row.repo} | ${row.track}${row.declaredTrack ? "" : " (derived)"} | ${row.deployModel ?? "UNDECLARED"} | ${row.release.version ?? "UNKNOWN"} | ${row.pluginVersion ?? (row.surfaces.plugin === "present" ? "present" : row.surfaces.plugin)} | ${row.classifications.join(", ") || "none"}`
|
|
15934
|
+
`${row.repo} | ${row.track}${row.declaredTrack ? "" : " (derived)"} | ${row.deployModel ?? "UNDECLARED"} | ${row.release.version ?? "UNKNOWN"} | ${row.pluginVersion ?? (row.surfaces.plugin === "present" ? "present" : row.surfaces.plugin)} | ${row.onboarding.status} | ${row.classifications.join(", ") || "none"}`
|
|
15623
15935
|
);
|
|
15624
15936
|
for (const unknown of row.unknowns) lines.push(` ? ${unknown}`);
|
|
15625
15937
|
}
|
|
@@ -15634,7 +15946,7 @@ function formatFleetTrackInventory(report) {
|
|
|
15634
15946
|
function registerFleetTrackInventory(program3) {
|
|
15635
15947
|
const train = program3.commands.find((c) => c.name() === "train");
|
|
15636
15948
|
if (!train) throw new Error("train inventory registration requires the train command group");
|
|
15637
|
-
train.command("inventory").description("live fleet version-track inventory (#4451) \u2014 every registered repo's track,
|
|
15949
|
+
train.command("inventory").description("live fleet version-track inventory (#4451) \u2014 every registered repo's track, release evidence, surface classification, and canonical Hub onboarding-link evidence").option("--json", "machine-readable output (the typed D2b input shape)").action(async (o) => {
|
|
15638
15950
|
try {
|
|
15639
15951
|
const report = await readFleetTrackInventory();
|
|
15640
15952
|
console.log(o.json ? JSON.stringify(report, null, 2) : formatFleetTrackInventory(report));
|
|
@@ -16311,7 +16623,7 @@ function planTrainApplyRepoGuard(applyRepo, cwdRepo, rerun) {
|
|
|
16311
16623
|
async function resolveFoldPaths(deps, model) {
|
|
16312
16624
|
const helper = "scripts/release-distribution.mjs";
|
|
16313
16625
|
if (model === "hub-serverless" || model === "registry-publish") {
|
|
16314
|
-
if ((0,
|
|
16626
|
+
if ((0, import_node_fs23.existsSync)(helper)) {
|
|
16315
16627
|
let out;
|
|
16316
16628
|
try {
|
|
16317
16629
|
out = await deps.run("node", [helper, "changed-files"]);
|
|
@@ -16430,7 +16742,7 @@ async function restoreMainNpmPackIdentities(deps, version) {
|
|
|
16430
16742
|
if (mainBom.version !== version) return void 0;
|
|
16431
16743
|
let localBom;
|
|
16432
16744
|
try {
|
|
16433
|
-
localBom = JSON.parse((0,
|
|
16745
|
+
localBom = JSON.parse((0, import_node_fs23.readFileSync)(bomPath, "utf8"));
|
|
16434
16746
|
} catch (e) {
|
|
16435
16747
|
throw new Error(
|
|
16436
16748
|
`version fold refused: ${bomPath} written by this fold's prepare is unreadable (${describeReadError(e)}) \u2014 cannot restore the published ${version} npm-pack identities from origin/main (#4503/#4713).`
|
|
@@ -16448,14 +16760,14 @@ async function restoreMainNpmPackIdentities(deps, version) {
|
|
|
16448
16760
|
restored += 1;
|
|
16449
16761
|
}
|
|
16450
16762
|
if (restored === 0) return void 0;
|
|
16451
|
-
(0,
|
|
16763
|
+
(0, import_node_fs23.writeFileSync)(bomPath, `${JSON.stringify(localBom, null, 2)}
|
|
16452
16764
|
`);
|
|
16453
16765
|
return `restored ${restored} npm-pack BOM identit${restored === 1 ? "y" : "ies"} from origin/main (#4503)`;
|
|
16454
16766
|
}
|
|
16455
16767
|
function publishVisibilityFor(surfaceId) {
|
|
16456
16768
|
let raw;
|
|
16457
16769
|
try {
|
|
16458
|
-
raw = (0,
|
|
16770
|
+
raw = (0, import_node_fs23.readFileSync)("surfaces.json", "utf8");
|
|
16459
16771
|
} catch (e) {
|
|
16460
16772
|
if (e.code === "ENOENT") return "unknown";
|
|
16461
16773
|
throw trainReadFailure(
|
|
@@ -16476,7 +16788,7 @@ function publishVisibilityFor(surfaceId) {
|
|
|
16476
16788
|
}
|
|
16477
16789
|
function npmPackArtifactName(packagePath) {
|
|
16478
16790
|
try {
|
|
16479
|
-
const pkg = JSON.parse((0,
|
|
16791
|
+
const pkg = JSON.parse((0, import_node_fs23.readFileSync)((0, import_node_path23.join)(packagePath, "package.json"), "utf8"));
|
|
16480
16792
|
return pkg.name || void 0;
|
|
16481
16793
|
} catch {
|
|
16482
16794
|
return void 0;
|
|
@@ -16485,7 +16797,7 @@ function npmPackArtifactName(packagePath) {
|
|
|
16485
16797
|
async function refuseDivergentPublishedNpmPack(deps, version) {
|
|
16486
16798
|
let localBom;
|
|
16487
16799
|
try {
|
|
16488
|
-
localBom = JSON.parse((0,
|
|
16800
|
+
localBom = JSON.parse((0, import_node_fs23.readFileSync)("distribution-bom.json", "utf8"));
|
|
16489
16801
|
} catch (e) {
|
|
16490
16802
|
throw new Error(
|
|
16491
16803
|
`version fold refused: distribution-bom.json written by this fold's prepare is unreadable (${e instanceof Error ? e.message.split("\n")[0] : String(e)}) \u2014 cannot compare the same-PATCH ${version} npm-pack identities against published npm (#4503/#4662).`
|
|
@@ -17647,17 +17959,17 @@ async function dispatchDeploy(deps, ctx, stage, ref, model, watch, autoRunSince,
|
|
|
17647
17959
|
return { note: `no manual dispatch: ${model} repo deploys via its own push-triggered workflow`, deployStatus: "pending" };
|
|
17648
17960
|
}
|
|
17649
17961
|
function readLocalGateWorkflows() {
|
|
17650
|
-
const dir = (0,
|
|
17962
|
+
const dir = (0, import_node_path23.join)(".github", "workflows");
|
|
17651
17963
|
let names;
|
|
17652
17964
|
try {
|
|
17653
|
-
names = (0,
|
|
17965
|
+
names = (0, import_node_fs23.readdirSync)(dir);
|
|
17654
17966
|
} catch {
|
|
17655
17967
|
return null;
|
|
17656
17968
|
}
|
|
17657
17969
|
const files = [];
|
|
17658
17970
|
for (const name of names.filter(isGateWorkflowPath)) {
|
|
17659
17971
|
try {
|
|
17660
|
-
files.push({ path: `${dir}/${name}`.replace(/\\/g, "/"), body: (0,
|
|
17972
|
+
files.push({ path: `${dir}/${name}`.replace(/\\/g, "/"), body: (0, import_node_fs23.readFileSync)((0, import_node_path23.join)(dir, name), "utf8") });
|
|
17661
17973
|
} catch {
|
|
17662
17974
|
}
|
|
17663
17975
|
}
|
|
@@ -19875,17 +20187,17 @@ async function runPrLand(prNumber, options, deps) {
|
|
|
19875
20187
|
}
|
|
19876
20188
|
|
|
19877
20189
|
// src/wave-status.ts
|
|
19878
|
-
var
|
|
20190
|
+
var import_node_fs25 = require("node:fs");
|
|
19879
20191
|
|
|
19880
20192
|
// src/stage-runner.ts
|
|
19881
20193
|
var import_node_child_process8 = require("node:child_process");
|
|
19882
|
-
var
|
|
19883
|
-
var
|
|
20194
|
+
var import_node_fs24 = require("node:fs");
|
|
20195
|
+
var import_node_path24 = require("node:path");
|
|
19884
20196
|
var import_node_net = require("node:net");
|
|
19885
20197
|
var import_node_util5 = require("node:util");
|
|
19886
20198
|
|
|
19887
20199
|
// src/rules-sync.ts
|
|
19888
|
-
function
|
|
20200
|
+
function normalizeEol2(s) {
|
|
19889
20201
|
return s.replace(/\r\n/g, "\n");
|
|
19890
20202
|
}
|
|
19891
20203
|
|
|
@@ -19938,8 +20250,8 @@ function envFileKeys(content) {
|
|
|
19938
20250
|
return keys;
|
|
19939
20251
|
}
|
|
19940
20252
|
function detectStaleEnvFile(exampleContent, targetContent, mtimes) {
|
|
19941
|
-
const example =
|
|
19942
|
-
const target =
|
|
20253
|
+
const example = normalizeEol2(exampleContent);
|
|
20254
|
+
const target = normalizeEol2(targetContent);
|
|
19943
20255
|
const exampleKeys = envFileKeys(example);
|
|
19944
20256
|
const targetKeys = envFileKeys(target);
|
|
19945
20257
|
for (const key of exampleKeys) {
|
|
@@ -20019,11 +20331,11 @@ function appendForceRecreate(up) {
|
|
|
20019
20331
|
return `${up.trimEnd()} --force-recreate`;
|
|
20020
20332
|
}
|
|
20021
20333
|
function stageStatePath(cwd = process.cwd()) {
|
|
20022
|
-
return (0,
|
|
20334
|
+
return (0, import_node_path24.join)(cwd, "tmp", "stage", "state.json");
|
|
20023
20335
|
}
|
|
20024
20336
|
function stageGlobalStatePath(cwd = process.cwd(), gitCommonDir = ".git") {
|
|
20025
|
-
const dir = (0,
|
|
20026
|
-
return (0,
|
|
20337
|
+
const dir = (0, import_node_path24.isAbsolute)(gitCommonDir) ? gitCommonDir : (0, import_node_path24.resolve)(cwd, gitCommonDir);
|
|
20338
|
+
return (0, import_node_path24.join)(dir, "mmi", "stage", "state.json");
|
|
20027
20339
|
}
|
|
20028
20340
|
function normPath3(path2) {
|
|
20029
20341
|
return path2.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
|
|
@@ -20247,14 +20559,14 @@ function stageProcessEnv(stagePort, extraEnv) {
|
|
|
20247
20559
|
}
|
|
20248
20560
|
async function ensureStageRuntimeEnv(config, opts, cwd) {
|
|
20249
20561
|
if (!config.ensureEnv) return;
|
|
20250
|
-
const target = (0,
|
|
20251
|
-
const example = (0,
|
|
20252
|
-
if (!(0,
|
|
20253
|
-
(0,
|
|
20254
|
-
} else if ((0,
|
|
20255
|
-
const stale = detectStaleEnvFile((0,
|
|
20256
|
-
exampleMtimeMs: (0,
|
|
20257
|
-
targetMtimeMs: (0,
|
|
20562
|
+
const target = (0, import_node_path24.join)(cwd, config.ensureEnv.target);
|
|
20563
|
+
const example = (0, import_node_path24.join)(cwd, config.ensureEnv.example);
|
|
20564
|
+
if (!(0, import_node_fs24.existsSync)(target) && (0, import_node_fs24.existsSync)(example)) {
|
|
20565
|
+
(0, import_node_fs24.copyFileSync)(example, target);
|
|
20566
|
+
} else if ((0, import_node_fs24.existsSync)(target) && (0, import_node_fs24.existsSync)(example)) {
|
|
20567
|
+
const stale = detectStaleEnvFile((0, import_node_fs24.readFileSync)(example, "utf8"), (0, import_node_fs24.readFileSync)(target, "utf8"), {
|
|
20568
|
+
exampleMtimeMs: (0, import_node_fs24.statSync)(example).mtimeMs,
|
|
20569
|
+
targetMtimeMs: (0, import_node_fs24.statSync)(target).mtimeMs
|
|
20258
20570
|
});
|
|
20259
20571
|
if (stale) {
|
|
20260
20572
|
const msg = `stale ${config.ensureEnv.target} (${stale}) \u2014 delete it or refresh from ${config.ensureEnv.example} before re-running /stage`;
|
|
@@ -20262,8 +20574,8 @@ async function ensureStageRuntimeEnv(config, opts, cwd) {
|
|
|
20262
20574
|
console.error(`mmi-cli stage: ${msg} (allowed via --allow-stale-env)`);
|
|
20263
20575
|
}
|
|
20264
20576
|
}
|
|
20265
|
-
if (opts.vaultEnvMerge && Object.keys(opts.vaultEnvMerge).length && (0,
|
|
20266
|
-
(0,
|
|
20577
|
+
if (opts.vaultEnvMerge && Object.keys(opts.vaultEnvMerge).length && (0, import_node_fs24.existsSync)(target)) {
|
|
20578
|
+
(0, import_node_fs24.writeFileSync)(target, mergeEnvSecretsIntoFile((0, import_node_fs24.readFileSync)(target, "utf8"), opts.vaultEnvMerge), "utf8");
|
|
20267
20579
|
}
|
|
20268
20580
|
}
|
|
20269
20581
|
async function gitText(cwd, args) {
|
|
@@ -20293,20 +20605,20 @@ async function resolveGlobalStatePath(cwd, explicit) {
|
|
|
20293
20605
|
return void 0;
|
|
20294
20606
|
}
|
|
20295
20607
|
function readState(path2) {
|
|
20296
|
-
if (!(0,
|
|
20608
|
+
if (!(0, import_node_fs24.existsSync)(path2)) return null;
|
|
20297
20609
|
try {
|
|
20298
|
-
return JSON.parse((0,
|
|
20610
|
+
return JSON.parse((0, import_node_fs24.readFileSync)(path2, "utf8"));
|
|
20299
20611
|
} catch {
|
|
20300
20612
|
return null;
|
|
20301
20613
|
}
|
|
20302
20614
|
}
|
|
20303
20615
|
function mkdirFor(path2) {
|
|
20304
20616
|
const dir = path2.slice(0, Math.max(path2.lastIndexOf("/"), path2.lastIndexOf("\\")));
|
|
20305
|
-
(0,
|
|
20617
|
+
(0, import_node_fs24.mkdirSync)(dir, { recursive: true });
|
|
20306
20618
|
}
|
|
20307
20619
|
function writeState(path2, state) {
|
|
20308
20620
|
mkdirFor(path2);
|
|
20309
|
-
(0,
|
|
20621
|
+
(0, import_node_fs24.writeFileSync)(path2, JSON.stringify(state, null, 2), "utf8");
|
|
20310
20622
|
}
|
|
20311
20623
|
function writeStagePortReservation(port, cwd, statePath, globalStatePath, now) {
|
|
20312
20624
|
const reservation = {
|
|
@@ -20326,7 +20638,7 @@ async function cleanupStageState(state, paths, timeoutMs, fallbackCwd) {
|
|
|
20326
20638
|
await shell(state.teardown.command.trim(), state.teardown.cwd || state.cwd || fallbackCwd, Math.max(timeoutMs, 1e4));
|
|
20327
20639
|
}
|
|
20328
20640
|
for (const path2 of [...new Set(paths.filter((p) => Boolean(p)))]) {
|
|
20329
|
-
(0,
|
|
20641
|
+
(0, import_node_fs24.rmSync)(path2, { force: true });
|
|
20330
20642
|
}
|
|
20331
20643
|
}
|
|
20332
20644
|
async function killTree(pid) {
|
|
@@ -20484,8 +20796,8 @@ async function runStage(config = {}, opts = {}) {
|
|
|
20484
20796
|
await ensureStageRuntimeEnv(config, opts, cwd);
|
|
20485
20797
|
if (build) await shell(sub(build), cwd, timeoutMs, stageProcessEnv(stagePort, extraEnv));
|
|
20486
20798
|
} catch (e) {
|
|
20487
|
-
(0,
|
|
20488
|
-
if (globalStatePath && globalStatePath !== statePath) (0,
|
|
20799
|
+
(0, import_node_fs24.rmSync)(statePath, { force: true });
|
|
20800
|
+
if (globalStatePath && globalStatePath !== statePath) (0, import_node_fs24.rmSync)(globalStatePath, { force: true });
|
|
20489
20801
|
throw e;
|
|
20490
20802
|
}
|
|
20491
20803
|
const started = await startStage(config, {
|
|
@@ -20506,9 +20818,9 @@ function parseNextFromHead(headText) {
|
|
|
20506
20818
|
}
|
|
20507
20819
|
function readStageSummary(worktreePath) {
|
|
20508
20820
|
const statePath = stageStatePath(worktreePath);
|
|
20509
|
-
if (!(0,
|
|
20821
|
+
if (!(0, import_node_fs25.existsSync)(statePath)) return void 0;
|
|
20510
20822
|
try {
|
|
20511
|
-
const state = JSON.parse((0,
|
|
20823
|
+
const state = JSON.parse((0, import_node_fs25.readFileSync)(statePath, "utf8"));
|
|
20512
20824
|
const port = typeof state.port === "number" ? state.port : void 0;
|
|
20513
20825
|
if (port == null || !Number.isInteger(port) || port <= 0) return void 0;
|
|
20514
20826
|
return { port, url: typeof state.url === "string" ? state.url : void 0 };
|
|
@@ -20613,13 +20925,13 @@ async function executeWaveLand(plan, deps, opts = { preserveWorktree: true }) {
|
|
|
20613
20925
|
}
|
|
20614
20926
|
|
|
20615
20927
|
// src/index.ts
|
|
20616
|
-
var
|
|
20928
|
+
var import_node_os21 = require("node:os");
|
|
20617
20929
|
|
|
20618
20930
|
// src/board.ts
|
|
20619
20931
|
var import_node_child_process9 = require("node:child_process");
|
|
20620
|
-
var
|
|
20621
|
-
var
|
|
20622
|
-
var
|
|
20932
|
+
var import_node_fs26 = require("node:fs");
|
|
20933
|
+
var import_node_os10 = require("node:os");
|
|
20934
|
+
var import_node_path25 = require("node:path");
|
|
20623
20935
|
var import_node_util6 = require("node:util");
|
|
20624
20936
|
|
|
20625
20937
|
// src/board-dependency.ts
|
|
@@ -22229,7 +22541,7 @@ var CLAIM_SESSION_ACTIVITY_MS = 30 * 6e4;
|
|
|
22229
22541
|
var CLAIM_SESSION_PROBE_CACHE_MS = 6e4;
|
|
22230
22542
|
var claimSessionProbeCache = /* @__PURE__ */ new Map();
|
|
22231
22543
|
function probeLocalClaimSession(marker, now = Date.now()) {
|
|
22232
|
-
if (!marker.session || !marker.host || marker.host.toLowerCase() !== (0,
|
|
22544
|
+
if (!marker.session || !marker.host || marker.host.toLowerCase() !== (0, import_node_os10.hostname)().toLowerCase()) return void 0;
|
|
22233
22545
|
if (!marker.surface?.toLowerCase().startsWith("claude")) return void 0;
|
|
22234
22546
|
const cacheKey = `${marker.host.toLowerCase()}/${marker.session}`;
|
|
22235
22547
|
const cached = claimSessionProbeCache.get(cacheKey);
|
|
@@ -22238,17 +22550,17 @@ function probeLocalClaimSession(marker, now = Date.now()) {
|
|
|
22238
22550
|
claimSessionProbeCache.set(cacheKey, { checkedAt: now, state });
|
|
22239
22551
|
return state;
|
|
22240
22552
|
};
|
|
22241
|
-
const root = (0,
|
|
22553
|
+
const root = (0, import_node_path25.join)((0, import_node_os10.homedir)(), ".claude", "projects");
|
|
22242
22554
|
try {
|
|
22243
22555
|
const wanted = `${marker.session}.jsonl`.toLowerCase();
|
|
22244
22556
|
const pending = [root];
|
|
22245
22557
|
while (pending.length) {
|
|
22246
22558
|
const dir = pending.pop();
|
|
22247
|
-
for (const entry of (0,
|
|
22248
|
-
const path2 = (0,
|
|
22559
|
+
for (const entry of (0, import_node_fs26.readdirSync)(dir, { withFileTypes: true })) {
|
|
22560
|
+
const path2 = (0, import_node_path25.join)(dir, entry.name);
|
|
22249
22561
|
if (entry.isDirectory()) pending.push(path2);
|
|
22250
22562
|
else if (entry.isFile() && entry.name.toLowerCase() === wanted) {
|
|
22251
|
-
return remember(now - (0,
|
|
22563
|
+
return remember(now - (0, import_node_fs26.statSync)(path2).mtimeMs <= CLAIM_SESSION_ACTIVITY_MS ? "live" : "dead");
|
|
22252
22564
|
}
|
|
22253
22565
|
}
|
|
22254
22566
|
}
|
|
@@ -22536,7 +22848,7 @@ async function resolveAutoAddBoardAttach(client, cfg, selector, priority, warn =
|
|
|
22536
22848
|
}
|
|
22537
22849
|
|
|
22538
22850
|
// src/issue-body.ts
|
|
22539
|
-
var
|
|
22851
|
+
var import_node_os11 = require("node:os");
|
|
22540
22852
|
var TextArgError = class extends Error {
|
|
22541
22853
|
constructor(message, code, offendingFlag) {
|
|
22542
22854
|
super(message);
|
|
@@ -22548,7 +22860,7 @@ var TextArgError = class extends Error {
|
|
|
22548
22860
|
offendingFlag;
|
|
22549
22861
|
};
|
|
22550
22862
|
function emptyStdinMessage(fileFlag) {
|
|
22551
|
-
if ((0,
|
|
22863
|
+
if ((0, import_node_os11.platform)() === "win32") {
|
|
22552
22864
|
return `${fileFlag} - read empty stdin (on Windows, ${fileFlag} - is unreliable through the npm .cmd shim \u2014 use ${fileFlag} <path>, or pipe to \`node cli/dist/index.cjs\` directly)`;
|
|
22553
22865
|
}
|
|
22554
22866
|
return `${fileFlag} - read empty stdin (nothing piped \u2014 pass a heredoc/pipe, or ${fileFlag} <path>)`;
|
|
@@ -22785,6 +23097,13 @@ function applyCommandTaxonomy(program3) {
|
|
|
22785
23097
|
}
|
|
22786
23098
|
program3.configureHelp({
|
|
22787
23099
|
...REQUIRED_OPTION_HELP,
|
|
23100
|
+
// #5014: the root help must show the CANONICAL house-prefixed term (`oracle board`, `devops pr`),
|
|
23101
|
+
// not the retired flat leaf name — an agent grounding on `mmi-cli help` and running the flat name
|
|
23102
|
+
// hits the Wave 3 refusal. `canonicalPathFor` is the identity for `core`, so core stays unprefixed.
|
|
23103
|
+
subcommandTerm(command) {
|
|
23104
|
+
const stock = Help.prototype.subcommandTerm.call(this, command);
|
|
23105
|
+
return `${canonicalPathFor(command.name()) ?? command.name()}${stock.slice(command.name().length)}`;
|
|
23106
|
+
},
|
|
22788
23107
|
visibleCommands(command) {
|
|
22789
23108
|
const visible = command.commands.filter((child2) => !child2._hidden);
|
|
22790
23109
|
const helpCommand = command._getHelpCommand();
|
|
@@ -23149,20 +23468,20 @@ function consolidateCommandNamespaces(program3) {
|
|
|
23149
23468
|
}
|
|
23150
23469
|
|
|
23151
23470
|
// src/claude-binary-doctor.ts
|
|
23471
|
+
var import_node_fs28 = require("node:fs");
|
|
23472
|
+
var import_node_os13 = require("node:os");
|
|
23473
|
+
var import_node_path27 = require("node:path");
|
|
23474
|
+
|
|
23475
|
+
// src/jerv-cli-spawn.ts
|
|
23152
23476
|
var import_node_fs27 = require("node:fs");
|
|
23153
23477
|
var import_node_os12 = require("node:os");
|
|
23154
23478
|
var import_node_path26 = require("node:path");
|
|
23155
|
-
|
|
23156
|
-
// src/jerv-cli-spawn.ts
|
|
23157
|
-
var import_node_fs26 = require("node:fs");
|
|
23158
|
-
var import_node_os11 = require("node:os");
|
|
23159
|
-
var import_node_path25 = require("node:path");
|
|
23160
23479
|
var WIN_NAMES = ["jerv-cli.cmd", "jerv-cli.exe", "jerv-cli"];
|
|
23161
23480
|
var POSIX_NAMES = ["jerv-cli"];
|
|
23162
|
-
var JERV_CLI_ENTRY = (0,
|
|
23481
|
+
var JERV_CLI_ENTRY = (0, import_node_path26.join)("node_modules", "@jervaise", "jerv-cli", "dist", "index.cjs");
|
|
23163
23482
|
function pathEnvEntries(pathEnv, platform2 = process.platform) {
|
|
23164
23483
|
if (platform2 !== "win32") {
|
|
23165
|
-
return pathEnv.split(
|
|
23484
|
+
return pathEnv.split(import_node_path26.delimiter).map((e) => e.trim()).filter(Boolean);
|
|
23166
23485
|
}
|
|
23167
23486
|
if (pathEnv.includes(";")) {
|
|
23168
23487
|
return pathEnv.split(";").map((e) => e.trim()).filter(Boolean);
|
|
@@ -23181,7 +23500,7 @@ function normalizeSpawnPathEntry(entry, platform2 = process.platform) {
|
|
|
23181
23500
|
if (msys) return `${msys[1].toUpperCase()}:\\${msys[2].replace(/\//g, "\\")}`;
|
|
23182
23501
|
return trimmed;
|
|
23183
23502
|
}
|
|
23184
|
-
function jervCliCandidateDirs(env = process.env, home = (0,
|
|
23503
|
+
function jervCliCandidateDirs(env = process.env, home = (0, import_node_os12.homedir)(), platform2 = process.platform) {
|
|
23185
23504
|
const seen = /* @__PURE__ */ new Set();
|
|
23186
23505
|
const out = [];
|
|
23187
23506
|
const push = (dir) => {
|
|
@@ -23195,35 +23514,35 @@ function jervCliCandidateDirs(env = process.env, home = (0, import_node_os11.hom
|
|
|
23195
23514
|
push(normalizeSpawnPathEntry(entry, platform2));
|
|
23196
23515
|
}
|
|
23197
23516
|
if (platform2 === "win32") {
|
|
23198
|
-
if (env.APPDATA) push((0,
|
|
23199
|
-
if (env.LOCALAPPDATA) push((0,
|
|
23517
|
+
if (env.APPDATA) push((0, import_node_path26.join)(env.APPDATA, "npm"));
|
|
23518
|
+
if (env.LOCALAPPDATA) push((0, import_node_path26.join)(env.LOCALAPPDATA, "npm"));
|
|
23200
23519
|
} else {
|
|
23201
|
-
push((0,
|
|
23520
|
+
push((0, import_node_path26.join)(home, ".local", "bin"));
|
|
23202
23521
|
}
|
|
23203
23522
|
return out;
|
|
23204
23523
|
}
|
|
23205
|
-
function jervCliCandidatePaths(env = process.env, home = (0,
|
|
23524
|
+
function jervCliCandidatePaths(env = process.env, home = (0, import_node_os12.homedir)(), platform2 = process.platform) {
|
|
23206
23525
|
const names = platform2 === "win32" ? WIN_NAMES : POSIX_NAMES;
|
|
23207
23526
|
const out = [];
|
|
23208
23527
|
for (const dir of jervCliCandidateDirs(env, home, platform2)) {
|
|
23209
|
-
for (const name of names) out.push((0,
|
|
23528
|
+
for (const name of names) out.push((0, import_node_path26.join)(dir, name));
|
|
23210
23529
|
}
|
|
23211
23530
|
return out;
|
|
23212
23531
|
}
|
|
23213
|
-
function resolveJervCliPath(env = process.env, home = (0,
|
|
23532
|
+
function resolveJervCliPath(env = process.env, home = (0, import_node_os12.homedir)(), platform2 = process.platform, exists = import_node_fs27.existsSync) {
|
|
23214
23533
|
for (const candidate of jervCliCandidatePaths(env, home, platform2)) {
|
|
23215
23534
|
if (exists(candidate)) return candidate;
|
|
23216
23535
|
}
|
|
23217
23536
|
return void 0;
|
|
23218
23537
|
}
|
|
23219
|
-
function resolveJervCliNodeEntry(shimPath, exists =
|
|
23220
|
-
const entry = (0,
|
|
23538
|
+
function resolveJervCliNodeEntry(shimPath, exists = import_node_fs27.existsSync) {
|
|
23539
|
+
const entry = (0, import_node_path26.join)((0, import_node_path26.dirname)(shimPath), JERV_CLI_ENTRY);
|
|
23221
23540
|
return exists(entry) ? entry : void 0;
|
|
23222
23541
|
}
|
|
23223
23542
|
function jervCliExecFileArgs(args, opts = {}) {
|
|
23224
23543
|
const platform2 = opts.platform ?? process.platform;
|
|
23225
|
-
const exists = opts.exists ??
|
|
23226
|
-
const resolved = resolveJervCliPath(opts.env ?? process.env, opts.home ?? (0,
|
|
23544
|
+
const exists = opts.exists ?? import_node_fs27.existsSync;
|
|
23545
|
+
const resolved = resolveJervCliPath(opts.env ?? process.env, opts.home ?? (0, import_node_os12.homedir)(), platform2, exists);
|
|
23227
23546
|
if (resolved) {
|
|
23228
23547
|
const entry = resolveJervCliNodeEntry(resolved, exists);
|
|
23229
23548
|
if (entry) {
|
|
@@ -23312,26 +23631,26 @@ function globalNodeModulesRoots(host) {
|
|
|
23312
23631
|
out.push(dir);
|
|
23313
23632
|
};
|
|
23314
23633
|
const prefix = env.npm_config_prefix?.trim();
|
|
23315
|
-
if (prefix) push(platform2 === "win32" ? (0,
|
|
23316
|
-
for (const dir of jervCliCandidateDirs(env, host.home ?? (0,
|
|
23317
|
-
push((0,
|
|
23318
|
-
push((0,
|
|
23634
|
+
if (prefix) push(platform2 === "win32" ? (0, import_node_path27.join)(prefix, "node_modules") : (0, import_node_path27.join)(prefix, "lib", "node_modules"));
|
|
23635
|
+
for (const dir of jervCliCandidateDirs(env, host.home ?? (0, import_node_os13.homedir)(), platform2)) {
|
|
23636
|
+
push((0, import_node_path27.join)(dir, "node_modules"));
|
|
23637
|
+
push((0, import_node_path27.join)((0, import_node_path27.dirname)(dir), "lib", "node_modules"));
|
|
23319
23638
|
}
|
|
23320
23639
|
return out;
|
|
23321
23640
|
}
|
|
23322
23641
|
function readHead(path2) {
|
|
23323
23642
|
let fd;
|
|
23324
23643
|
try {
|
|
23325
|
-
fd = (0,
|
|
23644
|
+
fd = (0, import_node_fs28.openSync)(path2, "r");
|
|
23326
23645
|
const buffer = new Uint8Array(MAGIC_HEAD_BYTES);
|
|
23327
|
-
const read = (0,
|
|
23646
|
+
const read = (0, import_node_fs28.readSync)(fd, buffer, 0, MAGIC_HEAD_BYTES, 0);
|
|
23328
23647
|
return buffer.subarray(0, read);
|
|
23329
23648
|
} catch {
|
|
23330
23649
|
return void 0;
|
|
23331
23650
|
} finally {
|
|
23332
23651
|
if (fd !== void 0) {
|
|
23333
23652
|
try {
|
|
23334
|
-
(0,
|
|
23653
|
+
(0, import_node_fs28.closeSync)(fd);
|
|
23335
23654
|
} catch {
|
|
23336
23655
|
}
|
|
23337
23656
|
}
|
|
@@ -23339,7 +23658,7 @@ function readHead(path2) {
|
|
|
23339
23658
|
}
|
|
23340
23659
|
function fileBytes(path2) {
|
|
23341
23660
|
try {
|
|
23342
|
-
return (0,
|
|
23661
|
+
return (0, import_node_fs28.statSync)(path2).size;
|
|
23343
23662
|
} catch {
|
|
23344
23663
|
return void 0;
|
|
23345
23664
|
}
|
|
@@ -23353,17 +23672,17 @@ function readClaudeBinaryState(host = {}) {
|
|
|
23353
23672
|
const arch = host.arch ?? process.arch;
|
|
23354
23673
|
const magic = EXECUTABLE_MAGIC[platform2];
|
|
23355
23674
|
if (!magic) return void 0;
|
|
23356
|
-
const packageRoot = globalNodeModulesRoots(host).map((root) => (0,
|
|
23675
|
+
const packageRoot = globalNodeModulesRoots(host).map((root) => (0, import_node_path27.join)(root, ...PACKAGE.split("/"))).find((dir) => (0, import_node_fs28.existsSync)((0, import_node_path27.join)(dir, "package.json")));
|
|
23357
23676
|
if (!packageRoot) return void 0;
|
|
23358
23677
|
const keys = platformPackageKeys(platform2, arch);
|
|
23359
23678
|
const fallbackPackage = `${PACKAGE}-${keys[0]}`;
|
|
23360
23679
|
let manifest;
|
|
23361
23680
|
try {
|
|
23362
|
-
manifest = JSON.parse((0,
|
|
23681
|
+
manifest = JSON.parse((0, import_node_fs28.readFileSync)((0, import_node_path27.join)(packageRoot, "package.json"), "utf8"));
|
|
23363
23682
|
} catch (e) {
|
|
23364
23683
|
return {
|
|
23365
23684
|
state: "unreadable",
|
|
23366
|
-
binPath: (0,
|
|
23685
|
+
binPath: (0, import_node_path27.join)(packageRoot, "package.json"),
|
|
23367
23686
|
expectedMagic: magic.name,
|
|
23368
23687
|
platformPackage: fallbackPackage,
|
|
23369
23688
|
error: `package.json could not be read \u2014 ${e.message}`
|
|
@@ -23380,17 +23699,17 @@ function readClaudeBinaryState(host = {}) {
|
|
|
23380
23699
|
error: "package.json declares no `bin` entry \u2014 the executed file cannot be resolved"
|
|
23381
23700
|
};
|
|
23382
23701
|
}
|
|
23383
|
-
const binPath = (0,
|
|
23384
|
-
const binName = (0,
|
|
23702
|
+
const binPath = (0, import_node_path27.join)(packageRoot, binRelative);
|
|
23703
|
+
const binName = (0, import_node_path27.basename)(binRelative);
|
|
23385
23704
|
const optional = manifest.optionalDependencies;
|
|
23386
23705
|
const publishes = (name) => typeof optional === "object" && optional !== null && Object.prototype.hasOwnProperty.call(optional, name);
|
|
23387
23706
|
const published = keys.map((key) => `${PACKAGE}-${key}`).filter(publishes);
|
|
23388
23707
|
if (published.length === 0) return void 0;
|
|
23389
23708
|
const binIn = (name) => [
|
|
23390
|
-
(0,
|
|
23391
|
-
(0,
|
|
23709
|
+
(0, import_node_path27.join)(packageRoot, "node_modules", ...name.split("/"), binName),
|
|
23710
|
+
(0, import_node_path27.join)((0, import_node_path27.dirname)((0, import_node_path27.dirname)(packageRoot)), ...name.split("/"), binName)
|
|
23392
23711
|
];
|
|
23393
|
-
const found = published.map((name) => ({ name, path: binIn(name).find((file) => (0,
|
|
23712
|
+
const found = published.map((name) => ({ name, path: binIn(name).find((file) => (0, import_node_fs28.existsSync)(file)) })).find((c) => c.path);
|
|
23394
23713
|
const platformPackage = found?.name ?? published[0];
|
|
23395
23714
|
let source;
|
|
23396
23715
|
let sourceProblem;
|
|
@@ -23401,7 +23720,7 @@ function readClaudeBinaryState(host = {}) {
|
|
|
23401
23720
|
} else {
|
|
23402
23721
|
source = { path: found.path, bytes: fileBytes(found.path) ?? 0 };
|
|
23403
23722
|
}
|
|
23404
|
-
if (!(0,
|
|
23723
|
+
if (!(0, import_node_fs28.existsSync)(binPath)) {
|
|
23405
23724
|
return { state: "missing", binPath, expectedMagic: magic.name, platformPackage, ...source ? { source } : {}, ...sourceProblem ? { sourceProblem } : {} };
|
|
23406
23725
|
}
|
|
23407
23726
|
const head = readHead(binPath);
|
|
@@ -23444,9 +23763,9 @@ function healClaudeBinary(host = {}, onStep) {
|
|
|
23444
23763
|
const platform2 = host.platform ?? process.platform;
|
|
23445
23764
|
const aside = `${probe.binPath}.stub-${Date.now()}`;
|
|
23446
23765
|
let renamed = false;
|
|
23447
|
-
if ((0,
|
|
23766
|
+
if ((0, import_node_fs28.existsSync)(probe.binPath)) {
|
|
23448
23767
|
try {
|
|
23449
|
-
(0,
|
|
23768
|
+
(0, import_node_fs28.renameSync)(probe.binPath, aside);
|
|
23450
23769
|
renamed = true;
|
|
23451
23770
|
onStep?.(`renamed the stub aside: ${aside}`);
|
|
23452
23771
|
} catch (e) {
|
|
@@ -23455,12 +23774,12 @@ function healClaudeBinary(host = {}, onStep) {
|
|
|
23455
23774
|
}
|
|
23456
23775
|
try {
|
|
23457
23776
|
onStep?.(`copying ${probe.source.path} \u2192 ${probe.binPath} (${(probe.source.bytes / 1e6).toFixed(0)} MB)`);
|
|
23458
|
-
(0,
|
|
23459
|
-
if (platform2 !== "win32") (0,
|
|
23777
|
+
(0, import_node_fs28.copyFileSync)(probe.source.path, probe.binPath);
|
|
23778
|
+
if (platform2 !== "win32") (0, import_node_fs28.chmodSync)(probe.binPath, 493);
|
|
23460
23779
|
} catch (e) {
|
|
23461
23780
|
if (renamed) {
|
|
23462
23781
|
try {
|
|
23463
|
-
(0,
|
|
23782
|
+
(0, import_node_fs28.renameSync)(aside, probe.binPath);
|
|
23464
23783
|
} catch {
|
|
23465
23784
|
return { ok: false, detail: `copy failed (${e.message}) and the stub could not be restored \u2014 the original is at ${aside}` };
|
|
23466
23785
|
}
|
|
@@ -23474,7 +23793,7 @@ function healClaudeBinary(host = {}, onStep) {
|
|
|
23474
23793
|
let kept = false;
|
|
23475
23794
|
if (renamed) {
|
|
23476
23795
|
try {
|
|
23477
|
-
(0,
|
|
23796
|
+
(0, import_node_fs28.rmSync)(aside);
|
|
23478
23797
|
} catch {
|
|
23479
23798
|
kept = true;
|
|
23480
23799
|
}
|
|
@@ -23952,19 +24271,19 @@ function renderVerifyBroker(input) {
|
|
|
23952
24271
|
|
|
23953
24272
|
// src/tenant-artifact.ts
|
|
23954
24273
|
var import_node_crypto4 = require("node:crypto");
|
|
23955
|
-
var
|
|
24274
|
+
var import_node_fs29 = require("node:fs");
|
|
23956
24275
|
var import_promises5 = require("node:fs/promises");
|
|
23957
|
-
var
|
|
24276
|
+
var import_node_path28 = require("node:path");
|
|
23958
24277
|
var ARTIFACT_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
23959
24278
|
var MAX_BYTES = 5 * 1024 * 1024 * 1024;
|
|
23960
24279
|
async function sha256File(path2) {
|
|
23961
24280
|
const hash = (0, import_node_crypto4.createHash)("sha256");
|
|
23962
|
-
for await (const chunk of (0,
|
|
24281
|
+
for await (const chunk of (0, import_node_fs29.createReadStream)(path2)) hash.update(chunk);
|
|
23963
24282
|
return hash.digest("hex");
|
|
23964
24283
|
}
|
|
23965
24284
|
async function putTenantArtifact(repo, stage, inputPath, deps) {
|
|
23966
24285
|
if (!["dev", "rc", "main"].includes(stage)) throw new Error("tenant artifact put: <stage> must be dev, rc, or main");
|
|
23967
|
-
const path2 = (0,
|
|
24286
|
+
const path2 = (0, import_node_path28.resolve)(inputPath);
|
|
23968
24287
|
const info = await (0, import_promises5.stat)(path2);
|
|
23969
24288
|
if (!info.isFile()) throw new Error("tenant artifact put: input path must be a file");
|
|
23970
24289
|
if (!Number.isSafeInteger(info.size) || info.size < 1 || info.size > MAX_BYTES) throw new Error(`tenant artifact put: file must be 1..${MAX_BYTES} bytes`);
|
|
@@ -23982,7 +24301,7 @@ async function putTenantArtifact(repo, stage, inputPath, deps) {
|
|
|
23982
24301
|
return [key, value];
|
|
23983
24302
|
}));
|
|
23984
24303
|
headers["content-length"] = String(info.size);
|
|
23985
|
-
const stream = (0,
|
|
24304
|
+
const stream = (0, import_node_fs29.createReadStream)(path2);
|
|
23986
24305
|
let uploaded;
|
|
23987
24306
|
try {
|
|
23988
24307
|
uploaded = await fetch(body.uploadUrl, {
|
|
@@ -25100,8 +25419,8 @@ async function announceRelease(deps, args) {
|
|
|
25100
25419
|
// src/repo-index.ts
|
|
25101
25420
|
var import_node_crypto5 = require("node:crypto");
|
|
25102
25421
|
var import_node_child_process12 = require("node:child_process");
|
|
25103
|
-
var
|
|
25104
|
-
var
|
|
25422
|
+
var import_node_fs30 = require("node:fs");
|
|
25423
|
+
var import_node_path29 = require("node:path");
|
|
25105
25424
|
var REPO_INDEX_SCHEMA = 1;
|
|
25106
25425
|
var HARD_DENY = [
|
|
25107
25426
|
/(^|\/)\.env(\.|$)/i,
|
|
@@ -25226,11 +25545,11 @@ function loadReadmeHints(cwd, candidatePaths) {
|
|
|
25226
25545
|
}
|
|
25227
25546
|
for (const rel of readmes) {
|
|
25228
25547
|
if (isHardDeniedPath(rel)) continue;
|
|
25229
|
-
const abs = (0,
|
|
25230
|
-
if (!(0,
|
|
25548
|
+
const abs = (0, import_node_path29.join)(cwd, ...rel.split("/"));
|
|
25549
|
+
if (!(0, import_node_fs30.existsSync)(abs)) continue;
|
|
25231
25550
|
let text;
|
|
25232
25551
|
try {
|
|
25233
|
-
text = (0,
|
|
25552
|
+
text = (0, import_node_fs30.readFileSync)(abs, "utf8");
|
|
25234
25553
|
} catch {
|
|
25235
25554
|
continue;
|
|
25236
25555
|
}
|
|
@@ -25243,7 +25562,7 @@ function loadReadmeHints(cwd, candidatePaths) {
|
|
|
25243
25562
|
return hints;
|
|
25244
25563
|
}
|
|
25245
25564
|
function toPosix(p) {
|
|
25246
|
-
return p.split(
|
|
25565
|
+
return p.split(import_node_path29.sep).join("/");
|
|
25247
25566
|
}
|
|
25248
25567
|
function listCandidatePaths(cwd, exec = import_node_child_process12.execFileSync) {
|
|
25249
25568
|
try {
|
|
@@ -25257,7 +25576,7 @@ function listCandidatePaths(cwd, exec = import_node_child_process12.execFileSync
|
|
|
25257
25576
|
return [];
|
|
25258
25577
|
}
|
|
25259
25578
|
}
|
|
25260
|
-
function rebuildRepoIndex(cwd,
|
|
25579
|
+
function rebuildRepoIndex(cwd, repoSlug3) {
|
|
25261
25580
|
const candidates = listCandidatePaths(cwd).filter(isIndexablePath);
|
|
25262
25581
|
const ignored = defaultIsIgnored(cwd, candidates);
|
|
25263
25582
|
const readmeHints = loadReadmeHints(cwd, candidates.filter((p) => !ignored.has(p)));
|
|
@@ -25265,11 +25584,11 @@ function rebuildRepoIndex(cwd, repoSlug2) {
|
|
|
25265
25584
|
for (const rel of candidates) {
|
|
25266
25585
|
if (ignored.has(rel)) continue;
|
|
25267
25586
|
if (isHardDeniedPath(rel)) continue;
|
|
25268
|
-
const abs = (0,
|
|
25269
|
-
if (!(0,
|
|
25587
|
+
const abs = (0, import_node_path29.join)(cwd, ...rel.split("/"));
|
|
25588
|
+
if (!(0, import_node_fs30.existsSync)(abs)) continue;
|
|
25270
25589
|
let text;
|
|
25271
25590
|
try {
|
|
25272
|
-
text = (0,
|
|
25591
|
+
text = (0, import_node_fs30.readFileSync)(abs, "utf8");
|
|
25273
25592
|
} catch {
|
|
25274
25593
|
continue;
|
|
25275
25594
|
}
|
|
@@ -25289,21 +25608,21 @@ function rebuildRepoIndex(cwd, repoSlug2) {
|
|
|
25289
25608
|
entries.sort((a, b) => a.path.localeCompare(b.path));
|
|
25290
25609
|
const projection = {
|
|
25291
25610
|
schema: REPO_INDEX_SCHEMA,
|
|
25292
|
-
repo:
|
|
25611
|
+
repo: repoSlug3,
|
|
25293
25612
|
builtAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
25294
25613
|
entries
|
|
25295
25614
|
};
|
|
25296
25615
|
const store = repoIndexStorePath(cwd);
|
|
25297
|
-
(0,
|
|
25298
|
-
(0,
|
|
25616
|
+
(0, import_node_fs30.mkdirSync)((0, import_node_path29.dirname)(store), { recursive: true });
|
|
25617
|
+
(0, import_node_fs30.writeFileSync)(store, `${JSON.stringify(projection, null, 2)}
|
|
25299
25618
|
`, "utf8");
|
|
25300
25619
|
return projection;
|
|
25301
25620
|
}
|
|
25302
25621
|
function loadRepoIndex(cwd) {
|
|
25303
25622
|
const store = repoIndexStorePath(cwd);
|
|
25304
|
-
if (!(0,
|
|
25623
|
+
if (!(0, import_node_fs30.existsSync)(store)) return null;
|
|
25305
25624
|
try {
|
|
25306
|
-
const raw = JSON.parse((0,
|
|
25625
|
+
const raw = JSON.parse((0, import_node_fs30.readFileSync)(store, "utf8"));
|
|
25307
25626
|
if (raw?.schema !== REPO_INDEX_SCHEMA || !Array.isArray(raw.entries)) return null;
|
|
25308
25627
|
return raw;
|
|
25309
25628
|
} catch {
|
|
@@ -25375,7 +25694,7 @@ function inferRepoSlug(cwd, exec = import_node_child_process12.execFileSync) {
|
|
|
25375
25694
|
if (m?.[1]) return m[1].toLowerCase();
|
|
25376
25695
|
} catch {
|
|
25377
25696
|
}
|
|
25378
|
-
return ((0,
|
|
25697
|
+
return ((0, import_node_path29.basename)(cwd) || "local").toLowerCase();
|
|
25379
25698
|
}
|
|
25380
25699
|
|
|
25381
25700
|
// src/repo-index-cloud-client.ts
|
|
@@ -25515,9 +25834,9 @@ async function gcRepoIndexCloud(deps) {
|
|
|
25515
25834
|
}
|
|
25516
25835
|
|
|
25517
25836
|
// src/repo-index-sync.ts
|
|
25518
|
-
var
|
|
25519
|
-
var
|
|
25520
|
-
var
|
|
25837
|
+
var import_node_fs31 = require("node:fs");
|
|
25838
|
+
var import_node_os14 = require("node:os");
|
|
25839
|
+
var import_node_path30 = require("node:path");
|
|
25521
25840
|
var import_node_child_process13 = require("node:child_process");
|
|
25522
25841
|
var MAX_EMBED_BACKFILL_ROUNDS = 40;
|
|
25523
25842
|
function normalizeRepo(raw) {
|
|
@@ -25561,7 +25880,7 @@ async function syncEstateRepoIndex(opts) {
|
|
|
25561
25880
|
const failed = [];
|
|
25562
25881
|
const skipped = [];
|
|
25563
25882
|
for (const repo of repos) {
|
|
25564
|
-
const dir = (0,
|
|
25883
|
+
const dir = (0, import_node_fs31.mkdtempSync)((0, import_node_path30.join)((0, import_node_os14.tmpdir)(), "mmi-repo-index-"));
|
|
25565
25884
|
try {
|
|
25566
25885
|
shallowClone(repo, dir, opts.githubToken);
|
|
25567
25886
|
const built = rebuildRepoIndex(dir, repo);
|
|
@@ -25611,7 +25930,7 @@ async function syncEstateRepoIndex(opts) {
|
|
|
25611
25930
|
failed.push({ repo, error: e.message });
|
|
25612
25931
|
} finally {
|
|
25613
25932
|
try {
|
|
25614
|
-
(0,
|
|
25933
|
+
(0, import_node_fs31.rmSync)(dir, { recursive: true, force: true });
|
|
25615
25934
|
} catch {
|
|
25616
25935
|
}
|
|
25617
25936
|
}
|
|
@@ -25620,7 +25939,7 @@ async function syncEstateRepoIndex(opts) {
|
|
|
25620
25939
|
}
|
|
25621
25940
|
|
|
25622
25941
|
// src/repo-index-health.ts
|
|
25623
|
-
var
|
|
25942
|
+
var import_node_fs32 = require("node:fs");
|
|
25624
25943
|
|
|
25625
25944
|
// testdata/repo-index-golden-queries.json
|
|
25626
25945
|
var repo_index_golden_queries_default = {
|
|
@@ -25662,7 +25981,7 @@ function assertGoldenSuite(raw, source) {
|
|
|
25662
25981
|
function loadGoldenSuite(path2) {
|
|
25663
25982
|
let text;
|
|
25664
25983
|
try {
|
|
25665
|
-
text = (0,
|
|
25984
|
+
text = (0, import_node_fs32.readFileSync)(path2, "utf8");
|
|
25666
25985
|
} catch (e) {
|
|
25667
25986
|
throw new Error(`golden suite unreadable at ${path2}: ${e.message}`);
|
|
25668
25987
|
}
|
|
@@ -25816,8 +26135,8 @@ async function runRepoIndexHealth(opts) {
|
|
|
25816
26135
|
|
|
25817
26136
|
// src/spawn-policy-core.ts
|
|
25818
26137
|
var import_node_child_process14 = require("node:child_process");
|
|
25819
|
-
var
|
|
25820
|
-
var
|
|
26138
|
+
var import_node_fs33 = require("node:fs");
|
|
26139
|
+
var import_node_path31 = require("node:path");
|
|
25821
26140
|
var SPAWNERS = ["spawn", "spawnSync", "exec", "execSync", "execFile", "execFileSync"];
|
|
25822
26141
|
var CALL_SOURCE = String.raw`(^|[^.\w$])(${SPAWNERS.join("|")})\s*\(`;
|
|
25823
26142
|
var SOURCE_EXT = /\.(ts|mts|cts|js|mjs|cjs)$/;
|
|
@@ -25903,7 +26222,7 @@ function runSpawnPolicy(root) {
|
|
|
25903
26222
|
for (const file of files) {
|
|
25904
26223
|
let raw;
|
|
25905
26224
|
try {
|
|
25906
|
-
raw = (0,
|
|
26225
|
+
raw = (0, import_node_fs33.readFileSync)((0, import_node_path31.join)(root, file), "utf8");
|
|
25907
26226
|
} catch {
|
|
25908
26227
|
continue;
|
|
25909
26228
|
}
|
|
@@ -25921,8 +26240,8 @@ function runSpawnPolicy(root) {
|
|
|
25921
26240
|
|
|
25922
26241
|
// src/test-policy-core.ts
|
|
25923
26242
|
var import_node_child_process15 = require("node:child_process");
|
|
25924
|
-
var
|
|
25925
|
-
var
|
|
26243
|
+
var import_node_fs34 = require("node:fs");
|
|
26244
|
+
var import_node_path32 = require("node:path");
|
|
25926
26245
|
var POLICY_FILE = "test-policy.json";
|
|
25927
26246
|
var TEST_RE = /\.(?:test|spec)\.[cm]?[jt]sx?$/;
|
|
25928
26247
|
var PY_TEST_RE = /(?:^|\/)test_[^/]*\.py$|_test\.py$/;
|
|
@@ -25975,7 +26294,7 @@ function isTestPath(path2) {
|
|
|
25975
26294
|
return TEST_RE.test(path2) || PY_TEST_RE.test(path2);
|
|
25976
26295
|
}
|
|
25977
26296
|
function loadPolicy(root, readFile9 = readFileOrNull2) {
|
|
25978
|
-
const raw = readFile9((0,
|
|
26297
|
+
const raw = readFile9((0, import_node_path32.join)(root, POLICY_FILE));
|
|
25979
26298
|
if (raw == null) return { mandatory: [], declared: false };
|
|
25980
26299
|
try {
|
|
25981
26300
|
return { ...JSON.parse(raw), declared: true };
|
|
@@ -25985,7 +26304,7 @@ function loadPolicy(root, readFile9 = readFileOrNull2) {
|
|
|
25985
26304
|
}
|
|
25986
26305
|
function readFileOrNull2(path2) {
|
|
25987
26306
|
try {
|
|
25988
|
-
return (0,
|
|
26307
|
+
return (0, import_node_fs34.readFileSync)(path2, "utf8");
|
|
25989
26308
|
} catch {
|
|
25990
26309
|
return null;
|
|
25991
26310
|
}
|
|
@@ -26012,12 +26331,12 @@ function classify(changed, policy, present = () => false) {
|
|
|
26012
26331
|
const removedProtected = [...removed].filter((p) => protectedBy.has(p)).map((p) => ({ path: p, why: protectedBy.get(p) ?? "" }));
|
|
26013
26332
|
return { mandatoryHits, untestedHits, testChanges, addedTests, removedProtected };
|
|
26014
26333
|
}
|
|
26015
|
-
function unresolvedProtectedEntries(policy, root, exists = (path2) => (0,
|
|
26016
|
-
return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0,
|
|
26334
|
+
function unresolvedProtectedEntries(policy, root, exists = (path2) => (0, import_node_fs34.existsSync)(path2)) {
|
|
26335
|
+
return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0, import_node_path32.join)(root, p)));
|
|
26017
26336
|
}
|
|
26018
|
-
function unresolvedSatisfiers(policy, root, exists = (path2) => (0,
|
|
26337
|
+
function unresolvedSatisfiers(policy, root, exists = (path2) => (0, import_node_fs34.existsSync)(path2)) {
|
|
26019
26338
|
const declared = (policy.mandatory ?? []).flatMap((m) => m.satisfiedBy ?? []);
|
|
26020
|
-
return [...new Set(declared)].filter((p) => !exists((0,
|
|
26339
|
+
return [...new Set(declared)].filter((p) => !exists((0, import_node_path32.join)(root, p)));
|
|
26021
26340
|
}
|
|
26022
26341
|
function evaluate(changed, policy, present = () => false) {
|
|
26023
26342
|
const { mandatoryHits, untestedHits, testChanges, addedTests, removedProtected } = classify(changed, policy, present);
|
|
@@ -26199,13 +26518,13 @@ function changedFilesSince(base, cwd) {
|
|
|
26199
26518
|
}
|
|
26200
26519
|
function runTestPolicy(root, deps = {}) {
|
|
26201
26520
|
const policy = deps.policy ?? loadPolicy(root);
|
|
26202
|
-
const exists = deps.exists ?? ((path2) => (0,
|
|
26521
|
+
const exists = deps.exists ?? ((path2) => (0, import_node_fs34.existsSync)(path2));
|
|
26203
26522
|
const counts = { mandatoryCount: (policy.mandatory ?? []).length, protectedCount: (policy.protected ?? []).length };
|
|
26204
26523
|
const base = deps.changed ? "(injected)" : resolveBase(root, deps.base);
|
|
26205
26524
|
const refusal = deps.changed ? null : untrustworthyRange(root, base);
|
|
26206
26525
|
const changed = deps.changed ?? (refusal ? [] : changedFilesSince(base, root));
|
|
26207
26526
|
const lookup = deps.override !== void 0 ? { override: deps.override, refusals: [] } : !deps.changed && !refusal ? readOverride(base, root) : { override: null, refusals: [] };
|
|
26208
|
-
const present = (path2) => exists((0,
|
|
26527
|
+
const present = (path2) => exists((0, import_node_path32.join)(root, path2));
|
|
26209
26528
|
const removedByThisDiff = removedPaths(changed);
|
|
26210
26529
|
const staleFindings = [];
|
|
26211
26530
|
const unresolved = unresolvedProtectedEntries(policy, root, exists).filter((p) => !removedByThisDiff.has(p));
|
|
@@ -26243,8 +26562,8 @@ function runTestPolicy(root, deps = {}) {
|
|
|
26243
26562
|
}
|
|
26244
26563
|
|
|
26245
26564
|
// src/project-info-sync.ts
|
|
26246
|
-
var
|
|
26247
|
-
var
|
|
26565
|
+
var import_node_fs35 = require("node:fs");
|
|
26566
|
+
var import_node_path33 = require("node:path");
|
|
26248
26567
|
var UPDATE_PROJECT_INFO = `mutation($projectId: ID!, $shortDescription: String!, $readme: String!) {
|
|
26249
26568
|
updateProjectV2(input: { projectId: $projectId, shortDescription: $shortDescription, readme: $readme }) {
|
|
26250
26569
|
projectV2 { id }
|
|
@@ -26289,14 +26608,14 @@ function sharedName(entries, fallback) {
|
|
|
26289
26608
|
}
|
|
26290
26609
|
function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
|
|
26291
26610
|
if (!project2.projectId) throw new Error(`org project sync-info: ${targetRepo2} registry META has no projectId`);
|
|
26292
|
-
const readmePath = (0,
|
|
26293
|
-
if (!(0,
|
|
26611
|
+
const readmePath = (0, import_node_path33.join)(repoRoot2, "README.md");
|
|
26612
|
+
if (!(0, import_node_fs35.existsSync)(readmePath)) throw new Error(`org project sync-info: ${targetRepo2} has no README.md`);
|
|
26294
26613
|
const entries = entriesFor(project2, projects);
|
|
26295
26614
|
const memberRepos = [...new Set(entries.flatMap((entry) => entry.repos ?? []))].filter((repo) => /^[^/]+\/[^/]+$/.test(repo)).sort((a, b) => a.localeCompare(b));
|
|
26296
26615
|
const projectName = sharedName(entries, project2.name?.trim() || targetRepo2.split("/").pop() || targetRepo2);
|
|
26297
26616
|
if (!memberRepos.length) throw new Error(`org project sync-info: project ${projectName} has no registered member repos`);
|
|
26298
26617
|
const entryNames = entries.map((entry) => entry.name?.trim()).filter((name) => Boolean(name));
|
|
26299
|
-
const shortDescription = memberRepos.length === 1 ? shortDescriptionFromReadme((0,
|
|
26618
|
+
const shortDescription = memberRepos.length === 1 ? shortDescriptionFromReadme((0, import_node_fs35.readFileSync)(readmePath, "utf8")) : `Shared work across ${new Intl.ListFormat("en", { type: "conjunction" }).format(entryNames)}.`;
|
|
26300
26619
|
const lines = [
|
|
26301
26620
|
`# ${projectName}`,
|
|
26302
26621
|
"",
|
|
@@ -26315,8 +26634,8 @@ function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
|
|
|
26315
26634
|
const targetBase = `https://github.com/${targetRepo2}`;
|
|
26316
26635
|
const targetBranch = branchFor(targetRepo2, projects);
|
|
26317
26636
|
const orgDocs = [
|
|
26318
|
-
(0,
|
|
26319
|
-
(0,
|
|
26637
|
+
(0, import_node_fs35.existsSync)((0, import_node_path33.join)(repoRoot2, "docs", "org-readme.md")) ? `- [Org identity](${targetBase}/blob/${targetBranch}/docs/org-readme.md)` : "",
|
|
26638
|
+
(0, import_node_fs35.existsSync)((0, import_node_path33.join)(repoRoot2, "docs", "org-architecture.md")) ? `- [Org architecture](${targetBase}/blob/${targetBranch}/docs/org-architecture.md)` : ""
|
|
26320
26639
|
].filter(Boolean);
|
|
26321
26640
|
if (orgDocs.length) lines.push("", "## Organisation docs", "", ...orgDocs);
|
|
26322
26641
|
return { projectId: project2.projectId, projectName, targetRepo: targetRepo2, memberRepos, shortDescription, readme: `${lines.join("\n")}
|
|
@@ -27142,9 +27461,9 @@ function writeError(res) {
|
|
|
27142
27461
|
}
|
|
27143
27462
|
|
|
27144
27463
|
// src/secrets-commands.ts
|
|
27145
|
-
var
|
|
27146
|
-
var
|
|
27147
|
-
var
|
|
27464
|
+
var import_node_fs36 = require("node:fs");
|
|
27465
|
+
var import_node_path34 = require("node:path");
|
|
27466
|
+
var import_node_os15 = require("node:os");
|
|
27148
27467
|
|
|
27149
27468
|
// src/secrets-diff.ts
|
|
27150
27469
|
var TIMEOUT_MS2 = 8e3;
|
|
@@ -27246,18 +27565,18 @@ function collectMap(value, previous = []) {
|
|
|
27246
27565
|
return [...previous, value];
|
|
27247
27566
|
}
|
|
27248
27567
|
async function decryptRailsCredentials(input) {
|
|
27249
|
-
const appDir = (0,
|
|
27568
|
+
const appDir = (0, import_node_path34.resolve)(input.appDir ?? process.cwd());
|
|
27250
27569
|
const credentialsFile = input.credentialsFile ?? DEFAULT_RAILS_CREDENTIALS_FILE;
|
|
27251
27570
|
const masterKeyFile = input.masterKeyFile ?? DEFAULT_RAILS_MASTER_KEY_FILE;
|
|
27252
|
-
const credentialsPath = (0,
|
|
27253
|
-
const masterKeyPath = (0,
|
|
27571
|
+
const credentialsPath = (0, import_node_path34.resolve)(appDir, credentialsFile);
|
|
27572
|
+
const masterKeyPath = (0, import_node_path34.resolve)(appDir, masterKeyFile);
|
|
27254
27573
|
const env = {
|
|
27255
27574
|
...process.env,
|
|
27256
27575
|
MMI_RAILS_CREDENTIALS_FILE: credentialsPath,
|
|
27257
27576
|
MMI_RAILS_MASTER_KEY_FILE: masterKeyPath
|
|
27258
27577
|
};
|
|
27259
|
-
if ((0,
|
|
27260
|
-
env.RAILS_MASTER_KEY = (0,
|
|
27578
|
+
if ((0, import_node_fs36.existsSync)(masterKeyPath)) {
|
|
27579
|
+
env.RAILS_MASTER_KEY = (0, import_node_fs36.readFileSync)(masterKeyPath, "utf8").trim();
|
|
27261
27580
|
}
|
|
27262
27581
|
const script = [
|
|
27263
27582
|
'require "json"',
|
|
@@ -27267,9 +27586,9 @@ async function decryptRailsCredentials(input) {
|
|
|
27267
27586
|
'config = ActiveSupport::EncryptedConfiguration.new(config_path: config_path, key_path: key_path, env_key: "RAILS_MASTER_KEY", raise_if_missing_key: true)',
|
|
27268
27587
|
"puts JSON.generate(config.config)"
|
|
27269
27588
|
].join("\n");
|
|
27270
|
-
const scriptDir = (0,
|
|
27271
|
-
const scriptPath = (0,
|
|
27272
|
-
(0,
|
|
27589
|
+
const scriptDir = (0, import_node_fs36.mkdtempSync)((0, import_node_path34.join)((0, import_node_os15.tmpdir)(), "mmi-rails-decrypt-"));
|
|
27590
|
+
const scriptPath = (0, import_node_path34.join)(scriptDir, "decrypt.rb");
|
|
27591
|
+
(0, import_node_fs36.writeFileSync)(scriptPath, script, "utf8");
|
|
27273
27592
|
try {
|
|
27274
27593
|
const args = ["exec", "ruby", scriptPath];
|
|
27275
27594
|
const cmd = process.platform === "win32" ? "cmd.exe" : "bundle";
|
|
@@ -27281,7 +27600,7 @@ async function decryptRailsCredentials(input) {
|
|
|
27281
27600
|
});
|
|
27282
27601
|
return JSON.parse(stdout);
|
|
27283
27602
|
} finally {
|
|
27284
|
-
(0,
|
|
27603
|
+
(0, import_node_fs36.rmSync)(scriptDir, { recursive: true, force: true });
|
|
27285
27604
|
}
|
|
27286
27605
|
}
|
|
27287
27606
|
async function readSecretStdin() {
|
|
@@ -27371,7 +27690,7 @@ function registerSecretsCommands(program3) {
|
|
|
27371
27690
|
let body;
|
|
27372
27691
|
if (o.file) {
|
|
27373
27692
|
try {
|
|
27374
|
-
body = (0,
|
|
27693
|
+
body = (0, import_node_fs36.readFileSync)((0, import_node_path34.resolve)(o.file), "utf8");
|
|
27375
27694
|
} catch (e) {
|
|
27376
27695
|
return fail(`secrets org-catalog: cannot read --file ${o.file}: ${e.message}`);
|
|
27377
27696
|
}
|
|
@@ -27476,7 +27795,7 @@ function registerSecretsCommands(program3) {
|
|
|
27476
27795
|
{
|
|
27477
27796
|
...d,
|
|
27478
27797
|
decryptRailsCredentials,
|
|
27479
|
-
removeFile: (path2) => (0,
|
|
27798
|
+
removeFile: (path2) => (0, import_node_fs36.unlinkSync)((0, import_node_path34.resolve)(o.appDir ?? process.cwd(), path2))
|
|
27480
27799
|
},
|
|
27481
27800
|
{
|
|
27482
27801
|
repo: o.repo,
|
|
@@ -27681,7 +28000,7 @@ function emitCliCallTelemetry(command) {
|
|
|
27681
28000
|
}
|
|
27682
28001
|
|
|
27683
28002
|
// src/box-commands.ts
|
|
27684
|
-
var
|
|
28003
|
+
var import_node_fs37 = require("node:fs");
|
|
27685
28004
|
|
|
27686
28005
|
// src/box.ts
|
|
27687
28006
|
var BOX_KEYS = {
|
|
@@ -27884,7 +28203,7 @@ function registerBoxCommands(program3) {
|
|
|
27884
28203
|
}
|
|
27885
28204
|
if (o.json) console.log(JSON.stringify({ box: found, incomplete }, null, 2));
|
|
27886
28205
|
else if (o.ssh && o.script) {
|
|
27887
|
-
(0,
|
|
28206
|
+
(0, import_node_fs37.writeFileSync)(o.script, sshRecipeScript(found), "utf8");
|
|
27888
28207
|
console.log(`wrote ${o.script} \u2014 run: bash "${o.script}"`);
|
|
27889
28208
|
} else if (o.ssh) console.log(`${formatSshRecipe(found)}
|
|
27890
28209
|
${SSH_RECIPE_AGENT_NOTE}`);
|
|
@@ -28203,7 +28522,7 @@ function registerSchedulesCommands(program3) {
|
|
|
28203
28522
|
|
|
28204
28523
|
// src/schedules-lift-command.ts
|
|
28205
28524
|
var import_promises7 = require("node:fs/promises");
|
|
28206
|
-
var
|
|
28525
|
+
var import_node_path35 = require("node:path");
|
|
28207
28526
|
var DEFAULT_WORKFLOWS_DIR = ".github/workflows";
|
|
28208
28527
|
var SCHEDULE_REPO_RE = /^[A-Za-z0-9_.-]+$/;
|
|
28209
28528
|
var SchedulesLiftUsageError = class extends Error {
|
|
@@ -28230,7 +28549,7 @@ async function readWorkflowFiles(dir) {
|
|
|
28230
28549
|
const files = [];
|
|
28231
28550
|
for (const name of names.sort()) {
|
|
28232
28551
|
if (!/\.ya?ml$/.test(name)) continue;
|
|
28233
|
-
files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises7.readFile)((0,
|
|
28552
|
+
files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises7.readFile)((0, import_node_path35.join)(dir, name), "utf8") });
|
|
28234
28553
|
}
|
|
28235
28554
|
return files;
|
|
28236
28555
|
}
|
|
@@ -28378,9 +28697,9 @@ function registerEdgeCommands(program3) {
|
|
|
28378
28697
|
}
|
|
28379
28698
|
|
|
28380
28699
|
// src/bootstrap-commands.ts
|
|
28381
|
-
var
|
|
28382
|
-
var
|
|
28383
|
-
var
|
|
28700
|
+
var import_node_fs38 = require("node:fs");
|
|
28701
|
+
var import_node_os16 = require("node:os");
|
|
28702
|
+
var import_node_path36 = require("node:path");
|
|
28384
28703
|
|
|
28385
28704
|
// src/bootstrap-drift.ts
|
|
28386
28705
|
var import_node_crypto7 = require("node:crypto");
|
|
@@ -28565,6 +28884,71 @@ function renderPropagationReport(plan) {
|
|
|
28565
28884
|
return lines.join("\n");
|
|
28566
28885
|
}
|
|
28567
28886
|
|
|
28887
|
+
// src/bootstrap-propagation-identity.ts
|
|
28888
|
+
var import_node_crypto8 = require("node:crypto");
|
|
28889
|
+
var PROPAGATION_BRANCH_PREFIX = "seed-propagate-";
|
|
28890
|
+
var TARGET_MARKER_NAME = "mmi-bootstrap-propagation-target";
|
|
28891
|
+
function safeBranchPart(value, maxLength, fallback) {
|
|
28892
|
+
const safe = value.normalize("NFKD").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, maxLength).replace(/-+$/g, "");
|
|
28893
|
+
return safe || fallback;
|
|
28894
|
+
}
|
|
28895
|
+
function repoSlug2(repo) {
|
|
28896
|
+
return repo.trim().split("/").pop()?.toLowerCase() || "repo";
|
|
28897
|
+
}
|
|
28898
|
+
function propagationBranch(repo, target) {
|
|
28899
|
+
const repoPart = safeBranchPart(repoSlug2(repo), 32, "repo");
|
|
28900
|
+
const targetPart = safeBranchPart(target, 48, "target");
|
|
28901
|
+
const hash = (0, import_node_crypto8.createHash)("sha256").update(repo.trim().toLowerCase()).update("\0").update(target).digest("hex").slice(0, 12);
|
|
28902
|
+
return `${PROPAGATION_BRANCH_PREFIX}${repoPart}-${targetPart}-${hash}`;
|
|
28903
|
+
}
|
|
28904
|
+
function legacyPropagationBranch(repo) {
|
|
28905
|
+
return `${PROPAGATION_BRANCH_PREFIX}${repoSlug2(repo)}`;
|
|
28906
|
+
}
|
|
28907
|
+
function renderPropagationTargetMarker(target) {
|
|
28908
|
+
return `<!-- ${TARGET_MARKER_NAME}: ${JSON.stringify(target)} -->`;
|
|
28909
|
+
}
|
|
28910
|
+
function targetMarkers(body) {
|
|
28911
|
+
const present = body.includes(`${TARGET_MARKER_NAME}:`);
|
|
28912
|
+
const targets = [];
|
|
28913
|
+
const marker = new RegExp(`<!--\\s*${TARGET_MARKER_NAME}:\\s*(.*?)\\s*-->`, "g");
|
|
28914
|
+
for (const match of body.matchAll(marker)) {
|
|
28915
|
+
try {
|
|
28916
|
+
const parsed = JSON.parse(match[1]);
|
|
28917
|
+
if (typeof parsed === "string") targets.push(parsed);
|
|
28918
|
+
} catch {
|
|
28919
|
+
}
|
|
28920
|
+
}
|
|
28921
|
+
return { present, targets };
|
|
28922
|
+
}
|
|
28923
|
+
function canonicalProseTargets(body) {
|
|
28924
|
+
const targets = [];
|
|
28925
|
+
const command = /bootstrap propagate --target (.+?) --execute`/g;
|
|
28926
|
+
const prose = /(?:org-owned copy of|declared marker-bounded block inside repo-owned) `([^`]+)` — the file this PR carries and nothing else/g;
|
|
28927
|
+
for (const match of body.matchAll(command)) targets.push(match[1]);
|
|
28928
|
+
for (const match of body.matchAll(prose)) targets.push(match[1]);
|
|
28929
|
+
return targets;
|
|
28930
|
+
}
|
|
28931
|
+
function propagationPrMatchesTarget(pr2, target, identity) {
|
|
28932
|
+
const body = pr2.body ?? "";
|
|
28933
|
+
const markers = targetMarkers(body);
|
|
28934
|
+
if (markers.present) {
|
|
28935
|
+
if (markers.targets.length !== 1 || markers.targets[0] !== target) return false;
|
|
28936
|
+
return !pr2.files?.length || pr2.files.length === 1 && pr2.files[0] === target;
|
|
28937
|
+
}
|
|
28938
|
+
if (identity === "target-specific") return false;
|
|
28939
|
+
const proseTargets = canonicalProseTargets(body);
|
|
28940
|
+
if (proseTargets.some((candidate) => candidate !== target)) return false;
|
|
28941
|
+
if (pr2.files?.length) {
|
|
28942
|
+
return pr2.files.length === 1 && pr2.files[0] === target;
|
|
28943
|
+
}
|
|
28944
|
+
return proseTargets.length > 0;
|
|
28945
|
+
}
|
|
28946
|
+
function propagationPrCandidatesForTarget(target, targetSpecificPrs, legacyPrs) {
|
|
28947
|
+
const targetSpecific = targetSpecificPrs.filter((pr2) => propagationPrMatchesTarget(pr2, target, "target-specific"));
|
|
28948
|
+
if (targetSpecific.length) return targetSpecific;
|
|
28949
|
+
return legacyPrs.filter((pr2) => propagationPrMatchesTarget(pr2, target, "legacy"));
|
|
28950
|
+
}
|
|
28951
|
+
|
|
28568
28952
|
// src/bootstrap-rollback.ts
|
|
28569
28953
|
function resolveRollbackRecord(records, repo, target) {
|
|
28570
28954
|
const candidates = records.filter((r) => r.repo === repo && r.target === target && r.mergeSha);
|
|
@@ -29260,13 +29644,13 @@ function registerBootstrapCommands(program3) {
|
|
|
29260
29644
|
client: defaultGitHubClient(),
|
|
29261
29645
|
projectMeta: meta,
|
|
29262
29646
|
deployModel: typeof meta?.deployModel === "string" ? meta.deployModel : void 0,
|
|
29263
|
-
readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0,
|
|
29647
|
+
readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs38.existsSync)(path2) ? (0, import_node_fs38.readFileSync)(path2, "utf8") : null,
|
|
29264
29648
|
// requiredGcpApis is stored as an array by a JSON write, but `org project set --var KEY=VALUE` stores a raw
|
|
29265
29649
|
// comma-string — accept either so the seeded value verifies regardless of how it was written.
|
|
29266
29650
|
// #3689: the same committed map the org access audit reads (#3664), so a sanctioned admin is not a
|
|
29267
29651
|
// permanent bootstrap failure on one surface and an intended state on the other. Absent file → no
|
|
29268
29652
|
// sanction, which is the pre-#3664 behaviour.
|
|
29269
|
-
sanctionedAdmins: (0,
|
|
29653
|
+
sanctionedAdmins: (0, import_node_fs38.existsSync)("access-matrix.json") ? entriesValueByCanonicalRepo(loadSanctionedAdmins((0, import_node_fs38.readFileSync)("access-matrix.json", "utf8")), repo) : void 0,
|
|
29270
29654
|
requiredGcpApis: (() => {
|
|
29271
29655
|
const v = meta?.requiredGcpApis;
|
|
29272
29656
|
if (Array.isArray(v)) return v;
|
|
@@ -29319,14 +29703,14 @@ function registerBootstrapCommands(program3) {
|
|
|
29319
29703
|
bootstrap.command("drift").description("#3818: compare every org-owned whole-file seed against MMI-Hub's copy across the registry roster; read-only").option("--repo <owner/repo>", "audit one repo instead of the roster (never a fleet verdict)").option("--json", "machine-readable output").action(async () => {
|
|
29320
29704
|
const o = { repo: rawValue("--repo", ""), json: rawFlag("--json") };
|
|
29321
29705
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
29322
|
-
if (!(0,
|
|
29706
|
+
if (!(0, import_node_fs38.existsSync)(manifestPath)) return fail(`bootstrap drift: ${manifestPath} not found; run from the MMI-Hub repo root \u2014 the Hub's copies ARE the reference this compares against`);
|
|
29323
29707
|
const seedSource = await resolveHubSeedSource(execGitForSeedSource);
|
|
29324
29708
|
if (!seedSource.ok) return fail(`bootstrap drift: ${seedSource.reason}`);
|
|
29325
|
-
const manifest = loadBootstrapSeeds((0,
|
|
29709
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs38.readFileSync)(manifestPath, "utf8"));
|
|
29326
29710
|
const hubContents = /* @__PURE__ */ new Map();
|
|
29327
29711
|
for (const s of manifest.seeds) {
|
|
29328
29712
|
if (s.ownership !== "org" || s.source !== "self") continue;
|
|
29329
|
-
hubContents.set(s.target, (0,
|
|
29713
|
+
hubContents.set(s.target, (0, import_node_fs38.existsSync)(s.target) ? (0, import_node_fs38.readFileSync)(s.target, "utf8") : null);
|
|
29330
29714
|
}
|
|
29331
29715
|
let targets;
|
|
29332
29716
|
let classOf = (_repo) => "deployable";
|
|
@@ -29405,10 +29789,10 @@ function registerBootstrapCommands(program3) {
|
|
|
29405
29789
|
return fail(`bootstrap apply: ${e.message}`);
|
|
29406
29790
|
}
|
|
29407
29791
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
29408
|
-
if (!(0,
|
|
29792
|
+
if (!(0, import_node_fs38.existsSync)(manifestPath)) return fail(`bootstrap apply: ${manifestPath} not found; bootstrap runs from the MMI-Hub repo root by design \u2014 it stamps org-level resources (Project, Ruleset, secrets, access) through the GitHub App, which is only authorized from the Hub checkout`);
|
|
29409
29793
|
const seedSource = await resolveHubSeedSource(execGitForSeedSource);
|
|
29410
29794
|
if (!seedSource.ok) return fail(`bootstrap apply: ${seedSource.reason}`);
|
|
29411
|
-
const manifest = loadBootstrapSeeds((0,
|
|
29795
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs38.readFileSync)(manifestPath, "utf8"));
|
|
29412
29796
|
const baseBranch = o.class === "content" ? "main" : "development";
|
|
29413
29797
|
const slug = parsedRepo.slug;
|
|
29414
29798
|
const onlyTarget = o.only.trim();
|
|
@@ -29418,17 +29802,18 @@ function registerBootstrapCommands(program3) {
|
|
|
29418
29802
|
return fail(`bootstrap apply: --only '${onlyTarget}' names no seed in ${manifestPath}. Declared targets:
|
|
29419
29803
|
${known}`);
|
|
29420
29804
|
}
|
|
29805
|
+
const onlyManagedBlock = onlyTarget ? seedsToApply[0]?.managedBlock != null : false;
|
|
29421
29806
|
const gh = async (args) => execFileP2("gh", args, { timeout: 2e4 });
|
|
29422
|
-
const readFile9 = (p) => (0,
|
|
29807
|
+
const readFile9 = (p) => (0, import_node_fs38.existsSync)(p) ? (0, import_node_fs38.readFileSync)(p, "utf8") : null;
|
|
29423
29808
|
const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
|
|
29424
29809
|
const putSeed = async (target, content, ref, sha) => {
|
|
29425
|
-
const tmp = (0,
|
|
29426
|
-
(0,
|
|
29810
|
+
const tmp = (0, import_node_path36.join)((0, import_node_os16.tmpdir)(), `mmi-seed-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
|
|
29811
|
+
(0, import_node_fs38.writeFileSync)(tmp, JSON.stringify(contentPutBody(target, content, ref, sha)), "utf8");
|
|
29427
29812
|
try {
|
|
29428
29813
|
await gh(contentPutInputArgs(repo, target, tmp));
|
|
29429
29814
|
} finally {
|
|
29430
29815
|
try {
|
|
29431
|
-
(0,
|
|
29816
|
+
(0, import_node_fs38.unlinkSync)(tmp);
|
|
29432
29817
|
} catch {
|
|
29433
29818
|
}
|
|
29434
29819
|
}
|
|
@@ -29525,9 +29910,22 @@ function registerBootstrapCommands(program3) {
|
|
|
29525
29910
|
exists = false;
|
|
29526
29911
|
}
|
|
29527
29912
|
const planned = planSeedAction(resolved, exists);
|
|
29528
|
-
const
|
|
29529
|
-
|
|
29530
|
-
|
|
29913
|
+
const isLegacyBlock = resolved.source === "managed-block";
|
|
29914
|
+
let content = null;
|
|
29915
|
+
let isManaged = isLegacyBlock || resolved.managedBlock != null;
|
|
29916
|
+
if (planned.action === "create" || planned.action === "update") {
|
|
29917
|
+
if (isLegacyBlock) {
|
|
29918
|
+
content = upsertManagedGitignoreBlock(remoteContent).content;
|
|
29919
|
+
} else {
|
|
29920
|
+
const writeContent = resolveSeedWriteContent(resolved, vars, readFile9, remoteContent);
|
|
29921
|
+
if (!writeContent.ok) {
|
|
29922
|
+
return fail(`bootstrap apply: ${resolved.target}: ${writeContent.reason} \u2014 refusing to overwrite repo-owned content`);
|
|
29923
|
+
}
|
|
29924
|
+
content = writeContent.content;
|
|
29925
|
+
isManaged = writeContent.managed;
|
|
29926
|
+
}
|
|
29927
|
+
}
|
|
29928
|
+
const action = reconcileSeedAction(planned, content, isManaged, remoteContent);
|
|
29531
29929
|
actions.push(action);
|
|
29532
29930
|
const docBody = content ?? remoteContent;
|
|
29533
29931
|
if (resolved.target.startsWith("docs/") && resolved.target.endsWith(".md") && docBody !== null) {
|
|
@@ -29580,11 +29978,11 @@ function registerBootstrapCommands(program3) {
|
|
|
29580
29978
|
"--head",
|
|
29581
29979
|
seedPlan.branch,
|
|
29582
29980
|
"--title",
|
|
29583
|
-
onlyTarget ? `chore: propagate org-owned ${onlyTarget} from MMI-Hub` : `bootstrap: seed ${parsedRepo.name} (${baseBranch} is protected)`,
|
|
29981
|
+
onlyTarget ? `chore: propagate ${onlyManagedBlock ? "Hub-managed block in" : "org-owned"} ${onlyTarget} from MMI-Hub` : `bootstrap: seed ${parsedRepo.name} (${baseBranch} is protected)`,
|
|
29584
29982
|
"--body",
|
|
29585
29983
|
onlyTarget ? `Auto-opened by \`mmi-cli devops bootstrap apply ${repo} --only ${onlyTarget} --execute\` (#3818).
|
|
29586
29984
|
|
|
29587
|
-
\`${onlyTarget}
|
|
29985
|
+
${onlyManagedBlock ? `Only the marker-bounded Hub-managed block inside repo-owned \`${onlyTarget}\`` : `The org-owned \`${onlyTarget}\` seed`} is declared by MMI-Hub's \`skills/bootstrap/seeds/manifest.json\` at MMI-Hub@${seedSource.sha}; this PR reconciles that declared scope and preserves everything outside it. It carries that file and nothing else \u2014 no labels, ruleset, merge settings or registry META were touched.
|
|
29588
29986
|
|
|
29589
29987
|
\`${baseBranch}\` is protected (${seedPlan.reason}), so delivery goes via this branch + PR \u2014 a direct contents PUT 409s on a protected base.` : `Auto-opened by \`mmi-cli devops bootstrap apply --execute ${repo}\` (#2286): \`${baseBranch}\` is protected (${seedPlan.reason}), so the org-managed seed files are delivered via this branch + PR \u2014 a direct contents PUT 409s on a protected base ("N of N required status checks are expected"). Seeds are from MMI-Hub@${seedSource.sha}.`
|
|
29590
29988
|
]);
|
|
@@ -29690,23 +30088,24 @@ LIVE apply to ${repo}:
|
|
|
29690
30088
|
${applied.join("\n ")}`);
|
|
29691
30089
|
}
|
|
29692
30090
|
});
|
|
29693
|
-
bootstrap.command("propagate").description("#4238: re-entrant canary\u2192wave tick \u2014 plan (or, with --execute, open) per-repo PRs fanning an org-owned seed out to the fleet").option("--target <path>", "the manifest target to propagate (an
|
|
30091
|
+
bootstrap.command("propagate").description("#4238: re-entrant canary\u2192wave tick \u2014 plan (or, with --execute, open) per-repo PRs fanning an org-owned seed out to the fleet").option("--target <path>", "the manifest target to propagate (an org-owned whole file or declared Hub-managed block)").option("--execute", "LIVE tick via gh (master-gated) \u2014 opens/reuses per-repo seed-propagate PRs; dry-run prints the plan only").option("--json", "machine-readable output").action(async () => {
|
|
29694
30092
|
const o = { target: rawValue("--target", ""), execute: rawFlag("--execute"), json: rawFlag("--json") };
|
|
29695
30093
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
29696
|
-
if (!(0,
|
|
30094
|
+
if (!(0, import_node_fs38.existsSync)(manifestPath)) return fail(`bootstrap propagate: ${manifestPath} not found; run from the MMI-Hub repo root \u2014 the Hub's copies ARE the desired state this tick propagates`);
|
|
29697
30095
|
const seedSource = await resolveHubSeedSource(execGitForSeedSource);
|
|
29698
30096
|
if (!seedSource.ok) return fail(`bootstrap propagate: ${seedSource.reason}`);
|
|
29699
|
-
const manifest = loadBootstrapSeeds((0,
|
|
29700
|
-
const propagatable = manifest.seeds.filter(
|
|
30097
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs38.readFileSync)(manifestPath, "utf8"));
|
|
30098
|
+
const propagatable = manifest.seeds.filter(isPropagatableSeed);
|
|
29701
30099
|
if (!o.target) {
|
|
29702
30100
|
return fail(`bootstrap propagate: --target <path> is required \u2014 one of:
|
|
29703
30101
|
${propagatable.map((s) => s.target).join("\n ")}`);
|
|
29704
30102
|
}
|
|
29705
30103
|
const seed = propagatable.find((s) => s.target === o.target);
|
|
29706
|
-
if (!seed) return fail(`bootstrap propagate: --target '${o.target}' names no
|
|
30104
|
+
if (!seed) return fail(`bootstrap propagate: --target '${o.target}' names no centrally propagatable seed in ${manifestPath}. Propagatable targets:
|
|
29707
30105
|
${propagatable.map((s) => s.target).join("\n ")}`);
|
|
29708
|
-
if (!(0,
|
|
29709
|
-
const hubContent = (0,
|
|
30106
|
+
if (!seed.managedBlock && !(0, import_node_fs38.existsSync)(seed.target)) return fail(`bootstrap propagate: the Hub's own copy of '${seed.target}' is missing \u2014 nothing to propagate`);
|
|
30107
|
+
const hubContent = seed.managedBlock ? null : (0, import_node_fs38.readFileSync)(seed.target, "utf8");
|
|
30108
|
+
const readSeedFile = (path2) => (0, import_node_fs38.existsSync)(path2) ? (0, import_node_fs38.readFileSync)(path2, "utf8") : null;
|
|
29710
30109
|
const isWorkflowSeed = seed.target.startsWith(".github/workflows/");
|
|
29711
30110
|
const cfg = await loadConfig();
|
|
29712
30111
|
const projects = await fetchProjectsList(registryClientDeps(cfg));
|
|
@@ -29715,9 +30114,9 @@ LIVE apply to ${repo}:
|
|
|
29715
30114
|
}
|
|
29716
30115
|
const rosterRepos2 = collectRegistryRepos(projects).filter((r) => r.toLowerCase() !== "mutmutco/mmi-hub");
|
|
29717
30116
|
let independentCount = rosterRepos2.length;
|
|
29718
|
-
if ((0,
|
|
30117
|
+
if ((0, import_node_fs38.existsSync)("projects.json")) {
|
|
29719
30118
|
try {
|
|
29720
|
-
const local = JSON.parse((0,
|
|
30119
|
+
const local = JSON.parse((0, import_node_fs38.readFileSync)("projects.json", "utf8"));
|
|
29721
30120
|
const localRepos = /* @__PURE__ */ new Set();
|
|
29722
30121
|
for (const p of local.projects ?? []) for (const r of p.repos ?? []) {
|
|
29723
30122
|
const full = (r.includes("/") ? r : `mutmutco/${r}`).toLowerCase();
|
|
@@ -29762,11 +30161,12 @@ LIVE apply to ${repo}:
|
|
|
29762
30161
|
});
|
|
29763
30162
|
const gh = async (args) => execFileP2("gh", args, { timeout: 2e4 });
|
|
29764
30163
|
const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
|
|
29765
|
-
const branchPrefix = "seed-propagate";
|
|
29766
30164
|
const reads = [];
|
|
30165
|
+
const desiredByRepo = /* @__PURE__ */ new Map();
|
|
29767
30166
|
for (const r of repos) {
|
|
29768
30167
|
if (r.waiver) continue;
|
|
29769
|
-
const
|
|
30168
|
+
const repoClass = classOf(r.repo);
|
|
30169
|
+
const baseBranch = repoClass === "content" ? "main" : "development";
|
|
29770
30170
|
let content = null;
|
|
29771
30171
|
try {
|
|
29772
30172
|
const resp = await gh(["api", `repos/${r.repo}/contents/${enc(seed.target)}?ref=${baseBranch}`]);
|
|
@@ -29775,13 +30175,35 @@ LIVE apply to ${repo}:
|
|
|
29775
30175
|
} catch {
|
|
29776
30176
|
content = null;
|
|
29777
30177
|
}
|
|
29778
|
-
|
|
30178
|
+
let desired;
|
|
30179
|
+
if (seed.managedBlock) {
|
|
30180
|
+
const vars = withDerivedRepoVars({}, parseOwnerRepo(r.repo), repoClass);
|
|
30181
|
+
const resolved = resolveSeedWriteContent(seed, vars, readSeedFile, content);
|
|
30182
|
+
if (!resolved.ok) {
|
|
30183
|
+
return fail(`bootstrap propagate: ${r.repo} ${seed.target}: ${resolved.reason} \u2014 refusing to overwrite repo-owned content`);
|
|
30184
|
+
}
|
|
30185
|
+
if (resolved.content == null) {
|
|
30186
|
+
return fail(`bootstrap propagate: cannot resolve the full template or managed block source for '${seed.target}' \u2014 refusing a partial fleet plan`);
|
|
30187
|
+
}
|
|
30188
|
+
desired = resolved.content;
|
|
30189
|
+
} else {
|
|
30190
|
+
desired = hubContent;
|
|
30191
|
+
}
|
|
30192
|
+
desiredByRepo.set(r.repo, desired);
|
|
30193
|
+
const drift = compareSeedBytes(desired, content);
|
|
29779
30194
|
let pr2;
|
|
29780
30195
|
try {
|
|
29781
|
-
const
|
|
29782
|
-
|
|
29783
|
-
|
|
29784
|
-
|
|
30196
|
+
const listBranchPrs = async (branch) => {
|
|
30197
|
+
const listed = await gh(["pr", "list", "--repo", r.repo, "--head", branch, "--base", baseBranch, "--state", "all", "--json", "number,url,state,statusCheckRollup,mergeCommit,body,files", "--limit", "100"]);
|
|
30198
|
+
return JSON.parse(listed.stdout || "[]");
|
|
30199
|
+
};
|
|
30200
|
+
const targetSpecific = (await listBranchPrs(propagationBranch(r.repo, seed.target))).map((p2) => ({ ...p2, files: p2.files?.map((f) => f.path) }));
|
|
30201
|
+
let identified = propagationPrCandidatesForTarget(seed.target, targetSpecific, []);
|
|
30202
|
+
if (!identified.length) {
|
|
30203
|
+
const legacy = (await listBranchPrs(legacyPropagationBranch(r.repo))).map((p2) => ({ ...p2, files: p2.files?.map((f) => f.path) }));
|
|
30204
|
+
identified = propagationPrCandidatesForTarget(seed.target, [], legacy);
|
|
30205
|
+
}
|
|
30206
|
+
const p = identified[0];
|
|
29785
30207
|
if (p) {
|
|
29786
30208
|
const rollup = p.statusCheckRollup ?? [];
|
|
29787
30209
|
const checks = rollup.length === 0 ? "none" : rollup.some((c) => c.conclusion === "FAILURE" || c.state === "FAILURE") ? "red" : rollup.every((c) => c.conclusion === "SUCCESS" || c.state === "SUCCESS") ? "success" : "pending";
|
|
@@ -29814,9 +30236,8 @@ LIVE apply to ${repo}:
|
|
|
29814
30236
|
const headSha = seedSource.sha;
|
|
29815
30237
|
for (const rec of plan.records) {
|
|
29816
30238
|
if (rec.action !== "open-pr") continue;
|
|
29817
|
-
const repoEntry = repos.find((r) => r.repo === rec.repo);
|
|
29818
30239
|
const baseBranch = classOf(rec.repo) === "content" ? "main" : "development";
|
|
29819
|
-
const branch =
|
|
30240
|
+
const branch = propagationBranch(rec.repo, seed.target);
|
|
29820
30241
|
const baseRef = await gh(["api", `repos/${rec.repo}/git/ref/heads/${baseBranch}`, "--jq", ".object.sha"]);
|
|
29821
30242
|
const baseSha = baseRef.stdout.trim();
|
|
29822
30243
|
let branchExists = true;
|
|
@@ -29833,18 +30254,26 @@ LIVE apply to ${repo}:
|
|
|
29833
30254
|
} catch {
|
|
29834
30255
|
existingSha = void 0;
|
|
29835
30256
|
}
|
|
29836
|
-
const tmp = (0,
|
|
29837
|
-
|
|
30257
|
+
const tmp = (0, import_node_path36.join)((0, import_node_os16.tmpdir)(), `mmi-propagate-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
|
|
30258
|
+
const desiredContent = desiredByRepo.get(rec.repo);
|
|
30259
|
+
if (desiredContent == null) return fail(`bootstrap propagate: no resolved content for ${rec.repo} ${seed.target} \u2014 refusing to write`);
|
|
30260
|
+
(0, import_node_fs38.writeFileSync)(tmp, JSON.stringify(contentPutBody(seed.target, desiredContent, branch, existingSha)), "utf8");
|
|
29838
30261
|
try {
|
|
29839
30262
|
await gh(contentPutInputArgs(rec.repo, seed.target, tmp));
|
|
29840
30263
|
} finally {
|
|
29841
30264
|
try {
|
|
29842
|
-
(0,
|
|
30265
|
+
(0, import_node_fs38.unlinkSync)(tmp);
|
|
29843
30266
|
} catch {
|
|
29844
30267
|
}
|
|
29845
30268
|
}
|
|
29846
|
-
const openPrs = await gh(["pr", "list", "--repo", rec.repo, "--head", branch, "--base", baseBranch, "--state", "open", "--json", "number,url"]);
|
|
29847
|
-
const
|
|
30269
|
+
const openPrs = await gh(["pr", "list", "--repo", rec.repo, "--head", branch, "--base", baseBranch, "--state", "open", "--json", "number,url,body,files"]);
|
|
30270
|
+
const listedOpenPrs = JSON.parse(openPrs.stdout || "[]");
|
|
30271
|
+
const identifiedOpenPrs = propagationPrCandidatesForTarget(
|
|
30272
|
+
seed.target,
|
|
30273
|
+
listedOpenPrs.map((p) => ({ ...p, files: p.files?.map((f) => f.path) })),
|
|
30274
|
+
[]
|
|
30275
|
+
);
|
|
30276
|
+
const prDecision = decideSeedPrAction(identifiedOpenPrs);
|
|
29848
30277
|
let prUrl;
|
|
29849
30278
|
if (prDecision.action === "reuse") {
|
|
29850
30279
|
prUrl = prDecision.url;
|
|
@@ -29859,11 +30288,12 @@ LIVE apply to ${repo}:
|
|
|
29859
30288
|
"--head",
|
|
29860
30289
|
branch,
|
|
29861
30290
|
"--title",
|
|
29862
|
-
`chore: propagate org-owned ${seed.target} from MMI-Hub (wave ${rec.wave})`,
|
|
30291
|
+
`chore: propagate ${seed.managedBlock ? "Hub-managed block in" : "org-owned"} ${seed.target} from MMI-Hub (wave ${rec.wave})`,
|
|
29863
30292
|
"--body",
|
|
29864
30293
|
`Auto-opened by \`mmi-cli devops bootstrap propagate --target ${seed.target} --execute\` (#4238), wave ${rec.wave}.
|
|
29865
30294
|
|
|
29866
|
-
Propagates MMI-Hub@${headSha}
|
|
30295
|
+
Propagates MMI-Hub@${headSha}'s ${seed.managedBlock ? "declared marker-bounded block inside repo-owned" : "org-owned copy of"} \`${seed.target}\` \u2014 the file this PR carries and nothing else; content outside a managed block remains untouched.
|
|
30296
|
+
${renderPropagationTargetMarker(seed.target)}
|
|
29867
30297
|
|
|
29868
30298
|
Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target} --execute\` (#4240) reverts this PR's merge commit; never re-run propagate with old bytes.`
|
|
29869
30299
|
]);
|
|
@@ -29897,30 +30327,30 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
|
|
|
29897
30327
|
return fail(`bootstrap rollback: ${e.message}`);
|
|
29898
30328
|
}
|
|
29899
30329
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
29900
|
-
if (!(0,
|
|
30330
|
+
if (!(0, import_node_fs38.existsSync)(manifestPath)) return fail(`bootstrap rollback: ${manifestPath} not found; run from the MMI-Hub repo root \u2014 the manifest names which targets are org-owned and therefore propagated (and rollback-able)`);
|
|
29901
30331
|
const seedSource = await resolveHubSeedSource(execGitForSeedSource);
|
|
29902
30332
|
if (!seedSource.ok) return fail(`bootstrap rollback: ${seedSource.reason}`);
|
|
29903
|
-
const manifest = loadBootstrapSeeds((0,
|
|
29904
|
-
const propagatable = manifest.seeds.filter(
|
|
30333
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs38.readFileSync)(manifestPath, "utf8"));
|
|
30334
|
+
const propagatable = manifest.seeds.filter(isPropagatableSeed);
|
|
29905
30335
|
if (!o.target) {
|
|
29906
30336
|
return fail(`bootstrap rollback: --target <path> is required \u2014 one of:
|
|
29907
30337
|
${propagatable.map((s) => s.target).join("\n ")}`);
|
|
29908
30338
|
}
|
|
29909
30339
|
const seed = propagatable.find((s) => s.target === o.target);
|
|
29910
|
-
if (!seed) return fail(`bootstrap rollback: --target '${o.target}' names no
|
|
30340
|
+
if (!seed) return fail(`bootstrap rollback: --target '${o.target}' names no centrally propagatable seed in ${manifestPath}. Propagatable (hence rollback-able) targets:
|
|
29911
30341
|
${propagatable.map((s) => s.target).join("\n ")}`);
|
|
29912
30342
|
const slug = parsedRepo.slug;
|
|
29913
30343
|
const baseBranch = o.class === "content" ? "main" : "development";
|
|
29914
|
-
const
|
|
29915
|
-
const
|
|
30344
|
+
const targetPropagateBranch = propagationBranch(repo, seed.target);
|
|
30345
|
+
const legacyPropagateBranch = legacyPropagationBranch(repo);
|
|
29916
30346
|
const gh = async (args) => execFileP2("gh", args, { timeout: 2e4 });
|
|
29917
30347
|
const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
|
|
29918
30348
|
let candidates;
|
|
29919
30349
|
if (o.record) {
|
|
29920
|
-
if (!(0,
|
|
30350
|
+
if (!(0, import_node_fs38.existsSync)(o.record)) return fail(`bootstrap rollback: --record '${o.record}' not found`);
|
|
29921
30351
|
let parsed;
|
|
29922
30352
|
try {
|
|
29923
|
-
parsed = JSON.parse((0,
|
|
30353
|
+
parsed = JSON.parse((0, import_node_fs38.readFileSync)(o.record, "utf8"));
|
|
29924
30354
|
} catch (e) {
|
|
29925
30355
|
return fail(`bootstrap rollback: --record '${o.record}' is not valid JSON: ${e.message}`);
|
|
29926
30356
|
}
|
|
@@ -29935,9 +30365,17 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
|
|
|
29935
30365
|
} else {
|
|
29936
30366
|
candidates = [];
|
|
29937
30367
|
try {
|
|
29938
|
-
const
|
|
29939
|
-
|
|
29940
|
-
|
|
30368
|
+
const listMergedBranchPrs = async (branch) => {
|
|
30369
|
+
const listed = await gh(["pr", "list", "--repo", repo, "--head", branch, "--base", baseBranch, "--state", "merged", "--json", "number,url,mergedAt,mergeCommit,body,files", "--limit", "100"]);
|
|
30370
|
+
return JSON.parse(listed.stdout || "[]");
|
|
30371
|
+
};
|
|
30372
|
+
const targetSpecific = (await listMergedBranchPrs(targetPropagateBranch)).map((p) => ({ ...p, files: p.files?.map((f) => f.path) }));
|
|
30373
|
+
let identified = propagationPrCandidatesForTarget(seed.target, targetSpecific, []);
|
|
30374
|
+
if (!identified.length) {
|
|
30375
|
+
const legacy = (await listMergedBranchPrs(legacyPropagateBranch)).map((p) => ({ ...p, files: p.files?.map((f) => f.path) }));
|
|
30376
|
+
identified = propagationPrCandidatesForTarget(seed.target, [], legacy);
|
|
30377
|
+
}
|
|
30378
|
+
for (const p of identified) {
|
|
29941
30379
|
if (!p.mergeCommit?.oid) continue;
|
|
29942
30380
|
candidates.push({
|
|
29943
30381
|
repo,
|
|
@@ -29946,11 +30384,11 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
|
|
|
29946
30384
|
url: p.url,
|
|
29947
30385
|
mergeSha: p.mergeCommit.oid,
|
|
29948
30386
|
mergedAt: p.mergedAt ?? "",
|
|
29949
|
-
files:
|
|
30387
|
+
files: p.files ?? []
|
|
29950
30388
|
});
|
|
29951
30389
|
}
|
|
29952
30390
|
} catch (e) {
|
|
29953
|
-
return fail(`bootstrap rollback: could not read ${repo}'s merged ${
|
|
30391
|
+
return fail(`bootstrap rollback: could not read ${repo}'s merged ${targetPropagateBranch} or positively matching legacy PR history: ${e.message}`);
|
|
29954
30392
|
}
|
|
29955
30393
|
}
|
|
29956
30394
|
const plan = planRollback(repo, seed.target, slug, candidates);
|
|
@@ -29989,13 +30427,13 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
|
|
|
29989
30427
|
} catch {
|
|
29990
30428
|
existingSha = void 0;
|
|
29991
30429
|
}
|
|
29992
|
-
const tmp = (0,
|
|
29993
|
-
(0,
|
|
30430
|
+
const tmp = (0, import_node_path36.join)((0, import_node_os16.tmpdir)(), `mmi-rollback-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
|
|
30431
|
+
(0, import_node_fs38.writeFileSync)(tmp, JSON.stringify(contentPutBody(seed.target, preSeedContent, plan.branch, existingSha)), "utf8");
|
|
29994
30432
|
try {
|
|
29995
30433
|
await gh(contentPutInputArgs(repo, seed.target, tmp));
|
|
29996
30434
|
} finally {
|
|
29997
30435
|
try {
|
|
29998
|
-
(0,
|
|
30436
|
+
(0, import_node_fs38.unlinkSync)(tmp);
|
|
29999
30437
|
} catch {
|
|
30000
30438
|
}
|
|
30001
30439
|
}
|
|
@@ -30017,12 +30455,12 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
|
|
|
30017
30455
|
}
|
|
30018
30456
|
|
|
30019
30457
|
// src/stage-commands.ts
|
|
30020
|
-
var
|
|
30021
|
-
var
|
|
30458
|
+
var import_node_fs40 = require("node:fs");
|
|
30459
|
+
var import_node_path38 = require("node:path");
|
|
30022
30460
|
|
|
30023
30461
|
// src/port-registry.ts
|
|
30024
|
-
var
|
|
30025
|
-
var
|
|
30462
|
+
var import_node_fs39 = require("node:fs");
|
|
30463
|
+
var import_node_path37 = require("node:path");
|
|
30026
30464
|
|
|
30027
30465
|
// ../infra/port-geometry.mjs
|
|
30028
30466
|
var PORT_BLOCK = 100;
|
|
@@ -30036,8 +30474,8 @@ function nextPortBlock(registry2) {
|
|
|
30036
30474
|
return [base, base + PORT_SPAN];
|
|
30037
30475
|
}
|
|
30038
30476
|
function loadPortRegistry(path2) {
|
|
30039
|
-
if (!(0,
|
|
30040
|
-
const raw = JSON.parse((0,
|
|
30477
|
+
if (!(0, import_node_fs39.existsSync)(path2)) return {};
|
|
30478
|
+
const raw = JSON.parse((0, import_node_fs39.readFileSync)(path2, "utf8"));
|
|
30041
30479
|
const out = {};
|
|
30042
30480
|
for (const [key, value] of Object.entries(raw)) {
|
|
30043
30481
|
if (Array.isArray(value) && value.length === 2 && value.every((n) => typeof n === "number")) {
|
|
@@ -30051,9 +30489,9 @@ function ensurePortRange(repo, path2) {
|
|
|
30051
30489
|
const existing = registry2[repo];
|
|
30052
30490
|
if (existing) return existing;
|
|
30053
30491
|
const range = nextPortBlock(registry2);
|
|
30054
|
-
const raw = (0,
|
|
30492
|
+
const raw = (0, import_node_fs39.existsSync)(path2) ? JSON.parse((0, import_node_fs39.readFileSync)(path2, "utf8")) : {};
|
|
30055
30493
|
raw[repo] = range;
|
|
30056
|
-
(0,
|
|
30494
|
+
(0, import_node_fs39.writeFileSync)(path2, JSON.stringify(raw, null, 2) + "\n", "utf8");
|
|
30057
30495
|
return range;
|
|
30058
30496
|
}
|
|
30059
30497
|
function portCursorSeed(registry2) {
|
|
@@ -30075,22 +30513,22 @@ function existingPortRange(repo, registry2) {
|
|
|
30075
30513
|
return registry2[repo] ?? null;
|
|
30076
30514
|
}
|
|
30077
30515
|
function portRangeInfraAt(root, source) {
|
|
30078
|
-
const registryPath = (0,
|
|
30079
|
-
const ddbScriptPath = (0,
|
|
30080
|
-
if (!(0,
|
|
30516
|
+
const registryPath = (0, import_node_path37.join)(root, "infra", "port-ranges.json");
|
|
30517
|
+
const ddbScriptPath = (0, import_node_path37.join)(root, "infra", "port-ddb.mjs");
|
|
30518
|
+
if (!(0, import_node_fs39.existsSync)(registryPath) || !(0, import_node_fs39.existsSync)(ddbScriptPath)) return null;
|
|
30081
30519
|
return { root, source, registryPath, ddbScriptPath };
|
|
30082
30520
|
}
|
|
30083
30521
|
function resolvePortRangeInfra(cwd, packageDir) {
|
|
30084
30522
|
const direct = portRangeInfraAt(cwd, "cwd");
|
|
30085
30523
|
if (direct) return direct;
|
|
30086
|
-
for (let dir = cwd; ; dir = (0,
|
|
30087
|
-
const sibling = portRangeInfraAt((0,
|
|
30524
|
+
for (let dir = cwd; ; dir = (0, import_node_path37.dirname)(dir)) {
|
|
30525
|
+
const sibling = portRangeInfraAt((0, import_node_path37.join)(dir, "MMI-Hub"), "sibling-hub");
|
|
30088
30526
|
if (sibling) return sibling;
|
|
30089
|
-
const parent = (0,
|
|
30527
|
+
const parent = (0, import_node_path37.dirname)(dir);
|
|
30090
30528
|
if (parent === dir) break;
|
|
30091
30529
|
}
|
|
30092
30530
|
if (packageDir) {
|
|
30093
|
-
const pkgRoot = (0,
|
|
30531
|
+
const pkgRoot = (0, import_node_path37.join)(packageDir, "..", "..");
|
|
30094
30532
|
const pkgFrom = portRangeInfraAt(pkgRoot, "pkg-root");
|
|
30095
30533
|
if (pkgFrom) return pkgFrom;
|
|
30096
30534
|
}
|
|
@@ -30284,8 +30722,8 @@ function registerStageCommands(program3) {
|
|
|
30284
30722
|
const portRange = portRangeMeta && typeof portRangeMeta.start === "number" && typeof portRangeMeta.end === "number" ? [portRangeMeta.start, portRangeMeta.end] : void 0;
|
|
30285
30723
|
return decideStage({
|
|
30286
30724
|
registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
|
|
30287
|
-
hasCompose: (0,
|
|
30288
|
-
hasEnvExample: (0,
|
|
30725
|
+
hasCompose: (0, import_node_fs40.existsSync)((0, import_node_path38.join)(process.cwd(), "docker-compose.yml")),
|
|
30726
|
+
hasEnvExample: (0, import_node_fs40.existsSync)((0, import_node_path38.join)(process.cwd(), ".env.example"))
|
|
30289
30727
|
});
|
|
30290
30728
|
}
|
|
30291
30729
|
async function fetchStageVaultEnvMerge() {
|
|
@@ -30776,10 +31214,10 @@ function registerBoardCommands(program3) {
|
|
|
30776
31214
|
}
|
|
30777
31215
|
|
|
30778
31216
|
// src/merge-cleanup.ts
|
|
30779
|
-
var
|
|
31217
|
+
var import_node_fs41 = require("node:fs");
|
|
30780
31218
|
var import_promises9 = require("node:fs/promises");
|
|
30781
|
-
var
|
|
30782
|
-
var
|
|
31219
|
+
var import_node_path40 = require("node:path");
|
|
31220
|
+
var import_node_os17 = require("node:os");
|
|
30783
31221
|
var import_node_child_process17 = require("node:child_process");
|
|
30784
31222
|
|
|
30785
31223
|
// src/board-advance.ts
|
|
@@ -30866,7 +31304,7 @@ function boardAdvanceFailureMessage(result) {
|
|
|
30866
31304
|
|
|
30867
31305
|
// src/deferred-registry-store.ts
|
|
30868
31306
|
var import_promises8 = require("node:fs/promises");
|
|
30869
|
-
var
|
|
31307
|
+
var import_node_path39 = require("node:path");
|
|
30870
31308
|
var sleep2 = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
30871
31309
|
async function atomicWrite(target, contents) {
|
|
30872
31310
|
const tmp = `${target}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
|
|
@@ -30917,12 +31355,12 @@ function makeDeferredWorktreeStore(registryPath, lockOpts = {}) {
|
|
|
30917
31355
|
},
|
|
30918
31356
|
// Standalone atomic write — THROWS on failure (no best-effort swallow, #2846).
|
|
30919
31357
|
write: async (entries) => {
|
|
30920
|
-
await (0, import_promises8.mkdir)((0,
|
|
31358
|
+
await (0, import_promises8.mkdir)((0, import_node_path39.dirname)(registryPath), { recursive: true });
|
|
30921
31359
|
await atomicWrite(registryPath, serializeDeferredWorktrees(entries));
|
|
30922
31360
|
},
|
|
30923
31361
|
// Serialized read-modify-write under the repo-wide lock (#2846).
|
|
30924
31362
|
update: async (mutate) => {
|
|
30925
|
-
await (0, import_promises8.mkdir)((0,
|
|
31363
|
+
await (0, import_promises8.mkdir)((0, import_node_path39.dirname)(registryPath), { recursive: true });
|
|
30926
31364
|
const deadline = Date.now() + opts.maxWaitMs;
|
|
30927
31365
|
for (; ; ) {
|
|
30928
31366
|
const guard = await acquireLock(lockPath, opts, deadline);
|
|
@@ -31086,17 +31524,17 @@ ${err.stderr ?? ""}`;
|
|
|
31086
31524
|
return { step, status: `failed: ${msg}` };
|
|
31087
31525
|
}
|
|
31088
31526
|
}
|
|
31089
|
-
async function bestEffortCloseMissingWorktreeLeases(leaseDir = (0,
|
|
31527
|
+
async function bestEffortCloseMissingWorktreeLeases(leaseDir = (0, import_node_path40.join)((0, import_node_os17.homedir)(), ".jerv", "leases"), exists = import_node_fs41.existsSync) {
|
|
31090
31528
|
let names = [];
|
|
31091
31529
|
try {
|
|
31092
|
-
names = (0,
|
|
31530
|
+
names = (0, import_node_fs41.readdirSync)(leaseDir);
|
|
31093
31531
|
} catch {
|
|
31094
31532
|
return;
|
|
31095
31533
|
}
|
|
31096
31534
|
for (const name of names) {
|
|
31097
31535
|
if (!name.endsWith(".json")) continue;
|
|
31098
31536
|
try {
|
|
31099
|
-
const rec = JSON.parse((0,
|
|
31537
|
+
const rec = JSON.parse((0, import_node_fs41.readFileSync)((0, import_node_path40.join)(leaseDir, name), "utf8"));
|
|
31100
31538
|
if (rec.kind !== "worktree" || rec.state === "closed" || typeof rec.ref !== "string" || !rec.ref.trim()) continue;
|
|
31101
31539
|
if (!exists(rec.ref)) await bestEffortLeaseClose(rec.ref);
|
|
31102
31540
|
} catch {
|
|
@@ -31110,7 +31548,7 @@ async function applyGcPlan(plan, remote, opts = {}) {
|
|
|
31110
31548
|
);
|
|
31111
31549
|
const repoRoot2 = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim() || process.cwd();
|
|
31112
31550
|
const worktreeGitRoot = await currentRepoWorktreeGitRoot(repoRoot2);
|
|
31113
|
-
const primaryRepoRoot = worktreeGitRoot ? (0,
|
|
31551
|
+
const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path40.dirname)((0, import_node_path40.dirname)(worktreeGitRoot)) : repoRoot2;
|
|
31114
31552
|
const gcActor = describeActor({ env: process.env, surface: detectSurface(process.env), cwd: process.cwd() });
|
|
31115
31553
|
const owners = readWorktreeOwners(primaryRepoRoot);
|
|
31116
31554
|
const removalNow = Date.now();
|
|
@@ -31169,7 +31607,7 @@ async function applyGcPlan(plan, remote, opts = {}) {
|
|
|
31169
31607
|
const cleanup = await cleanupPrMergeLocalBranch(branch.branch, {
|
|
31170
31608
|
beforeWorktrees,
|
|
31171
31609
|
startingPath: branch.worktreePath,
|
|
31172
|
-
pathExists: (p) => (0,
|
|
31610
|
+
pathExists: (p) => (0, import_node_fs41.existsSync)(p),
|
|
31173
31611
|
execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
|
|
31174
31612
|
teardownWorktreeStage,
|
|
31175
31613
|
deferredStore,
|
|
@@ -31208,7 +31646,7 @@ async function applyGcPlan(plan, remote, opts = {}) {
|
|
|
31208
31646
|
let removalAttempted = false;
|
|
31209
31647
|
try {
|
|
31210
31648
|
const cleanupTarget = resolveSafeSiblingWorktreeCleanupTarget(wt.path, cleanupRoots, {
|
|
31211
|
-
realpath: (path2) => (0,
|
|
31649
|
+
realpath: (path2) => (0, import_node_fs41.realpathSync)(path2)
|
|
31212
31650
|
});
|
|
31213
31651
|
if (!cleanupTarget.ok) {
|
|
31214
31652
|
result.failed.push(`${wt.path}: ${cleanupTarget.reason}`);
|
|
@@ -31310,13 +31748,13 @@ async function composeOverrideBodyFile(prNumber, repoArgs, gh) {
|
|
|
31310
31748
|
const commits = JSON.parse(raw).commits ?? [];
|
|
31311
31749
|
const body = squashBodyWithOverride(commits.map((c) => ({ headline: c.messageHeadline ?? "", body: c.messageBody ?? "" })), process.cwd());
|
|
31312
31750
|
if (!body) return void 0;
|
|
31313
|
-
const dir = (0,
|
|
31314
|
-
const path2 = (0,
|
|
31315
|
-
(0,
|
|
31751
|
+
const dir = (0, import_node_fs41.mkdtempSync)((0, import_node_path40.join)((0, import_node_os17.tmpdir)(), "mmi-squash-body-"));
|
|
31752
|
+
const path2 = (0, import_node_path40.join)(dir, "body.txt");
|
|
31753
|
+
(0, import_node_fs41.writeFileSync)(path2, `${body}
|
|
31316
31754
|
`, "utf8");
|
|
31317
31755
|
return { path: path2, cleanup: () => {
|
|
31318
31756
|
try {
|
|
31319
|
-
(0,
|
|
31757
|
+
(0, import_node_fs41.rmSync)(dir, { recursive: true, force: true });
|
|
31320
31758
|
} catch {
|
|
31321
31759
|
}
|
|
31322
31760
|
} };
|
|
@@ -31439,13 +31877,13 @@ var realWorktreeDirRemover = {
|
|
|
31439
31877
|
const target = win32LongPath(p);
|
|
31440
31878
|
let st;
|
|
31441
31879
|
try {
|
|
31442
|
-
st = (0,
|
|
31880
|
+
st = (0, import_node_fs41.lstatSync)(target);
|
|
31443
31881
|
} catch {
|
|
31444
31882
|
return null;
|
|
31445
31883
|
}
|
|
31446
31884
|
if (st.isSymbolicLink()) return "link";
|
|
31447
31885
|
try {
|
|
31448
|
-
(0,
|
|
31886
|
+
(0, import_node_fs41.readlinkSync)(target);
|
|
31449
31887
|
return "link";
|
|
31450
31888
|
} catch {
|
|
31451
31889
|
}
|
|
@@ -31453,7 +31891,7 @@ var realWorktreeDirRemover = {
|
|
|
31453
31891
|
},
|
|
31454
31892
|
readdir: (p) => {
|
|
31455
31893
|
try {
|
|
31456
|
-
return (0,
|
|
31894
|
+
return (0, import_node_fs41.readdirSync)(win32LongPath(p));
|
|
31457
31895
|
} catch {
|
|
31458
31896
|
return [];
|
|
31459
31897
|
}
|
|
@@ -31463,9 +31901,9 @@ var realWorktreeDirRemover = {
|
|
|
31463
31901
|
detachLink: (p) => {
|
|
31464
31902
|
const target = win32LongPath(p);
|
|
31465
31903
|
try {
|
|
31466
|
-
(0,
|
|
31904
|
+
(0, import_node_fs41.rmdirSync)(target);
|
|
31467
31905
|
} catch {
|
|
31468
|
-
(0,
|
|
31906
|
+
(0, import_node_fs41.unlinkSync)(target);
|
|
31469
31907
|
}
|
|
31470
31908
|
},
|
|
31471
31909
|
// #4904: Windows MAX_PATH aborts git worktree remove; the fallback must use \\?\ so the dir
|
|
@@ -31488,7 +31926,7 @@ function worktreeRemoveDeps(execGit) {
|
|
|
31488
31926
|
detachReparsePoints: (worktreePath) => detachReparsePoints(worktreePath, realWorktreeDirRemover),
|
|
31489
31927
|
removeWorktreeDir: async (worktreePath) => removeWorktreeTree(worktreePath, await resolvePrimaryCheckout(execGit), realWorktreeDirRemover),
|
|
31490
31928
|
// #4850: verify the directory is actually gone before any caller reports completion.
|
|
31491
|
-
pathExists: (worktreePath) => (0,
|
|
31929
|
+
pathExists: (worktreePath) => (0, import_node_fs41.existsSync)(worktreePath)
|
|
31492
31930
|
};
|
|
31493
31931
|
}
|
|
31494
31932
|
async function worktreeHasStageState(worktreePath) {
|
|
@@ -31502,9 +31940,9 @@ async function worktreeHasStageState(worktreePath) {
|
|
|
31502
31940
|
}
|
|
31503
31941
|
}
|
|
31504
31942
|
function stageStateFileBelongsToWorktree(statePath, worktreePath) {
|
|
31505
|
-
if (!(0,
|
|
31943
|
+
if (!(0, import_node_fs41.existsSync)(statePath)) return false;
|
|
31506
31944
|
try {
|
|
31507
|
-
const state = JSON.parse((0,
|
|
31945
|
+
const state = JSON.parse((0, import_node_fs41.readFileSync)(statePath, "utf8"));
|
|
31508
31946
|
const recordedCwd = typeof state.identity?.cwd === "string" ? state.identity.cwd : typeof state.cwd === "string" ? state.cwd : "";
|
|
31509
31947
|
return Boolean(recordedCwd && isPathUnderDirectory2(recordedCwd, worktreePath));
|
|
31510
31948
|
} catch {
|
|
@@ -31939,15 +32377,15 @@ async function checkDocsIndexAtHead(opts, deps) {
|
|
|
31939
32377
|
}
|
|
31940
32378
|
|
|
31941
32379
|
// src/worktree-lifecycle-commands.ts
|
|
31942
|
-
var
|
|
32380
|
+
var import_node_fs43 = require("node:fs");
|
|
31943
32381
|
var import_promises10 = require("node:fs/promises");
|
|
31944
|
-
var
|
|
31945
|
-
var
|
|
32382
|
+
var import_node_os18 = require("node:os");
|
|
32383
|
+
var import_node_path42 = require("node:path");
|
|
31946
32384
|
|
|
31947
32385
|
// src/worktree-install-cache.ts
|
|
31948
|
-
var
|
|
31949
|
-
var
|
|
31950
|
-
var
|
|
32386
|
+
var import_node_crypto9 = require("node:crypto");
|
|
32387
|
+
var import_node_fs42 = require("node:fs");
|
|
32388
|
+
var import_node_path41 = require("node:path");
|
|
31951
32389
|
var CACHE_DIR = "worktree-install-cache";
|
|
31952
32390
|
var MANIFEST = "manifest.json";
|
|
31953
32391
|
var NODE_MODULES2 = "node_modules";
|
|
@@ -31960,24 +32398,24 @@ var LOCKFILE_NAMES = [
|
|
|
31960
32398
|
"package-lock.json"
|
|
31961
32399
|
];
|
|
31962
32400
|
var realWorktreeInstallCacheFs = {
|
|
31963
|
-
exists:
|
|
31964
|
-
readFile: (path2) => (0,
|
|
31965
|
-
lstat: (path2) => (0,
|
|
31966
|
-
copyDir: (from, to) => (0,
|
|
32401
|
+
exists: import_node_fs42.existsSync,
|
|
32402
|
+
readFile: (path2) => (0, import_node_fs42.readFileSync)(path2, "utf8"),
|
|
32403
|
+
lstat: (path2) => (0, import_node_fs42.lstatSync)(path2),
|
|
32404
|
+
copyDir: (from, to) => (0, import_node_fs42.cpSync)(from, to, { recursive: true, force: true }),
|
|
31967
32405
|
mkdirp: (path2) => {
|
|
31968
|
-
(0,
|
|
32406
|
+
(0, import_node_fs42.mkdirSync)(path2, { recursive: true });
|
|
31969
32407
|
},
|
|
31970
|
-
writeFile: (path2, contents) => (0,
|
|
32408
|
+
writeFile: (path2, contents) => (0, import_node_fs42.writeFileSync)(path2, contents, "utf8"),
|
|
31971
32409
|
rm: (path2) => {
|
|
31972
|
-
(0,
|
|
32410
|
+
(0, import_node_fs42.rmSync)(path2, { recursive: true, force: true });
|
|
31973
32411
|
}
|
|
31974
32412
|
};
|
|
31975
32413
|
function hashLockfileBytes(contents) {
|
|
31976
|
-
return (0,
|
|
32414
|
+
return (0, import_node_crypto9.createHash)("sha256").update(contents).digest("hex");
|
|
31977
32415
|
}
|
|
31978
32416
|
function resolveWorktreeInstallLockfile(packageDir, fs2 = realWorktreeInstallCacheFs) {
|
|
31979
32417
|
for (const name of LOCKFILE_NAMES) {
|
|
31980
|
-
const path2 = (0,
|
|
32418
|
+
const path2 = (0, import_node_path41.join)(packageDir, name);
|
|
31981
32419
|
if (!fs2.exists(path2)) continue;
|
|
31982
32420
|
try {
|
|
31983
32421
|
const hash = hashLockfileBytes(fs2.readFile(path2));
|
|
@@ -31992,8 +32430,8 @@ function worktreeInstallCacheEntry(primaryRoot, lockfileHash) {
|
|
|
31992
32430
|
const root = repoRuntimeStatePath(primaryRoot, CACHE_DIR, lockfileHash);
|
|
31993
32431
|
return {
|
|
31994
32432
|
root,
|
|
31995
|
-
manifestPath: (0,
|
|
31996
|
-
nodeModulesPath: (0,
|
|
32433
|
+
manifestPath: (0, import_node_path41.join)(root, MANIFEST),
|
|
32434
|
+
nodeModulesPath: (0, import_node_path41.join)(root, NODE_MODULES2)
|
|
31997
32435
|
};
|
|
31998
32436
|
}
|
|
31999
32437
|
function readWorktreeInstallCacheManifest(manifestPath, fs2 = realWorktreeInstallCacheFs) {
|
|
@@ -32033,7 +32471,7 @@ function invalidateWorktreeInstallCacheEntry(entry, fs2 = realWorktreeInstallCac
|
|
|
32033
32471
|
}
|
|
32034
32472
|
}
|
|
32035
32473
|
function removeMaterializedTree(packageDir, fs2) {
|
|
32036
|
-
const dest = (0,
|
|
32474
|
+
const dest = (0, import_node_path41.join)(packageDir, NODE_MODULES2);
|
|
32037
32475
|
if (!fs2.exists(dest)) return;
|
|
32038
32476
|
fs2.rm(dest);
|
|
32039
32477
|
if (fs2.exists(dest)) {
|
|
@@ -32041,7 +32479,7 @@ function removeMaterializedTree(packageDir, fs2) {
|
|
|
32041
32479
|
}
|
|
32042
32480
|
}
|
|
32043
32481
|
function materializeCachedNodeModules(entry, destPackageDir, fs2 = realWorktreeInstallCacheFs) {
|
|
32044
|
-
const dest = (0,
|
|
32482
|
+
const dest = (0, import_node_path41.join)(destPackageDir, NODE_MODULES2);
|
|
32045
32483
|
try {
|
|
32046
32484
|
if (fs2.exists(dest)) fs2.rm(dest);
|
|
32047
32485
|
fs2.mkdirp(destPackageDir);
|
|
@@ -32056,7 +32494,7 @@ function materializeCachedNodeModules(entry, destPackageDir, fs2 = realWorktreeI
|
|
|
32056
32494
|
}
|
|
32057
32495
|
}
|
|
32058
32496
|
async function storeWorktreeInstallCacheEntry(primaryRoot, lockfile, command, sourcePackageDir, fs2 = realWorktreeInstallCacheFs, now = Date.now()) {
|
|
32059
|
-
const source = (0,
|
|
32497
|
+
const source = (0, import_node_path41.join)(sourcePackageDir, NODE_MODULES2);
|
|
32060
32498
|
if (!isMaterializableNodeModulesDir(source, fs2)) return;
|
|
32061
32499
|
const entry = worktreeInstallCacheEntry(primaryRoot, lockfile.hash);
|
|
32062
32500
|
const manifest = {
|
|
@@ -32340,7 +32778,7 @@ function classifyStaleLeaks(input) {
|
|
|
32340
32778
|
var defaultOrphanDirScanDeps = {
|
|
32341
32779
|
listDirs: (root) => {
|
|
32342
32780
|
try {
|
|
32343
|
-
return (0,
|
|
32781
|
+
return (0, import_node_fs43.readdirSync)(root, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => (0, import_node_path42.join)(root, e.name));
|
|
32344
32782
|
} catch {
|
|
32345
32783
|
return [];
|
|
32346
32784
|
}
|
|
@@ -32530,13 +32968,13 @@ function registerWorktreeCommands(program3) {
|
|
|
32530
32968
|
const detached = headBorn && !symbolicBranch;
|
|
32531
32969
|
const branch = symbolicBranch || (detached ? "HEAD" : "");
|
|
32532
32970
|
if (!wtPath || !branch) return fail("worktree land: not inside a git worktree");
|
|
32533
|
-
const gitFile = (0,
|
|
32534
|
-
const isLinked = (0,
|
|
32971
|
+
const gitFile = (0, import_node_path42.join)(wtPath, ".git");
|
|
32972
|
+
const isLinked = (0, import_node_fs43.existsSync)(gitFile) && (0, import_node_fs43.statSync)(gitFile).isFile();
|
|
32535
32973
|
if (apply && !isLinked) {
|
|
32536
32974
|
return fail("worktree land: run from inside the linked worktree you want to land (this is the primary checkout)");
|
|
32537
32975
|
}
|
|
32538
32976
|
const commonDir = (await execFileP2("git", ["rev-parse", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim();
|
|
32539
|
-
const primaryCheckout = commonDir ? (0,
|
|
32977
|
+
const primaryCheckout = commonDir ? (0, import_node_path42.dirname)(commonDir) : wtPath;
|
|
32540
32978
|
const localBranchNames = await execFileP2("git", ["-C", primaryCheckout, "for-each-ref", "--format=%(refname:short)", "refs/heads"], { timeout: GIT_TIMEOUT_MS }).then(({ stdout }) => new Set((stdout || "").split("\n").map((l) => l.trim()).filter(Boolean))).catch(() => void 0);
|
|
32541
32979
|
const orphan = classifyOrphanedWorktree({
|
|
32542
32980
|
branch,
|
|
@@ -32796,7 +33234,7 @@ async function gatherWorktreeContext() {
|
|
|
32796
33234
|
const porcelain = (await execFileP2("git", ["worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout;
|
|
32797
33235
|
const parsed = parseWorktreePorcelain(porcelain);
|
|
32798
33236
|
const primaryPath = parsed[0]?.path ?? repoRoot2;
|
|
32799
|
-
const worktrees = parsed.map((w) => ({ path: (0,
|
|
33237
|
+
const worktrees = parsed.map((w) => ({ path: (0, import_node_path42.resolve)(toNativePath(w.path)), branch: w.branch, primary: w.path === primaryPath }));
|
|
32800
33238
|
const branchOut = (await execFileP2("git", ["branch", "--format=%(refname:short)"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout;
|
|
32801
33239
|
const localBranches = branchOut.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
|
|
32802
33240
|
const currentBranch2 = (await execFileP2("git", ["rev-parse", "--abbrev-ref", "HEAD"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim() || void 0;
|
|
@@ -32834,17 +33272,17 @@ async function gatherWorktreeContext() {
|
|
|
32834
33272
|
if (s) stages.push({ path: wt.path, port: s.port });
|
|
32835
33273
|
}
|
|
32836
33274
|
const worktreeGitRoot = await currentRepoWorktreeGitRoot(repoRoot2);
|
|
32837
|
-
const primaryRepoRoot = worktreeGitRoot ? (0,
|
|
33275
|
+
const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path42.dirname)((0, import_node_path42.dirname)(worktreeGitRoot)) : repoRoot2;
|
|
32838
33276
|
const wtRoot = siblingMmiWorktreesRoot(primaryRepoRoot);
|
|
32839
33277
|
let orphanDirs = [];
|
|
32840
|
-
if ((0,
|
|
33278
|
+
if ((0, import_node_fs43.existsSync)(wtRoot)) {
|
|
32841
33279
|
orphanDirs = scanOrphanDirs(wtRoot, worktreeGitRoot, {
|
|
32842
33280
|
...defaultOrphanDirScanDeps,
|
|
32843
33281
|
listDirs: (root) => worktreeScanDirs(root, primaryRepoRoot, defaultOrphanDirScanDeps.listDirs, isRepoCheckoutDir)
|
|
32844
33282
|
});
|
|
32845
33283
|
}
|
|
32846
33284
|
const repoContainer = worktreesRootOf(primaryRepoRoot);
|
|
32847
|
-
if ((0,
|
|
33285
|
+
if ((0, import_node_fs43.existsSync)(repoContainer)) {
|
|
32848
33286
|
for (const dir of defaultOrphanDirScanDeps.listDirs(repoContainer)) {
|
|
32849
33287
|
if (orphanDirs.some((o) => o.path === dir)) continue;
|
|
32850
33288
|
const inspected = inspectSiblingWorktreeDir(dir, worktreeGitRoot);
|
|
@@ -32868,7 +33306,7 @@ async function gatherWorktreeContext() {
|
|
|
32868
33306
|
});
|
|
32869
33307
|
const helperWorktrees = scanHelperWorktrees(primaryRepoRoot, worktreeGitRoot);
|
|
32870
33308
|
const leaseRefs = readJervWorktreeLeaseRefs();
|
|
32871
|
-
const missingLeaseRefs = leaseRefs.filter((ref) => !(0,
|
|
33309
|
+
const missingLeaseRefs = leaseRefs.filter((ref) => !(0, import_node_fs43.existsSync)(ref));
|
|
32872
33310
|
const remoteUrls = await gitRemoteUrls();
|
|
32873
33311
|
const otherCheckouts = discoverOtherPrimaries(primaryRepoRoot).map((path2) => ({
|
|
32874
33312
|
path: path2,
|
|
@@ -32898,16 +33336,16 @@ async function gatherWorktreeContext() {
|
|
|
32898
33336
|
};
|
|
32899
33337
|
}
|
|
32900
33338
|
function gitConfigRemoteUrls(checkout) {
|
|
32901
|
-
const gitPath = (0,
|
|
32902
|
-
let configPath = (0,
|
|
33339
|
+
const gitPath = (0, import_node_path42.join)(checkout, ".git");
|
|
33340
|
+
let configPath = (0, import_node_path42.join)(checkout, ".git", "config");
|
|
32903
33341
|
try {
|
|
32904
|
-
const st = (0,
|
|
33342
|
+
const st = (0, import_node_fs43.statSync)(gitPath);
|
|
32905
33343
|
if (st.isFile()) return [];
|
|
32906
33344
|
} catch {
|
|
32907
33345
|
return [];
|
|
32908
33346
|
}
|
|
32909
33347
|
try {
|
|
32910
|
-
const text = (0,
|
|
33348
|
+
const text = (0, import_node_fs43.readFileSync)(configPath, "utf8");
|
|
32911
33349
|
return [...text.matchAll(/^\s*url\s*=\s*(.+)$/gm)].map((m) => m[1].trim());
|
|
32912
33350
|
} catch {
|
|
32913
33351
|
return [];
|
|
@@ -32921,26 +33359,26 @@ async function gitRemoteUrls() {
|
|
|
32921
33359
|
}
|
|
32922
33360
|
function discoverOtherPrimaries(primaryRepoRoot) {
|
|
32923
33361
|
const found = /* @__PURE__ */ new Set();
|
|
32924
|
-
const parent = (0,
|
|
33362
|
+
const parent = (0, import_node_path42.dirname)(primaryRepoRoot);
|
|
32925
33363
|
try {
|
|
32926
|
-
for (const name of (0,
|
|
32927
|
-
const path2 = (0,
|
|
33364
|
+
for (const name of (0, import_node_fs43.readdirSync)(parent)) {
|
|
33365
|
+
const path2 = (0, import_node_path42.join)(parent, name);
|
|
32928
33366
|
if (path2 === primaryRepoRoot) continue;
|
|
32929
|
-
if ((0,
|
|
33367
|
+
if ((0, import_node_fs43.existsSync)((0, import_node_path42.join)(path2, ".git"))) found.add(path2);
|
|
32930
33368
|
}
|
|
32931
33369
|
} catch {
|
|
32932
33370
|
}
|
|
32933
|
-
const mirror = (0,
|
|
32934
|
-
if (mirror !== primaryRepoRoot && (0,
|
|
33371
|
+
const mirror = (0, import_node_path42.join)((0, import_node_os18.homedir)(), "Projects", (0, import_node_path42.basename)(primaryRepoRoot));
|
|
33372
|
+
if (mirror !== primaryRepoRoot && (0, import_node_fs43.existsSync)((0, import_node_path42.join)(mirror, ".git"))) found.add(mirror);
|
|
32935
33373
|
return [...found];
|
|
32936
33374
|
}
|
|
32937
|
-
function readJervWorktreeLeaseRefs(leaseDir = (0,
|
|
33375
|
+
function readJervWorktreeLeaseRefs(leaseDir = (0, import_node_path42.join)((0, import_node_os18.homedir)(), ".jerv", "leases")) {
|
|
32938
33376
|
try {
|
|
32939
33377
|
const refs = [];
|
|
32940
|
-
for (const name of (0,
|
|
33378
|
+
for (const name of (0, import_node_fs43.readdirSync)(leaseDir)) {
|
|
32941
33379
|
if (!name.endsWith(".json")) continue;
|
|
32942
33380
|
try {
|
|
32943
|
-
const rec = JSON.parse((0,
|
|
33381
|
+
const rec = JSON.parse((0, import_node_fs43.readFileSync)((0, import_node_path42.join)(leaseDir, name), "utf8"));
|
|
32944
33382
|
if (rec.kind === "worktree" && rec.state !== "closed" && typeof rec.ref === "string" && rec.ref.trim()) {
|
|
32945
33383
|
refs.push(rec.ref);
|
|
32946
33384
|
}
|
|
@@ -32956,9 +33394,9 @@ function scanHelperWorktrees(thisPrimary, thisWorktreeGitRoot) {
|
|
|
32956
33394
|
const primaries = [thisPrimary, ...discoverOtherPrimaries(thisPrimary)];
|
|
32957
33395
|
const out = [];
|
|
32958
33396
|
for (const primary of primaries) {
|
|
32959
|
-
const gitRoot = primary === thisPrimary ? thisWorktreeGitRoot : (0,
|
|
33397
|
+
const gitRoot = primary === thisPrimary ? thisWorktreeGitRoot : (0, import_node_path42.join)(primary, ".git", "worktrees");
|
|
32960
33398
|
for (const root of helperWorktreeRoots(primary)) {
|
|
32961
|
-
if (!(0,
|
|
33399
|
+
if (!(0, import_node_fs43.existsSync)(root)) continue;
|
|
32962
33400
|
for (const dir of defaultOrphanDirScanDeps.listDirs(root)) {
|
|
32963
33401
|
const inspected = inspectSiblingWorktreeDir(dir, gitRoot);
|
|
32964
33402
|
const classified = classifySiblingWorktreeDir(inspected);
|
|
@@ -32998,8 +33436,8 @@ ${err.stderr ?? ""}`;
|
|
|
32998
33436
|
}
|
|
32999
33437
|
|
|
33000
33438
|
// src/issue-commands.ts
|
|
33001
|
-
var
|
|
33002
|
-
var
|
|
33439
|
+
var import_node_fs44 = require("node:fs");
|
|
33440
|
+
var import_node_crypto10 = require("node:crypto");
|
|
33003
33441
|
var ghRunner = async (args, timeoutMs) => (await execFileP2("gh", args, { timeout: timeoutMs })).stdout;
|
|
33004
33442
|
var ReparentConflictError = class extends Error {
|
|
33005
33443
|
constructor(message, payload) {
|
|
@@ -33016,7 +33454,7 @@ async function editIssue(client, options, deps = {}) {
|
|
|
33016
33454
|
const url = `https://github.com/${repo}/issues/${parsed.number}`;
|
|
33017
33455
|
const patch = {};
|
|
33018
33456
|
let bodyChanged = false;
|
|
33019
|
-
const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0,
|
|
33457
|
+
const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0, import_node_fs44.readFileSync)(p, e)), readStdin: () => Promise.resolve("") };
|
|
33020
33458
|
if (options.titleFile !== void 0) {
|
|
33021
33459
|
patch.title = await resolveIssueTitle({ title: options.title, titleFile: options.titleFile }, textDeps());
|
|
33022
33460
|
} else if (options.title !== void 0) {
|
|
@@ -33290,7 +33728,7 @@ function rowIdempotencyKey(batchKey, spec) {
|
|
|
33290
33728
|
const identity = `${spec.type}
|
|
33291
33729
|
${spec.title.trim()}
|
|
33292
33730
|
${spec.body ?? ""}`;
|
|
33293
|
-
const hash = (0,
|
|
33731
|
+
const hash = (0, import_node_crypto10.createHash)("sha256").update(identity).digest("hex").slice(0, 16);
|
|
33294
33732
|
return `${batchKey}:${hash}`;
|
|
33295
33733
|
}
|
|
33296
33734
|
var BATCH_SPEC_KEYS = /* @__PURE__ */ new Set(["type", "title", "body", "priority", "labels", "label", "parent", "repo", "surface"]);
|
|
@@ -33621,7 +34059,7 @@ function extendCreateCommand(issue2, batchAttach) {
|
|
|
33621
34059
|
if (opts.batch) {
|
|
33622
34060
|
let specs;
|
|
33623
34061
|
try {
|
|
33624
|
-
const raw = (0,
|
|
34062
|
+
const raw = (0, import_node_fs44.readFileSync)(opts.batch, "utf8");
|
|
33625
34063
|
specs = JSON.parse(raw);
|
|
33626
34064
|
if (!Array.isArray(specs)) throw new Error("batch file must contain a JSON array");
|
|
33627
34065
|
} catch (e) {
|
|
@@ -33696,8 +34134,8 @@ ${lines}`, {
|
|
|
33696
34134
|
}
|
|
33697
34135
|
|
|
33698
34136
|
// src/train-commands.ts
|
|
33699
|
-
var
|
|
33700
|
-
var
|
|
34137
|
+
var import_node_fs45 = require("node:fs");
|
|
34138
|
+
var import_node_path43 = require("node:path");
|
|
33701
34139
|
var RELEASE_BUMP_INTENTS = ["major", "minor", "patch"];
|
|
33702
34140
|
function resolveReleaseBumpIntent(raw) {
|
|
33703
34141
|
const intent = typeof raw === "string" ? raw.trim() : "";
|
|
@@ -33708,7 +34146,7 @@ function resolveReleaseBumpIntent(raw) {
|
|
|
33708
34146
|
}
|
|
33709
34147
|
function readRepoVersion() {
|
|
33710
34148
|
try {
|
|
33711
|
-
return JSON.parse((0,
|
|
34149
|
+
return JSON.parse((0, import_node_fs45.readFileSync)((0, import_node_path43.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
|
|
33712
34150
|
} catch {
|
|
33713
34151
|
return void 0;
|
|
33714
34152
|
}
|
|
@@ -33865,9 +34303,9 @@ function registerDeployCommands(program3) {
|
|
|
33865
34303
|
}
|
|
33866
34304
|
|
|
33867
34305
|
// src/discovery-commands.ts
|
|
33868
|
-
var
|
|
33869
|
-
var
|
|
33870
|
-
var
|
|
34306
|
+
var import_node_fs46 = require("node:fs");
|
|
34307
|
+
var import_node_os19 = require("node:os");
|
|
34308
|
+
var import_node_path44 = require("node:path");
|
|
33871
34309
|
var GC_GH_TIMEOUT_MS3 = 2e4;
|
|
33872
34310
|
async function collectStatus() {
|
|
33873
34311
|
const repo = await resolveRepo();
|
|
@@ -33969,7 +34407,7 @@ function onboardPluginGate(deps) {
|
|
|
33969
34407
|
declared,
|
|
33970
34408
|
settingsDeclared: readSettingsAutoUpdate(deps.readSettings(), MMI_MARKETPLACE_NAME)
|
|
33971
34409
|
}).effective;
|
|
33972
|
-
return autoUpdate ? { ok: false, detail: "background auto-update is ON \u2014 duplicate writer; run `mmi-
|
|
34410
|
+
return autoUpdate ? { ok: false, detail: "background auto-update is ON \u2014 duplicate writer; run `mmi-hub update` outside Claude Code to disable it" } : { ok: true, detail: "background auto-update off; mmi-hub is the single release-gated writer" };
|
|
33973
34411
|
}
|
|
33974
34412
|
async function collectOnboardStatus(opts = {}) {
|
|
33975
34413
|
const cfg = await loadConfig();
|
|
@@ -34051,10 +34489,10 @@ async function collectOnboardStatus(opts = {}) {
|
|
|
34051
34489
|
else if (top) nextCommand = `mmi-cli oracle board claim ${top.number} # ${top.title}`;
|
|
34052
34490
|
else nextCommand = "mmi-cli oracle board read \u2014 no claimable items found";
|
|
34053
34491
|
}
|
|
34054
|
-
const home = (0,
|
|
34492
|
+
const home = (0, import_node_os19.homedir)();
|
|
34055
34493
|
const plugin = onboardPluginGate({
|
|
34056
|
-
readKnown: () => readFileSyncSafe((0,
|
|
34057
|
-
readSettings: () => readFileSyncSafe((0,
|
|
34494
|
+
readKnown: () => readFileSyncSafe((0, import_node_path44.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs46.readFileSync),
|
|
34495
|
+
readSettings: () => readFileSyncSafe((0, import_node_path44.join)(home, ".claude", "settings.json"), import_node_fs46.readFileSync)
|
|
34058
34496
|
});
|
|
34059
34497
|
return { track, board, registry: registry2, secrets, plugin, estateCli, doors: opts.doors ?? [], nextCommand };
|
|
34060
34498
|
}
|
|
@@ -36269,13 +36707,13 @@ var surfaces_default = {
|
|
|
36269
36707
|
publishVisibility: "public"
|
|
36270
36708
|
},
|
|
36271
36709
|
{
|
|
36272
|
-
id: "mmi-
|
|
36710
|
+
id: "mmi-hub",
|
|
36273
36711
|
classification: "capability",
|
|
36274
36712
|
kind: "cli",
|
|
36275
36713
|
ownerPath: "updater/package.json",
|
|
36276
36714
|
deliveryPath: "updater/package.json",
|
|
36277
36715
|
delivery: "npm",
|
|
36278
|
-
applicability: "editor-agnostic
|
|
36716
|
+
applicability: "editor-agnostic MMI installation and maintenance; npm package @mutmutco/hub exposing mmi-hub",
|
|
36279
36717
|
versionCoordinated: true,
|
|
36280
36718
|
versionPaths: [
|
|
36281
36719
|
{
|
|
@@ -36292,6 +36730,34 @@ var surfaces_default = {
|
|
|
36292
36730
|
},
|
|
36293
36731
|
publishVisibility: "public"
|
|
36294
36732
|
},
|
|
36733
|
+
{
|
|
36734
|
+
id: "mmi-updater-compat",
|
|
36735
|
+
classification: "packaging",
|
|
36736
|
+
kind: "cli",
|
|
36737
|
+
ownerPath: "packages/updater-compat/package.json",
|
|
36738
|
+
deliveryPath: "packages/updater-compat/package.json",
|
|
36739
|
+
delivery: "npm",
|
|
36740
|
+
applicability: "one-window deprecated @mutmutco/updater / mmi-updater wrapper delegating to the exact coordinated @mutmutco/hub version",
|
|
36741
|
+
versionCoordinated: true,
|
|
36742
|
+
versionPaths: [
|
|
36743
|
+
{
|
|
36744
|
+
path: "packages/updater-compat/package.json",
|
|
36745
|
+
pointer: "version"
|
|
36746
|
+
},
|
|
36747
|
+
{
|
|
36748
|
+
path: "packages/updater-compat/package.json",
|
|
36749
|
+
pointer: "dependencies.@mutmutco/hub"
|
|
36750
|
+
}
|
|
36751
|
+
],
|
|
36752
|
+
additionalPaths: [
|
|
36753
|
+
"packages/updater-compat/dist"
|
|
36754
|
+
],
|
|
36755
|
+
artifactIdentity: {
|
|
36756
|
+
kind: "npm-pack",
|
|
36757
|
+
packagePath: "packages/updater-compat"
|
|
36758
|
+
},
|
|
36759
|
+
publishVisibility: "public"
|
|
36760
|
+
},
|
|
36295
36761
|
{
|
|
36296
36762
|
id: "mmi-cli-lock",
|
|
36297
36763
|
classification: "packaging",
|
|
@@ -36753,7 +37219,7 @@ function checkCliVersion(input, releasedNote) {
|
|
|
36753
37219
|
warn: true,
|
|
36754
37220
|
label: "mmi-cli",
|
|
36755
37221
|
detail: `${report.currentVersion} \u2014 freshness UNKNOWN, the published version could not be read`,
|
|
36756
|
-
fix: "check
|
|
37222
|
+
fix: "check actual maintenance state with `mmi-hub status`; converge with `mmi-hub update`",
|
|
36757
37223
|
verbose: evidence
|
|
36758
37224
|
};
|
|
36759
37225
|
}
|
|
@@ -36763,7 +37229,7 @@ function checkCliVersion(input, releasedNote) {
|
|
|
36763
37229
|
ok: false,
|
|
36764
37230
|
label: "mmi-cli",
|
|
36765
37231
|
detail: `${report.currentVersion} \u2192 ${report.releasedVersion}`,
|
|
36766
|
-
fix:
|
|
37232
|
+
fix: "run `mmi-hub update` to converge the CLI and every present host surface",
|
|
36767
37233
|
verbose: evidence
|
|
36768
37234
|
};
|
|
36769
37235
|
}
|
|
@@ -36776,8 +37242,8 @@ function checkFleetDrift(probe) {
|
|
|
36776
37242
|
id,
|
|
36777
37243
|
ok: true,
|
|
36778
37244
|
label,
|
|
36779
|
-
detail: "no
|
|
36780
|
-
verbose: [`journal: ${probe.journalPath} (absent)`, "
|
|
37245
|
+
detail: "no MMI Hub maintenance journal on this machine",
|
|
37246
|
+
verbose: [`journal: ${probe.journalPath} (absent)`, "bootstrap with `npm install -g @mutmutco/hub` then `mmi-hub install`"]
|
|
36781
37247
|
};
|
|
36782
37248
|
}
|
|
36783
37249
|
if (probe.unreadable) {
|
|
@@ -36788,14 +37254,14 @@ function checkFleetDrift(probe) {
|
|
|
36788
37254
|
warn: true,
|
|
36789
37255
|
label,
|
|
36790
37256
|
detail: `journal could not be read \u2014 ${probe.unreadable}`,
|
|
36791
|
-
fix: `read ${probe.journalPath} directly, or
|
|
37257
|
+
fix: `read ${probe.journalPath} directly, or repair maintenance with \`mmi-hub install\``,
|
|
36792
37258
|
verbose: [`journal: ${probe.journalPath}`, `read failed: ${probe.unreadable}`]
|
|
36793
37259
|
};
|
|
36794
37260
|
}
|
|
36795
37261
|
const evidence = [
|
|
36796
37262
|
`journal: ${probe.journalPath}${probe.lastEventAt ? ` (last event ${probe.lastEventAt})` : ""}${probe.truncated ? ` \u2014 newest ${JOURNAL_TAIL_BYTES / 1024} KiB read` : ""}`,
|
|
36797
37263
|
`expected: ${probe.expected ?? "(no gated release recorded yet)"}`,
|
|
36798
|
-
...probe.surfaces.map((s) => `${s.surface}:
|
|
37264
|
+
...probe.surfaces.map((s) => `${s.surface}: last recorded ${s.installed ?? "unknown"} \u2014 ${s.verdict}${s.detail ? ` (${s.detail})` : ""}${s.compat ? ` [compat ${s.compat}]` : ""}`),
|
|
36799
37265
|
...probe.absent?.length ? [`absent since their last arm, skipped by the reconciler: ${probe.absent.join(", ")}`] : []
|
|
36800
37266
|
];
|
|
36801
37267
|
if (!probe.expected) {
|
|
@@ -36840,7 +37306,7 @@ function checkFleetDrift(probe) {
|
|
|
36840
37306
|
warn: true,
|
|
36841
37307
|
label,
|
|
36842
37308
|
detail: `${probe.expected} \u2014 ${probe.surfaces.length - unverified.length} converged, ${unverifiedNote}: no installed version recorded`,
|
|
36843
|
-
fix: `read why in ${probe.journalPath} (each arm records its own reason) \u2014 \`mmi-
|
|
37309
|
+
fix: `read why in ${probe.journalPath} (each arm records its own reason) \u2014 \`mmi-hub update\` retries now; doctor never installs`,
|
|
36844
37310
|
verbose: evidence
|
|
36845
37311
|
};
|
|
36846
37312
|
}
|
|
@@ -36851,7 +37317,7 @@ function checkFleetDrift(probe) {
|
|
|
36851
37317
|
label,
|
|
36852
37318
|
...unverified.length ? { verified: false } : {},
|
|
36853
37319
|
detail: `${drifted.length} of ${probe.surfaces.length} surface(s) behind ${probe.expected}: ${drifted.map((s) => `${s.surface} ${s.installed}`).join(", ")}${unverifiedNote ? `; ${unverifiedNote}` : ""}`,
|
|
36854
|
-
fix: `
|
|
37320
|
+
fix: `hourly Hub maintenance converges these on its next tick \u2014 \`mmi-hub update\` runs one now; doctor never installs${unverified.length ? `. The unverified surface(s) recorded no version \u2014 read their reason in ${probe.journalPath}` : ""}`,
|
|
36855
37321
|
verbose: evidence
|
|
36856
37322
|
};
|
|
36857
37323
|
}
|
|
@@ -37144,7 +37610,7 @@ function gcReapable(plan) {
|
|
|
37144
37610
|
async function runDoctorClean(opts, io, deps) {
|
|
37145
37611
|
const full = !opts.fast && !opts.banner && !opts.preflight;
|
|
37146
37612
|
const applyEnv = full;
|
|
37147
|
-
const applyRepo = full && opts.repoWrites
|
|
37613
|
+
const applyRepo = full && opts.repoWrites === true;
|
|
37148
37614
|
const lane = {
|
|
37149
37615
|
banner: Boolean(opts.banner),
|
|
37150
37616
|
fast: Boolean(opts.fast),
|
|
@@ -37259,7 +37725,7 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
37259
37725
|
...cli,
|
|
37260
37726
|
ok: false,
|
|
37261
37727
|
detail: cli.ok ? `missing commands: ${missing.join(", ")}` : cli.detail,
|
|
37262
|
-
fix: `run
|
|
37728
|
+
fix: `run \`mmi-hub update\` \u2014 the CLI lacks ${missing.join(", ")} (or wait for hourly Hub maintenance)`,
|
|
37263
37729
|
verbose: [...cli.verbose ?? [], `missing commands: ${missing.join(", ")}`]
|
|
37264
37730
|
});
|
|
37265
37731
|
}
|
|
@@ -37292,7 +37758,7 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
37292
37758
|
emitNow({ ok: true, id: "gitignore-block", label: "gitignore block", detail: "rewrote managed block", verbose: giEvidence });
|
|
37293
37759
|
restartPending = true;
|
|
37294
37760
|
} else {
|
|
37295
|
-
emitNow({ ok: false, id: "gitignore-block", label: "gitignore block", fix: "run `mmi-cli doctor
|
|
37761
|
+
emitNow({ ok: false, id: "gitignore-block", label: "gitignore block", fix: "run `mmi-cli doctor --apply` to write the org-managed .gitignore block", verbose: giEvidence });
|
|
37296
37762
|
}
|
|
37297
37763
|
}
|
|
37298
37764
|
async function runPluginCacheRow() {
|
|
@@ -37715,7 +38181,7 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
37715
38181
|
// #3574: `${n} stale` was prepended to `fix` because the ✗ branch would not render `detail` —
|
|
37716
38182
|
// the same fact this row already puts in `detail` on its ✓ path four lines up. One convention now.
|
|
37717
38183
|
detail: `${n} stale`,
|
|
37718
|
-
fix: "run `mmi-cli doctor
|
|
38184
|
+
fix: "run `mmi-cli doctor --apply` to reap merged branches, stale refs, and dead worktrees",
|
|
37719
38185
|
verbose: gcEvidence
|
|
37720
38186
|
});
|
|
37721
38187
|
}
|
|
@@ -37733,7 +38199,7 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
37733
38199
|
restartPending = true;
|
|
37734
38200
|
}
|
|
37735
38201
|
} else {
|
|
37736
|
-
emitNow(scratch.plan.safeAuto.length === 0 ? { id: "scratch", ok: true, label: "scratch", verbose: scratchEvidence } : { id: "scratch", ok: false, label: "scratch", detail: `${scratch.plan.safeAuto.length} aged item(s)`, fix: "run `mmi-cli doctor
|
|
38202
|
+
emitNow(scratch.plan.safeAuto.length === 0 ? { id: "scratch", ok: true, label: "scratch", verbose: scratchEvidence } : { id: "scratch", ok: false, label: "scratch", detail: `${scratch.plan.safeAuto.length} aged item(s)`, fix: "run `mmi-cli doctor --apply`", verbose: scratchEvidence });
|
|
37737
38203
|
}
|
|
37738
38204
|
}
|
|
37739
38205
|
const prefix = [
|
|
@@ -37835,17 +38301,17 @@ function parseOriginRepo(remoteUrl) {
|
|
|
37835
38301
|
}
|
|
37836
38302
|
function ghHostsConfigPath(env, platform2) {
|
|
37837
38303
|
const sep3 = platform2 === "win32" ? "\\" : "/";
|
|
37838
|
-
const
|
|
38304
|
+
const join41 = (...parts) => parts.join(sep3);
|
|
37839
38305
|
const explicit = env.GH_CONFIG_DIR?.trim();
|
|
37840
|
-
if (explicit) return
|
|
38306
|
+
if (explicit) return join41(explicit, "hosts.yml");
|
|
37841
38307
|
if (platform2 === "win32") {
|
|
37842
38308
|
const appData = (env.AppData ?? env.APPDATA)?.trim();
|
|
37843
|
-
return appData ?
|
|
38309
|
+
return appData ? join41(appData, "GitHub CLI", "hosts.yml") : void 0;
|
|
37844
38310
|
}
|
|
37845
38311
|
const xdg = env.XDG_CONFIG_HOME?.trim();
|
|
37846
|
-
if (xdg) return
|
|
38312
|
+
if (xdg) return join41(xdg, "gh", "hosts.yml");
|
|
37847
38313
|
const home = env.HOME?.trim();
|
|
37848
|
-
return home ?
|
|
38314
|
+
return home ? join41(home, ".config", "gh", "hosts.yml") : void 0;
|
|
37849
38315
|
}
|
|
37850
38316
|
function parseGhHostsAccounts(yaml, host = "github.com") {
|
|
37851
38317
|
let hostIndent = null;
|
|
@@ -37895,9 +38361,9 @@ function ghAccountCaveat(announcedLogin, accounts) {
|
|
|
37895
38361
|
}
|
|
37896
38362
|
|
|
37897
38363
|
// src/doctor-io.ts
|
|
37898
|
-
var
|
|
37899
|
-
var
|
|
37900
|
-
var
|
|
38364
|
+
var import_node_fs47 = require("node:fs");
|
|
38365
|
+
var import_node_os20 = require("node:os");
|
|
38366
|
+
var import_node_path45 = require("node:path");
|
|
37901
38367
|
var import_node_child_process18 = require("node:child_process");
|
|
37902
38368
|
var import_node_util8 = require("node:util");
|
|
37903
38369
|
var execFileP6 = (0, import_node_util8.promisify)(import_node_child_process18.execFile);
|
|
@@ -37905,7 +38371,7 @@ var MMI_PLUGIN_ID2 = "mmi@mutmutco";
|
|
|
37905
38371
|
function installedClaudePluginVersion() {
|
|
37906
38372
|
try {
|
|
37907
38373
|
const file = JSON.parse(
|
|
37908
|
-
(0,
|
|
38374
|
+
(0, import_node_fs47.readFileSync)((0, import_node_path45.join)((0, import_node_os20.homedir)(), ".claude", "plugins", "installed_plugins.json"), "utf8")
|
|
37909
38375
|
);
|
|
37910
38376
|
const versions = (file.plugins?.[MMI_PLUGIN_ID2] ?? []).map((r) => r.version).filter((v) => Boolean(v));
|
|
37911
38377
|
if (versions.length === 0) return void 0;
|
|
@@ -37916,7 +38382,7 @@ function installedClaudePluginVersion() {
|
|
|
37916
38382
|
}
|
|
37917
38383
|
function manifestVersion(path2) {
|
|
37918
38384
|
try {
|
|
37919
|
-
const manifest = JSON.parse((0,
|
|
38385
|
+
const manifest = JSON.parse((0, import_node_fs47.readFileSync)(path2, "utf8"));
|
|
37920
38386
|
return typeof manifest.version === "string" && manifest.version.trim() ? manifest.version.trim() : void 0;
|
|
37921
38387
|
} catch {
|
|
37922
38388
|
return void 0;
|
|
@@ -37926,20 +38392,20 @@ function installedSurfacePluginVersion(surface) {
|
|
|
37926
38392
|
const token = surfaceToken(surface);
|
|
37927
38393
|
if (token === "kilo") {
|
|
37928
38394
|
try {
|
|
37929
|
-
const stamp = (0,
|
|
38395
|
+
const stamp = (0, import_node_fs47.readFileSync)((0, import_node_path45.join)((0, import_node_os20.homedir)(), ".kilo", ".mmi-kilo-version"), "utf8").trim();
|
|
37930
38396
|
return stamp || void 0;
|
|
37931
38397
|
} catch {
|
|
37932
38398
|
return void 0;
|
|
37933
38399
|
}
|
|
37934
38400
|
}
|
|
37935
38401
|
if (token === "cursor") {
|
|
37936
|
-
return manifestVersion((0,
|
|
38402
|
+
return manifestVersion((0, import_node_path45.join)(cursorLocalPluginRoot(), ".cursor-plugin", "plugin.json"));
|
|
37937
38403
|
}
|
|
37938
38404
|
if (token === "jervcode") {
|
|
37939
38405
|
return installedJervCodePackageVersion();
|
|
37940
38406
|
}
|
|
37941
38407
|
if (token === "kimi") {
|
|
37942
|
-
return manifestVersion((0,
|
|
38408
|
+
return manifestVersion((0, import_node_path45.join)(surfaceConfigRoot(surface), "plugins", "managed", "mmi", ".kimi-plugin", "plugin.json"));
|
|
37943
38409
|
}
|
|
37944
38410
|
if (token === "claude") return installedClaudePluginVersion();
|
|
37945
38411
|
if (token !== "codex") return void 0;
|
|
@@ -37977,13 +38443,13 @@ function worktreeRootSync() {
|
|
|
37977
38443
|
}
|
|
37978
38444
|
var gitignorePath = () => {
|
|
37979
38445
|
const root = worktreeRootSync();
|
|
37980
|
-
return root === null ? null : (0,
|
|
38446
|
+
return root === null ? null : (0, import_node_path45.join)(root, ".gitignore");
|
|
37981
38447
|
};
|
|
37982
38448
|
function readGitignore() {
|
|
37983
38449
|
const path2 = gitignorePath();
|
|
37984
38450
|
if (path2 === null) return null;
|
|
37985
38451
|
try {
|
|
37986
|
-
return (0,
|
|
38452
|
+
return (0, import_node_fs47.readFileSync)(path2, "utf8");
|
|
37987
38453
|
} catch {
|
|
37988
38454
|
return null;
|
|
37989
38455
|
}
|
|
@@ -37992,7 +38458,7 @@ function writeGitignore(content) {
|
|
|
37992
38458
|
const path2 = gitignorePath();
|
|
37993
38459
|
if (path2 === null) return false;
|
|
37994
38460
|
try {
|
|
37995
|
-
(0,
|
|
38461
|
+
(0, import_node_fs47.writeFileSync)(path2, content, "utf8");
|
|
37996
38462
|
return true;
|
|
37997
38463
|
} catch {
|
|
37998
38464
|
return false;
|
|
@@ -38011,7 +38477,7 @@ async function repoRoot() {
|
|
|
38011
38477
|
}
|
|
38012
38478
|
function hasRepoLocalWorktrees() {
|
|
38013
38479
|
const root = worktreeRootSync();
|
|
38014
|
-
return root !== null && ((0,
|
|
38480
|
+
return root !== null && ((0, import_node_fs47.existsSync)((0, import_node_path45.join)(root, ".worktrees")) || (0, import_node_fs47.existsSync)((0, import_node_path45.join)(root, ".claude", "worktrees")));
|
|
38015
38481
|
}
|
|
38016
38482
|
|
|
38017
38483
|
// src/cross-repo-filing-issue.ts
|
|
@@ -38117,8 +38583,8 @@ ${r.stderr ?? ""}`).catch(() => "");
|
|
|
38117
38583
|
function ghMultiAccountCaveat(announcedLogin) {
|
|
38118
38584
|
try {
|
|
38119
38585
|
const hostsPath = ghHostsConfigPath(process.env, process.platform);
|
|
38120
|
-
if (!hostsPath || !(0,
|
|
38121
|
-
return ghAccountCaveat(announcedLogin, parseGhHostsAccounts((0,
|
|
38586
|
+
if (!hostsPath || !(0, import_node_fs48.existsSync)(hostsPath)) return void 0;
|
|
38587
|
+
return ghAccountCaveat(announcedLogin, parseGhHostsAccounts((0, import_node_fs48.readFileSync)(hostsPath, "utf8")));
|
|
38122
38588
|
} catch {
|
|
38123
38589
|
return void 0;
|
|
38124
38590
|
}
|
|
@@ -38126,12 +38592,12 @@ function ghMultiAccountCaveat(announcedLogin) {
|
|
|
38126
38592
|
var ENV_HEAL_LOCK_STALE_MS = 10 * 6e4;
|
|
38127
38593
|
var ENV_HEAL_LOCK_MAX_WAIT_MS = 2 * 6e4;
|
|
38128
38594
|
function envHealLockPath(home) {
|
|
38129
|
-
return (0,
|
|
38595
|
+
return (0, import_node_path46.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
|
|
38130
38596
|
}
|
|
38131
38597
|
async function withEnvHealLock(what, run) {
|
|
38132
38598
|
try {
|
|
38133
38599
|
return await withFileLock(
|
|
38134
|
-
envHealLockPath((0,
|
|
38600
|
+
envHealLockPath((0, import_node_os21.homedir)()),
|
|
38135
38601
|
{ staleMs: ENV_HEAL_LOCK_STALE_MS, maxWaitMs: ENV_HEAL_LOCK_MAX_WAIT_MS, label: "mmi env-heal lock" },
|
|
38136
38602
|
run
|
|
38137
38603
|
);
|
|
@@ -38195,9 +38661,8 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
38195
38661
|
pluginTrustState: () => codexHookTrustState(),
|
|
38196
38662
|
releasedVersion: throttled ? throttled.read : fetchNpmReleasedVersion,
|
|
38197
38663
|
releasedVersionNote: throttled ? throttled.note : void 0,
|
|
38198
|
-
// #4954: the
|
|
38199
|
-
//
|
|
38200
|
-
// journal `mmi-updater stamp` reports from, parsed here into one row.
|
|
38664
|
+
// #4954/#5023: the Hub maintenance journal is read-only last-run evidence. `@mutmutco/hub`
|
|
38665
|
+
// owns every version install; `mmi-hub status` separately probes actual installed state.
|
|
38201
38666
|
fleetDrift: () => readFleetDrift(process.env),
|
|
38202
38667
|
// #3282: the --apply self-heal for a stale/unresolved Claude plugin — the same marketplace reinstall
|
|
38203
38668
|
// `mmi-cli plugin heal` drives. Takes effect on the next Claude reload, so the row asks for a restart.
|
|
@@ -38226,7 +38691,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
38226
38691
|
const configRoot = surfaceConfigRoot(surface);
|
|
38227
38692
|
const running = surface === "codex" ? installedActivePluginVersion(surface) : runningPluginVersion(process.env, resolveClientVersion());
|
|
38228
38693
|
const plan = buildPluginCachePlan(
|
|
38229
|
-
(0,
|
|
38694
|
+
(0, import_node_os21.homedir)(),
|
|
38230
38695
|
running,
|
|
38231
38696
|
pluginCacheFsDeps(configRoot, () => 0),
|
|
38232
38697
|
{ configRoot, includeStaging: surface !== "codex" }
|
|
@@ -38250,14 +38715,14 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
38250
38715
|
const running = surface === "codex" ? installedActivePluginVersion(surface) : runningPluginVersion(process.env, resolveClientVersion());
|
|
38251
38716
|
const installed = installedActivePluginVersion(surface);
|
|
38252
38717
|
const plan = buildPluginCachePlan(
|
|
38253
|
-
(0,
|
|
38718
|
+
(0, import_node_os21.homedir)(),
|
|
38254
38719
|
running,
|
|
38255
38720
|
pluginCacheFsDeps(configRoot, () => 0),
|
|
38256
38721
|
{ configRoot, includeStaging: surface !== "codex", installedVersion: installed }
|
|
38257
38722
|
);
|
|
38258
38723
|
const result = applyPluginCachePlan(
|
|
38259
38724
|
plan,
|
|
38260
|
-
(p) => (0,
|
|
38725
|
+
(p) => (0, import_node_fs48.rmSync)(p, { recursive: true }),
|
|
38261
38726
|
stagingApplyFsGuard(configRoot)
|
|
38262
38727
|
);
|
|
38263
38728
|
return {
|
|
@@ -38295,7 +38760,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
38295
38760
|
// adopted the generated routing index (MMG-Unlive: `docs/Archive/**` only, no index, no gate.yml) would
|
|
38296
38761
|
// get a permanent — demanding an artifact it never asked for.
|
|
38297
38762
|
docsIndexState: (root) => {
|
|
38298
|
-
if (!(0,
|
|
38763
|
+
if (!(0, import_node_fs48.existsSync)((0, import_node_path46.join)(root, DOCS_INDEX_PATH))) return void 0;
|
|
38299
38764
|
const real = createDocsIndexDeps(root);
|
|
38300
38765
|
let docs2;
|
|
38301
38766
|
const listDocs = () => docs2 ??= real.listDocs();
|
|
@@ -38304,7 +38769,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
38304
38769
|
},
|
|
38305
38770
|
// #4168: working-tree heal — write if drifted, then re-check. Commit remains the operator's step.
|
|
38306
38771
|
healDocsIndex: (root) => {
|
|
38307
|
-
if (!(0,
|
|
38772
|
+
if (!(0, import_node_fs48.existsSync)((0, import_node_path46.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
|
|
38308
38773
|
const real = createDocsIndexDeps(root);
|
|
38309
38774
|
let docs2;
|
|
38310
38775
|
const listDocs = () => docs2 ??= real.listDocs();
|
|
@@ -38670,19 +39135,19 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
|
|
|
38670
39135
|
});
|
|
38671
39136
|
var rules = program2.command("rules").description("org-managed .gitignore delivery");
|
|
38672
39137
|
rules.command("gitignore").option("--write", "upsert the managed block into .gitignore (default: check only, non-zero exit on drift)").option("--json", "machine-readable output").description("verify (or --write) this repo's org-managed .gitignore block matches the SSOT").action((opts) => {
|
|
38673
|
-
const path2 = (0,
|
|
38674
|
-
const current = (0,
|
|
39138
|
+
const path2 = (0, import_node_path46.join)(process.cwd(), ".gitignore");
|
|
39139
|
+
const current = (0, import_node_fs48.existsSync)(path2) ? (0, import_node_fs48.readFileSync)(path2, "utf8") : null;
|
|
38675
39140
|
const plan = planManagedGitignore(current);
|
|
38676
39141
|
const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
|
|
38677
39142
|
if (opts.json) {
|
|
38678
|
-
if (opts.write && plan.changed) (0,
|
|
39143
|
+
if (opts.write && plan.changed) (0, import_node_fs48.writeFileSync)(path2, plan.content, "utf8");
|
|
38679
39144
|
console.log(JSON.stringify(plan, null, 2));
|
|
38680
39145
|
if (!opts.write && plan.changed) process.exitCode = 1;
|
|
38681
39146
|
return;
|
|
38682
39147
|
}
|
|
38683
39148
|
if (opts.write) {
|
|
38684
39149
|
if (plan.changed) {
|
|
38685
|
-
(0,
|
|
39150
|
+
(0, import_node_fs48.writeFileSync)(path2, plan.content, "utf8");
|
|
38686
39151
|
console.log(`mmi-cli devops org rules gitignore: updated .gitignore (${drift})`);
|
|
38687
39152
|
} else {
|
|
38688
39153
|
console.log("mmi-cli devops org rules gitignore: up to date");
|
|
@@ -38841,8 +39306,8 @@ gcCmd.option("--dry-run", "show what would be deleted (default)").option("--appl
|
|
|
38841
39306
|
if (!Number.isFinite(limit) || limit < 1) return fail("worktree gc: --limit must be a positive integer");
|
|
38842
39307
|
let root;
|
|
38843
39308
|
if (o.root !== void 0) {
|
|
38844
|
-
root = (0,
|
|
38845
|
-
if (!(0,
|
|
39309
|
+
root = (0, import_node_path46.resolve)(o.root);
|
|
39310
|
+
if (!(0, import_node_fs48.existsSync)(root) || !(0, import_node_fs48.statSync)(root).isDirectory()) return fail(`worktree gc: --root ${o.root} is not a directory`);
|
|
38846
39311
|
const gcRepoRoot = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim() || process.cwd();
|
|
38847
39312
|
if (isPathUnderDirectory2(gcRepoRoot, root)) {
|
|
38848
39313
|
return fail(`worktree gc: --root ${root} contains this checkout \u2014 name a worktrees root, not the repo or an ancestor of it`);
|
|
@@ -38941,7 +39406,7 @@ async function currentWorktreeRemovalContext(command, force) {
|
|
|
38941
39406
|
};
|
|
38942
39407
|
}
|
|
38943
39408
|
async function unprovenWorktreeReason(wtPath, repoRoot2) {
|
|
38944
|
-
if (!(0,
|
|
39409
|
+
if (!(0, import_node_fs48.existsSync)(wtPath)) return `${wtPath} does not exist on disk`;
|
|
38945
39410
|
const porcelain = (await execFileP2("git", ["-C", repoRoot2, "worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout;
|
|
38946
39411
|
const registered = parseWorktreePorcelainEntries(porcelain);
|
|
38947
39412
|
if (!registered.length) {
|
|
@@ -38971,26 +39436,26 @@ function makeProvisionDeps(worktreeRoot, quiet, log) {
|
|
|
38971
39436
|
function acquireWorktreeSetupLock(worktreeRoot) {
|
|
38972
39437
|
const lockPath = repoRuntimeStatePath(worktreeRoot, "worktree-setup.lock");
|
|
38973
39438
|
const take = () => {
|
|
38974
|
-
const fd = (0,
|
|
39439
|
+
const fd = (0, import_node_fs48.openSync)(lockPath, "wx");
|
|
38975
39440
|
try {
|
|
38976
|
-
(0,
|
|
39441
|
+
(0, import_node_fs48.writeSync)(fd, String(Date.now()));
|
|
38977
39442
|
} finally {
|
|
38978
|
-
(0,
|
|
39443
|
+
(0, import_node_fs48.closeSync)(fd);
|
|
38979
39444
|
}
|
|
38980
39445
|
return () => {
|
|
38981
39446
|
try {
|
|
38982
|
-
(0,
|
|
39447
|
+
(0, import_node_fs48.rmSync)(lockPath, { force: true });
|
|
38983
39448
|
} catch {
|
|
38984
39449
|
}
|
|
38985
39450
|
};
|
|
38986
39451
|
};
|
|
38987
39452
|
try {
|
|
38988
|
-
(0,
|
|
39453
|
+
(0, import_node_fs48.mkdirSync)((0, import_node_path46.dirname)(lockPath), { recursive: true });
|
|
38989
39454
|
return take();
|
|
38990
39455
|
} catch {
|
|
38991
39456
|
try {
|
|
38992
|
-
if (Date.now() - (0,
|
|
38993
|
-
(0,
|
|
39457
|
+
if (Date.now() - (0, import_node_fs48.statSync)(lockPath).mtimeMs > WORKTREE_SETUP_LOCK_TTL_MS) {
|
|
39458
|
+
(0, import_node_fs48.rmSync)(lockPath, { force: true });
|
|
38994
39459
|
return take();
|
|
38995
39460
|
}
|
|
38996
39461
|
} catch {
|
|
@@ -39070,8 +39535,10 @@ withExamples(mutating(
|
|
|
39070
39535
|
}
|
|
39071
39536
|
};
|
|
39072
39537
|
if (preferRemote && await revParseRef(preferRemote)) base = preferRemote;
|
|
39538
|
+
const resolvedBaseOid = await revParseRef(base);
|
|
39539
|
+
if (!resolvedBaseOid) return fail(`worktree create: could not resolve creation base '${base}' to an immutable commit`);
|
|
39073
39540
|
if (!o.json) {
|
|
39074
|
-
const baseSha =
|
|
39541
|
+
const baseSha = resolvedBaseOid.slice(0, 12);
|
|
39075
39542
|
const localOnly = preferRemote && base !== preferRemote ? ` (no ${preferRemote} ? local ref)` : "";
|
|
39076
39543
|
console.error(` base ${base} ${baseSha}${localOnly}`);
|
|
39077
39544
|
}
|
|
@@ -39090,23 +39557,45 @@ withExamples(mutating(
|
|
|
39090
39557
|
const status = (await execFileP2("git", ["-C", wtPath, "status", "--porcelain"], { timeout: GIT_TIMEOUT_MS })).stdout.trim();
|
|
39091
39558
|
if (status) return fail(`worktree create: refusing to resume ${wtPath} \u2014 it has uncommitted changes`);
|
|
39092
39559
|
const head = await revParseRef(`refs/heads/${branch}`);
|
|
39093
|
-
const baseOid =
|
|
39094
|
-
if (!head
|
|
39560
|
+
const baseOid = resolvedBaseOid;
|
|
39561
|
+
if (!head) return fail(`worktree create: could not prove '${branch}' and '${base}' before resume`);
|
|
39095
39562
|
const canFastForward = await execFileP2(
|
|
39096
39563
|
"git",
|
|
39097
39564
|
["-C", repoRoot2, "merge-base", "--is-ancestor", head, baseOid],
|
|
39098
39565
|
{ timeout: GIT_TIMEOUT_MS }
|
|
39099
39566
|
// io-census-allow: an unprovable ancestor probe conservatively refuses the resume below rather than fast-forwarding onto unproven history
|
|
39100
39567
|
).then(() => true).catch(() => false);
|
|
39101
|
-
if (
|
|
39102
|
-
|
|
39568
|
+
if (canFastForward) {
|
|
39569
|
+
await execFileP2("git", ["-C", wtPath, "merge", "--ff-only", base], { timeout: GIT_TIMEOUT_MS });
|
|
39570
|
+
resumed = true;
|
|
39571
|
+
} else {
|
|
39572
|
+
const owner2 = lookupWorktreeOwner(repoRoot2, wtPath);
|
|
39573
|
+
const delivered = await proveExactTreeDelivery({
|
|
39574
|
+
repoRoot: repoRoot2,
|
|
39575
|
+
branch,
|
|
39576
|
+
workerOid: head,
|
|
39577
|
+
creationBaseOid: owner2?.provenance?.creationBaseOid,
|
|
39578
|
+
recordedWorkerBranch: owner2?.provenance?.workerBranch,
|
|
39579
|
+
landedTips: [baseOid]
|
|
39580
|
+
});
|
|
39581
|
+
if (delivered.action !== "settle") {
|
|
39582
|
+
return fail(`worktree create: refusing to resume '${branch}' \u2014 it has local commits or diverges from ${base}; exact-tree delivery proof refused (${describeExactTreeRefusal(delivered)})`);
|
|
39583
|
+
}
|
|
39584
|
+
const repoint = await repointDeliveredWorktreeTransactionally({
|
|
39585
|
+
branch,
|
|
39586
|
+
expectedWorkerOid: head,
|
|
39587
|
+
landedTipOid: baseOid,
|
|
39588
|
+
git: async (args) => (await execFileP2("git", ["-C", wtPath, ...args], { timeout: GIT_TIMEOUT_MS })).stdout
|
|
39589
|
+
});
|
|
39590
|
+
if (repoint.action !== "repointed") {
|
|
39591
|
+
return fail(`worktree create: exact-tree delivery was proven by ${delivered.candidateOid}, but transactional resume refused (${repoint.reason}${repoint.detail ? `: ${repoint.detail}` : ""})`);
|
|
39592
|
+
}
|
|
39593
|
+
resumed = true;
|
|
39103
39594
|
}
|
|
39104
|
-
await execFileP2("git", ["-C", wtPath, "merge", "--ff-only", base], { timeout: GIT_TIMEOUT_MS });
|
|
39105
|
-
resumed = true;
|
|
39106
39595
|
}
|
|
39107
39596
|
if (!resumed) {
|
|
39108
39597
|
step = `git worktree add ${wtPath}`;
|
|
39109
|
-
const wtPathPreExisted = (0,
|
|
39598
|
+
const wtPathPreExisted = (0, import_node_fs48.existsSync)(wtPath);
|
|
39110
39599
|
const partialRemove = worktreeRemoveDeps(async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout);
|
|
39111
39600
|
await withWorktreeAddLock(repoRoot2, () => addWorktreeRobust(wtPath, branch, base, {
|
|
39112
39601
|
// #4834: `-c core.longpaths=true` rides the add command itself — a Windows worktree path
|
|
@@ -39122,7 +39611,7 @@ withExamples(mutating(
|
|
|
39122
39611
|
},
|
|
39123
39612
|
deleteBranch: (b) => execFileP2("git", ["branch", "-D", b], { timeout: GIT_TIMEOUT_MS }).then(() => void 0),
|
|
39124
39613
|
cleanupPartial: async () => {
|
|
39125
|
-
if (wtPathPreExisted || !(0,
|
|
39614
|
+
if (wtPathPreExisted || !(0, import_node_fs48.existsSync)(wtPath)) return;
|
|
39126
39615
|
partialRemove.detachReparsePoints(wtPath);
|
|
39127
39616
|
await execFileP2("git", ["worktree", "remove", "--force", wtPath], { timeout: GIT_TIMEOUT_MS }).catch(() => partialRemove.removeWorktreeDir(wtPath).then(() => void 0));
|
|
39128
39617
|
await execFileP2("git", ["worktree", "prune"], { timeout: GIT_TIMEOUT_MS }).catch(() => {
|
|
@@ -39166,7 +39655,14 @@ withExamples(mutating(
|
|
|
39166
39655
|
}
|
|
39167
39656
|
const createActor = describeActor({ env: process.env, surface: detectSurface(process.env), cwd: process.cwd() });
|
|
39168
39657
|
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
39169
|
-
const owner = {
|
|
39658
|
+
const owner = {
|
|
39659
|
+
path: wtPath,
|
|
39660
|
+
branch,
|
|
39661
|
+
provenance: { creationBaseOid: resolvedBaseOid, workerBranch: branch },
|
|
39662
|
+
createdAt,
|
|
39663
|
+
lastSeenAt: createdAt,
|
|
39664
|
+
actor: createActor
|
|
39665
|
+
};
|
|
39170
39666
|
recordWorktreeOwner(repoRoot2, owner);
|
|
39171
39667
|
appendWorktreeEvent(repoRoot2, { action: "created", command: "worktree create", target: wtPath, branch, actor: owner.actor });
|
|
39172
39668
|
let lease;
|
|
@@ -39903,7 +40399,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
|
|
|
39903
40399
|
if (dupe) return fail(`org project set: KEY "${dupe}" was passed to both --var and --set; --set is an alias of --var, so pass each KEY once`);
|
|
39904
40400
|
if (o.secretsFile) {
|
|
39905
40401
|
try {
|
|
39906
|
-
vars.push(`secrets=${(0,
|
|
40402
|
+
vars.push(`secrets=${(0, import_node_fs48.readFileSync)(o.secretsFile, "utf8")}`);
|
|
39907
40403
|
} catch (e) {
|
|
39908
40404
|
return fail(`org project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
|
|
39909
40405
|
}
|
|
@@ -40671,11 +41167,11 @@ pr.command("view <number>").description("read a PR as structured JSON (merged st
|
|
|
40671
41167
|
}
|
|
40672
41168
|
});
|
|
40673
41169
|
async function listCiWorkflowPaths(cwd = process.cwd()) {
|
|
40674
|
-
const wfDir = (0,
|
|
40675
|
-
if (!(0,
|
|
40676
|
-
return (0,
|
|
41170
|
+
const wfDir = (0, import_node_path46.join)(cwd, ".github", "workflows");
|
|
41171
|
+
if (!(0, import_node_fs48.existsSync)(wfDir)) return [];
|
|
41172
|
+
return (0, import_node_fs48.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
|
|
40677
41173
|
try {
|
|
40678
|
-
return workflowReportsPrChecks((0,
|
|
41174
|
+
return workflowReportsPrChecks((0, import_node_fs48.readFileSync)((0, import_node_path46.join)(wfDir, name), "utf8"));
|
|
40679
41175
|
} catch {
|
|
40680
41176
|
return true;
|
|
40681
41177
|
}
|
|
@@ -40727,16 +41223,16 @@ function ciAuditDeps() {
|
|
|
40727
41223
|
// gate re-seed step is skipped gracefully rather than failing mid-run.
|
|
40728
41224
|
readSeedFile: (path2) => {
|
|
40729
41225
|
if (!root) return null;
|
|
40730
|
-
const fullPath = (0,
|
|
40731
|
-
return (0,
|
|
41226
|
+
const fullPath = (0, import_node_path46.join)(root, path2);
|
|
41227
|
+
return (0, import_node_fs48.existsSync)(fullPath) ? (0, import_node_fs48.readFileSync)(fullPath, "utf8") : null;
|
|
40732
41228
|
}
|
|
40733
41229
|
};
|
|
40734
41230
|
}
|
|
40735
41231
|
function hubRoot() {
|
|
40736
|
-
const fromPkg = (0,
|
|
41232
|
+
const fromPkg = (0, import_node_path46.join)(__dirname, "..", "..");
|
|
40737
41233
|
const marker = "skills/bootstrap/seeds/manifest.json";
|
|
40738
|
-
if ((0,
|
|
40739
|
-
if ((0,
|
|
41234
|
+
if ((0, import_node_fs48.existsSync)((0, import_node_path46.join)(fromPkg, marker))) return fromPkg;
|
|
41235
|
+
if ((0, import_node_fs48.existsSync)((0, import_node_path46.join)(process.cwd(), marker))) return process.cwd();
|
|
40740
41236
|
return null;
|
|
40741
41237
|
}
|
|
40742
41238
|
async function waitLoopCorePool(label) {
|
|
@@ -41067,7 +41563,7 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
|
|
|
41067
41563
|
}
|
|
41068
41564
|
if (!repoForPostCleanup) throw e;
|
|
41069
41565
|
console.warn(`pr merge: gh GraphQL rate-limited \u2014 merging PR #${number} via REST PUT instead (#4588).`);
|
|
41070
|
-
const commitMessage = bodyFile ? (0,
|
|
41566
|
+
const commitMessage = bodyFile ? (0, import_node_fs48.readFileSync)(bodyFile, "utf8") : void 0;
|
|
41071
41567
|
await defaultGitHubClient().rest("PUT", `repos/${repoForPostCleanup}/pulls/${number}/merge`, {
|
|
41072
41568
|
body: { merge_method: method.slice(2), ...commitMessage ? { commit_message: commitMessage } : {} },
|
|
41073
41569
|
timeoutMs: GH_MUTATION_TIMEOUT_MS
|
|
@@ -41163,7 +41659,7 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
|
|
|
41163
41659
|
localCleanup = await cleanupPrMergeLocalBranch(headRef, {
|
|
41164
41660
|
beforeWorktrees,
|
|
41165
41661
|
startingPath,
|
|
41166
|
-
pathExists: (p) => (0,
|
|
41662
|
+
pathExists: (p) => (0, import_node_fs48.existsSync)(p),
|
|
41167
41663
|
execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
|
|
41168
41664
|
teardownWorktreeStage,
|
|
41169
41665
|
deferredStore,
|
|
@@ -41767,26 +42263,26 @@ access.command("audit").description("audit collaborator roles + train-branch pus
|
|
|
41767
42263
|
targets = resolution.targets;
|
|
41768
42264
|
}
|
|
41769
42265
|
const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
|
|
41770
|
-
const fileMatrix = (0,
|
|
42266
|
+
const fileMatrix = (0, import_node_fs48.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs48.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
41771
42267
|
const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
|
|
41772
42268
|
const derivedContracts = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
|
|
41773
|
-
const fileContracts = (0,
|
|
42269
|
+
const fileContracts = (0, import_node_fs48.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs48.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
|
|
41774
42270
|
const dataAccess = mergeDataAccessContracts(fileContracts, derivedContracts);
|
|
41775
|
-
const sanctioned = (0,
|
|
42271
|
+
const sanctioned = (0, import_node_fs48.existsSync)("access-matrix.json") ? loadSanctionedAdmins((0, import_node_fs48.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
41776
42272
|
const report = await auditOrgAccess(targets, deps, matrix, dataAccess, sanctioned);
|
|
41777
42273
|
console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
|
|
41778
42274
|
if (!report.ok) process.exitCode = 1;
|
|
41779
42275
|
});
|
|
41780
42276
|
access.command("capabilities").description("enumerate your effective vault reach \u2014 every credential NAME + tier + scope you can read/use across project + org/master tiers (names only, no values) (#1615)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json", "machine-readable output").action((o) => withSecrets((d) => secretsCapabilities(d, o)));
|
|
41781
42277
|
var isWin2 = process.platform === "win32";
|
|
41782
|
-
program2.command("doctor").description("heal plugin wiring and
|
|
42278
|
+
program2.command("doctor").description("safely heal active plugin wiring and report hygiene; repository cleanup requires explicit --apply (#5023), while version convergence belongs to `mmi-hub update`").option("--banner", "one-line resume summary; silent when all gates pass").option("--fast", "offline-safe fast lane; read-only, skips slow checks and network probes").option("--preflight", "read-only fast gate for automation: measures and reports with the shared exit code, zero writes").option("--verbose", "print the evidence behind every check \u2014 probes, resolved paths, versions compared, and the names behind each count (#2977)").option("--guide", "print the canonical MMI Agentic Onboarding URL").option("--json", "machine-readable output (an output format \u2014 repairs still run by lane)").option("--apply", "advanced explicit lane: also apply guarded repository cleanup (gitignore, docs index, board mechanics, merged branches, dead worktrees, aged scratch)").option("--no-repo-writes", "compatibility spelling for the safe default: env/plugin repairs only, never mutate the repository").option("--self", "verify CLI/plugin version parity and gh auth reach; suggests plugin-heal on a hard gap (reads the published version, so not offline-safe) (#2689)").addHelpText("after", "\nExit codes:\n 0 no hard gaps remain; advisory gaps may exist\n 1 one or more included hard checks failed\n\nA plain run may heal safe machine-global plugin wiring but never mutates the repository. Run\n`mmi-cli doctor --apply` only when you explicitly want the guarded full repository cleanup lane.\nCLI and host version convergence belongs to `mmi-hub update`; doctor reports lag and last-run evidence.\n--banner/--fast/--self/--preflight are read-only lanes.\n").action(async (opts) => {
|
|
41783
42279
|
if (opts.guide) {
|
|
41784
|
-
consoleIo.log(
|
|
42280
|
+
consoleIo.log(`MMI Agentic Onboarding: ${CANONICAL_ONBOARDING_URL}`);
|
|
41785
42281
|
return;
|
|
41786
42282
|
}
|
|
41787
42283
|
process.exitCode = await runDoctorClean(
|
|
41788
42284
|
{
|
|
41789
|
-
repoWrites: opts.repoWrites,
|
|
42285
|
+
repoWrites: opts.apply === true && opts.repoWrites !== false,
|
|
41790
42286
|
banner: opts.banner,
|
|
41791
42287
|
preflight: opts.preflight,
|
|
41792
42288
|
fast: opts.fast || opts.self,
|
|
@@ -41804,16 +42300,16 @@ function directoryBytes(path2) {
|
|
|
41804
42300
|
let total = 0;
|
|
41805
42301
|
let entries;
|
|
41806
42302
|
try {
|
|
41807
|
-
entries = (0,
|
|
42303
|
+
entries = (0, import_node_fs48.readdirSync)(path2, { withFileTypes: true });
|
|
41808
42304
|
} catch {
|
|
41809
42305
|
return 0;
|
|
41810
42306
|
}
|
|
41811
42307
|
for (const entry of entries) {
|
|
41812
|
-
const child2 = (0,
|
|
42308
|
+
const child2 = (0, import_node_path46.join)(path2, entry.name);
|
|
41813
42309
|
if (entry.isDirectory()) total += directoryBytes(child2);
|
|
41814
42310
|
else {
|
|
41815
42311
|
try {
|
|
41816
|
-
total += (0,
|
|
42312
|
+
total += (0, import_node_fs48.statSync)(child2).size;
|
|
41817
42313
|
} catch {
|
|
41818
42314
|
}
|
|
41819
42315
|
}
|
|
@@ -41821,25 +42317,25 @@ function directoryBytes(path2) {
|
|
|
41821
42317
|
return total;
|
|
41822
42318
|
}
|
|
41823
42319
|
function listDirEntries(dir) {
|
|
41824
|
-
return (0,
|
|
42320
|
+
return (0, import_node_fs48.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
|
|
41825
42321
|
}
|
|
41826
42322
|
function readInstalledPluginRefs(configRoot) {
|
|
41827
42323
|
const p = installedPluginsPathForConfig(configRoot);
|
|
41828
|
-
if (!(0,
|
|
42324
|
+
if (!(0, import_node_fs48.existsSync)(p)) return [];
|
|
41829
42325
|
try {
|
|
41830
|
-
return installedPluginPaths((0,
|
|
42326
|
+
return installedPluginPaths((0, import_node_fs48.readFileSync)(p, "utf8"));
|
|
41831
42327
|
} catch {
|
|
41832
42328
|
return null;
|
|
41833
42329
|
}
|
|
41834
42330
|
}
|
|
41835
42331
|
function pluginCacheFsDeps(configRoot, dirBytes) {
|
|
41836
42332
|
return {
|
|
41837
|
-
exists: (p) => (0,
|
|
41838
|
-
listVersionDirs: (root) => (0,
|
|
42333
|
+
exists: (p) => (0, import_node_fs48.existsSync)(p),
|
|
42334
|
+
listVersionDirs: (root) => (0, import_node_fs48.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
|
|
41839
42335
|
dirBytes,
|
|
41840
|
-
listStagingDirs: (root) => (0,
|
|
42336
|
+
listStagingDirs: (root) => (0, import_node_fs48.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
|
|
41841
42337
|
try {
|
|
41842
|
-
return { name: d.name, mtimeMs: newestMtimeMs((0,
|
|
42338
|
+
return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path46.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs48.statSync)(p).mtimeMs) };
|
|
41843
42339
|
} catch {
|
|
41844
42340
|
return { name: d.name, mtimeMs: Date.now() };
|
|
41845
42341
|
}
|
|
@@ -41853,10 +42349,10 @@ function stagingApplyFsGuard(configRoot) {
|
|
|
41853
42349
|
return {
|
|
41854
42350
|
referencedPaths: () => readInstalledPluginRefs(configRoot),
|
|
41855
42351
|
mtimeMs: (name) => {
|
|
41856
|
-
const p = (0,
|
|
41857
|
-
if (!(0,
|
|
42352
|
+
const p = (0, import_node_path46.join)(stagingRoot, name);
|
|
42353
|
+
if (!(0, import_node_fs48.existsSync)(p)) return null;
|
|
41858
42354
|
try {
|
|
41859
|
-
return newestMtimeMs(p, listDirEntries, (q) => (0,
|
|
42355
|
+
return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs48.statSync)(q).mtimeMs);
|
|
41860
42356
|
} catch {
|
|
41861
42357
|
return null;
|
|
41862
42358
|
}
|
|
@@ -41876,13 +42372,13 @@ program2.command("plugin-prune").description(`prune stale cached MMI plugin vers
|
|
|
41876
42372
|
return;
|
|
41877
42373
|
}
|
|
41878
42374
|
const plan = buildPluginCachePlan(
|
|
41879
|
-
(0,
|
|
42375
|
+
(0, import_node_os21.homedir)(),
|
|
41880
42376
|
running,
|
|
41881
42377
|
pluginCacheFsDeps(configRoot, directoryBytes),
|
|
41882
42378
|
{ withBytes: true, configRoot, includeStaging: surface !== "codex" }
|
|
41883
42379
|
);
|
|
41884
42380
|
const anythingToDelete = plan.prune.length > 0 || plan.staging.length > 0;
|
|
41885
|
-
const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0,
|
|
42381
|
+
const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs48.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard(configRoot)) : void 0;
|
|
41886
42382
|
const warnings = plan.prune.length > 0 ? [CONCURRENT_SESSION_WARNING] : [];
|
|
41887
42383
|
if (o.json) console.log(JSON.stringify({ ...plan, warnings, applied: result ?? null }));
|
|
41888
42384
|
else console.log(renderPluginCachePlan(plan, result));
|