@tablation/crew 0.45.2 → 0.45.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
CHANGED
|
@@ -4804,7 +4804,6 @@ function referenceKeys(t) {
|
|
|
4804
4804
|
}
|
|
4805
4805
|
var RESOLVED_STATUSES = new Set(DEFAULT_CONTRACT.statuses.resolved);
|
|
4806
4806
|
var encodeFilters = (f) => JSON.stringify(f);
|
|
4807
|
-
var encodeSort = (s) => JSON.stringify(s);
|
|
4808
4807
|
var Tracker = class {
|
|
4809
4808
|
// NB: explicit fields, not TypeScript parameter properties — node's
|
|
4810
4809
|
// --experimental-strip-types cannot transform those, and the test runner
|
|
@@ -4864,37 +4863,41 @@ var Tracker = class {
|
|
|
4864
4863
|
});
|
|
4865
4864
|
}
|
|
4866
4865
|
/**
|
|
4867
|
-
*
|
|
4868
|
-
*
|
|
4866
|
+
* The tickets behind a set of ticket numbers — the cleanup sweeps' input
|
|
4867
|
+
* (CREW-1593).
|
|
4869
4868
|
*
|
|
4870
|
-
* The
|
|
4871
|
-
*
|
|
4872
|
-
*
|
|
4873
|
-
*
|
|
4874
|
-
*
|
|
4869
|
+
* The sweeps used to fetch "every closed ticket, newest `updated_at` first,
|
|
4870
|
+
* capped at 500" and hope the tickets they cared about were in the page. They
|
|
4871
|
+
* were not once the board held more than 500 closed tickets, and a bulk
|
|
4872
|
+
* write gave 1457 of them one identical `updated_at`, so which ones made the
|
|
4873
|
+
* page was arbitrary and a deployed ticket's worktree/branch was silently
|
|
4874
|
+
* never swept. The caller now names the numbers it actually has a worktree
|
|
4875
|
+
* or remote branch for, so the request is bounded by those and not by how
|
|
4876
|
+
* many tickets are closed.
|
|
4875
4877
|
*
|
|
4876
|
-
*
|
|
4877
|
-
*
|
|
4878
|
-
*
|
|
4879
|
-
* `updatedAt` descending means a truncated page still holds the tickets
|
|
4880
|
-
* most likely to need their worktree swept — the ones that closed most
|
|
4881
|
-
* recently — rather than an arbitrary, possibly stale slice (ISSUE-403).
|
|
4878
|
+
* Matched on `issue_id`, in the raw, unpadded and 3-digit-padded spellings a
|
|
4879
|
+
* number has been stored under. `onTruncated` is told when a page came back
|
|
4880
|
+
* full, since anything past it would be missing without a trace.
|
|
4882
4881
|
*/
|
|
4883
|
-
async
|
|
4882
|
+
async ticketsByNumbers(nums, onTruncated) {
|
|
4884
4883
|
const c = this.contract;
|
|
4885
|
-
const
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
const
|
|
4891
|
-
|
|
4892
|
-
const
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4884
|
+
const keys = /* @__PURE__ */ new Set();
|
|
4885
|
+
for (const raw of nums) {
|
|
4886
|
+
const unpadded = String(Number.parseInt(raw, 10));
|
|
4887
|
+
for (const n of [raw, unpadded, unpadded.padStart(3, "0")]) keys.add(`ISSUE-${n}`);
|
|
4888
|
+
}
|
|
4889
|
+
const all = [...keys];
|
|
4890
|
+
const out = [];
|
|
4891
|
+
const limit = 500;
|
|
4892
|
+
for (let i = 0; i < all.length; i += 100) {
|
|
4893
|
+
const page = await this.client.records.list(this.models.issues, {
|
|
4894
|
+
filters: encodeFilters([{ columnName: c.columns.key, operator: "IN", value: all.slice(i, i + 100) }]),
|
|
4895
|
+
limit
|
|
4896
|
+
});
|
|
4897
|
+
if (page.length >= limit) onTruncated?.(limit);
|
|
4898
|
+
out.push(...page);
|
|
4899
|
+
}
|
|
4900
|
+
return out;
|
|
4898
4901
|
}
|
|
4899
4902
|
/**
|
|
4900
4903
|
* Tickets by id, for blockers that have already closed and so are absent
|
|
@@ -11098,6 +11101,14 @@ function planWorktreeSweep(target, tickets, contract) {
|
|
|
11098
11101
|
}
|
|
11099
11102
|
return actions;
|
|
11100
11103
|
}
|
|
11104
|
+
function worktreeTicketNumbers(target) {
|
|
11105
|
+
const nums = /* @__PURE__ */ new Set();
|
|
11106
|
+
for (const w of worktrees(target.dir)) {
|
|
11107
|
+
const m = /(?:^|-)(\d+)$/.exec(basename8(w.path));
|
|
11108
|
+
if (m) nums.add(m[1]);
|
|
11109
|
+
}
|
|
11110
|
+
return [...nums];
|
|
11111
|
+
}
|
|
11101
11112
|
async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin") {
|
|
11102
11113
|
let removed = 0;
|
|
11103
11114
|
let keptBranches = 0;
|
|
@@ -11156,6 +11167,19 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin")
|
|
|
11156
11167
|
}
|
|
11157
11168
|
return { removed, keptBranches };
|
|
11158
11169
|
}
|
|
11170
|
+
function remoteBranchTicketNumbers(cwd, remote, base) {
|
|
11171
|
+
if (!remoteConfigured(cwd, remote)) return [];
|
|
11172
|
+
const prefix = `refs/remotes/${remote}/`;
|
|
11173
|
+
const refs = (gitOk(cwd, ["for-each-ref", "--format=%(refname)", prefix]) ?? "").split("\n").filter(Boolean);
|
|
11174
|
+
const nums = /* @__PURE__ */ new Set();
|
|
11175
|
+
for (const ref of refs) {
|
|
11176
|
+
const name = ref.slice(prefix.length);
|
|
11177
|
+
if (name === "HEAD" || name === base) continue;
|
|
11178
|
+
const m = /^.+?-(\d+)(?:-.*)?$/.exec(name);
|
|
11179
|
+
if (m) nums.add(m[1]);
|
|
11180
|
+
}
|
|
11181
|
+
return [...nums];
|
|
11182
|
+
}
|
|
11159
11183
|
function planRemoteBranchCleanup(cwd, remote, base, tickets, contract) {
|
|
11160
11184
|
if (!remoteConfigured(cwd, remote)) return [];
|
|
11161
11185
|
const stems = deployedBranchStems(tickets, contract);
|
|
@@ -11188,6 +11212,14 @@ function sweepCandidatesExist(cwd, base) {
|
|
|
11188
11212
|
if (linked.some((w) => /(?:^|-)\d+$/.test(basename8(w.path)))) return true;
|
|
11189
11213
|
return localBranches(cwd).some((b) => b !== base && /-\d+(?:-.*)?$/.test(b));
|
|
11190
11214
|
}
|
|
11215
|
+
function localBranchTicketNumbers(cwd, base) {
|
|
11216
|
+
const nums = /* @__PURE__ */ new Set();
|
|
11217
|
+
for (const b of localBranches(cwd)) {
|
|
11218
|
+
const m = b === base ? null : /^.+?-(\d+)(?:-.*)?$/.exec(b);
|
|
11219
|
+
if (m) nums.add(m[1]);
|
|
11220
|
+
}
|
|
11221
|
+
return [...nums];
|
|
11222
|
+
}
|
|
11191
11223
|
function localBranches(cwd) {
|
|
11192
11224
|
return (gitOk(cwd, ["branch", "--list", "--format=%(refname:short)"]) ?? "").split("\n").filter(Boolean);
|
|
11193
11225
|
}
|
|
@@ -13017,10 +13049,14 @@ ${changed2.map((c) => ` - ${c}
|
|
|
13017
13049
|
async function sweepClosedTickets(c, target, branch, tracker, dryRun2, remit, idle = false) {
|
|
13018
13050
|
try {
|
|
13019
13051
|
if (idle && !sweepCandidatesExist(target.dir, branch.base)) return;
|
|
13020
|
-
const
|
|
13021
|
-
const
|
|
13022
|
-
|
|
13023
|
-
|
|
13052
|
+
const truncated = (limit) => remit.warn(`ticket lookup for the sweep hit its ${limit}-row limit; some tickets may be missed`, { step: "worktree" });
|
|
13053
|
+
const nums = /* @__PURE__ */ new Set([
|
|
13054
|
+
...worktreeTicketNumbers(target),
|
|
13055
|
+
...localBranchTicketNumbers(target.dir, branch.base),
|
|
13056
|
+
...remoteBranchTicketNumbers(target.dir, branch.remote, branch.base)
|
|
13057
|
+
]);
|
|
13058
|
+
const scoped = ticketsByRepo(c, await tracker.ticketsByNumbers([...nums], truncated)).byRepo.get(target.name) ?? [];
|
|
13059
|
+
const actions = planWorktreeSweep(target, scoped, tracker.contract);
|
|
13024
13060
|
if (actions.length) {
|
|
13025
13061
|
remit.enter("worktree");
|
|
13026
13062
|
const r = await applyWorktreeSweep(target.dir, actions, dryRun2, remit, branch.remote);
|
|
@@ -14709,15 +14745,15 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
14709
14745
|
}
|
|
14710
14746
|
if (!dryRun && orphans.length) await killGently(orphans.map((o) => o.pid));
|
|
14711
14747
|
const tracker = new Tracker(route, cfg.ship);
|
|
14712
|
-
const
|
|
14713
|
-
const { byRepo } = ticketsByRepo(route, terminal);
|
|
14714
|
-
const verifiedByRepo = ticketsByRepo(
|
|
14715
|
-
route,
|
|
14716
|
-
(await tracker.openTickets()).filter((t) => t.status === tracker.contract.statuses.verified)
|
|
14717
|
-
).byRepo;
|
|
14748
|
+
const truncated = (limit) => emit.warn(`ticket lookup for the sweep hit its ${limit}-row limit; some tickets may be missed`, { step: "worktree" });
|
|
14718
14749
|
for (const r of await resolvedRepos(route)) {
|
|
14719
|
-
const
|
|
14720
|
-
const
|
|
14750
|
+
const wtTickets = ticketsByRepo(route, await tracker.ticketsByNumbers(worktreeTicketNumbers(r), truncated)).byRepo.get(r.name) ?? [];
|
|
14751
|
+
const brTickets = ticketsByRepo(
|
|
14752
|
+
route,
|
|
14753
|
+
await tracker.ticketsByNumbers(remoteBranchTicketNumbers(r.dir, r.config.branch.remote, r.config.branch.base), truncated)
|
|
14754
|
+
).byRepo.get(r.name) ?? [];
|
|
14755
|
+
const actions = planWorktreeSweep(r, wtTickets, tracker.contract);
|
|
14756
|
+
const stale = planRemoteBranchCleanup(r.dir, r.config.branch.remote, r.config.branch.base, brTickets, tracker.contract);
|
|
14721
14757
|
if (stale.length) {
|
|
14722
14758
|
anything = true;
|
|
14723
14759
|
applyRemoteBranchCleanup(r.dir, r.config.branch.remote, stale, dryRun, emit);
|
package/package.json
CHANGED
|
@@ -45,6 +45,13 @@ request — a default client user agent is rejected before it reaches the API,
|
|
|
45
45
|
and the failure looks like a network problem. Do not guess column names; the
|
|
46
46
|
Environment section lists them for this workspace.
|
|
47
47
|
|
|
48
|
+
A write to a record can be refused by a rule on the table. The response is a
|
|
49
|
+
422 whose `message` says what is missing and whose `fieldErrors` names the
|
|
50
|
+
field. Do not retry the same write. If the missing value is yours to supply
|
|
51
|
+
(a resolution note when closing, a duplicate target), supply it and write
|
|
52
|
+
once more; otherwise leave the ticket where it is and say so in your
|
|
53
|
+
hand-off comment.
|
|
54
|
+
|
|
48
55
|
Your identity is the Crew row named for your seat in the `## Your crew`
|
|
49
56
|
roster at the top of this prompt. Use its id as the assignee and as the
|
|
50
57
|
comment author. Every agent and every person who touches this tracker has
|
|
@@ -46,6 +46,13 @@ request — a default client user agent is rejected before it reaches the API,
|
|
|
46
46
|
and the failure looks like a network problem. Do not guess column names; the
|
|
47
47
|
Environment section lists them for this workspace.
|
|
48
48
|
|
|
49
|
+
A write to a record can be refused by a rule on the table. The response is a
|
|
50
|
+
422 whose `message` says what is missing and whose `fieldErrors` names the
|
|
51
|
+
field. Do not retry the same write. If the missing value is yours to supply
|
|
52
|
+
(a resolution note when closing, a duplicate target), supply it and write
|
|
53
|
+
once more; otherwise leave the ticket where it is and say so in your
|
|
54
|
+
hand-off comment.
|
|
55
|
+
|
|
49
56
|
Your identity is the Crew row named for your seat in the `## Your crew`
|
|
50
57
|
roster at the top of this prompt. Use its id as the assignee and as the
|
|
51
58
|
comment author. Every agent and every person who touches this tracker has
|