@seanyao/roll 4.719.3 → 4.720.1
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/CHANGELOG.md +6 -0
- package/dist/roll.mjs +147 -43
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
package/dist/roll.mjs
CHANGED
|
@@ -247550,6 +247550,7 @@ Loop \u5DF2\u5904\u4E8E\u6682\u505C
|
|
|
247550
247550
|
Loop \u5DF2\u6682\u505C \u2014 \u540E\u7EED\u6392\u7A0B\u5468\u671F\u5C06\u8DF3\u8FC7
|
|
247551
247551
|
`);
|
|
247552
247552
|
process.stdout.write("mode: guided \u2014 scheduler will not start long-running Story execution until `roll loop resume`\n");
|
|
247553
|
+
process.stdout.write(" supervisor one-shot: `roll loop go --cards <ids> --max-cycles 1` runs exactly those cards while staying paused (no autonomous scheduling).\n");
|
|
247553
247554
|
return 0;
|
|
247554
247555
|
}
|
|
247555
247556
|
async function loopResumeCommand(_args, deps = realDeps()) {
|
|
@@ -264497,6 +264498,7 @@ import { createInterface as createInterface3 } from "node:readline";
|
|
|
264497
264498
|
// packages/cli/dist/lib/goal-progress.js
|
|
264498
264499
|
init_dist();
|
|
264499
264500
|
var GOAL_ALLOWED_CARDS_ENV = "ROLL_LOOP_GO_ALLOWED_CARDS";
|
|
264501
|
+
var GOAL_GUIDED_ENV = "ROLL_LOOP_GO_GUIDED";
|
|
264500
264502
|
var DELIVERY_STATUSES = /* @__PURE__ */ new Set(["built", "published", "done", "merged"]);
|
|
264501
264503
|
var DELIVERY_OUTCOMES = /* @__PURE__ */ new Set(["published_pending_merge", "delivered"]);
|
|
264502
264504
|
function parseAllowedCardsEnv(env = process.env) {
|
|
@@ -264505,6 +264507,11 @@ function parseAllowedCardsEnv(env = process.env) {
|
|
|
264505
264507
|
const raw = env[GOAL_ALLOWED_CARDS_ENV] ?? "";
|
|
264506
264508
|
return new Set(raw.split(",").map((s) => s.trim()).filter((s) => s !== ""));
|
|
264507
264509
|
}
|
|
264510
|
+
function isGuidedRunOnce(allowed2, env = process.env) {
|
|
264511
|
+
if ((env[GOAL_GUIDED_ENV] ?? "").trim() !== "1")
|
|
264512
|
+
return false;
|
|
264513
|
+
return allowed2 !== void 0 && allowed2.size > 0;
|
|
264514
|
+
}
|
|
264508
264515
|
var OUT_OF_SCOPE_STATUS = "\u{1F6AB} Hold (outside goal scope)";
|
|
264509
264516
|
function scopeBacklogForAllowedCards(items, allowed2) {
|
|
264510
264517
|
if (allowed2 === void 0)
|
|
@@ -265428,6 +265435,7 @@ function parseOptions(args) {
|
|
|
265428
265435
|
let noTmux = false;
|
|
265429
265436
|
let attach = false;
|
|
265430
265437
|
let scopeSpecified = false;
|
|
265438
|
+
let cardsFlagSpecified = false;
|
|
265431
265439
|
let reviewMode = "auto";
|
|
265432
265440
|
let reviewModeSpecified = false;
|
|
265433
265441
|
for (let i = 0; i < args.length; i += 1) {
|
|
@@ -265497,11 +265505,13 @@ function parseOptions(args) {
|
|
|
265497
265505
|
}
|
|
265498
265506
|
if (arg === "--cards") {
|
|
265499
265507
|
cards.push(...parseCards(args[i + 1]));
|
|
265508
|
+
cardsFlagSpecified = true;
|
|
265500
265509
|
i += 1;
|
|
265501
265510
|
continue;
|
|
265502
265511
|
}
|
|
265503
265512
|
if (arg.startsWith("--cards=")) {
|
|
265504
265513
|
cards.push(...parseCards(arg.slice("--cards=".length)));
|
|
265514
|
+
cardsFlagSpecified = true;
|
|
265505
265515
|
continue;
|
|
265506
265516
|
}
|
|
265507
265517
|
if (!arg.startsWith("-"))
|
|
@@ -265517,6 +265527,7 @@ function parseOptions(args) {
|
|
|
265517
265527
|
attach,
|
|
265518
265528
|
scope,
|
|
265519
265529
|
scopeSpecified,
|
|
265530
|
+
cardsFlagSpecified,
|
|
265520
265531
|
reviewMode,
|
|
265521
265532
|
reviewModeSpecified,
|
|
265522
265533
|
...maxCycles !== void 0 ? { maxCycles } : {},
|
|
@@ -265526,6 +265537,9 @@ function parseOptions(args) {
|
|
|
265526
265537
|
function hasHelpArg2(args) {
|
|
265527
265538
|
return args.includes("--help") || args.includes("-h");
|
|
265528
265539
|
}
|
|
265540
|
+
function isGuidedRun(opts) {
|
|
265541
|
+
return opts.cardsFlagSpecified && opts.scopeSpecified && opts.scope.kind === "cards" && opts.scope.cards.length > 0;
|
|
265542
|
+
}
|
|
265529
265543
|
function loopGoHelp() {
|
|
265530
265544
|
return [
|
|
265531
265545
|
"Usage: roll loop go [--epic <name>|--cards <ids>|--all] [--for <duration>] [--max-cycles <n>] [--review <auto|hetero|self|off>] [--attach] [--no-tmux]",
|
|
@@ -265550,6 +265564,12 @@ function loopGoHelp() {
|
|
|
265550
265564
|
" The startup banner shows the EFFECTIVE scope; an inherited scope is flagged so a flagless go can never silently narrow. Pass --all to reset to the full backlog.",
|
|
265551
265565
|
" \u542F\u52A8\u6A2A\u5E45\u663E\u793A\u751F\u6548\u7684 scope\uFF1B\u6CBF\u7528\u7684 scope \u4F1A\u88AB\u6807\u6CE8\uFF0Cflagless go \u7EDD\u4E0D\u9759\u9ED8\u6536\u7A84\u3002\u7528 --all \u91CD\u7F6E\u56DE\u5168\u91CF\u3002",
|
|
265552
265566
|
"",
|
|
265567
|
+
"Guided one-shot while paused (FIX-1472):",
|
|
265568
|
+
" An explicit `roll loop go --cards X` runs the named card(s) even while `roll loop pause` is in effect \u2014 it is a supervisor action, not autonomous scheduling. It installs no scheduler and picks no other card; bound it with --max-cycles.",
|
|
265569
|
+
" \u663E\u5F0F\u7684 `roll loop go --cards X` \u5373\u4F7F\u5728 `roll loop pause` \u751F\u6548\u65F6\u4E5F\u4F1A\u6267\u884C\u6307\u5B9A\u5361\u2014\u2014\u8FD9\u662F Supervisor \u624B\u52A8\u52A8\u4F5C\uFF0C\u4E0D\u662F\u81EA\u4E3B\u6392\u7A0B\u3002\u5B83\u4E0D\u5B89\u88C5\u6392\u7A0B\u3001\u4E0D\u9009\u522B\u7684\u5361\uFF1B\u7528 --max-cycles \u9650\u5B9A\u6B21\u6570\u3002",
|
|
265570
|
+
" Pause still stops autonomous scheduling; only an explicitly-named --cards scope bypasses it (a flagless/inherited/--all/--epic go keeps honoring the pause).",
|
|
265571
|
+
" pause \u4ECD\u7136\u505C\u6B62\u81EA\u4E3B\u6392\u7A0B\uFF1B\u53EA\u6709\u672C\u6B21\u663E\u5F0F\u6307\u5B9A\u7684 --cards \u8303\u56F4\u624D\u4F1A\u7ED5\u8FC7\uFF08flagless/\u6CBF\u7528/--all/--epic \u7684 go \u4ECD\u9075\u5B88\u6682\u505C\uFF09\u3002",
|
|
265572
|
+
"",
|
|
265553
265573
|
"Progress guardrails (the loop stops on NO PROGRESS, not on cost):",
|
|
265554
265574
|
" productivity floor A cycle whose agent EXECUTED but produced 0 commits and no delivery is a `gave_up` terminal \u2014 alerted on the first occurrence (no streak).",
|
|
265555
265575
|
" dead-loop breaker A card is skipped after consecutive no-progress cycles; the whole goal STOPS after K consecutive no-progress cycles (a loud ALERT) \u2014 an unmergeable card can never spin forever.",
|
|
@@ -265673,6 +265693,20 @@ function followGoLiveFeed(projectPath3, rollBin4) {
|
|
|
265673
265693
|
watch.on("error", finish);
|
|
265674
265694
|
});
|
|
265675
265695
|
}
|
|
265696
|
+
function buildRunOnceChildEnv(input, base = process.env) {
|
|
265697
|
+
return {
|
|
265698
|
+
...base,
|
|
265699
|
+
ROLL_LOOP_GO_CHILD: "1",
|
|
265700
|
+
// FIX-1022: unattended go-driver child must never trigger the macOS
|
|
265701
|
+
// screencapture TCC prompt (isTTY is unreliable under PTY wrapping).
|
|
265702
|
+
ROLL_NO_SCREENCAP: base["ROLL_NO_SCREENCAP"] ?? "1",
|
|
265703
|
+
...input.allowedCards !== void 0 ? { [GOAL_ALLOWED_CARDS_ENV]: input.allowedCards.join(",") } : {},
|
|
265704
|
+
// FIX-1472: guided is set on EVERY call (never conditionally spread) so an
|
|
265705
|
+
// inherited ambient "1" cannot survive into an unguided child. Fail closed
|
|
265706
|
+
// to "0" — isGuidedRunOnce treats anything but "1" as not guided.
|
|
265707
|
+
[GOAL_GUIDED_ENV]: input.guided === true ? "1" : "0"
|
|
265708
|
+
};
|
|
265709
|
+
}
|
|
265676
265710
|
function realRunOnce(input) {
|
|
265677
265711
|
const bin = rollBin();
|
|
265678
265712
|
const cmd = bin.endsWith(".js") || bin.endsWith(".mjs") ? process.execPath : bin;
|
|
@@ -265681,14 +265715,7 @@ function realRunOnce(input) {
|
|
|
265681
265715
|
const child = spawn8(cmd, args, {
|
|
265682
265716
|
cwd: input.projectPath,
|
|
265683
265717
|
detached: true,
|
|
265684
|
-
env:
|
|
265685
|
-
...process.env,
|
|
265686
|
-
ROLL_LOOP_GO_CHILD: "1",
|
|
265687
|
-
// FIX-1022: unattended go-driver child must never trigger the macOS
|
|
265688
|
-
// screencapture TCC prompt (isTTY is unreliable under PTY wrapping).
|
|
265689
|
-
ROLL_NO_SCREENCAP: process.env["ROLL_NO_SCREENCAP"] ?? "1",
|
|
265690
|
-
...input.allowedCards !== void 0 ? { [GOAL_ALLOWED_CARDS_ENV]: input.allowedCards.join(",") } : {}
|
|
265691
|
-
},
|
|
265718
|
+
env: buildRunOnceChildEnv(input),
|
|
265692
265719
|
stdio: "inherit"
|
|
265693
265720
|
});
|
|
265694
265721
|
child.on("exit", (code) => resolve17(code ?? 1));
|
|
@@ -266587,11 +266614,12 @@ async function runGoWorker(id, opts, deps) {
|
|
|
266587
266614
|
progress = progressFromGoal(goal);
|
|
266588
266615
|
initialUsage = runSummaryFromGoal(goal);
|
|
266589
266616
|
const deadlineSec = timeboxDeadlineSec(goal, opts, startedSec);
|
|
266617
|
+
const guided = isGuidedRun(opts);
|
|
266590
266618
|
bus.appendEvent(evPath, { type: "goal:session_start", sessionId: sid, scope: goal.scope, ts: startedSec });
|
|
266591
266619
|
while (true) {
|
|
266592
266620
|
if (stopRequested)
|
|
266593
266621
|
break;
|
|
266594
|
-
if (existsSync64(pauseMarkerPath2(id.path, id.slug))) {
|
|
266622
|
+
if (!guided && existsSync64(pauseMarkerPath2(id.path, id.slug))) {
|
|
266595
266623
|
stopReason2 = "pause_marker";
|
|
266596
266624
|
break;
|
|
266597
266625
|
}
|
|
@@ -266647,10 +266675,10 @@ async function runGoWorker(id, opts, deps) {
|
|
|
266647
266675
|
}
|
|
266648
266676
|
if (stopRequested)
|
|
266649
266677
|
break;
|
|
266650
|
-
if (existsSync64(pauseMarkerPath2(id.path, id.slug)))
|
|
266678
|
+
if (!guided && existsSync64(pauseMarkerPath2(id.path, id.slug)))
|
|
266651
266679
|
continue;
|
|
266652
266680
|
const before = readRunSnapshot(runsPath2(id.path));
|
|
266653
|
-
await deps.runOnce({ projectPath: id.path, allowedCards });
|
|
266681
|
+
await deps.runOnce({ projectPath: id.path, allowedCards, ...guided ? { guided: true } : {} });
|
|
266654
266682
|
goal = updateUsage(id.path, goal, baseline, initialUsage, deps.nowIso());
|
|
266655
266683
|
writeGoal(gPath, goal);
|
|
266656
266684
|
const after = readRunSnapshot(runsPath2(id.path));
|
|
@@ -266707,7 +266735,7 @@ async function runGoWorker(id, opts, deps) {
|
|
|
266707
266735
|
stopReason2 = "safety_pause";
|
|
266708
266736
|
break;
|
|
266709
266737
|
}
|
|
266710
|
-
if (existsSync64(pauseMarkerPath2(id.path, id.slug))) {
|
|
266738
|
+
if (!guided && existsSync64(pauseMarkerPath2(id.path, id.slug))) {
|
|
266711
266739
|
stopReason2 = "pause_marker";
|
|
266712
266740
|
break;
|
|
266713
266741
|
}
|
|
@@ -288829,7 +288857,7 @@ import { execFileSync as execFileSync32 } from "node:child_process";
|
|
|
288829
288857
|
import { homedir as homedir34 } from "node:os";
|
|
288830
288858
|
import { appendFileSync as appendFileSync20, existsSync as existsSync134, mkdirSync as mkdirSync73, rmSync as rmSync25 } from "node:fs";
|
|
288831
288859
|
import { dirname as dirname73, join as join157, relative as relative14, resolve as resolve14, sep as sep8 } from "node:path";
|
|
288832
|
-
var MERGED_KINDS = /* @__PURE__ */ new Set(["ancestor", "patch_equivalent"]);
|
|
288860
|
+
var MERGED_KINDS = /* @__PURE__ */ new Set(["ancestor", "patch_equivalent", "final_tree"]);
|
|
288833
288861
|
function isSafelyDisposable(rec) {
|
|
288834
288862
|
return rec.owner === "loop" && rec.active === false && rec.dirtyTracked === false && rec.disposition === "disposable_candidate" && MERGED_KINDS.has(rec.mergeEvidence.kind) && typeof rec.head === "string" && rec.head.length > 0;
|
|
288835
288863
|
}
|
|
@@ -288906,9 +288934,12 @@ function planWorktreeCleanup(audit, threshold, standaloneMergedBranches = []) {
|
|
|
288906
288934
|
preserved
|
|
288907
288935
|
};
|
|
288908
288936
|
}
|
|
288909
|
-
function defaultRemoveBranch(repositoryRoot, branch) {
|
|
288937
|
+
function defaultRemoveBranch(repositoryRoot, branch, expectedSha) {
|
|
288938
|
+
if (!isFullGitOid(expectedSha)) {
|
|
288939
|
+
return { ok: false, detail: `refused: expected sha ${expectedSha} is not a full git OID` };
|
|
288940
|
+
}
|
|
288910
288941
|
try {
|
|
288911
|
-
execFileSync32("git", ["-C", repositoryRoot, "
|
|
288942
|
+
execFileSync32("git", ["-C", repositoryRoot, "update-ref", "-d", `refs/heads/${branch}`, expectedSha], {
|
|
288912
288943
|
encoding: "utf8",
|
|
288913
288944
|
stdio: ["ignore", "pipe", "pipe"]
|
|
288914
288945
|
});
|
|
@@ -289111,7 +289142,7 @@ async function applyWorktreeCleanup(plan, options) {
|
|
|
289111
289142
|
branchesRemoved.push(removalB);
|
|
289112
289143
|
continue;
|
|
289113
289144
|
}
|
|
289114
|
-
const r = removeBranchFn(repositoryRoot, bc.branch);
|
|
289145
|
+
const r = removeBranchFn(repositoryRoot, bc.branch, bc.expectedSha);
|
|
289115
289146
|
if (!r.ok) {
|
|
289116
289147
|
refuseB(`delete-failed: ${r.detail}`);
|
|
289117
289148
|
continue;
|
|
@@ -289257,7 +289288,7 @@ function renderResultHuman(result2) {
|
|
|
289257
289288
|
}
|
|
289258
289289
|
return lines3.join("\n").trimEnd() + "\n";
|
|
289259
289290
|
}
|
|
289260
|
-
var CLEANUP_USAGE = "Usage: roll worktree cleanup [--dry-run | --apply] [--json] [--repo <path>]\n Safely recover from branch/worktree canary pressure using the worktree\n audit as the SOLE authority. Removes ONLY inactive, merged, clean\n `disposable_candidate` loop worktrees, plus (FIX-1454) standalone ephemeral\n branches that are verifiably
|
|
289291
|
+
var CLEANUP_USAGE = "Usage: roll worktree cleanup [--dry-run | --apply] [--json] [--repo <path>]\n Safely recover from branch/worktree canary pressure using the worktree\n audit as the SOLE authority. Removes ONLY inactive, merged, clean\n `disposable_candidate` loop worktrees, plus (FIX-1454) standalone ephemeral\n branches that are verifiably delivered and attached to no worktree. Delivery\n is PROVEN by one of: every commit is an ancestor of the integration branch;\n `git cherry` shows every commit already has an equivalent patch upstream; or\n (FIX-1471, squash merges) the branch tip tree is byte-identical to the merge\n commit of the branch's OWN merged GitHub PR (matched by exact head ref) AND\n that merge commit is an ancestor of the integration branch. A merged PR alone\n is NEVER sufficient, and no arbitrary same-tree commit on the integration\n branch is ever used. Never a path/ref that is merely old or counted, and never\n a preserved (unpublished / dirty / active / external / current / protected /\n unmerged) one.\n\n Always dry-run first. Default (no flag) is --dry-run.\n --dry-run print counted refs/dirs, audit dispositions, and the minimal\n candidate set to clear pressure. Never mutates git state.\n --apply re-run the audit before EVERY removal; remove only revalidated\n candidates via git, prune registration, emit events. A changed\n head / new dirt / missing path / concurrent activation fails\n closed (no substitution). Then resume explicitly: roll loop resume\n --json emit the schema-1 plan (dry-run) or result (apply) as JSON\n --repo override the project root (default: current directory)\n --reclaim-orphan <path> (FIX-1460) bounded-rm ONE named orphan loop dir\n (deregistered from git; delivery not auto-provable) after you\n review it. Fails closed unless it is an inactive loop orphan\n inside .roll/loop/worktrees. Auto-reclaim of provably-delivered\n orphans happens under --apply.\n\n \u5B89\u5168\u6E05\u7406:\u4EC5\u79FB\u9664\u5BA1\u8BA1\u5224\u5B9A\u4E3A\u5DF2\u5408\u5E76\u3001\u5E72\u51C0\u3001\u975E\u6D3B\u8DC3\u7684 disposable_candidate;\n \u5148\u8DD1 --dry-run,\u518D --apply,\u6700\u540E\u624B\u52A8 roll loop resume\u3002";
|
|
289261
289292
|
function resolveThreshold() {
|
|
289262
289293
|
const parsed = parseInt(process.env["ROLL_BRANCH_CANARY_MAX"] ?? "", 10);
|
|
289263
289294
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_BRANCH_CANARY_MAX;
|
|
@@ -289265,20 +289296,41 @@ function resolveThreshold() {
|
|
|
289265
289296
|
function gitCap(repoRoot3, args) {
|
|
289266
289297
|
return execFileSync32("git", ["-C", repoRoot3, ...args], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
289267
289298
|
}
|
|
289268
|
-
function classifyBranchMerge(branch, integrationBranch, git5) {
|
|
289299
|
+
function classifyBranchMerge(branch, integrationBranch, git5, prMergeCommit) {
|
|
289269
289300
|
if (git5(["merge-base", "--is-ancestor", branch, integrationBranch]).ok)
|
|
289270
289301
|
return "ancestor";
|
|
289271
289302
|
const cherry = git5(["cherry", integrationBranch, branch]);
|
|
289272
|
-
if (
|
|
289303
|
+
if (cherry.ok) {
|
|
289304
|
+
const lines3 = cherry.stdout.split("\n").map((l) => l.trim()).filter((l) => l !== "");
|
|
289305
|
+
if (lines3.length > 0 && lines3.every((l) => l.startsWith("-")))
|
|
289306
|
+
return "patch_equivalent";
|
|
289307
|
+
}
|
|
289308
|
+
return classifyFinalTreeDelivery(branch, integrationBranch, git5, prMergeCommit);
|
|
289309
|
+
}
|
|
289310
|
+
function classifyFinalTreeDelivery(branch, integrationBranch, git5, prMergeCommit) {
|
|
289311
|
+
const rawMerge = prMergeCommit(branch);
|
|
289312
|
+
if (rawMerge === null)
|
|
289273
289313
|
return null;
|
|
289274
|
-
const
|
|
289275
|
-
if (
|
|
289314
|
+
const merge = rawMerge.trim();
|
|
289315
|
+
if (!isFullGitOid(merge))
|
|
289276
289316
|
return null;
|
|
289277
|
-
if (
|
|
289317
|
+
if (!git5(["merge-base", "--is-ancestor", merge, integrationBranch]).ok)
|
|
289278
289318
|
return null;
|
|
289279
|
-
|
|
289280
|
-
|
|
289281
|
-
|
|
289319
|
+
const branchTreeProbe = git5(["rev-parse", `${branch}^{tree}`]);
|
|
289320
|
+
if (!branchTreeProbe.ok)
|
|
289321
|
+
return null;
|
|
289322
|
+
const branchTree = branchTreeProbe.stdout.trim();
|
|
289323
|
+
if (branchTree === "")
|
|
289324
|
+
return null;
|
|
289325
|
+
const mergeTreeProbe = git5(["rev-parse", `${merge}^{tree}`]);
|
|
289326
|
+
if (!mergeTreeProbe.ok)
|
|
289327
|
+
return null;
|
|
289328
|
+
const mergeTree = mergeTreeProbe.stdout.trim();
|
|
289329
|
+
if (mergeTree === "")
|
|
289330
|
+
return null;
|
|
289331
|
+
if (branchTree !== mergeTree)
|
|
289332
|
+
return null;
|
|
289333
|
+
return "final_tree";
|
|
289282
289334
|
}
|
|
289283
289335
|
function buildStandaloneBranchDeps(repoRoot3, audit, integrationBranch) {
|
|
289284
289336
|
const attachedBranches = new Set(audit.records.filter((r) => r.owner === "loop" && typeof r.branch === "string" && r.branch !== "").map((r) => r.branch.replace(/^refs\/heads\//, "")));
|
|
@@ -289298,11 +289350,12 @@ function buildStandaloneBranchDeps(repoRoot3, audit, integrationBranch) {
|
|
|
289298
289350
|
return null;
|
|
289299
289351
|
}
|
|
289300
289352
|
},
|
|
289301
|
-
// FIX-1458 (#1465): delivery is proven
|
|
289302
|
-
//
|
|
289303
|
-
//
|
|
289304
|
-
//
|
|
289305
|
-
//
|
|
289353
|
+
// FIX-1458 (#1465), FIX-1471: delivery is proven by fresh git patch evidence
|
|
289354
|
+
// (ancestor OR `git cherry` patch-equivalence) OR, for the squash-merge case,
|
|
289355
|
+
// by the branch tip tree matching the merge commit of the branch's OWN merged
|
|
289356
|
+
// GitHub PR (an ancestor of integration). A merged PR label alone never
|
|
289357
|
+
// authorizes deletion, and no arbitrary same-tree integration commit is ever
|
|
289358
|
+
// consulted — that would silently discard unique commits (US-ORG-003/007/004).
|
|
289306
289359
|
branchMerge: (branch) => classifyBranchMerge(branch, integrationBranch, (args) => {
|
|
289307
289360
|
try {
|
|
289308
289361
|
return { ok: true, stdout: gitCap(repoRoot3, args) };
|
|
@@ -289310,9 +289363,39 @@ function buildStandaloneBranchDeps(repoRoot3, audit, integrationBranch) {
|
|
|
289310
289363
|
const stdout = typeof err16.stdout === "string" ? err16.stdout : "";
|
|
289311
289364
|
return { ok: false, stdout };
|
|
289312
289365
|
}
|
|
289313
|
-
})
|
|
289366
|
+
}, (b) => ghMergedPrMergeCommit(repoRoot3, b))
|
|
289314
289367
|
};
|
|
289315
289368
|
}
|
|
289369
|
+
function isFullGitOid(value) {
|
|
289370
|
+
return /^[0-9a-f]{40}$/.test(value) || /^[0-9a-f]{64}$/.test(value);
|
|
289371
|
+
}
|
|
289372
|
+
function parseMergedPrMergeCommit(raw, branch) {
|
|
289373
|
+
let j;
|
|
289374
|
+
try {
|
|
289375
|
+
j = JSON.parse(raw);
|
|
289376
|
+
} catch {
|
|
289377
|
+
return null;
|
|
289378
|
+
}
|
|
289379
|
+
if (j.state !== "MERGED")
|
|
289380
|
+
return null;
|
|
289381
|
+
if (typeof j.mergedAt !== "string" || j.mergedAt === "")
|
|
289382
|
+
return null;
|
|
289383
|
+
if (typeof j.headRefName !== "string" || j.headRefName !== branch)
|
|
289384
|
+
return null;
|
|
289385
|
+
const oid = j.mergeCommit?.oid;
|
|
289386
|
+
if (typeof oid !== "string" || !isFullGitOid(oid))
|
|
289387
|
+
return null;
|
|
289388
|
+
return oid;
|
|
289389
|
+
}
|
|
289390
|
+
function ghMergedPrMergeCommit(repoRoot3, branch) {
|
|
289391
|
+
let out3;
|
|
289392
|
+
try {
|
|
289393
|
+
out3 = execFileSync32("gh", ["pr", "view", branch, "--json", "state,mergedAt,mergeCommit,headRefName"], { cwd: repoRoot3, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
289394
|
+
} catch {
|
|
289395
|
+
return null;
|
|
289396
|
+
}
|
|
289397
|
+
return parseMergedPrMergeCommit(out3, branch);
|
|
289398
|
+
}
|
|
289316
289399
|
function resolveIntegrationForCleanup(repoRoot3) {
|
|
289317
289400
|
try {
|
|
289318
289401
|
const head = gitCap(repoRoot3, ["symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD"]).trim();
|
|
@@ -290111,7 +290194,7 @@ async function loopRunOnceCommand(args) {
|
|
|
290111
290194
|
removePendingPublish(rt, storyId);
|
|
290112
290195
|
}
|
|
290113
290196
|
}
|
|
290114
|
-
if (isLoopPaused(id.path, id.slug)) {
|
|
290197
|
+
if (isLoopPaused(id.path, id.slug) && !isGuidedRunOnce(allowedCards)) {
|
|
290115
290198
|
const lang10 = resolveLang({
|
|
290116
290199
|
rollLang: process.env["ROLL_LANG"],
|
|
290117
290200
|
lcAll: process.env["LC_ALL"],
|
|
@@ -292823,28 +292906,49 @@ function assertTestProofFresh(exec) {
|
|
|
292823
292906
|
throw new Error("code changed since last test run \u2014 run `roll test` on the current staged tree");
|
|
292824
292907
|
}
|
|
292825
292908
|
}
|
|
292909
|
+
function hasOnlyPreparedReleaseChanges(exec) {
|
|
292910
|
+
const status2 = exec("git", ["status", "--porcelain", "--untracked-files=all"]).trim();
|
|
292911
|
+
if (status2 === "")
|
|
292912
|
+
return false;
|
|
292913
|
+
const allowed2 = /* @__PURE__ */ new Set(["package.json", "CHANGELOG.md"]);
|
|
292914
|
+
const unexpected = status2.split("\n").map((line) => line.slice(2).trim()).filter((path) => !allowed2.has(path));
|
|
292915
|
+
if (unexpected.length > 0) {
|
|
292916
|
+
throw new Error(`unexpected worktree changes during release recovery: ${unexpected.join(", ")}`);
|
|
292917
|
+
}
|
|
292918
|
+
return true;
|
|
292919
|
+
}
|
|
292826
292920
|
function commitPushWithGate(opts) {
|
|
292827
292921
|
const { branch, message, rollManaged, exec } = opts;
|
|
292828
292922
|
const original = exec("git", ["rev-parse", "--abbrev-ref", "HEAD"]).trim();
|
|
292923
|
+
const preparedChanges = hasOnlyPreparedReleaseChanges(exec);
|
|
292829
292924
|
let createdLocal = false;
|
|
292925
|
+
const checkoutExistingBranch = () => {
|
|
292926
|
+
if (preparedChanges) {
|
|
292927
|
+
exec("git", ["checkout", "--merge", branch]);
|
|
292928
|
+
return;
|
|
292929
|
+
}
|
|
292930
|
+
exec("git", ["checkout", branch]);
|
|
292931
|
+
};
|
|
292830
292932
|
const checkoutBranch = () => {
|
|
292933
|
+
let localSha = "";
|
|
292831
292934
|
try {
|
|
292832
|
-
|
|
292833
|
-
if (localSha !== "") {
|
|
292834
|
-
exec("git", ["checkout", branch]);
|
|
292835
|
-
return;
|
|
292836
|
-
}
|
|
292935
|
+
localSha = exec("git", ["rev-parse", "--verify", `refs/heads/${branch}`]).trim();
|
|
292837
292936
|
} catch {
|
|
292838
292937
|
}
|
|
292938
|
+
if (localSha !== "") {
|
|
292939
|
+
checkoutExistingBranch();
|
|
292940
|
+
return;
|
|
292941
|
+
}
|
|
292942
|
+
let remote = "";
|
|
292839
292943
|
try {
|
|
292840
|
-
|
|
292841
|
-
if (remote !== "") {
|
|
292842
|
-
exec("git", ["fetch", "origin", `${branch}:${branch}`]);
|
|
292843
|
-
exec("git", ["checkout", branch]);
|
|
292844
|
-
return;
|
|
292845
|
-
}
|
|
292944
|
+
remote = exec("git", ["ls-remote", "--heads", "origin", branch]).trim();
|
|
292846
292945
|
} catch {
|
|
292847
292946
|
}
|
|
292947
|
+
if (remote !== "") {
|
|
292948
|
+
exec("git", ["fetch", "origin", `${branch}:${branch}`]);
|
|
292949
|
+
checkoutExistingBranch();
|
|
292950
|
+
return;
|
|
292951
|
+
}
|
|
292848
292952
|
createdLocal = true;
|
|
292849
292953
|
exec("git", ["checkout", "-b", branch]);
|
|
292850
292954
|
};
|