@tablation/crew 0.45.0 → 0.45.2
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 +61 -24
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -11158,6 +11158,13 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin")
|
|
|
11158
11158
|
}
|
|
11159
11159
|
function planRemoteBranchCleanup(cwd, remote, base, tickets, contract) {
|
|
11160
11160
|
if (!remoteConfigured(cwd, remote)) return [];
|
|
11161
|
+
const stems = deployedBranchStems(tickets, contract);
|
|
11162
|
+
if (stems.size === 0) return [];
|
|
11163
|
+
const prefix = `refs/remotes/${remote}/`;
|
|
11164
|
+
const refs = (gitOk(cwd, ["for-each-ref", "--format=%(refname)", prefix]) ?? "").split("\n").filter(Boolean);
|
|
11165
|
+
return refs.map((ref) => ref.slice(prefix.length)).filter((name) => isTicketBranch(name, base, stems));
|
|
11166
|
+
}
|
|
11167
|
+
function deployedBranchStems(tickets, contract) {
|
|
11161
11168
|
const stems = /* @__PURE__ */ new Set();
|
|
11162
11169
|
for (const t of tickets) {
|
|
11163
11170
|
if (t.status !== contract.statuses.deployed) continue;
|
|
@@ -11169,17 +11176,40 @@ function planRemoteBranchCleanup(cwd, remote, base, tickets, contract) {
|
|
|
11169
11176
|
stems.add(`${p}-${unpadded}`);
|
|
11170
11177
|
}
|
|
11171
11178
|
}
|
|
11179
|
+
return stems;
|
|
11180
|
+
}
|
|
11181
|
+
function isTicketBranch(name, base, stems) {
|
|
11182
|
+
if (name === "HEAD" || name === base) return false;
|
|
11183
|
+
const m = /^(.+?-\d+)(?:-.*)?$/.exec(name);
|
|
11184
|
+
return !!m && stems.has(m[1]);
|
|
11185
|
+
}
|
|
11186
|
+
function sweepCandidatesExist(cwd, base) {
|
|
11187
|
+
const linked = worktrees(cwd).slice(1);
|
|
11188
|
+
if (linked.some((w) => /(?:^|-)\d+$/.test(basename8(w.path)))) return true;
|
|
11189
|
+
return localBranches(cwd).some((b) => b !== base && /-\d+(?:-.*)?$/.test(b));
|
|
11190
|
+
}
|
|
11191
|
+
function localBranches(cwd) {
|
|
11192
|
+
return (gitOk(cwd, ["branch", "--list", "--format=%(refname:short)"]) ?? "").split("\n").filter(Boolean);
|
|
11193
|
+
}
|
|
11194
|
+
function planLocalBranchCleanup(cwd, base, tickets, contract) {
|
|
11195
|
+
const stems = deployedBranchStems(tickets, contract);
|
|
11172
11196
|
if (stems.size === 0) return [];
|
|
11173
|
-
const
|
|
11174
|
-
|
|
11175
|
-
|
|
11176
|
-
|
|
11177
|
-
|
|
11178
|
-
|
|
11179
|
-
|
|
11180
|
-
|
|
11197
|
+
const checkedOut = new Set(worktrees(cwd).map((w) => w.branch).filter((b) => !!b));
|
|
11198
|
+
return localBranches(cwd).filter((b) => !checkedOut.has(b) && isTicketBranch(b, base, stems));
|
|
11199
|
+
}
|
|
11200
|
+
function applyLocalBranchCleanup(cwd, branches2, dryRun, log) {
|
|
11201
|
+
let deleted = 0;
|
|
11202
|
+
for (const b of branches2) {
|
|
11203
|
+
if (dryRun) {
|
|
11204
|
+
log.emit(`would delete local branch ${b} \u2014 its ticket is deployed`, { step: "worktree" });
|
|
11205
|
+
continue;
|
|
11206
|
+
}
|
|
11207
|
+
if (gitOk(cwd, ["branch", "-D", b]) !== null) {
|
|
11208
|
+
deleted++;
|
|
11209
|
+
log.emit(`deleted local branch ${b} \u2014 its ticket is deployed`, { step: "worktree" });
|
|
11210
|
+
} else log.warn(`could not delete local branch ${b} \u2014 leaving it for the next sweep`, { step: "worktree" });
|
|
11181
11211
|
}
|
|
11182
|
-
return
|
|
11212
|
+
return deleted;
|
|
11183
11213
|
}
|
|
11184
11214
|
function applyRemoteBranchCleanup(cwd, remote, branches2, dryRun, log) {
|
|
11185
11215
|
let deleted = 0;
|
|
@@ -12984,6 +13014,26 @@ ${changed2.map((c) => ` - ${c}
|
|
|
12984
13014
|
}
|
|
12985
13015
|
return `${who2}; ${lock2}`;
|
|
12986
13016
|
}
|
|
13017
|
+
async function sweepClosedTickets(c, target, branch, tracker, dryRun2, remit, idle = false) {
|
|
13018
|
+
try {
|
|
13019
|
+
if (idle && !sweepCandidatesExist(target.dir, branch.base)) return;
|
|
13020
|
+
const terminal = await tracker.terminalTickets();
|
|
13021
|
+
const verifiedNow = (await tracker.openTickets()).filter((t) => t.status === tracker.contract.statuses.verified);
|
|
13022
|
+
const scoped = ticketsByRepo(c, terminal).byRepo.get(target.name) ?? [];
|
|
13023
|
+
const actions = planWorktreeSweep(target, [...scoped, ...ticketsByRepo(c, verifiedNow).byRepo.get(target.name) ?? []], tracker.contract);
|
|
13024
|
+
if (actions.length) {
|
|
13025
|
+
remit.enter("worktree");
|
|
13026
|
+
const r = await applyWorktreeSweep(target.dir, actions, dryRun2, remit, branch.remote);
|
|
13027
|
+
remit.emit(`swept ${r.removed} worktree(s), kept ${r.keptBranches} branch(es)`, { step: "worktree" });
|
|
13028
|
+
}
|
|
13029
|
+
const orphaned = planLocalBranchCleanup(target.dir, branch.base, scoped, tracker.contract);
|
|
13030
|
+
if (orphaned.length) applyLocalBranchCleanup(target.dir, orphaned, dryRun2, remit);
|
|
13031
|
+
const stale = planRemoteBranchCleanup(target.dir, branch.remote, branch.base, scoped, tracker.contract);
|
|
13032
|
+
if (stale.length) applyRemoteBranchCleanup(target.dir, branch.remote, stale, dryRun2, remit);
|
|
13033
|
+
} catch (e) {
|
|
13034
|
+
remit.warn(`worktree sweep failed: ${e.message}`, { step: "worktree" });
|
|
13035
|
+
}
|
|
13036
|
+
}
|
|
12987
13037
|
const precheckTickets = /* @__PURE__ */ new Map();
|
|
12988
13038
|
async function releasePhase(c, target, opts = {}) {
|
|
12989
13039
|
const scope = releaseLockScope(c.route, target.name);
|
|
@@ -13053,6 +13103,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
13053
13103
|
unstamped
|
|
13054
13104
|
})) {
|
|
13055
13105
|
remit.emit(`${target.name}: nothing verified \u2014 not locking`, { step: "release" });
|
|
13106
|
+
await sweepClosedTickets(c, target, repo.branch, tracker, dryRun, remit, true);
|
|
13056
13107
|
return { scope, tests: "skipped", outcome: "skipped", detail: "nothing verified \u2014 not locking" };
|
|
13057
13108
|
}
|
|
13058
13109
|
}
|
|
@@ -13257,21 +13308,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
13257
13308
|
}
|
|
13258
13309
|
}
|
|
13259
13310
|
}
|
|
13260
|
-
|
|
13261
|
-
const terminal = await tracker.terminalTickets();
|
|
13262
|
-
const verifiedNow = (await tracker.openTickets()).filter((t) => t.status === tracker.contract.statuses.verified);
|
|
13263
|
-
const scoped = ticketsByRepo(c, terminal).byRepo.get(target.name) ?? [];
|
|
13264
|
-
const actions = planWorktreeSweep(target, [...scoped, ...ticketsByRepo(c, verifiedNow).byRepo.get(target.name) ?? []], tracker.contract);
|
|
13265
|
-
if (actions.length) {
|
|
13266
|
-
remit.enter("worktree");
|
|
13267
|
-
const r = await applyWorktreeSweep(target.dir, actions, dryRun, remit, repo.branch.remote);
|
|
13268
|
-
remit.emit(`swept ${r.removed} worktree(s), kept ${r.keptBranches} branch(es)`, { step: "worktree" });
|
|
13269
|
-
}
|
|
13270
|
-
const stale = planRemoteBranchCleanup(target.dir, repo.branch.remote, repo.branch.base, scoped, tracker.contract);
|
|
13271
|
-
if (stale.length) applyRemoteBranchCleanup(target.dir, repo.branch.remote, stale, dryRun, remit);
|
|
13272
|
-
} catch (e) {
|
|
13273
|
-
remit.warn(`worktree sweep failed: ${e.message}`, { step: "worktree" });
|
|
13274
|
-
}
|
|
13311
|
+
await sweepClosedTickets(c, target, repo.branch, tracker, dryRun, remit);
|
|
13275
13312
|
return summary;
|
|
13276
13313
|
} catch (e) {
|
|
13277
13314
|
remit.warn(`release phase failed: ${e.message}`, { step: "release" });
|
package/package.json
CHANGED