@mutmutco/cli 4.3.2 → 4.3.3
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 +133 -35
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -5555,6 +5555,31 @@ function rewriteNegatedClosingPhrases(text) {
|
|
|
5555
5555
|
(seg, i) => i % 2 === 1 ? seg : seg.replace(NEGATED_CLOSING_PHRASE_RE, (_m, plain, linked) => `leaves #${plain ?? linked} open`)
|
|
5556
5556
|
).join("");
|
|
5557
5557
|
}
|
|
5558
|
+
var LINE_START_CLOSING_RE = /^\s*(?:[-*+]\s+)?(?:\*\*|__)?\s*(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s+(?:#\d+|\[#\d+\]\()/i;
|
|
5559
|
+
var FENCE_LINE_RE = /^\s*(?:```|~~~)/;
|
|
5560
|
+
function rewriteCloseKeywordProse(text) {
|
|
5561
|
+
if (!text.includes("#")) return { text, rewritten: [] };
|
|
5562
|
+
const rewritten = /* @__PURE__ */ new Set();
|
|
5563
|
+
let inFence = false;
|
|
5564
|
+
const lines = text.split("\n").map((line) => {
|
|
5565
|
+
if (FENCE_LINE_RE.test(line)) {
|
|
5566
|
+
inFence = !inFence;
|
|
5567
|
+
return line;
|
|
5568
|
+
}
|
|
5569
|
+
if (inFence || LINE_START_CLOSING_RE.test(line)) return line;
|
|
5570
|
+
CLOSING_MENTION_RE.lastIndex = 0;
|
|
5571
|
+
return line.split(/(`[^`\n]*`)/g).map((seg, i) => {
|
|
5572
|
+
if (i % 2 === 1) return seg;
|
|
5573
|
+
return seg.replace(CLOSING_MENTION_RE, (match, _keyword, plain, linked) => {
|
|
5574
|
+
const n = Number(plain ?? linked);
|
|
5575
|
+
if (!Number.isInteger(n) || n <= 0) return match;
|
|
5576
|
+
rewritten.add(n);
|
|
5577
|
+
return `part of #${n}`;
|
|
5578
|
+
});
|
|
5579
|
+
}).join("");
|
|
5580
|
+
});
|
|
5581
|
+
return rewritten.size ? { text: lines.join("\n"), rewritten: [...rewritten] } : { text, rewritten: [] };
|
|
5582
|
+
}
|
|
5558
5583
|
function findClosingMentions(text) {
|
|
5559
5584
|
const mentions = [];
|
|
5560
5585
|
for (const match of text.matchAll(CLOSING_MENTION_RE)) {
|
|
@@ -5588,11 +5613,12 @@ var CROSS_REPO_TOKEN_RE = /\b([\w.-]+\/[\w.-]+)#(\d+)\b/g;
|
|
|
5588
5613
|
function findCrossRepoAmbiguousClosings(text, closingNumbers, repo) {
|
|
5589
5614
|
if (!closingNumbers.length) return [];
|
|
5590
5615
|
const closing = new Set(closingNumbers);
|
|
5616
|
+
const bareClosing = new Set(findClosingMentions(text).map((m) => m.issue));
|
|
5591
5617
|
const flagged = /* @__PURE__ */ new Set();
|
|
5592
5618
|
const repoLower = repo.toLowerCase();
|
|
5593
5619
|
for (const match of text.matchAll(CROSS_REPO_TOKEN_RE)) {
|
|
5594
5620
|
const n = Number(match[2]);
|
|
5595
|
-
if (!closing.has(n)) continue;
|
|
5621
|
+
if (!closing.has(n) || !bareClosing.has(n)) continue;
|
|
5596
5622
|
if ((match[1] ?? "").toLowerCase() === repoLower) continue;
|
|
5597
5623
|
flagged.add(n);
|
|
5598
5624
|
}
|
|
@@ -11708,7 +11734,16 @@ async function gatherClaimLiveness(client, repo, number, fetchOpenPulls) {
|
|
|
11708
11734
|
const out = { failed: [] };
|
|
11709
11735
|
try {
|
|
11710
11736
|
const comments = await client.restPaginate(`repos/${repo}/issues/${number}/comments`);
|
|
11711
|
-
|
|
11737
|
+
const rows = comments.map((comment) => ({ body: comment.body ?? "", login: comment.user?.login ?? void 0 }));
|
|
11738
|
+
out.marker = latestClaimMarker(rows);
|
|
11739
|
+
if (out.marker) {
|
|
11740
|
+
for (let i = rows.length - 1; i >= 0; i -= 1) {
|
|
11741
|
+
if (latestClaimMarker([rows[i]])) {
|
|
11742
|
+
out.markerBy = rows[i].login;
|
|
11743
|
+
break;
|
|
11744
|
+
}
|
|
11745
|
+
}
|
|
11746
|
+
}
|
|
11712
11747
|
out.markerAgeMs = out.marker ? Date.now() - Date.parse(out.marker.ts) : void 0;
|
|
11713
11748
|
if (out.marker) {
|
|
11714
11749
|
const state = probeLocalClaimSession(out.marker);
|
|
@@ -11775,17 +11810,26 @@ function laneOwnership(marker, current) {
|
|
|
11775
11810
|
}
|
|
11776
11811
|
return "unknown";
|
|
11777
11812
|
}
|
|
11778
|
-
|
|
11813
|
+
function laneResumeEvidence(evidence) {
|
|
11814
|
+
if (!evidence.marker || evidence.sessionState !== "dead" || evidence.failed.length > 0) return void 0;
|
|
11815
|
+
if (!isHostSessionId(evidence.marker.session)) return void 0;
|
|
11816
|
+
const artifacts = [evidence.openPr, evidence.branch ? `live branch ${evidence.branch}` : void 0].filter(Boolean);
|
|
11817
|
+
return `prior claim by lane ${describeClaimMarker(evidence.marker)} (@${evidence.markerBy}, ${formatClaimAge(evidence.markerAgeMs)} old) is verifiably not running on this host \u2014 its local session transcript is gone or stale${artifacts.length ? `; resuming over ${artifacts.join("; ")}` : ""}`;
|
|
11818
|
+
}
|
|
11819
|
+
async function checkLaneContest(client, item, actor = describeSessionIdentity(), viewerLogin) {
|
|
11779
11820
|
const evidence = await gatherClaimLiveness(client, item.repository, item.number, openPullsFetcher(client));
|
|
11780
11821
|
const ownership = laneOwnership(evidence.marker, actor);
|
|
11822
|
+
const sameOwner = Boolean(viewerLogin && evidence.markerBy && evidence.markerBy.toLowerCase() === viewerLogin.toLowerCase());
|
|
11823
|
+
const resume = ownership !== "mine" && sameOwner ? laneResumeEvidence(evidence) : void 0;
|
|
11781
11824
|
const live = ownership === "mine" ? [] : liveEvidenceLines(evidence, item.repository);
|
|
11782
11825
|
const unverifiable = ownership === "mine" ? [] : evidence.failed;
|
|
11783
11826
|
return {
|
|
11784
|
-
contested: ownership !== "mine" && (live.length > 0 || unverifiable.length > 0),
|
|
11827
|
+
contested: ownership !== "mine" && !resume && (live.length > 0 || unverifiable.length > 0),
|
|
11785
11828
|
live,
|
|
11786
11829
|
unverifiable,
|
|
11787
11830
|
ownership,
|
|
11788
|
-
marker: evidence.marker
|
|
11831
|
+
marker: evidence.marker,
|
|
11832
|
+
resume
|
|
11789
11833
|
};
|
|
11790
11834
|
}
|
|
11791
11835
|
function laneContestMessage(ref, contest, verb) {
|
|
@@ -12084,8 +12128,9 @@ async function claimOneBoardItem(ctx, selector, options) {
|
|
|
12084
12128
|
host: ctx.session.host
|
|
12085
12129
|
};
|
|
12086
12130
|
let previousHolder;
|
|
12087
|
-
|
|
12088
|
-
const
|
|
12131
|
+
let resumeEvidence;
|
|
12132
|
+
const claimedReceipt = () => previousHolder ? resumeEvidence ? { outcome: "resumed", holder, previousHolder, resumeEvidence } : { outcome: "took-over", holder, previousHolder } : { outcome: "claimed", holder };
|
|
12133
|
+
const heldReceipt = () => previousHolder ? resumeEvidence ? { outcome: "resumed", holder, previousHolder, resumeEvidence } : { outcome: "took-over", holder, previousHolder } : { outcome: "held", holder };
|
|
12089
12134
|
if (flatItem.contentType !== "Issue") throw new Error(`${flatItem.ref} is not an issue`);
|
|
12090
12135
|
const pre = evaluateClaim(flatItem, assignedLogin);
|
|
12091
12136
|
if (!pre.ok) throw new Error(pre.reason);
|
|
@@ -12096,17 +12141,25 @@ async function claimOneBoardItem(ctx, selector, options) {
|
|
|
12096
12141
|
if (!verdict.ok) throw new Error(verdict.reason);
|
|
12097
12142
|
item = fresh;
|
|
12098
12143
|
const refuseIfContested = async () => {
|
|
12099
|
-
const contest = await checkLaneContest(client, item, ctx.session);
|
|
12100
|
-
|
|
12101
|
-
|
|
12102
|
-
previousHolder = {
|
|
12144
|
+
const contest = await checkLaneContest(client, item, ctx.session, report.viewer);
|
|
12145
|
+
resumeEvidence = void 0;
|
|
12146
|
+
const displaced = () => ({
|
|
12103
12147
|
login: assignedLogin,
|
|
12104
12148
|
...contest.marker ? {
|
|
12105
12149
|
session: contest.marker.session,
|
|
12106
12150
|
surface: contest.marker.surface,
|
|
12107
12151
|
host: contest.marker.host
|
|
12108
12152
|
} : {}
|
|
12109
|
-
};
|
|
12153
|
+
});
|
|
12154
|
+
if (!contest.contested) {
|
|
12155
|
+
if (contest.resume && contest.marker) {
|
|
12156
|
+
resumeEvidence = contest.resume;
|
|
12157
|
+
previousHolder = displaced();
|
|
12158
|
+
}
|
|
12159
|
+
return;
|
|
12160
|
+
}
|
|
12161
|
+
if (!options.force) throw new Error(laneContestMessage(item.ref, contest, "claim"));
|
|
12162
|
+
previousHolder = displaced();
|
|
12110
12163
|
};
|
|
12111
12164
|
await refuseIfContested();
|
|
12112
12165
|
if (verdict.alreadyClaimed) {
|
|
@@ -12176,7 +12229,7 @@ async function claimBoardIssues(options, deps = {}) {
|
|
|
12176
12229
|
const ref = `${selector.repo}#${selector.number}`;
|
|
12177
12230
|
try {
|
|
12178
12231
|
const result = await claimOneBoardItem(ctx, selector, options);
|
|
12179
|
-
results[index] = { ref: result.item.ref, claimed: true, item: result.item, status: result.status, partial: result.partial, warning: result.warning, outcome: result.outcome, holder: result.holder, previousHolder: result.previousHolder, alreadyClaimed: result.alreadyClaimed, checked: result.checked };
|
|
12232
|
+
results[index] = { ref: result.item.ref, claimed: true, item: result.item, status: result.status, partial: result.partial, warning: result.warning, outcome: result.outcome, holder: result.holder, previousHolder: result.previousHolder, resumeEvidence: result.resumeEvidence, alreadyClaimed: result.alreadyClaimed, checked: result.checked };
|
|
12180
12233
|
} catch (e) {
|
|
12181
12234
|
results[index] = { ref, claimed: false, reason: e.message };
|
|
12182
12235
|
}
|
|
@@ -15322,10 +15375,10 @@ var rollout_plan_default = {
|
|
|
15322
15375
|
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)."
|
|
15323
15376
|
},
|
|
15324
15377
|
baseline: {
|
|
15325
|
-
version: "4.3.
|
|
15326
|
-
tag: "v4.3.
|
|
15327
|
-
commit: "
|
|
15328
|
-
npm: "@mutmutco/cli@4.3.
|
|
15378
|
+
version: "4.3.3",
|
|
15379
|
+
tag: "v4.3.3",
|
|
15380
|
+
commit: "851ec4c56e34",
|
|
15381
|
+
npm: "@mutmutco/cli@4.3.3"
|
|
15329
15382
|
},
|
|
15330
15383
|
exitCriterion: "fleet-n-of-n",
|
|
15331
15384
|
hubOnlyShortcut: "forbidden",
|
|
@@ -15342,14 +15395,14 @@ var rollout_plan_default = {
|
|
|
15342
15395
|
repo: "mutmutco/mmi-hub",
|
|
15343
15396
|
role: "canary",
|
|
15344
15397
|
schedule: "train",
|
|
15345
|
-
v3Target: "v4.3.
|
|
15398
|
+
v3Target: "v4.3.3"
|
|
15346
15399
|
}
|
|
15347
15400
|
],
|
|
15348
15401
|
rollbackTrigger: "Any red inside the post-contract soak window: `devops train gate` FAIL attributable to the v4 doors, Hub endpoint health probe failure, a pre-v4 client admitted instead of receiving actionable HTTP 426, or npm consumer install/doctor failure on the v4-only dist.",
|
|
15349
15402
|
rollback: {
|
|
15350
15403
|
independent: true,
|
|
15351
|
-
mechanism: "npm dist-tag latest -> 4.3.
|
|
15352
|
-
v3Target: "v4.3.
|
|
15404
|
+
mechanism: "npm dist-tag latest -> 4.3.3 and redeploy the Hub Lambda from tag v4.3.3 (851ec4c56e34); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
15405
|
+
v3Target: "v4.3.3 (@mutmutco/cli@4.3.3, tag commit 851ec4c56e34 \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
15353
15406
|
}
|
|
15354
15407
|
},
|
|
15355
15408
|
{
|
|
@@ -23765,19 +23818,23 @@ function registerBoardCommands(program3) {
|
|
|
23765
23818
|
if (result.checked) {
|
|
23766
23819
|
if (result.outcome === "held") return `Check ${ref}: held by ${holder} - claim would renew the lease (nothing written)`;
|
|
23767
23820
|
if (result.outcome === "took-over") return `Check ${ref}: held by ${previousHolder} - --force claim would take it over for ${holder} (nothing written)`;
|
|
23821
|
+
if (result.outcome === "resumed") return `Check ${ref}: prior lane ${previousHolder} is verifiably dead - claim would resume its work for ${holder} (nothing written; ${result.resumeEvidence})`;
|
|
23768
23822
|
return `Check ${ref}: free - claim would proceed for ${holder} (nothing written)`;
|
|
23769
23823
|
}
|
|
23770
23824
|
if (result.partial) {
|
|
23771
|
-
|
|
23825
|
+
if (result.outcome === "took-over") return `Partially took over ${ref} from ${previousHolder}: ${result.warning}`;
|
|
23826
|
+
if (result.outcome === "resumed") return `Partially resumed ${ref} from ${previousHolder}: ${result.warning}`;
|
|
23827
|
+
return `Partially claimed ${ref} for ${holder}: ${result.warning}`;
|
|
23772
23828
|
}
|
|
23773
23829
|
if (result.outcome === "took-over") return `Took over ${ref} from ${previousHolder} for ${holder} - In Progress`;
|
|
23830
|
+
if (result.outcome === "resumed") return `Resumed ${ref} from ${previousHolder} for ${holder} - In Progress (${result.resumeEvidence})`;
|
|
23774
23831
|
if (result.outcome === "held") return `${ref} is held by ${holder} - In Progress`;
|
|
23775
23832
|
return `Claimed ${ref} for ${holder} - In Progress`;
|
|
23776
23833
|
}
|
|
23777
23834
|
const board = program3.command("board").description("read, claim, show, and move Project v2 work items for the current repo");
|
|
23778
23835
|
board.command("read", { isDefault: true }).alias("list").description("read the board and print user-owned, claimable, and taken items").option("--json", "machine-readable output").option("--repo <owner/repo>", "current repo (defaults to git origin)").option("--direct", "bypass the Hub snapshot and read the live board through direct GitHub GraphQL").option("--bundle-details", "fetch body/comments only for user-owned and claimable issues").option("--bodies", "fetch body/comments for EVERY scoped row, including taken and unowned in-flight ones \u2014 for consumers that scope by Status rather than ownership (#4861); implies --bundle-details and costs one extra read per row").option("--allow-partial", "return partial board results when later page/detail reads fail").option("--out <path>", "write the output to this file as UTF-8 (no BOM) instead of stdout \u2014 the shell-free receipt path (#5802)").addHelpText("after", "\nread is always the authoritative live GitHub Project v2 board (#4926).\n--direct skips the Hub snapshot and uses the existing direct GitHub GraphQL read immediately.\n--allow-partial applies to the paginated path and detail reads.\n\nNever capture the JSON with a shell redirect on Windows: PowerShell 5.1's `> file.json` is Out-File,\nwhich writes UTF-16LE with a BOM, and Node reading it as 'utf8' then fails JSON.parse at position 1\n(#5802). Use --out instead \u2014 the CLI writes the file itself as UTF-8:\n mmi-cli oracle board read --json --out .jerv/tmp/board.json\n").action((o) => runBoardRead(o));
|
|
23779
23836
|
withExamples(mutating(
|
|
23780
|
-
board.command("claim <issues...>").description("claim issues: assign them and move their Project v2 Status to In Progress \u2014 idempotent, so an item already yours and In Progress succeeds unchanged (one or more refs)").addHelpText("after", "\nevery claim stamps a lane-identity marker comment on the issue (`<!-- mmi-claim: \u2026 -->`,\nsurface/session@host) so other agents can attribute the hold (#3727). The session is the\nhost-exported id when the surface provides one, otherwise a per-process `synth-` fallback \u2014\na claim is never anonymous (#5245). `board show`, doctor and unclaim read the latest marker.\n").option("--json", "machine-readable output").option("--repo <owner/repo>", "current repo for local issue numbers (defaults to git origin)").option("--for <login>", "assign to this login instead of @me \u2014 agent claims on behalf of the master").option("--force", "take an item already claimed by another lane that shows live evidence of active work (#3727)").option("--check", "read-only: run every claim gate and report the verdict, writing nothing \u2014 exits 1 with the same refusal a real claim would raise (#4511)").option("--allow-partial", "return success JSON if assignment succeeds but the status move fails"),
|
|
23837
|
+
board.command("claim <issues...>").description("claim issues: assign them and move their Project v2 Status to In Progress \u2014 idempotent, so an item already yours and In Progress succeeds unchanged (one or more refs)").addHelpText("after", "\nevery claim stamps a lane-identity marker comment on the issue (`<!-- mmi-claim: \u2026 -->`,\nsurface/session@host) so other agents can attribute the hold (#3727). The session is the\nhost-exported id when the surface provides one, otherwise a per-process `synth-` fallback \u2014\na claim is never anonymous (#5245). `board show`, doctor and unclaim read the latest marker.\n\nsame-owner resume (#6035): when the prior marker was posted by YOUR login on THIS host and its\nlocal session is verifiably dead (transcript probe), the claim proceeds as a resume without\n--force and names the evidence. A live, foreign-host, or unprobeable prior lane still refuses.\n").option("--json", "machine-readable output").option("--repo <owner/repo>", "current repo for local issue numbers (defaults to git origin)").option("--for <login>", "assign to this login instead of @me \u2014 agent claims on behalf of the master").option("--force", "take an item already claimed by another lane that shows live evidence of active work (#3727)").option("--check", "read-only: run every claim gate and report the verdict, writing nothing \u2014 exits 1 with the same refusal a real claim would raise (#4511)").option("--allow-partial", "return success JSON if assignment succeeds but the status move fails"),
|
|
23781
23838
|
(_opts, args) => ({ command: "board claim", issues: args[0] ?? [] })
|
|
23782
23839
|
).action(async (issueRefs, o) => {
|
|
23783
23840
|
if (issueRefs.length === 1) {
|
|
@@ -35616,7 +35673,8 @@ async function mergeAutoEnqueueWithBody(prNumber, args, method, io, bodyFile) {
|
|
|
35616
35673
|
return confirmEnqueueOutcome();
|
|
35617
35674
|
}
|
|
35618
35675
|
function cleanupGitArgs(cwd, args) {
|
|
35619
|
-
|
|
35676
|
+
if (!cwd || args[0] === "-C") return args;
|
|
35677
|
+
return ["-C", cwd, ...args];
|
|
35620
35678
|
}
|
|
35621
35679
|
async function remoteBranchExists2(branch, options = {}) {
|
|
35622
35680
|
if (!branch) return void 0;
|
|
@@ -36028,6 +36086,24 @@ async function checkDocsIndexAtHead(opts, deps) {
|
|
|
36028
36086
|
};
|
|
36029
36087
|
}
|
|
36030
36088
|
|
|
36089
|
+
// src/pr-create-claim-guard.ts
|
|
36090
|
+
async function prCreateClaimRefusal(body, repoOption, deps = {}) {
|
|
36091
|
+
const issues = [...new Set(findClosingMentions(body).map((mention) => mention.issue))];
|
|
36092
|
+
if (!issues.length) return void 0;
|
|
36093
|
+
const repo = await requireRepo(repoOption);
|
|
36094
|
+
const client = deps.client ?? defaultGitHubClient();
|
|
36095
|
+
const actor = deps.actor ?? describeSessionIdentity();
|
|
36096
|
+
const checkContest = deps.checkContest ?? checkLaneContest;
|
|
36097
|
+
for (const number of issues) {
|
|
36098
|
+
const contest = await checkContest(client, { repository: repo, number }, actor);
|
|
36099
|
+
if (!contest.contested) continue;
|
|
36100
|
+
const ref = `${repo}#${number}`;
|
|
36101
|
+
const holder = contest.marker ? `lane ${describeClaimMarker(contest.marker)}` : "another lane";
|
|
36102
|
+
return `pr create: REFUSED \u2014 ${ref} is held by ${holder} with live or unreadable work evidence; run \`mmi-cli oracle board claim ${ref} --force\` before creating this PR`;
|
|
36103
|
+
}
|
|
36104
|
+
return void 0;
|
|
36105
|
+
}
|
|
36106
|
+
|
|
36031
36107
|
// src/worktree-merge-cleanup.ts
|
|
36032
36108
|
var import_node_fs40 = require("node:fs");
|
|
36033
36109
|
var import_node_path37 = require("node:path");
|
|
@@ -36481,6 +36557,10 @@ async function removeWorktreeWithReconcile(wtPath, git3, listWorktrees, pathExis
|
|
|
36481
36557
|
})
|
|
36482
36558
|
};
|
|
36483
36559
|
}
|
|
36560
|
+
function isPrMergeWorktreePartial(cleanup) {
|
|
36561
|
+
const worktree = cleanup?.worktree;
|
|
36562
|
+
return Boolean(worktree?.path && worktree.status !== "removed" && worktree.status !== "preserved");
|
|
36563
|
+
}
|
|
36484
36564
|
function prMergeLocalCleanupExitCode(cleanup) {
|
|
36485
36565
|
return cleanup?.worktree?.status === "failed" || cleanup?.localBranch?.status === "failed" ? 1 : void 0;
|
|
36486
36566
|
}
|
|
@@ -37331,10 +37411,20 @@ ${list}`);
|
|
|
37331
37411
|
}
|
|
37332
37412
|
body = normalizeClosingDirectives(body);
|
|
37333
37413
|
body = rewriteNegatedClosingPhrases(body);
|
|
37414
|
+
const prose = rewriteCloseKeywordProse(body);
|
|
37415
|
+
if (prose.rewritten.length) {
|
|
37416
|
+
body = prose.text;
|
|
37417
|
+
process.stderr.write(
|
|
37418
|
+
`pr create: WARNING \u2014 rewrote close-keyword prose to "part of #N" for ${prose.rewritten.map((n) => `#${n}`).join(", ")}: GitHub parses close/fix/resolve + #N even mid-sentence (#6039). Keep closing keywords on a dedicated "Closes #N" line.
|
|
37419
|
+
`
|
|
37420
|
+
);
|
|
37421
|
+
}
|
|
37334
37422
|
const docsCheck = await checkDocsIndexAtHead({ repo: o.repo, head: o.head }, createPrCreateDocsIndexDeps());
|
|
37335
37423
|
if (docsCheck && !docsCheck.ok) {
|
|
37336
37424
|
return fail(`pr create: ${docsCheck.detail} \u2014 ${docsCheck.fix}`);
|
|
37337
37425
|
}
|
|
37426
|
+
const claimRefusal = await prCreateClaimRefusal(body, o.repo);
|
|
37427
|
+
if (claimRefusal) return fail(claimRefusal);
|
|
37338
37428
|
const created = await ghCreate(buildPrArgs({ title, body, base: o.base, head: o.head, repo: o.repo, draft: o.draft }));
|
|
37339
37429
|
if (isGhCreateRateLimited(created)) {
|
|
37340
37430
|
console.log(JSON.stringify(created));
|
|
@@ -37349,7 +37439,8 @@ ${list}`);
|
|
|
37349
37439
|
], [
|
|
37350
37440
|
"--head and --base default to the current branch and the repo default; only pass them to override.",
|
|
37351
37441
|
"Use --body-file for multiline PR bodies instead of shell-escaped inline markdown.",
|
|
37352
|
-
"Write that file under .jerv/ inside the host workspace (#4405); host policy governs untracked files."
|
|
37442
|
+
"Write that file under .jerv/ inside the host workspace (#4405); host policy governs untracked files.",
|
|
37443
|
+
'Keep closing keywords on a dedicated line ("Closes #N") \u2014 GitHub parses close/fix/resolve + #N even mid-sentence, so pr create rewrites such prose to "part of #N" (#6039).'
|
|
37353
37444
|
]);
|
|
37354
37445
|
pr.command("view <ref>").description("read a PR as structured JSON (merged state, head/base, URL, merge commit) \u2014 the mmi-cli read path (#2347). --comments folds in every comment; --context also adds linkedIssues (the issues it closes/references, #2894)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json [fields...]", 'gh --json field list (overrides the default field set). Accepts commas, spaces, or repeated --json flags \u2014 in PowerShell an unquoted comma list is an array literal, so QUOTE it: --json "state,baseRefName,mergeCommit"').option("--comments", "include every comment (body + comments in one call) \u2014 read the whole PR before landing it (#2894)").option("--context", "full working context in one call: implies --comments and also adds linkedIssues (the issues the PR closes/references) (#2894)").action(async (ref, o) => {
|
|
37355
37446
|
let parsed;
|
|
@@ -37738,7 +37829,7 @@ ${list}`);
|
|
|
37738
37829
|
else printLine(`pr land: ${result.status}${result.error ? ` \u2014 ${result.error}` : ""}`);
|
|
37739
37830
|
if (result.status === "failed") process.exitCode = 1;
|
|
37740
37831
|
});
|
|
37741
|
-
jsonParity(pr.command("merge <number>").description("merge a PR (squash by default); archives gitignored tmp/** before worktree teardown; on no-ci repos run pr ci-policy / checks-wait first (#1432, #5679)").option("--squash", "squash merge (default)").option("--merge", "create a merge commit").option("--rebase", "rebase merge").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--auto", "enable auto-merge \u2014 merge once the base-branch policy is satisfied (use for policy-gated repos)").addOption(new Option("--disable-auto", "disable a queued auto-merge without merging").conflicts(["auto", "wait", "squash", "merge", "rebase", "preserveWorktree", "gc", "squashBodyFile", "force"])).option("--wait", `wait for checks to reach a terminal passing verdict before merging (default budget ${PR_CHECKS_TIMEOUT_MS / 6e4}m)`).option("--preserve-worktree", "after merge, keep the local PR worktree/branch for an active batch (#1888)").option("--gc", "acknowledge deleting unarchived gitignored tmp/** evidence newer than the branch base (#5679)").option("--squash-body-file <path>", "squash commit body (overrides GitHub COMMIT_MESSAGES); use when a pushed commit mentions close/fix/resolve + #N that must not close (#5723)").option("--force", "acknowledge and merge past a remaining prunable/severe scratch housekeeping block (advisory kept plans/ never blocks, #3012) or a negated-closing-keyword (#3718) or ambiguous-cross-repo-closing (#4279) refusal")).action(async (number, o) => {
|
|
37832
|
+
jsonParity(pr.command("merge <number>").description("merge a PR (squash by default); archives gitignored tmp/** before worktree teardown; on no-ci repos run pr ci-policy / checks-wait first (#1432, #5679)").option("--squash", "squash merge (default)").option("--merge", "create a merge commit").option("--rebase", "rebase merge").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--auto", "enable auto-merge \u2014 merge once the base-branch policy is satisfied (use for policy-gated repos)").addOption(new Option("--disable-auto", "disable a queued auto-merge without merging").conflicts(["auto", "wait", "squash", "merge", "rebase", "preserveWorktree", "gc", "squashBodyFile", "force"])).option("--wait", `wait for checks to reach a terminal passing verdict before merging (default budget ${PR_CHECKS_TIMEOUT_MS / 6e4}m) \u2014 run as a background/monitor task or under a shell timeout above that budget; a short foreground timeout (e.g. 120s) kills it after checks pass and leaves the PR open (#6027)`).option("--preserve-worktree", "after merge, keep the local PR worktree/branch for an active batch (#1888)").option("--gc", "acknowledge deleting unarchived gitignored tmp/** evidence newer than the branch base (#5679)").option("--squash-body-file <path>", "squash commit body (overrides GitHub COMMIT_MESSAGES); use when a pushed commit mentions close/fix/resolve + #N that must not close (#5723)").option("--force", "acknowledge and merge past a remaining prunable/severe scratch housekeeping block (advisory kept plans/ never blocks, #3012) or a negated-closing-keyword (#3718) or ambiguous-cross-repo-closing (#4279) refusal")).action(async (number, o) => {
|
|
37742
37833
|
const method = o.rebase ? "--rebase" : o.merge ? "--merge" : "--squash";
|
|
37743
37834
|
const repoArgs = o.repo ? ["--repo", o.repo] : [];
|
|
37744
37835
|
if (o.disableAuto) {
|
|
@@ -37802,6 +37893,7 @@ ${list}`);
|
|
|
37802
37893
|
const ciHeadRef = repoForPostCleanup ? await prHeadRefForCiProbe(number, repoForPostCleanup) : void 0;
|
|
37803
37894
|
const ciPolicy = await resolveMergeCiPolicyForCheckout(o.repo, ciHeadRef);
|
|
37804
37895
|
if (o.wait) {
|
|
37896
|
+
console.warn(`pr merge: --wait can hold the shell up to the full ${PR_CHECKS_TIMEOUT_MS / 6e4}m budget plus the merge \u2014 run under a shell timeout above that budget or as a background/monitor task (#6027)`);
|
|
37805
37897
|
const repo = await requireRepo(o.repo);
|
|
37806
37898
|
const budgetMs = PR_CHECKS_TIMEOUT_MS;
|
|
37807
37899
|
const waitStarted = Date.now();
|
|
@@ -37995,12 +38087,13 @@ ${list}`);
|
|
|
37995
38087
|
execGit: async (args) => (await execFileP("git", cleanupGitArgs(primaryRoot, args), { timeout: GIT_TIMEOUT_MS })).stdout,
|
|
37996
38088
|
branchExists: (b) => remoteBranchExists2(b, { cwd: primaryRoot })
|
|
37997
38089
|
});
|
|
37998
|
-
const
|
|
38090
|
+
const worktree = localCleanup?.worktree;
|
|
38091
|
+
const worktreePartial = isPrMergeWorktreePartial(localCleanup) && worktree?.path ? {
|
|
37999
38092
|
kind: "worktree-directory",
|
|
38000
|
-
path:
|
|
38001
|
-
reason:
|
|
38002
|
-
error:
|
|
38003
|
-
remediation:
|
|
38093
|
+
path: worktree.path,
|
|
38094
|
+
reason: worktree.reason ?? worktree.status,
|
|
38095
|
+
error: worktree.error ?? worktree.residueError,
|
|
38096
|
+
remediation: worktree.remediation ?? `Remove-Item -LiteralPath '${worktree.path.replace(/'/g, "''")}' -Recurse -Force`
|
|
38004
38097
|
} : void 0;
|
|
38005
38098
|
const partialCleanup = [
|
|
38006
38099
|
...worktreePartial ? [worktreePartial] : [],
|
|
@@ -38345,11 +38438,16 @@ function registerBoxCommands(program3) {
|
|
|
38345
38438
|
await failGraceful("runtime box get: --script requires --ssh (it writes the ssh connect recipe)");
|
|
38346
38439
|
return;
|
|
38347
38440
|
}
|
|
38348
|
-
|
|
38349
|
-
|
|
38350
|
-
|
|
38351
|
-
console.log(
|
|
38352
|
-
|
|
38441
|
+
const wroteScript = o.ssh && o.script ? o.script : null;
|
|
38442
|
+
if (wroteScript) (0, import_node_fs42.writeFileSync)(wroteScript, sshRecipeScript(found), "utf8");
|
|
38443
|
+
if (o.json) {
|
|
38444
|
+
console.log(JSON.stringify({
|
|
38445
|
+
box: found,
|
|
38446
|
+
incomplete,
|
|
38447
|
+
...wroteScript ? { script: { path: wroteScript, run: `bash "${wroteScript}"` } } : {}
|
|
38448
|
+
}, null, 2));
|
|
38449
|
+
} else if (wroteScript) console.log(`wrote ${wroteScript} \u2014 run: bash "${wroteScript}"`);
|
|
38450
|
+
else if (o.ssh) console.log(`${formatSshRecipe(found)}
|
|
38353
38451
|
${SSH_RECIPE_AGENT_NOTE}`);
|
|
38354
38452
|
else console.log(formatBoxTable([found]));
|
|
38355
38453
|
if (!o.json) warnIncomplete2(incomplete);
|
package/package.json
CHANGED