@tablation/crew 0.73.1 → 0.73.4
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/cli.js +109 -21
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -3563,7 +3563,7 @@ function remoteBranchForIssue(cwd, remote, issueKey, templates = { name: "issue-
|
|
|
3563
3563
|
}
|
|
3564
3564
|
return null;
|
|
3565
3565
|
}
|
|
3566
|
-
function levelRemoteBranch(cwd, remote, branch) {
|
|
3566
|
+
function levelRemoteBranch(cwd, remote, branch, baseRef) {
|
|
3567
3567
|
const local = gitOk(cwd, ["rev-parse", "-q", "--verify", `refs/heads/${branch}`]);
|
|
3568
3568
|
if (!remoteBranchExists(cwd, remote, branch)) return { exists: local !== null };
|
|
3569
3569
|
if (local === null) {
|
|
@@ -3572,6 +3572,20 @@ function levelRemoteBranch(cwd, remote, branch) {
|
|
|
3572
3572
|
const remoteTip2 = gitOk(cwd, ["rev-parse", "-q", "--verify", `refs/remotes/${remote}/${branch}`]);
|
|
3573
3573
|
if (!remoteTip2 || remoteTip2 === local) return { exists: true };
|
|
3574
3574
|
if (!isAncestor(cwd, local, remoteTip2)) {
|
|
3575
|
+
if (baseRef && isAncestor(cwd, local, baseRef)) {
|
|
3576
|
+
const holder3 = worktrees(cwd).find((w) => w.branch === branch && !w.prunable);
|
|
3577
|
+
if (holder3) {
|
|
3578
|
+
if (gitOk(holder3.path, ["status", "--porcelain"])) {
|
|
3579
|
+
return { exists: true, problem: `${branch} holds only commits already on ${baseRef}, but the worktree ${holder3.path} that holds it has uncommitted changes` };
|
|
3580
|
+
}
|
|
3581
|
+
if (gitOk(holder3.path, ["reset", "--hard", "--quiet", remoteTip2]) === null) {
|
|
3582
|
+
return { exists: true, problem: `could not reset ${branch} to ${remote}/${branch} in ${holder3.path}` };
|
|
3583
|
+
}
|
|
3584
|
+
} else if (gitOk(cwd, ["branch", "-f", branch, remoteTip2]) === null) {
|
|
3585
|
+
return { exists: true, problem: `could not reset ${branch} to ${remote}/${branch}` };
|
|
3586
|
+
}
|
|
3587
|
+
return { exists: true, reset: `${branch} held only commits already on ${baseRef}; reset ${local.slice(0, 8)} -> ${remote}/${branch} at ${remoteTip2.slice(0, 8)}` };
|
|
3588
|
+
}
|
|
3575
3589
|
const diverged = !isAncestor(cwd, remoteTip2, local);
|
|
3576
3590
|
const holder2 = diverged ? worktrees(cwd).find((w) => w.branch === branch && !w.prunable) : void 0;
|
|
3577
3591
|
if (holder2) {
|
|
@@ -4225,7 +4239,7 @@ function describeComposition(c) {
|
|
|
4225
4239
|
|
|
4226
4240
|
${rows}
|
|
4227
4241
|
|
|
4228
|
-
The gate runs on the base plus every verified branch above, not on the base alone: reproduce on that composition.`;
|
|
4242
|
+
The gate runs on the base plus every verified branch above, not on the base alone: reproduce on that composition. A fix that compiles only on the composition belongs on one of those branches (the one owning the failing file, after merging the others into it), not on a new branch cut from the base: a separate fix branch conflicts with the branches it contains when the release squash-merges them all. Re-queue the touched ticket to QA and close this alert with a note.`;
|
|
4229
4243
|
}
|
|
4230
4244
|
|
|
4231
4245
|
// src/shell.ts
|
|
@@ -6483,11 +6497,12 @@ function locateBranchForTicket(dir, cfg, t, role, fetchedDirs) {
|
|
|
6483
6497
|
const found = remoteBranchForIssue(dir, remote, t.issue_id, branchTemplates(cfg, t), branchRenderer(t, role, cfg.branch.lowercase));
|
|
6484
6498
|
return found ? { branch: found, where: "remote" } : null;
|
|
6485
6499
|
}
|
|
6486
|
-
function materializeBranchForTicket(dir, cfg, t, role, onProblem) {
|
|
6500
|
+
function materializeBranchForTicket(dir, cfg, t, role, onProblem, onReset) {
|
|
6487
6501
|
const loc = locateBranchForTicket(dir, cfg, t, role);
|
|
6488
6502
|
if (!loc) return null;
|
|
6489
|
-
const r = levelRemoteBranch(dir, cfg.branch.remote, loc.branch);
|
|
6503
|
+
const r = levelRemoteBranch(dir, cfg.branch.remote, loc.branch, `${cfg.branch.remote}/${cfg.branch.base}`);
|
|
6490
6504
|
if (r.problem) onProblem?.(r.problem);
|
|
6505
|
+
if (r.reset) onReset?.(r.reset);
|
|
6491
6506
|
return loc.branch;
|
|
6492
6507
|
}
|
|
6493
6508
|
|
|
@@ -6774,10 +6789,10 @@ function applyRunWorktree(dir, cfg, plan) {
|
|
|
6774
6789
|
case "recut":
|
|
6775
6790
|
git(dir, ["worktree", "remove", "--force", plan.path]);
|
|
6776
6791
|
gitOk(dir, ["branch", "-D", plan.branch]);
|
|
6777
|
-
git(dir, ["worktree", "add", "-b", plan.branch, plan.path, plan.base]);
|
|
6792
|
+
git(dir, ["worktree", "add", "--no-track", "-b", plan.branch, plan.path, plan.base]);
|
|
6778
6793
|
return plan.path;
|
|
6779
6794
|
case "cut-fresh":
|
|
6780
|
-
git(dir, ["worktree", "add", "-b", plan.branch, plan.path, plan.base]);
|
|
6795
|
+
git(dir, ["worktree", "add", "--no-track", "-b", plan.branch, plan.path, plan.base]);
|
|
6781
6796
|
return plan.path;
|
|
6782
6797
|
}
|
|
6783
6798
|
}
|
|
@@ -6785,6 +6800,19 @@ function worktreeHoldsUnpushed(path, remote) {
|
|
|
6785
6800
|
const out = gitOk(path, ["for-each-ref", "--contains", "HEAD", "--count=1", "--format=%(refname)", `refs/remotes/${remote}/`]);
|
|
6786
6801
|
return out === null || out.trim() === "";
|
|
6787
6802
|
}
|
|
6803
|
+
function dropEmptyRunBranch(dir, cfg, branch) {
|
|
6804
|
+
const { remote, base } = cfg.branch;
|
|
6805
|
+
const baseRef = remoteConfigured(dir, remote) ? `${remote}/${base}` : base;
|
|
6806
|
+
if (gitOk(dir, ["rev-parse", "-q", "--verify", `refs/heads/${branch}`]) === null) return false;
|
|
6807
|
+
if (remoteConfigured(dir, remote) && remoteBranchExists(dir, remote, branch)) return false;
|
|
6808
|
+
if (countCommits(dir, `${baseRef}..${branch}`) !== 0) return false;
|
|
6809
|
+
const wt = worktrees(dir).find((w) => w.branch === branch && !w.prunable && w.path !== dir);
|
|
6810
|
+
if (wt && existsSync14(wt.path)) {
|
|
6811
|
+
if (status(wt.path).length > 0) return false;
|
|
6812
|
+
if (gitOk(dir, ["worktree", "remove", wt.path]) === null) return false;
|
|
6813
|
+
}
|
|
6814
|
+
return gitOk(dir, ["branch", "-D", branch]) !== null;
|
|
6815
|
+
}
|
|
6788
6816
|
async function finishRunWorktree(dir, cfg, branch) {
|
|
6789
6817
|
const wt = worktrees(dir).find((w) => w.branch === branch && !w.prunable && w.path !== dir);
|
|
6790
6818
|
if (!wt || !existsSync14(wt.path)) return { kind: "none" };
|
|
@@ -6953,6 +6981,12 @@ async function guardRunHandoff(route, ship, tracker, role, workingId, log, since
|
|
|
6953
6981
|
log
|
|
6954
6982
|
});
|
|
6955
6983
|
if (outcome.kind === "pushed") await retireRunWorktree(tracker, target.dir, cfg, t, role, seatId, ship.name, log);
|
|
6984
|
+
else {
|
|
6985
|
+
const branch = existingBranchForTicket(target.dir, cfg, t, role);
|
|
6986
|
+
if (branch && dropEmptyRunBranch(target.dir, cfg, branch)) {
|
|
6987
|
+
log.emit(`${t.issue_id}: dropped ${branch} \u2014 the run made no commits beyond ${cfg.branch.base}`, { ticket: t.issue_id, step: "handoff" });
|
|
6988
|
+
}
|
|
6989
|
+
}
|
|
6956
6990
|
if (since2) await guardRunStrays(route, ship, tracker, role, workingId, since2, log);
|
|
6957
6991
|
return outcome;
|
|
6958
6992
|
} catch (e) {
|
|
@@ -10297,7 +10331,8 @@ var lastActedHandBack = (ticket, events) => {
|
|
|
10297
10331
|
return mine.filter((e) => e.event_type === "conflict_handed_back" && pushes.some((p) => p > stampOf(e))).sort((a, b) => stampOf(b).localeCompare(stampOf(a)))[0];
|
|
10298
10332
|
};
|
|
10299
10333
|
var MAX_ABSORBED_HANDBACKS = 2;
|
|
10300
|
-
function planConflictBounce(cwd, ticket, branch, base, paths, events) {
|
|
10334
|
+
function planConflictBounce(cwd, ticket, branch, base, paths, events, merged = []) {
|
|
10335
|
+
const siblings = merged.filter((m) => m.branch !== branch && paths.some((p) => (gitOk(cwd, ["diff", "--name-only", `${base}...${m.branch}`, "--", p]) ?? "").trim() !== ""));
|
|
10301
10336
|
return {
|
|
10302
10337
|
ticket,
|
|
10303
10338
|
branch,
|
|
@@ -10307,7 +10342,8 @@ function planConflictBounce(cwd, ticket, branch, base, paths, events) {
|
|
|
10307
10342
|
priorBounces: priorBounces(ticket, events),
|
|
10308
10343
|
cwd,
|
|
10309
10344
|
baseSha: gitOk(cwd, ["rev-parse", "--verify", "-q", base]) || null,
|
|
10310
|
-
priorBaseSha: baseShaOf(lastActedHandBack(ticket, events) ?? { record_id: "", event_type: "" })
|
|
10345
|
+
priorBaseSha: baseShaOf(lastActedHandBack(ticket, events) ?? { record_id: "", event_type: "" }),
|
|
10346
|
+
siblings
|
|
10311
10347
|
};
|
|
10312
10348
|
}
|
|
10313
10349
|
function refreshBranch(worktree, base) {
|
|
@@ -10336,7 +10372,7 @@ function handBackComment(b, merged) {
|
|
|
10336
10372
|
|
|
10337
10373
|
**Back with the dev lane \u2014 this branch no longer merges.**
|
|
10338
10374
|
|
|
10339
|
-
\`${b.branch}\` was verified, but squash-merging it into \`${b.base}\` conflicts in ${list}. Work that landed on \`${b.base}\` after this branch was cut has changed the same lines
|
|
10375
|
+
\`${b.branch}\` was verified, but squash-merging it into \`${b.base}\` conflicts in ${list}. ${b.siblings?.length ? `This conflicts with ${b.siblings.map((x) => `${x.key} (\`${x.branch}\`)`).join(", ")}, merged earlier in the same release, not with a \`${b.base}\` that moved: merging \`${b.base}\` in will change nothing. Merge ${b.siblings.length === 1 ? "that branch" : "those branches"} into this one (or move this change onto ${b.siblings.length === 1 ? "it" : "one of them"}), resolve, and push.` : `Work that landed on \`${b.base}\` after this branch was cut has changed the same lines.`}
|
|
10340
10376
|
|
|
10341
10377
|
${merged ? `\`${b.base}\` has been merged into \`${b.branch}\` in its worktree and the conflict is left in place, so \`git status\` there shows exactly what disagrees. Resolve it, commit, push as a plain fast-forward (the branch is pushed, so rebasing it is not an option) and hand back to QA as usual \u2014 \`git merge --abort\` backs the merge out if you would rather start differently.` : `This ship has no worktree for \`${b.branch}\`, so nothing has been merged for you. Merge \`${b.base}\` into the branch wherever it lives, resolve the conflict, and commit.`}
|
|
10342
10378
|
|
|
@@ -10584,6 +10620,19 @@ async function applyStrandedVerified(writer, s, contract, operatorId, memberId,
|
|
|
10584
10620
|
}
|
|
10585
10621
|
}
|
|
10586
10622
|
|
|
10623
|
+
// src/local-ref-skip.ts
|
|
10624
|
+
var LOCAL_REF_SKIP_MARKER = calloutHeader("WARNING", "Release Skipped");
|
|
10625
|
+
function localRefSkipComment(shipName2, problem) {
|
|
10626
|
+
return `${LOCAL_REF_SKIP_MARKER}
|
|
10627
|
+
> ${problem}
|
|
10628
|
+
|
|
10629
|
+
**This ticket is verified but was not released: the release ship's local branch disagrees with the remote.**
|
|
10630
|
+
|
|
10631
|
+
Ship ${shipName2} found: ${problem}. The release refuses to squash a ref nobody verified and retries every cycle. If the local branch holds only commits already on the base, run \`git branch -f <branch> <remote>/<branch>\` in the release checkout (the crew now does this itself when it can); otherwise a person needs to reconcile the branch.
|
|
10632
|
+
`;
|
|
10633
|
+
}
|
|
10634
|
+
var localRefSkipReported = (ticket, problem, events) => events.some((e) => e.record_id === ticket.id && e.event_type === "local_ref_skip" && (e.message ?? "").includes(problem));
|
|
10635
|
+
|
|
10587
10636
|
// src/connect-wizard.ts
|
|
10588
10637
|
import { delimiter, dirname as dirname10, join as join23 } from "path";
|
|
10589
10638
|
import { accessSync as accessSync2, constants as constants2, existsSync as existsSync21, mkdirSync as mkdirSync13, writeFileSync as writeFileSync15 } from "fs";
|
|
@@ -11641,7 +11690,7 @@ function openReleaseCheckout(o) {
|
|
|
11641
11690
|
o.emit.emit(`release checkout: ${dir}`, { step: "release" });
|
|
11642
11691
|
const close = () => closeReleaseCheckout(o.cwd, root, dir, branch);
|
|
11643
11692
|
try {
|
|
11644
|
-
git(o.cwd, ["worktree", "add", "-q", "-b", branch, dir, `${remote}/${base}`]);
|
|
11693
|
+
git(o.cwd, ["worktree", "add", "-q", "--no-track", "-b", branch, dir, `${remote}/${base}`]);
|
|
11645
11694
|
} catch (e) {
|
|
11646
11695
|
close();
|
|
11647
11696
|
throw e;
|
|
@@ -11667,10 +11716,15 @@ function reportHeld(o, held) {
|
|
|
11667
11716
|
}
|
|
11668
11717
|
async function runRelease(o) {
|
|
11669
11718
|
const movedPast2 = [];
|
|
11670
|
-
const
|
|
11671
|
-
|
|
11719
|
+
const localRefSkips = [];
|
|
11720
|
+
const out = await runReleaseCycle(o, movedPast2, localRefSkips);
|
|
11721
|
+
return {
|
|
11722
|
+
...out,
|
|
11723
|
+
...movedPast2.length ? { movedPast: movedPast2 } : {},
|
|
11724
|
+
...localRefSkips.length ? { localRefSkips } : {}
|
|
11725
|
+
};
|
|
11672
11726
|
}
|
|
11673
|
-
async function runReleaseCycle(o, movedPast2) {
|
|
11727
|
+
async function runReleaseCycle(o, movedPast2, localRefSkips) {
|
|
11674
11728
|
const scoped = (msg) => o.scope ? `${o.scope}: ${msg}` : msg;
|
|
11675
11729
|
const tagPattern = o.repo.release.tagPattern ?? "v*";
|
|
11676
11730
|
o.emit.enter("release");
|
|
@@ -11682,8 +11736,9 @@ async function runReleaseCycle(o, movedPast2) {
|
|
|
11682
11736
|
if (t.status !== o.contract.statuses.verified) continue;
|
|
11683
11737
|
materializeBranchForTicket(o.cwd, o.repo, t, void 0, (problem) => {
|
|
11684
11738
|
skipped.add(t.id);
|
|
11739
|
+
localRefSkips.push({ ticket: t, problem });
|
|
11685
11740
|
o.emit.error(`not releasing ${t.issue_id} this cycle: ${problem}`, { ticket: t.issue_id });
|
|
11686
|
-
});
|
|
11741
|
+
}, (note) => o.emit.emit(`${t.issue_id}: ${note}`, { ticket: t.issue_id }));
|
|
11687
11742
|
}
|
|
11688
11743
|
if (skipped.size) releasable = o.tickets.filter((t) => !skipped.has(t.id));
|
|
11689
11744
|
}
|
|
@@ -11702,6 +11757,10 @@ async function runReleaseCycle(o, movedPast2) {
|
|
|
11702
11757
|
`not releasing ${t.issue_id}: local ${branch} at ${moved.tip.slice(0, 8)} is not on ${o.repo.branch.remote}/${branch} (${remoteTip2.slice(0, 8)}); not requeuing it`,
|
|
11703
11758
|
{ ticket: t.issue_id }
|
|
11704
11759
|
);
|
|
11760
|
+
localRefSkips.push({
|
|
11761
|
+
ticket: t,
|
|
11762
|
+
problem: `local ${branch} at ${moved.tip.slice(0, 8)} is not on ${o.repo.branch.remote}/${branch} (${remoteTip2.slice(0, 8)})`
|
|
11763
|
+
});
|
|
11705
11764
|
continue;
|
|
11706
11765
|
}
|
|
11707
11766
|
movedPast2.push({ ticket: t, verified: moved.verified, head: moved.tip, branch: pushedBranchForTicket(o.repo, t) });
|
|
@@ -14692,7 +14751,15 @@ function planFix(f) {
|
|
|
14692
14751
|
const actions = [];
|
|
14693
14752
|
const notes = [];
|
|
14694
14753
|
for (const r of f.routes) {
|
|
14695
|
-
if (!r.enabled && sound(r))
|
|
14754
|
+
if (!r.enabled && sound(r)) {
|
|
14755
|
+
actions.push({ kind: "enable", route: r.route });
|
|
14756
|
+
const gaps = r.readiness ?? [];
|
|
14757
|
+
if (gaps.length) {
|
|
14758
|
+
notes.push(`${r.route}: ${gaps.length} release-readiness gap${gaps.length === 1 ? "" : "s"} (${gaps[0]}${gaps.length > 1 ? ", ..." : ""}) \u2014 the route will run, but releases stop at the first missing hook until it is added`);
|
|
14759
|
+
}
|
|
14760
|
+
} else if (!sound(r) && !r.enabled) {
|
|
14761
|
+
notes.push(`${r.route}: not enabled \u2014 ${r.hasRepos ? r.problems[0] : "no checkout is configured"}`);
|
|
14762
|
+
}
|
|
14696
14763
|
}
|
|
14697
14764
|
if (f.schedulerAbsent && f.routes.some(sound)) {
|
|
14698
14765
|
if (f.host === "windows") notes.push("scheduler support is not built for Windows yet \u2014 nothing to install; run `crew run` by hand");
|
|
@@ -14702,12 +14769,15 @@ function planFix(f) {
|
|
|
14702
14769
|
}
|
|
14703
14770
|
async function gatherHealth(c, check) {
|
|
14704
14771
|
let problems;
|
|
14772
|
+
let readiness = [];
|
|
14705
14773
|
try {
|
|
14706
|
-
|
|
14774
|
+
const r = await check();
|
|
14775
|
+
if (Array.isArray(r)) problems = r;
|
|
14776
|
+
else ({ problems, readiness } = r);
|
|
14707
14777
|
} catch (e) {
|
|
14708
14778
|
problems = [`checks could not run: ${e instanceof Error ? e.message : String(e)}`];
|
|
14709
14779
|
}
|
|
14710
|
-
return { route: c.route, enabled: c.enabled, hasRepos: c.hasRepos, problems };
|
|
14780
|
+
return { route: c.route, enabled: c.enabled, hasRepos: c.hasRepos, problems, readiness };
|
|
14711
14781
|
}
|
|
14712
14782
|
function routesInScope(routes, named) {
|
|
14713
14783
|
return named ? routes.filter((r) => r.route === named) : routes;
|
|
@@ -17039,6 +17109,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
17039
17109
|
async function routeProblems(c) {
|
|
17040
17110
|
const host = hostPlatform();
|
|
17041
17111
|
const problems = [];
|
|
17112
|
+
const readiness = [];
|
|
17042
17113
|
if (c.hostPassengers) {
|
|
17043
17114
|
if (!dockerAvailable()) problems.push("host passengers: docker is not available");
|
|
17044
17115
|
if (!sshKeygenAvailable()) problems.push("host passengers: ssh-keygen is not available");
|
|
@@ -17048,7 +17119,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
17048
17119
|
for (const r of await resolvedRepos(c)) {
|
|
17049
17120
|
if (!existsSync34(r.dir)) problems.push(`repo ${r.name}: ${r.dir} is missing on disk`);
|
|
17050
17121
|
if (!satisfies(host, r.config.platform)) problems.push(`repo ${r.name}: requires ${r.config.platform}, this host is ${host}`);
|
|
17051
|
-
|
|
17122
|
+
readiness.push(...validateEffective(r.config).map((p) => `repo ${r.name}: ${p}`));
|
|
17052
17123
|
}
|
|
17053
17124
|
try {
|
|
17054
17125
|
const ships = await tracker.shipRows();
|
|
@@ -17060,7 +17131,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
17060
17131
|
} catch (e) {
|
|
17061
17132
|
problems.push(`tracker unreachable: ${e instanceof Error ? e.message : String(e)}`);
|
|
17062
17133
|
}
|
|
17063
|
-
return problems;
|
|
17134
|
+
return { problems, readiness };
|
|
17064
17135
|
}
|
|
17065
17136
|
async function doctorFix(o) {
|
|
17066
17137
|
const routes = await Promise.all(routesInScope(cfg.routes, named).map((c) => gatherHealth({ route: c.route, enabled: c.enabled, hasRepos: reposOf(c).length > 0 }, () => routeProblems(c))));
|
|
@@ -17401,6 +17472,18 @@ ${changed2.map((c) => ` - ${c}
|
|
|
17401
17472
|
remit.warn(`could not note the red test gate on the board: ${e.message}`, { step: "merge" });
|
|
17402
17473
|
}
|
|
17403
17474
|
}
|
|
17475
|
+
if (outcome.localRefSkips?.length && !dryRun) {
|
|
17476
|
+
try {
|
|
17477
|
+
const reported = await tracker.events("local_ref_skip");
|
|
17478
|
+
const seat = releaseSeat(c);
|
|
17479
|
+
for (const s of outcome.localRefSkips) {
|
|
17480
|
+
if (localRefSkipReported(s.ticket, s.problem, reported)) continue;
|
|
17481
|
+
await tracker.postEvent(s.ticket.id, localRefSkipComment(cfg.ship.name, s.problem), seat, "local_ref_skip");
|
|
17482
|
+
}
|
|
17483
|
+
} catch (e) {
|
|
17484
|
+
remit.warn(`could not note the skipped release on the board: ${e.message}`, { step: "merge" });
|
|
17485
|
+
}
|
|
17486
|
+
}
|
|
17404
17487
|
if (outcome.externalClosures?.length) {
|
|
17405
17488
|
await applyExternalClosures(tracker, outcome.externalClosures, tracker.contract, remit, dryRun, target.dir);
|
|
17406
17489
|
}
|
|
@@ -17490,7 +17573,8 @@ ${changed2.map((c) => ` - ${c}
|
|
|
17490
17573
|
f.candidate.branch,
|
|
17491
17574
|
repo.branch.base,
|
|
17492
17575
|
f.paths,
|
|
17493
|
-
handedBack
|
|
17576
|
+
handedBack,
|
|
17577
|
+
outcome.merged.filter((m) => m.branch).map((m) => ({ key: m.ticket.issue_id, branch: m.branch }))
|
|
17494
17578
|
);
|
|
17495
17579
|
const r = await applyConflictBounce(tracker, bounce, tracker.contract, seat, remit, dryRun);
|
|
17496
17580
|
if (r.kind === "failed") {
|
|
@@ -19835,6 +19919,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
19835
19919
|
continue;
|
|
19836
19920
|
}
|
|
19837
19921
|
if (!s2.upstream) continue;
|
|
19922
|
+
if (w.branch !== r.config.branch.base && s2.upstream === `${r.config.branch.remote}/${r.config.branch.base}`) continue;
|
|
19838
19923
|
tracked++;
|
|
19839
19924
|
if (s2.canFastForward) {
|
|
19840
19925
|
acted++;
|
|
@@ -20334,6 +20419,9 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
20334
20419
|
// Additive (CREW-1379): `unsafe` means a ship must not cut a
|
|
20335
20420
|
// worktree or release from this base. `version` stays 1.
|
|
20336
20421
|
baseBranch: baseHealth(r),
|
|
20422
|
+
// Additive (CREW-2537): gaps that only matter once a ticket reaches
|
|
20423
|
+
// release (no deploy hook, no CI provider...). They never stop a poll.
|
|
20424
|
+
releaseReadiness: validateEffective(r.config),
|
|
20337
20425
|
// Additive (CREW-2051): worktrees the sweep refuses to remove.
|
|
20338
20426
|
heldWorktrees: openShipAttention(state).filter((i) => i.kind === "worktree_held" && i.held?.repo === r.name).map((i) => ({
|
|
20339
20427
|
path: i.held.path,
|
|
@@ -20543,7 +20631,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
20543
20631
|
);
|
|
20544
20632
|
}
|
|
20545
20633
|
for (const p2 of validateEffective(config)) {
|
|
20546
|
-
process.stdout.write(` - ${p2}
|
|
20634
|
+
process.stdout.write(` - release readiness: ${p2}
|
|
20547
20635
|
`);
|
|
20548
20636
|
}
|
|
20549
20637
|
const dh = doctorBaseReport(r, config.branch.base, baseHealth);
|
package/package.json
CHANGED