@tablation/crew 0.73.2 → 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 +29 -11
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -4239,7 +4239,7 @@ function describeComposition(c) {
|
|
|
4239
4239
|
|
|
4240
4240
|
${rows}
|
|
4241
4241
|
|
|
4242
|
-
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.`;
|
|
4243
4243
|
}
|
|
4244
4244
|
|
|
4245
4245
|
// src/shell.ts
|
|
@@ -10331,7 +10331,8 @@ var lastActedHandBack = (ticket, events) => {
|
|
|
10331
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];
|
|
10332
10332
|
};
|
|
10333
10333
|
var MAX_ABSORBED_HANDBACKS = 2;
|
|
10334
|
-
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() !== ""));
|
|
10335
10336
|
return {
|
|
10336
10337
|
ticket,
|
|
10337
10338
|
branch,
|
|
@@ -10341,7 +10342,8 @@ function planConflictBounce(cwd, ticket, branch, base, paths, events) {
|
|
|
10341
10342
|
priorBounces: priorBounces(ticket, events),
|
|
10342
10343
|
cwd,
|
|
10343
10344
|
baseSha: gitOk(cwd, ["rev-parse", "--verify", "-q", base]) || null,
|
|
10344
|
-
priorBaseSha: baseShaOf(lastActedHandBack(ticket, events) ?? { record_id: "", event_type: "" })
|
|
10345
|
+
priorBaseSha: baseShaOf(lastActedHandBack(ticket, events) ?? { record_id: "", event_type: "" }),
|
|
10346
|
+
siblings
|
|
10345
10347
|
};
|
|
10346
10348
|
}
|
|
10347
10349
|
function refreshBranch(worktree, base) {
|
|
@@ -10370,7 +10372,7 @@ function handBackComment(b, merged) {
|
|
|
10370
10372
|
|
|
10371
10373
|
**Back with the dev lane \u2014 this branch no longer merges.**
|
|
10372
10374
|
|
|
10373
|
-
\`${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.`}
|
|
10374
10376
|
|
|
10375
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.`}
|
|
10376
10378
|
|
|
@@ -14749,7 +14751,15 @@ function planFix(f) {
|
|
|
14749
14751
|
const actions = [];
|
|
14750
14752
|
const notes = [];
|
|
14751
14753
|
for (const r of f.routes) {
|
|
14752
|
-
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
|
+
}
|
|
14753
14763
|
}
|
|
14754
14764
|
if (f.schedulerAbsent && f.routes.some(sound)) {
|
|
14755
14765
|
if (f.host === "windows") notes.push("scheduler support is not built for Windows yet \u2014 nothing to install; run `crew run` by hand");
|
|
@@ -14759,12 +14769,15 @@ function planFix(f) {
|
|
|
14759
14769
|
}
|
|
14760
14770
|
async function gatherHealth(c, check) {
|
|
14761
14771
|
let problems;
|
|
14772
|
+
let readiness = [];
|
|
14762
14773
|
try {
|
|
14763
|
-
|
|
14774
|
+
const r = await check();
|
|
14775
|
+
if (Array.isArray(r)) problems = r;
|
|
14776
|
+
else ({ problems, readiness } = r);
|
|
14764
14777
|
} catch (e) {
|
|
14765
14778
|
problems = [`checks could not run: ${e instanceof Error ? e.message : String(e)}`];
|
|
14766
14779
|
}
|
|
14767
|
-
return { route: c.route, enabled: c.enabled, hasRepos: c.hasRepos, problems };
|
|
14780
|
+
return { route: c.route, enabled: c.enabled, hasRepos: c.hasRepos, problems, readiness };
|
|
14768
14781
|
}
|
|
14769
14782
|
function routesInScope(routes, named) {
|
|
14770
14783
|
return named ? routes.filter((r) => r.route === named) : routes;
|
|
@@ -17096,6 +17109,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
17096
17109
|
async function routeProblems(c) {
|
|
17097
17110
|
const host = hostPlatform();
|
|
17098
17111
|
const problems = [];
|
|
17112
|
+
const readiness = [];
|
|
17099
17113
|
if (c.hostPassengers) {
|
|
17100
17114
|
if (!dockerAvailable()) problems.push("host passengers: docker is not available");
|
|
17101
17115
|
if (!sshKeygenAvailable()) problems.push("host passengers: ssh-keygen is not available");
|
|
@@ -17105,7 +17119,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
17105
17119
|
for (const r of await resolvedRepos(c)) {
|
|
17106
17120
|
if (!existsSync34(r.dir)) problems.push(`repo ${r.name}: ${r.dir} is missing on disk`);
|
|
17107
17121
|
if (!satisfies(host, r.config.platform)) problems.push(`repo ${r.name}: requires ${r.config.platform}, this host is ${host}`);
|
|
17108
|
-
|
|
17122
|
+
readiness.push(...validateEffective(r.config).map((p) => `repo ${r.name}: ${p}`));
|
|
17109
17123
|
}
|
|
17110
17124
|
try {
|
|
17111
17125
|
const ships = await tracker.shipRows();
|
|
@@ -17117,7 +17131,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
17117
17131
|
} catch (e) {
|
|
17118
17132
|
problems.push(`tracker unreachable: ${e instanceof Error ? e.message : String(e)}`);
|
|
17119
17133
|
}
|
|
17120
|
-
return problems;
|
|
17134
|
+
return { problems, readiness };
|
|
17121
17135
|
}
|
|
17122
17136
|
async function doctorFix(o) {
|
|
17123
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))));
|
|
@@ -17559,7 +17573,8 @@ ${changed2.map((c) => ` - ${c}
|
|
|
17559
17573
|
f.candidate.branch,
|
|
17560
17574
|
repo.branch.base,
|
|
17561
17575
|
f.paths,
|
|
17562
|
-
handedBack
|
|
17576
|
+
handedBack,
|
|
17577
|
+
outcome.merged.filter((m) => m.branch).map((m) => ({ key: m.ticket.issue_id, branch: m.branch }))
|
|
17563
17578
|
);
|
|
17564
17579
|
const r = await applyConflictBounce(tracker, bounce, tracker.contract, seat, remit, dryRun);
|
|
17565
17580
|
if (r.kind === "failed") {
|
|
@@ -20404,6 +20419,9 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
20404
20419
|
// Additive (CREW-1379): `unsafe` means a ship must not cut a
|
|
20405
20420
|
// worktree or release from this base. `version` stays 1.
|
|
20406
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),
|
|
20407
20425
|
// Additive (CREW-2051): worktrees the sweep refuses to remove.
|
|
20408
20426
|
heldWorktrees: openShipAttention(state).filter((i) => i.kind === "worktree_held" && i.held?.repo === r.name).map((i) => ({
|
|
20409
20427
|
path: i.held.path,
|
|
@@ -20613,7 +20631,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
20613
20631
|
);
|
|
20614
20632
|
}
|
|
20615
20633
|
for (const p2 of validateEffective(config)) {
|
|
20616
|
-
process.stdout.write(` - ${p2}
|
|
20634
|
+
process.stdout.write(` - release readiness: ${p2}
|
|
20617
20635
|
`);
|
|
20618
20636
|
}
|
|
20619
20637
|
const dh = doctorBaseReport(r, config.branch.base, baseHealth);
|
package/package.json
CHANGED