@clipboard-health/groundcrew 4.52.9 → 4.52.11
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/commands/prStageSync.d.ts +31 -9
- package/dist/commands/prStageSync.d.ts.map +1 -1
- package/dist/commands/prStageSync.js +146 -65
- package/dist/lib/cmuxStatusFields.d.ts +9 -0
- package/dist/lib/cmuxStatusFields.d.ts.map +1 -1
- package/dist/lib/cmuxStatusFields.js +9 -0
- package/dist/lib/prStageRules.d.ts +50 -0
- package/dist/lib/prStageRules.d.ts.map +1 -1
- package/dist/lib/prStageRules.js +94 -0
- package/dist/lib/pullRequests.d.ts +54 -5
- package/dist/lib/pullRequests.d.ts.map +1 -1
- package/dist/lib/pullRequests.js +96 -6
- package/package.json +5 -6
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Per-tick sync that paints each tracked task's cmux sidebar with its ticket
|
|
3
|
-
* id, and — when `cmux.prStages.enabled` — its pull
|
|
3
|
+
* id, and — when `cmux.prStages.enabled` — its pull requests' review stages
|
|
4
4
|
* and gating labels too. Replaces the standalone `crew-pr-stages` bash
|
|
5
5
|
* poller: same stage/ticket derivation rules (`../lib/prStageRules.ts`),
|
|
6
|
-
* same status-key contract the installed sidebar reads, but PRs are
|
|
7
|
-
*
|
|
8
|
-
* (`
|
|
9
|
-
* detection.
|
|
6
|
+
* same status-key contract the installed sidebar reads, but a task's PRs are
|
|
7
|
+
* discovered from its worktree branch plus every branch stacked off it
|
|
8
|
+
* (`listPullRequestsForRepositoryOrThrow` + `selectTaskPullRequests`)
|
|
9
|
+
* instead of cmux's own single-branch PR detection. That repository-wide
|
|
10
|
+
* list is bounded and author-scoped (`--author @me`, 100 PRs), so a task
|
|
11
|
+
* whose PR falls outside it is recovered by a per-task exact-branch fallback
|
|
12
|
+
* (`findTaskPullRequestsForBranchOrThrow`) — see
|
|
13
|
+
* `resolveTaskPullRequestsByWorkspace` for the two-tier lookup.
|
|
14
|
+
*
|
|
15
|
+
* A task can have several shown PRs (an unsplit branch, or a stack of
|
|
16
|
+
* follow-up PRs chained by base branch). `crew_stage`/`crew_labels` describe
|
|
17
|
+
* only the single most urgent one (`selectDrivingPullRequest`) — the
|
|
18
|
+
* "stage-driving" PR a right-click label toggle acts on — while the
|
|
19
|
+
* `crew_prs` status carries every shown PR so the sidebar can render one row
|
|
20
|
+
* per PR. See `contrib/cmux/README.md` for the full status-key contract.
|
|
10
21
|
*
|
|
11
22
|
* `crew_ticket` is written for every matched cmux task workspace regardless
|
|
12
23
|
* of the `cmux.prStages.enabled` opt-in, since a custom sidebar may read it
|
|
@@ -25,13 +36,23 @@ import { type CmuxWorkspaceSummary } from "../lib/cmuxAdapter.ts";
|
|
|
25
36
|
import { type CmuxStatusWrite } from "../lib/cmuxStatusFields.ts";
|
|
26
37
|
import type { ResolvedConfig } from "../lib/config.ts";
|
|
27
38
|
import { type PullRequestDetail } from "../lib/pullRequestDetails.ts";
|
|
28
|
-
import { type
|
|
39
|
+
import { type TaskPullRequest } from "../lib/pullRequests.ts";
|
|
29
40
|
import type { WorktreeEntry } from "../lib/worktrees.ts";
|
|
30
|
-
|
|
41
|
+
/** One `gh pr list` call per repository per tick; matched tasks sharing a repository share its result. */
|
|
42
|
+
export type ListPullRequestsForRepository = (arguments_: {
|
|
43
|
+
cwd: string;
|
|
44
|
+
signal?: AbortSignal;
|
|
45
|
+
}) => Promise<readonly TaskPullRequest[]>;
|
|
46
|
+
/**
|
|
47
|
+
* Per-task fallback used only when a matched task's branch (and its stack)
|
|
48
|
+
* are absent from the repository-wide list — e.g. the task's PR is older
|
|
49
|
+
* than that list's 100-PR window. One call per such task, not per tick.
|
|
50
|
+
*/
|
|
51
|
+
export type FindPullRequestsForBranch = (arguments_: {
|
|
31
52
|
cwd: string;
|
|
32
53
|
branchName: string;
|
|
33
54
|
signal?: AbortSignal;
|
|
34
|
-
}) => Promise<readonly
|
|
55
|
+
}) => Promise<readonly TaskPullRequest[]>;
|
|
35
56
|
export type FetchPullRequestDetails = (arguments_: {
|
|
36
57
|
urls: readonly string[];
|
|
37
58
|
signal?: AbortSignal;
|
|
@@ -42,7 +63,8 @@ export type WriteCmuxStatus = (workspaceId: string, write: CmuxStatusWrite, sign
|
|
|
42
63
|
export type IsCmuxAdapterActive = (signal?: AbortSignal) => Promise<boolean>;
|
|
43
64
|
export interface PrStageSyncDeps {
|
|
44
65
|
config: ResolvedConfig;
|
|
45
|
-
|
|
66
|
+
listPullRequests: ListPullRequestsForRepository;
|
|
67
|
+
findPullRequestsForBranch: FindPullRequestsForBranch;
|
|
46
68
|
fetchPullRequestDetails: FetchPullRequestDetails;
|
|
47
69
|
listCmuxWorkspaces: ListCmuxWorkspaces;
|
|
48
70
|
readCmuxStatus: ReadCmuxStatus;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prStageSync.d.ts","sourceRoot":"","sources":["../../src/commands/prStageSync.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"prStageSync.d.ts","sourceRoot":"","sources":["../../src/commands/prStageSync.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Per-tick sync that paints each tracked task's cmux sidebar with its ticket
|
|
3
|
-
* id, and — when `cmux.prStages.enabled` — its pull
|
|
3
|
+
* id, and — when `cmux.prStages.enabled` — its pull requests' review stages
|
|
4
4
|
* and gating labels too. Replaces the standalone `crew-pr-stages` bash
|
|
5
5
|
* poller: same stage/ticket derivation rules (`../lib/prStageRules.ts`),
|
|
6
|
-
* same status-key contract the installed sidebar reads, but PRs are
|
|
7
|
-
*
|
|
8
|
-
* (`
|
|
9
|
-
* detection.
|
|
6
|
+
* same status-key contract the installed sidebar reads, but a task's PRs are
|
|
7
|
+
* discovered from its worktree branch plus every branch stacked off it
|
|
8
|
+
* (`listPullRequestsForRepositoryOrThrow` + `selectTaskPullRequests`)
|
|
9
|
+
* instead of cmux's own single-branch PR detection. That repository-wide
|
|
10
|
+
* list is bounded and author-scoped (`--author @me`, 100 PRs), so a task
|
|
11
|
+
* whose PR falls outside it is recovered by a per-task exact-branch fallback
|
|
12
|
+
* (`findTaskPullRequestsForBranchOrThrow`) — see
|
|
13
|
+
* `resolveTaskPullRequestsByWorkspace` for the two-tier lookup.
|
|
14
|
+
*
|
|
15
|
+
* A task can have several shown PRs (an unsplit branch, or a stack of
|
|
16
|
+
* follow-up PRs chained by base branch). `crew_stage`/`crew_labels` describe
|
|
17
|
+
* only the single most urgent one (`selectDrivingPullRequest`) — the
|
|
18
|
+
* "stage-driving" PR a right-click label toggle acts on — while the
|
|
19
|
+
* `crew_prs` status carries every shown PR so the sidebar can render one row
|
|
20
|
+
* per PR. See `contrib/cmux/README.md` for the full status-key contract.
|
|
10
21
|
*
|
|
11
22
|
* `crew_ticket` is written for every matched cmux task workspace regardless
|
|
12
23
|
* of the `cmux.prStages.enabled` opt-in, since a custom sidebar may read it
|
|
@@ -22,11 +33,11 @@
|
|
|
22
33
|
* workspace must never break the watch loop or starve the other steps.
|
|
23
34
|
*/
|
|
24
35
|
import { listCmuxWorkspaceSummaries } from "../lib/cmuxAdapter.js";
|
|
25
|
-
import { readCmuxStatus, TICKET_KEY, TICKET_PRIORITY, writeCmuxStatus, } from "../lib/cmuxStatusFields.js";
|
|
36
|
+
import { PULL_REQUESTS_KEY, PULL_REQUESTS_PRIORITY, readCmuxStatus, TICKET_KEY, TICKET_PRIORITY, writeCmuxStatus, } from "../lib/cmuxStatusFields.js";
|
|
26
37
|
import { detectHostCapabilities } from "../lib/host.js";
|
|
27
38
|
import { fetchPullRequestDetails } from "../lib/pullRequestDetails.js";
|
|
28
|
-
import {
|
|
29
|
-
import { deriveTicket, derivePrStage, managedLabelsField, } from "../lib/prStageRules.js";
|
|
39
|
+
import { findTaskPullRequestsForBranchOrThrow, listPullRequestsForRepositoryOrThrow, } from "../lib/pullRequests.js";
|
|
40
|
+
import { deriveTicket, derivePrStage, encodePrStatuses, managedLabelsField, PR_STAGE_URGENCY_ORDER, selectTaskPullRequests, } from "../lib/prStageRules.js";
|
|
30
41
|
import { debug, errorMessage, logEvent, readEnvironmentVariable } from "../lib/util.js";
|
|
31
42
|
import { resolveWorkspaceKind } from "../lib/workspaces.js";
|
|
32
43
|
import { effectiveBranchName } from "../lib/worktreeRunState.js";
|
|
@@ -70,13 +81,6 @@ function matchWorkspacesToTasks(workspaces, worktreeEntries) {
|
|
|
70
81
|
}
|
|
71
82
|
return matches;
|
|
72
83
|
}
|
|
73
|
-
/** One PR to track per workspace: prefer a live one, then whichever terminal state, else whatever gh returned first. */
|
|
74
|
-
function selectTrackedPullRequest(pullRequests) {
|
|
75
|
-
return (pullRequests.find((pr) => pr.state === "open") ??
|
|
76
|
-
pullRequests.find((pr) => pr.state === "merged") ??
|
|
77
|
-
pullRequests.find((pr) => pr.state === "closed") ??
|
|
78
|
-
pullRequests[0]);
|
|
79
|
-
}
|
|
80
84
|
function newCounters() {
|
|
81
85
|
return { written: 0, cleared: 0, skipped: 0 };
|
|
82
86
|
}
|
|
@@ -118,55 +122,68 @@ async function writeTicketField(arguments_) {
|
|
|
118
122
|
counters,
|
|
119
123
|
});
|
|
120
124
|
}
|
|
125
|
+
/** The single most urgent PR among a task's shown PRs, per `PR_STAGE_URGENCY_ORDER` — never empty, since `staged` is only built from a non-empty pull-request list. */
|
|
126
|
+
function selectDrivingPullRequest(staged) {
|
|
127
|
+
return staged.reduce((best, candidate) => PR_STAGE_URGENCY_ORDER.indexOf(candidate.stage) < PR_STAGE_URGENCY_ORDER.indexOf(best.stage)
|
|
128
|
+
? candidate
|
|
129
|
+
: best);
|
|
130
|
+
}
|
|
131
|
+
async function clearPullRequestFields(arguments_) {
|
|
132
|
+
const { workspaceId, current, deps, signal, counters } = arguments_;
|
|
133
|
+
for (const { key, priority } of [
|
|
134
|
+
{ key: STAGE_KEY, priority: STAGE_PRIORITY },
|
|
135
|
+
{ key: LABELS_KEY, priority: LABELS_PRIORITY },
|
|
136
|
+
{ key: PULL_REQUESTS_KEY, priority: PULL_REQUESTS_PRIORITY },
|
|
137
|
+
]) {
|
|
138
|
+
// oxlint-disable-next-line no-await-in-loop -- three related status writes for one workspace; sequential keeps them from interleaving with another workspace's
|
|
139
|
+
await applyField({
|
|
140
|
+
workspaceId,
|
|
141
|
+
key,
|
|
142
|
+
priority,
|
|
143
|
+
current: current.get(key) ?? "",
|
|
144
|
+
desired: "",
|
|
145
|
+
signal,
|
|
146
|
+
writeStatus: deps.writeCmuxStatus,
|
|
147
|
+
counters,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
121
151
|
/**
|
|
122
|
-
* Stage and
|
|
123
|
-
* PR; when discovery itself failed (network/auth error) or
|
|
124
|
-
*
|
|
125
|
-
* GraphQL batch),
|
|
126
|
-
* cleared.
|
|
152
|
+
* Stage, labels, and the PR list are cleared only when the task is confirmed
|
|
153
|
+
* to have no shown PR; when discovery itself failed (network/auth error) or
|
|
154
|
+
* a shown PR's detail couldn't be (re)confirmed this tick (a failed or
|
|
155
|
+
* partial GraphQL batch), all three are left exactly as they were — never
|
|
156
|
+
* guessed, never cleared.
|
|
127
157
|
*/
|
|
128
158
|
async function syncWorkspace(arguments_) {
|
|
129
|
-
const { match, labelNames,
|
|
159
|
+
const { match, labelNames, pullRequests, pullRequestLookupFailed, detailsByUrl, deps, signal, counters, } = arguments_;
|
|
130
160
|
const { workspace } = match;
|
|
131
161
|
const current = await deps.readCmuxStatus(workspace.id, signal);
|
|
132
162
|
await writeTicketField({ workspace, current, deps, signal, counters });
|
|
133
163
|
if (pullRequestLookupFailed) {
|
|
134
|
-
counters.skipped +=
|
|
164
|
+
counters.skipped += 3;
|
|
135
165
|
return;
|
|
136
166
|
}
|
|
137
|
-
if (
|
|
138
|
-
await
|
|
139
|
-
workspaceId: workspace.id,
|
|
140
|
-
key: STAGE_KEY,
|
|
141
|
-
priority: STAGE_PRIORITY,
|
|
142
|
-
current: current.get(STAGE_KEY) ?? "",
|
|
143
|
-
desired: "",
|
|
144
|
-
signal,
|
|
145
|
-
writeStatus: deps.writeCmuxStatus,
|
|
146
|
-
counters,
|
|
147
|
-
});
|
|
148
|
-
await applyField({
|
|
149
|
-
workspaceId: workspace.id,
|
|
150
|
-
key: LABELS_KEY,
|
|
151
|
-
priority: LABELS_PRIORITY,
|
|
152
|
-
current: current.get(LABELS_KEY) ?? "",
|
|
153
|
-
desired: "",
|
|
154
|
-
signal,
|
|
155
|
-
writeStatus: deps.writeCmuxStatus,
|
|
156
|
-
counters,
|
|
157
|
-
});
|
|
167
|
+
if (pullRequests.length === 0) {
|
|
168
|
+
await clearPullRequestFields({ workspaceId: workspace.id, current, deps, signal, counters });
|
|
158
169
|
return;
|
|
159
170
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
171
|
+
const staged = [];
|
|
172
|
+
for (const pullRequest of pullRequests) {
|
|
173
|
+
const detail = detailsByUrl.get(pullRequest.url);
|
|
174
|
+
if (detail === undefined) {
|
|
175
|
+
counters.skipped += 3;
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
staged.push({ pullRequest, detail, stage: derivePrStage(detail, labelNames) });
|
|
163
179
|
}
|
|
180
|
+
const driving = selectDrivingPullRequest(staged);
|
|
164
181
|
await applyField({
|
|
165
182
|
workspaceId: workspace.id,
|
|
166
183
|
key: STAGE_KEY,
|
|
167
184
|
priority: STAGE_PRIORITY,
|
|
168
185
|
current: current.get(STAGE_KEY) ?? "",
|
|
169
|
-
desired:
|
|
186
|
+
desired: driving.stage,
|
|
170
187
|
signal,
|
|
171
188
|
writeStatus: deps.writeCmuxStatus,
|
|
172
189
|
counters,
|
|
@@ -176,30 +193,93 @@ async function syncWorkspace(arguments_) {
|
|
|
176
193
|
key: LABELS_KEY,
|
|
177
194
|
priority: LABELS_PRIORITY,
|
|
178
195
|
current: current.get(LABELS_KEY) ?? "",
|
|
179
|
-
desired: managedLabelsField(detail.labels, labelNames),
|
|
196
|
+
desired: managedLabelsField(driving.detail.labels, labelNames),
|
|
197
|
+
signal,
|
|
198
|
+
writeStatus: deps.writeCmuxStatus,
|
|
199
|
+
counters,
|
|
200
|
+
});
|
|
201
|
+
await applyField({
|
|
202
|
+
workspaceId: workspace.id,
|
|
203
|
+
key: PULL_REQUESTS_KEY,
|
|
204
|
+
priority: PULL_REQUESTS_PRIORITY,
|
|
205
|
+
current: current.get(PULL_REQUESTS_KEY) ?? "",
|
|
206
|
+
desired: encodePrStatuses(staged.map((entry) => ({
|
|
207
|
+
number: entry.pullRequest.number,
|
|
208
|
+
stage: entry.stage,
|
|
209
|
+
url: entry.pullRequest.url,
|
|
210
|
+
}))),
|
|
180
211
|
signal,
|
|
181
212
|
writeStatus: deps.writeCmuxStatus,
|
|
182
213
|
counters,
|
|
183
214
|
});
|
|
184
215
|
}
|
|
185
|
-
|
|
186
|
-
|
|
216
|
+
/**
|
|
217
|
+
* One `gh pr list` call per distinct repository among the matched
|
|
218
|
+
* workspaces, not one per workspace: every matched task sharing a
|
|
219
|
+
* repository reuses that single repository-wide list, filtered client-side
|
|
220
|
+
* (`selectTaskPullRequests`) down to each task's own branch and its stack.
|
|
221
|
+
*
|
|
222
|
+
* That repository-wide list is bounded (100 PRs, `--author @me`), so a task
|
|
223
|
+
* whose PR is older than the window — or for any other reason absent from
|
|
224
|
+
* it — would otherwise read as "no PR" and get its stage/labels/PR list
|
|
225
|
+
* cleared. When a matched task's filtered result is empty, this falls back
|
|
226
|
+
* to one exact-branch `gh pr list --head` lookup for that task alone
|
|
227
|
+
* (`findPullRequestsForBranch`), the same lookup `pr-stage-sync` used before
|
|
228
|
+
* stack discovery existed. A fallback failure is treated exactly like a
|
|
229
|
+
* repository-list failure — the workspace is marked failed so its fields are
|
|
230
|
+
* left untouched rather than cleared — never silently treated as "no PR".
|
|
231
|
+
*/
|
|
232
|
+
async function resolveTaskPullRequestsByWorkspace(arguments_) {
|
|
233
|
+
const { matches, config, listPullRequests, findPullRequestsForBranch, signal } = arguments_;
|
|
187
234
|
const byWorkspace = new Map();
|
|
188
235
|
const failedWorkspaceIds = new Set();
|
|
236
|
+
const representativeByRepository = new Map();
|
|
237
|
+
for (const match of matches) {
|
|
238
|
+
if (!representativeByRepository.has(match.entry.repository)) {
|
|
239
|
+
representativeByRepository.set(match.entry.repository, match);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
const pullRequestsByRepository = new Map();
|
|
243
|
+
const failedRepositories = new Set();
|
|
244
|
+
for (const [repository, representative] of representativeByRepository) {
|
|
245
|
+
try {
|
|
246
|
+
// oxlint-disable-next-line no-await-in-loop -- one gh lookup per repository, shared by every matched task in it
|
|
247
|
+
const pullRequests = await listPullRequests({
|
|
248
|
+
cwd: representative.entry.dir,
|
|
249
|
+
...(signal === undefined ? {} : { signal }),
|
|
250
|
+
});
|
|
251
|
+
pullRequestsByRepository.set(repository, pullRequests);
|
|
252
|
+
}
|
|
253
|
+
catch (error) {
|
|
254
|
+
debug(`pr-stage-sync: PR list failed for repository ${repository}: ${errorMessage(error)}`);
|
|
255
|
+
failedRepositories.add(repository);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
189
258
|
for (const match of matches) {
|
|
190
|
-
|
|
191
|
-
|
|
259
|
+
if (failedRepositories.has(match.entry.repository)) {
|
|
260
|
+
failedWorkspaceIds.add(match.workspace.id);
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
// oxlint-disable-next-line no-await-in-loop -- one branch resolution per matched workspace; mirrors the per-repository gh lookup above
|
|
264
|
+
const taskBranch = await effectiveBranchName({ config, entry: match.entry });
|
|
265
|
+
/* v8 ignore next @preserve -- every non-failed repository was populated in the loop above, since every match's repository is added to representativeByRepository first */
|
|
266
|
+
const repositoryPullRequests = pullRequestsByRepository.get(match.entry.repository) ?? [];
|
|
267
|
+
const discovered = selectTaskPullRequests(repositoryPullRequests, taskBranch);
|
|
268
|
+
if (discovered.length > 0) {
|
|
269
|
+
byWorkspace.set(match.workspace.id, discovered);
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
192
272
|
try {
|
|
193
|
-
// oxlint-disable-next-line no-await-in-loop --
|
|
194
|
-
const
|
|
273
|
+
// oxlint-disable-next-line no-await-in-loop -- one exact-branch fallback call per task whose PR fell outside the repository-wide window; sequential keeps it from interleaving with another task's status writes
|
|
274
|
+
const fallbackPullRequests = await findPullRequestsForBranch({
|
|
195
275
|
cwd: match.entry.dir,
|
|
196
|
-
branchName,
|
|
276
|
+
branchName: taskBranch,
|
|
197
277
|
...(signal === undefined ? {} : { signal }),
|
|
198
278
|
});
|
|
199
|
-
byWorkspace.set(match.workspace.id,
|
|
279
|
+
byWorkspace.set(match.workspace.id, selectTaskPullRequests(fallbackPullRequests, taskBranch));
|
|
200
280
|
}
|
|
201
281
|
catch (error) {
|
|
202
|
-
debug(`pr-stage-sync: PR
|
|
282
|
+
debug(`pr-stage-sync: exact-branch PR fallback failed for ${match.entry.task}: ${errorMessage(error)}`);
|
|
203
283
|
failedWorkspaceIds.add(match.workspace.id);
|
|
204
284
|
}
|
|
205
285
|
}
|
|
@@ -294,10 +374,11 @@ export function createPrStageSync(deps) {
|
|
|
294
374
|
return;
|
|
295
375
|
}
|
|
296
376
|
const matches = matchWorkspacesToTasks(workspaces, worktreeEntries);
|
|
297
|
-
const { byWorkspace:
|
|
377
|
+
const { byWorkspace: pullRequestsByWorkspace, failedWorkspaceIds } = await resolveTaskPullRequestsByWorkspace({
|
|
298
378
|
matches,
|
|
299
379
|
config,
|
|
300
|
-
|
|
380
|
+
listPullRequests: deps.listPullRequests,
|
|
381
|
+
findPullRequestsForBranch: deps.findPullRequestsForBranch,
|
|
301
382
|
signal,
|
|
302
383
|
});
|
|
303
384
|
if (failedWorkspaceIds.size > 0) {
|
|
@@ -305,7 +386,7 @@ export function createPrStageSync(deps) {
|
|
|
305
386
|
reason = "pull_request_lookup_failed";
|
|
306
387
|
}
|
|
307
388
|
const urls = [
|
|
308
|
-
...new Set([...
|
|
389
|
+
...new Set([...pullRequestsByWorkspace.values()].flatMap((prs) => prs.map((pr) => pr.url))),
|
|
309
390
|
];
|
|
310
391
|
let details = new Map();
|
|
311
392
|
if (urls.length > 0) {
|
|
@@ -322,16 +403,15 @@ export function createPrStageSync(deps) {
|
|
|
322
403
|
}
|
|
323
404
|
}
|
|
324
405
|
for (const match of matches) {
|
|
325
|
-
const
|
|
326
|
-
const detail = pullRequest === undefined ? undefined : details.get(pullRequest.url);
|
|
406
|
+
const pullRequests = pullRequestsByWorkspace.get(match.workspace.id) ?? [];
|
|
327
407
|
try {
|
|
328
408
|
// oxlint-disable-next-line no-await-in-loop -- sequential per workspace so one workspace's status calls never interleave with another's
|
|
329
409
|
await syncWorkspace({
|
|
330
410
|
match,
|
|
331
411
|
labelNames: config.cmux.prStages.labels,
|
|
332
|
-
|
|
412
|
+
pullRequests,
|
|
333
413
|
pullRequestLookupFailed: failedWorkspaceIds.has(match.workspace.id),
|
|
334
|
-
|
|
414
|
+
detailsByUrl: details,
|
|
335
415
|
deps,
|
|
336
416
|
signal,
|
|
337
417
|
counters,
|
|
@@ -387,7 +467,8 @@ export function createPrStageSync(deps) {
|
|
|
387
467
|
export function createPrStageSyncDeps(config) {
|
|
388
468
|
return {
|
|
389
469
|
config,
|
|
390
|
-
|
|
470
|
+
listPullRequests: listPullRequestsForRepositoryOrThrow,
|
|
471
|
+
findPullRequestsForBranch: findTaskPullRequestsForBranchOrThrow,
|
|
391
472
|
fetchPullRequestDetails,
|
|
392
473
|
listCmuxWorkspaces: listCmuxWorkspaceSummaries,
|
|
393
474
|
readCmuxStatus,
|
|
@@ -14,6 +14,15 @@
|
|
|
14
14
|
*/
|
|
15
15
|
export declare const TICKET_KEY = "crew_ticket";
|
|
16
16
|
export declare const TICKET_PRIORITY = -11;
|
|
17
|
+
/**
|
|
18
|
+
* Every shown PR for the task, encoded by `prStageRules.ts#encodePrStatuses`
|
|
19
|
+
* so the cmux sidebar can render one line per PR without re-deriving stage
|
|
20
|
+
* from scratch per row. Written by `prStageSync.ts` alongside `crew_stage`
|
|
21
|
+
* (which carries just the single most urgent stage across these PRs) and
|
|
22
|
+
* `crew_labels` (which describes the same most-urgent, "stage-driving" PR).
|
|
23
|
+
*/
|
|
24
|
+
export declare const PULL_REQUESTS_KEY = "crew_prs";
|
|
25
|
+
export declare const PULL_REQUESTS_PRIORITY = -14;
|
|
17
26
|
export interface CmuxStatusWrite {
|
|
18
27
|
key: string;
|
|
19
28
|
priority: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cmuxStatusFields.d.ts","sourceRoot":"","sources":["../../src/lib/cmuxStatusFields.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH;;;;;GAKG;AACH,eAAO,MAAM,UAAU,gBAAgB,CAAC;AACxC,eAAO,MAAM,eAAe,MAAM,CAAC;AAEnC,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,iEAAiE;IACjE,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAkBxE;AAED,wBAAsB,cAAc,CAClC,WAAW,EAAE,MAAM,EACnB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAO9B;AAED,kEAAkE;AAClE,wBAAsB,eAAe,CACnC,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,eAAe,EACtB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,IAAI,CAAC,CAsBf"}
|
|
1
|
+
{"version":3,"file":"cmuxStatusFields.d.ts","sourceRoot":"","sources":["../../src/lib/cmuxStatusFields.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH;;;;;GAKG;AACH,eAAO,MAAM,UAAU,gBAAgB,CAAC;AACxC,eAAO,MAAM,eAAe,MAAM,CAAC;AAEnC;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,aAAa,CAAC;AAC5C,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAE1C,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,iEAAiE;IACjE,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAkBxE;AAED,wBAAsB,cAAc,CAClC,WAAW,EAAE,MAAM,EACnB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAO9B;AAED,kEAAkE;AAClE,wBAAsB,eAAe,CACnC,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,eAAe,EACtB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,IAAI,CAAC,CAsBf"}
|
|
@@ -15,6 +15,15 @@ import { runWorkspaceCommand } from "./workspaceAdapter.js";
|
|
|
15
15
|
*/
|
|
16
16
|
export const TICKET_KEY = "crew_ticket";
|
|
17
17
|
export const TICKET_PRIORITY = -11;
|
|
18
|
+
/**
|
|
19
|
+
* Every shown PR for the task, encoded by `prStageRules.ts#encodePrStatuses`
|
|
20
|
+
* so the cmux sidebar can render one line per PR without re-deriving stage
|
|
21
|
+
* from scratch per row. Written by `prStageSync.ts` alongside `crew_stage`
|
|
22
|
+
* (which carries just the single most urgent stage across these PRs) and
|
|
23
|
+
* `crew_labels` (which describes the same most-urgent, "stage-driving" PR).
|
|
24
|
+
*/
|
|
25
|
+
export const PULL_REQUESTS_KEY = "crew_prs";
|
|
26
|
+
export const PULL_REQUESTS_PRIORITY = -14;
|
|
18
27
|
/**
|
|
19
28
|
* Parses `cmux list-status`'s `key=value [attr=val ...]` lines into a
|
|
20
29
|
* key -> value map. A value is the token immediately after `=`, up to the
|
|
@@ -30,6 +30,56 @@ export interface PrStageLabelNames {
|
|
|
30
30
|
export declare function derivePrStage(input: PrStageInput, labelNames: PrStageLabelNames): PrStage;
|
|
31
31
|
/** The managed-label subset of a PR's labels, sorted and comma-joined — the `crew_labels` sidebar value. */
|
|
32
32
|
export declare function managedLabelsField(labels: readonly string[], labelNames: PrStageLabelNames): string;
|
|
33
|
+
/**
|
|
34
|
+
* Most urgent to least urgent, mirroring the sidebar's section order
|
|
35
|
+
* (`contrib/cmux/groundcrew.swift`) for the stages this module derives —
|
|
36
|
+
* `merged`/`closed` sit last since a shown closed PR only appears when the
|
|
37
|
+
* task has no open or merged PR, and a shown merged PR only appears
|
|
38
|
+
* alongside PRs this urgent or less. Defined once here so `prStageSync.ts`
|
|
39
|
+
* (picking the task's single most urgent stage across its shown PRs) and any
|
|
40
|
+
* other ranking need share the same order instead of re-deriving it.
|
|
41
|
+
*/
|
|
42
|
+
export declare const PR_STAGE_URGENCY_ORDER: readonly PrStage[];
|
|
43
|
+
/** The most urgent stage present in `stages`, per `PR_STAGE_URGENCY_ORDER`. */
|
|
44
|
+
export declare function mostUrgentStage(stages: readonly PrStage[]): PrStage | undefined;
|
|
45
|
+
export interface TaskPullRequestRef {
|
|
46
|
+
headRefName: string;
|
|
47
|
+
baseRefName: string;
|
|
48
|
+
}
|
|
49
|
+
/** A task's PRs: its exact branch, plus every branch stacked off it (`<taskBranch>-*`). */
|
|
50
|
+
export declare function isTaskBranch(headRefName: string, taskBranch: string): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Which of a task's matched pull requests the sidebar shows: every open or
|
|
53
|
+
* merged one, falling back to closed ones only when none are open or
|
|
54
|
+
* merged — a closed PR superseded by a later stack split is noise once the
|
|
55
|
+
* replacement stack exists, but is the only signal left once it's all that
|
|
56
|
+
* remains.
|
|
57
|
+
*/
|
|
58
|
+
export declare function selectShownPullRequests<T extends {
|
|
59
|
+
state: string;
|
|
60
|
+
}>(pullRequests: readonly T[]): T[];
|
|
61
|
+
/**
|
|
62
|
+
* Orders a task's shown PRs bottom-of-stack to top when they chain cleanly
|
|
63
|
+
* through baseRefName -> headRefName (each PR's base is either outside the
|
|
64
|
+
* shown set — the bottom — or exactly one other shown PR's head, and no two
|
|
65
|
+
* PRs share a base); falls back to ascending PR number for anything that
|
|
66
|
+
* doesn't form that single linear chain (a fork, a gap, or a cycle).
|
|
67
|
+
*/
|
|
68
|
+
export declare function orderPullRequestStack<T extends TaskPullRequestRef & {
|
|
69
|
+
number: number;
|
|
70
|
+
}>(pullRequests: readonly T[]): T[];
|
|
71
|
+
/** Shown PRs for a task, in display order: `selectShownPullRequests` then `orderPullRequestStack`. */
|
|
72
|
+
export declare function selectTaskPullRequests<T extends TaskPullRequestRef & {
|
|
73
|
+
number: number;
|
|
74
|
+
state: string;
|
|
75
|
+
}>(pullRequests: readonly T[], taskBranch: string): T[];
|
|
76
|
+
export interface PrStatusEntry {
|
|
77
|
+
number: number;
|
|
78
|
+
stage: PrStage;
|
|
79
|
+
url: string;
|
|
80
|
+
}
|
|
81
|
+
/** The `crew_prs` sidebar value: `<number>,<stage>,<url>` entries joined by `;`. */
|
|
82
|
+
export declare function encodePrStatuses(entries: readonly PrStatusEntry[]): string;
|
|
33
83
|
export interface TicketDerivationInput {
|
|
34
84
|
title?: string | null;
|
|
35
85
|
currentDirectory?: string | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prStageRules.d.ts","sourceRoot":"","sources":["../../src/lib/prStageRules.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,MAAM,yBAAyB,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAErE,MAAM,MAAM,yBAAyB,GAAG,UAAU,GAAG,mBAAmB,GAAG,iBAAiB,GAAG,IAAI,CAAC;AAEpG,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,yBAAyB,CAAC;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1B,cAAc,EAAE,yBAAyB,CAAC;IAC1C,2FAA2F;IAC3F,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,QAAA,MAAM,SAAS,YACb,QAAQ,EACR,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,eAAe,EACf,mBAAmB,EACnB,gBAAgB,EAChB,aAAa,CACL,CAAC;AAEX,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC;AAEjD,MAAM,WAAW,iBAAiB;IAChC,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;CAChB;AAKD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,iBAAiB,GAAG,OAAO,CA0BzF;AAED,4GAA4G;AAC5G,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,SAAS,MAAM,EAAE,EACzB,UAAU,EAAE,iBAAiB,GAC5B,MAAM,CAMR;AAmDD,MAAM,WAAW,qBAAqB;IACpC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED,4EAA4E;AAC5E,wBAAgB,YAAY,CAAC,KAAK,EAAE,qBAAqB,GAAG,MAAM,GAAG,SAAS,CAE7E"}
|
|
1
|
+
{"version":3,"file":"prStageRules.d.ts","sourceRoot":"","sources":["../../src/lib/prStageRules.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,MAAM,yBAAyB,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAErE,MAAM,MAAM,yBAAyB,GAAG,UAAU,GAAG,mBAAmB,GAAG,iBAAiB,GAAG,IAAI,CAAC;AAEpG,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,yBAAyB,CAAC;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1B,cAAc,EAAE,yBAAyB,CAAC;IAC1C,2FAA2F;IAC3F,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,QAAA,MAAM,SAAS,YACb,QAAQ,EACR,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,eAAe,EACf,mBAAmB,EACnB,gBAAgB,EAChB,aAAa,CACL,CAAC;AAEX,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC;AAEjD,MAAM,WAAW,iBAAiB;IAChC,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;CAChB;AAKD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,iBAAiB,GAAG,OAAO,CA0BzF;AAED,4GAA4G;AAC5G,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,SAAS,MAAM,EAAE,EACzB,UAAU,EAAE,iBAAiB,GAC5B,MAAM,CAMR;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB,EAAE,SAAS,OAAO,EAUpD,CAAC;AAEF,+EAA+E;AAC/E,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,OAAO,EAAE,GAAG,OAAO,GAAG,SAAS,CAE/E;AAED,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,2FAA2F;AAC3F,wBAAgB,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAE7E;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,CAAC,SAAS;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,EACjE,YAAY,EAAE,SAAS,CAAC,EAAE,GACzB,CAAC,EAAE,CAML;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,SAAS,kBAAkB,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,EACrF,YAAY,EAAE,SAAS,CAAC,EAAE,GACzB,CAAC,EAAE,CAiCL;AAED,sGAAsG;AACtG,wBAAgB,sBAAsB,CACpC,CAAC,SAAS,kBAAkB,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EAChE,YAAY,EAAE,SAAS,CAAC,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,CAAC,EAAE,CAGrD;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,OAAO,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;CACb;AAKD,oFAAoF;AACpF,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,SAAS,aAAa,EAAE,GAAG,MAAM,CAI1E;AAmDD,MAAM,WAAW,qBAAqB;IACpC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED,4EAA4E;AAC5E,wBAAgB,YAAY,CAAC,KAAK,EAAE,qBAAqB,GAAG,MAAM,GAAG,SAAS,CAE7E"}
|
package/dist/lib/prStageRules.js
CHANGED
|
@@ -59,6 +59,100 @@ export function managedLabelsField(labels, labelNames) {
|
|
|
59
59
|
.toSorted((a, b) => a.localeCompare(b))
|
|
60
60
|
.join(",");
|
|
61
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Most urgent to least urgent, mirroring the sidebar's section order
|
|
64
|
+
* (`contrib/cmux/groundcrew.swift`) for the stages this module derives —
|
|
65
|
+
* `merged`/`closed` sit last since a shown closed PR only appears when the
|
|
66
|
+
* task has no open or merged PR, and a shown merged PR only appears
|
|
67
|
+
* alongside PRs this urgent or less. Defined once here so `prStageSync.ts`
|
|
68
|
+
* (picking the task's single most urgent stage across its shown PRs) and any
|
|
69
|
+
* other ranking need share the same order instead of re-deriving it.
|
|
70
|
+
*/
|
|
71
|
+
export const PR_STAGE_URGENCY_ORDER = [
|
|
72
|
+
"my_review",
|
|
73
|
+
"ci_failing",
|
|
74
|
+
"changes_requested",
|
|
75
|
+
"needs_testing",
|
|
76
|
+
"ready_to_merge",
|
|
77
|
+
"ci_running",
|
|
78
|
+
"peer_review",
|
|
79
|
+
"merged",
|
|
80
|
+
"closed",
|
|
81
|
+
];
|
|
82
|
+
/** The most urgent stage present in `stages`, per `PR_STAGE_URGENCY_ORDER`. */
|
|
83
|
+
export function mostUrgentStage(stages) {
|
|
84
|
+
return PR_STAGE_URGENCY_ORDER.find((stage) => stages.includes(stage));
|
|
85
|
+
}
|
|
86
|
+
/** A task's PRs: its exact branch, plus every branch stacked off it (`<taskBranch>-*`). */
|
|
87
|
+
export function isTaskBranch(headRefName, taskBranch) {
|
|
88
|
+
return headRefName === taskBranch || headRefName.startsWith(`${taskBranch}-`);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Which of a task's matched pull requests the sidebar shows: every open or
|
|
92
|
+
* merged one, falling back to closed ones only when none are open or
|
|
93
|
+
* merged — a closed PR superseded by a later stack split is noise once the
|
|
94
|
+
* replacement stack exists, but is the only signal left once it's all that
|
|
95
|
+
* remains.
|
|
96
|
+
*/
|
|
97
|
+
export function selectShownPullRequests(pullRequests) {
|
|
98
|
+
const openOrMerged = pullRequests.filter((pr) => pr.state === "open" || pr.state === "merged");
|
|
99
|
+
if (openOrMerged.length > 0) {
|
|
100
|
+
return openOrMerged;
|
|
101
|
+
}
|
|
102
|
+
return pullRequests.filter((pr) => pr.state === "closed");
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Orders a task's shown PRs bottom-of-stack to top when they chain cleanly
|
|
106
|
+
* through baseRefName -> headRefName (each PR's base is either outside the
|
|
107
|
+
* shown set — the bottom — or exactly one other shown PR's head, and no two
|
|
108
|
+
* PRs share a base); falls back to ascending PR number for anything that
|
|
109
|
+
* doesn't form that single linear chain (a fork, a gap, or a cycle).
|
|
110
|
+
*/
|
|
111
|
+
export function orderPullRequestStack(pullRequests) {
|
|
112
|
+
if (pullRequests.length <= 1) {
|
|
113
|
+
return [...pullRequests];
|
|
114
|
+
}
|
|
115
|
+
const byHead = new Map(pullRequests.map((pr) => [pr.headRefName, pr]));
|
|
116
|
+
const childByParentHead = new Map();
|
|
117
|
+
const roots = [];
|
|
118
|
+
for (const pr of pullRequests) {
|
|
119
|
+
const parent = byHead.get(pr.baseRefName);
|
|
120
|
+
if (parent === undefined) {
|
|
121
|
+
roots.push(pr);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (childByParentHead.has(parent.headRefName)) {
|
|
125
|
+
return pullRequests.toSorted((a, b) => a.number - b.number);
|
|
126
|
+
}
|
|
127
|
+
childByParentHead.set(parent.headRefName, pr);
|
|
128
|
+
}
|
|
129
|
+
if (roots.length !== 1) {
|
|
130
|
+
return pullRequests.toSorted((a, b) => a.number - b.number);
|
|
131
|
+
}
|
|
132
|
+
const ordered = [];
|
|
133
|
+
let current = roots[0];
|
|
134
|
+
while (current !== undefined) {
|
|
135
|
+
ordered.push(current);
|
|
136
|
+
current = childByParentHead.get(current.headRefName);
|
|
137
|
+
}
|
|
138
|
+
if (ordered.length !== pullRequests.length) {
|
|
139
|
+
return pullRequests.toSorted((a, b) => a.number - b.number);
|
|
140
|
+
}
|
|
141
|
+
return ordered;
|
|
142
|
+
}
|
|
143
|
+
/** Shown PRs for a task, in display order: `selectShownPullRequests` then `orderPullRequestStack`. */
|
|
144
|
+
export function selectTaskPullRequests(pullRequests, taskBranch) {
|
|
145
|
+
const matched = pullRequests.filter((pr) => isTaskBranch(pr.headRefName, taskBranch));
|
|
146
|
+
return orderPullRequestStack(selectShownPullRequests(matched));
|
|
147
|
+
}
|
|
148
|
+
const PR_ENTRY_SEPARATOR = ";";
|
|
149
|
+
const PR_FIELD_SEPARATOR = ",";
|
|
150
|
+
/** The `crew_prs` sidebar value: `<number>,<stage>,<url>` entries joined by `;`. */
|
|
151
|
+
export function encodePrStatuses(entries) {
|
|
152
|
+
return entries
|
|
153
|
+
.map((entry) => [entry.number, entry.stage, entry.url].join(PR_FIELD_SEPARATOR))
|
|
154
|
+
.join(PR_ENTRY_SEPARATOR);
|
|
155
|
+
}
|
|
62
156
|
const TICKET_PREFIX_RE = /^[A-Za-z]+$/;
|
|
63
157
|
const TICKET_NUMBER_RE = /^[0-9]+$/;
|
|
64
158
|
const TICKET_PREFIX_MAX_LENGTH = 5;
|
|
@@ -59,11 +59,60 @@ interface ResolvePullRequestArgs {
|
|
|
59
59
|
export declare function resolvePullRequest(arguments_: ResolvePullRequestArgs): Promise<ResolvedPullRequest>;
|
|
60
60
|
export declare function findPullRequestsForBranch(arguments_: LookupArgs): Promise<readonly PullRequestSummary[]>;
|
|
61
61
|
/**
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
* branch
|
|
65
|
-
* `
|
|
62
|
+
* A repository's full open/merged/closed PR list, with the head/base branch
|
|
63
|
+
* names `pr-stage-sync` needs for stack discovery (a task's PRs are its exact
|
|
64
|
+
* branch plus everything stacked off it) and stack ordering. Unlike
|
|
65
|
+
* `findPullRequestsForBranch`, this isn't filtered server-side by `--head` —
|
|
66
|
+
* `gh` has no "head starts with" filter — so the task-branch match happens
|
|
67
|
+
* client-side (`prStageRules.ts#selectTaskPullRequests`) against one list
|
|
68
|
+
* fetched per repository per tick, not one `gh` call per task.
|
|
66
69
|
*/
|
|
67
|
-
export
|
|
70
|
+
export interface TaskPullRequest {
|
|
71
|
+
url: string;
|
|
72
|
+
number: number;
|
|
73
|
+
/** Lowercased lifecycle: "open" | "merged" | "closed". */
|
|
74
|
+
state: string;
|
|
75
|
+
title: string;
|
|
76
|
+
headRefName: string;
|
|
77
|
+
baseRefName: string;
|
|
78
|
+
}
|
|
79
|
+
interface ListPullRequestsForRepositoryArgs {
|
|
80
|
+
/** Any worktree dir for the repository; `gh` resolves the GitHub repo from its git remote. */
|
|
81
|
+
cwd: string;
|
|
82
|
+
signal?: AbortSignal;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Lists every open/merged/closed PR for the repository `cwd` belongs to, in
|
|
86
|
+
* one `gh pr list` call. Lets failures propagate — `pr-stage-sync` needs to
|
|
87
|
+
* tell "the repository truly has no matching PR" apart from "couldn't
|
|
88
|
+
* confirm this tick" so a flaky `gh` call never clears a saved stage.
|
|
89
|
+
*
|
|
90
|
+
* Scoped to `--author @me`: groundcrew runs under the operator's own `gh`
|
|
91
|
+
* identity, which is who every agent-created PR is authored by, so this
|
|
92
|
+
* keeps the 100-PR window to PRs that can plausibly be a tracked task's
|
|
93
|
+
* rather than spending it on the rest of the repository's traffic. A task
|
|
94
|
+
* whose PR still falls outside that window — or isn't under the operator's
|
|
95
|
+
* identity for some other reason — is recovered by the exact-branch fallback
|
|
96
|
+
* in `findTaskPullRequestsForBranchOrThrow`, not by widening this call.
|
|
97
|
+
*/
|
|
98
|
+
export declare function listPullRequestsForRepositoryOrThrow(arguments_: ListPullRequestsForRepositoryArgs): Promise<readonly TaskPullRequest[]>;
|
|
99
|
+
interface FindTaskPullRequestsForBranchArgs {
|
|
100
|
+
/** Worktree directory; `gh` resolves the GitHub repo from its git remote. */
|
|
101
|
+
cwd: string;
|
|
102
|
+
/** Exact head branch name to filter PRs by (no prefix/stack matching — that needs the repository-wide list). */
|
|
103
|
+
branchName: string;
|
|
104
|
+
signal?: AbortSignal;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Exact-branch fallback for when a task's PR fell outside the repository-wide
|
|
108
|
+
* list's 100-PR window (`listPullRequestsForRepositoryOrThrow`): the same
|
|
109
|
+
* `gh pr list --head <branch>` lookup `pr-stage-sync` used before stack
|
|
110
|
+
* discovery existed, reshaped to the `TaskPullRequest` fields so its result
|
|
111
|
+
* composes with `prStageRules.ts#selectTaskPullRequests` like the
|
|
112
|
+
* repository-wide list does. Lets failures propagate, same as
|
|
113
|
+
* `listPullRequestsForRepositoryOrThrow` and for the same reason: a flaky
|
|
114
|
+
* `gh` call must read as "couldn't confirm this tick", not "no PR".
|
|
115
|
+
*/
|
|
116
|
+
export declare function findTaskPullRequestsForBranchOrThrow(arguments_: FindTaskPullRequestsForBranchArgs): Promise<readonly TaskPullRequest[]>;
|
|
68
117
|
export {};
|
|
69
118
|
//# sourceMappingURL=pullRequests.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pullRequests.d.ts","sourceRoot":"","sources":["../../src/lib/pullRequests.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,0DAA0D;IAC1D,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AASD,UAAU,UAAU;IAClB,6EAA6E;IAC7E,GAAG,EAAE,MAAM,CAAC;IACZ,oCAAoC;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAgDD,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,6DAA6D;IAC7D,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,0DAA0D;IAC1D,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,iBAAiB,EAAE,OAAO,CAAC;CAC5B;AAED,UAAU,sBAAsB;IAC9B,yFAAyF;IACzF,OAAO,EAAE,MAAM,CAAC;IAChB,iDAAiD;IACjD,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AA2BD;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CACtC,UAAU,EAAE,sBAAsB,GACjC,OAAO,CAAC,mBAAmB,CAAC,CAsC9B;AA0BD,wBAAsB,yBAAyB,CAC7C,UAAU,EAAE,UAAU,GACrB,OAAO,CAAC,SAAS,kBAAkB,EAAE,CAAC,CAWxC;AAED
|
|
1
|
+
{"version":3,"file":"pullRequests.d.ts","sourceRoot":"","sources":["../../src/lib/pullRequests.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,0DAA0D;IAC1D,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AASD,UAAU,UAAU;IAClB,6EAA6E;IAC7E,GAAG,EAAE,MAAM,CAAC;IACZ,oCAAoC;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAgDD,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,6DAA6D;IAC7D,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,0DAA0D;IAC1D,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,iBAAiB,EAAE,OAAO,CAAC;CAC5B;AAED,UAAU,sBAAsB;IAC9B,yFAAyF;IACzF,OAAO,EAAE,MAAM,CAAC;IAChB,iDAAiD;IACjD,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AA2BD;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CACtC,UAAU,EAAE,sBAAsB,GACjC,OAAO,CAAC,mBAAmB,CAAC,CAsC9B;AA0BD,wBAAsB,yBAAyB,CAC7C,UAAU,EAAE,UAAU,GACrB,OAAO,CAAC,SAAS,kBAAkB,EAAE,CAAC,CAWxC;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,0DAA0D;IAC1D,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;CACrB;AAwDD,UAAU,iCAAiC;IACzC,8FAA8F;IAC9F,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,oCAAoC,CACxD,UAAU,EAAE,iCAAiC,GAC5C,OAAO,CAAC,SAAS,eAAe,EAAE,CAAC,CAoBrC;AAED,UAAU,iCAAiC;IACzC,6EAA6E;IAC7E,GAAG,EAAE,MAAM,CAAC;IACZ,gHAAgH;IAChH,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED;;;;;;;;;GASG;AACH,wBAAsB,oCAAoC,CACxD,UAAU,EAAE,iCAAiC,GAC5C,OAAO,CAAC,SAAS,eAAe,EAAE,CAAC,CAoBrC"}
|
package/dist/lib/pullRequests.js
CHANGED
|
@@ -144,12 +144,102 @@ export async function findPullRequestsForBranch(arguments_) {
|
|
|
144
144
|
return [];
|
|
145
145
|
}
|
|
146
146
|
}
|
|
147
|
+
const GH_PR_LIST_TASK_DISCOVERY_LIMIT = 100;
|
|
148
|
+
function isRawTaskPullRequest(value) {
|
|
149
|
+
if (typeof value !== "object" || value === null) {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- narrowing untyped JSON.parse output to a record so we can probe its keys
|
|
153
|
+
const record = value;
|
|
154
|
+
return (typeof record["url"] === "string" &&
|
|
155
|
+
typeof record["number"] === "number" &&
|
|
156
|
+
typeof record["state"] === "string" &&
|
|
157
|
+
typeof record["title"] === "string" &&
|
|
158
|
+
typeof record["headRefName"] === "string" &&
|
|
159
|
+
typeof record["baseRefName"] === "string");
|
|
160
|
+
}
|
|
161
|
+
function parseTaskPullRequests(output) {
|
|
162
|
+
let parsed;
|
|
163
|
+
try {
|
|
164
|
+
parsed = JSON.parse(output);
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
return [];
|
|
168
|
+
}
|
|
169
|
+
if (!Array.isArray(parsed)) {
|
|
170
|
+
return [];
|
|
171
|
+
}
|
|
172
|
+
const summaries = [];
|
|
173
|
+
for (const entry of parsed) {
|
|
174
|
+
if (!isRawTaskPullRequest(entry)) {
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
summaries.push({
|
|
178
|
+
url: entry.url,
|
|
179
|
+
number: entry.number,
|
|
180
|
+
state: STATE_MAP[entry.state] ?? entry.state.toLowerCase(),
|
|
181
|
+
title: entry.title,
|
|
182
|
+
headRefName: entry.headRefName,
|
|
183
|
+
baseRefName: entry.baseRefName,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
return summaries;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Lists every open/merged/closed PR for the repository `cwd` belongs to, in
|
|
190
|
+
* one `gh pr list` call. Lets failures propagate — `pr-stage-sync` needs to
|
|
191
|
+
* tell "the repository truly has no matching PR" apart from "couldn't
|
|
192
|
+
* confirm this tick" so a flaky `gh` call never clears a saved stage.
|
|
193
|
+
*
|
|
194
|
+
* Scoped to `--author @me`: groundcrew runs under the operator's own `gh`
|
|
195
|
+
* identity, which is who every agent-created PR is authored by, so this
|
|
196
|
+
* keeps the 100-PR window to PRs that can plausibly be a tracked task's
|
|
197
|
+
* rather than spending it on the rest of the repository's traffic. A task
|
|
198
|
+
* whose PR still falls outside that window — or isn't under the operator's
|
|
199
|
+
* identity for some other reason — is recovered by the exact-branch fallback
|
|
200
|
+
* in `findTaskPullRequestsForBranchOrThrow`, not by widening this call.
|
|
201
|
+
*/
|
|
202
|
+
export async function listPullRequestsForRepositoryOrThrow(arguments_) {
|
|
203
|
+
const { cwd, signal } = arguments_;
|
|
204
|
+
const options = signal === undefined ? { cwd } : { cwd, signal };
|
|
205
|
+
const output = await runCommandAsync("gh", [
|
|
206
|
+
"pr",
|
|
207
|
+
"list",
|
|
208
|
+
"--state",
|
|
209
|
+
"all",
|
|
210
|
+
"--author",
|
|
211
|
+
"@me",
|
|
212
|
+
"--limit",
|
|
213
|
+
String(GH_PR_LIST_TASK_DISCOVERY_LIMIT),
|
|
214
|
+
"--json",
|
|
215
|
+
"url,number,state,title,headRefName,baseRefName",
|
|
216
|
+
], options);
|
|
217
|
+
return parseTaskPullRequests(output);
|
|
218
|
+
}
|
|
147
219
|
/**
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
220
|
+
* Exact-branch fallback for when a task's PR fell outside the repository-wide
|
|
221
|
+
* list's 100-PR window (`listPullRequestsForRepositoryOrThrow`): the same
|
|
222
|
+
* `gh pr list --head <branch>` lookup `pr-stage-sync` used before stack
|
|
223
|
+
* discovery existed, reshaped to the `TaskPullRequest` fields so its result
|
|
224
|
+
* composes with `prStageRules.ts#selectTaskPullRequests` like the
|
|
225
|
+
* repository-wide list does. Lets failures propagate, same as
|
|
226
|
+
* `listPullRequestsForRepositoryOrThrow` and for the same reason: a flaky
|
|
227
|
+
* `gh` call must read as "couldn't confirm this tick", not "no PR".
|
|
152
228
|
*/
|
|
153
|
-
export async function
|
|
154
|
-
|
|
229
|
+
export async function findTaskPullRequestsForBranchOrThrow(arguments_) {
|
|
230
|
+
const { cwd, branchName, signal } = arguments_;
|
|
231
|
+
const options = signal === undefined ? { cwd } : { cwd, signal };
|
|
232
|
+
const output = await runCommandAsync("gh", [
|
|
233
|
+
"pr",
|
|
234
|
+
"list",
|
|
235
|
+
"--head",
|
|
236
|
+
branchName,
|
|
237
|
+
"--state",
|
|
238
|
+
"all",
|
|
239
|
+
"--limit",
|
|
240
|
+
String(GH_PR_LIST_LIMIT),
|
|
241
|
+
"--json",
|
|
242
|
+
"url,number,state,title,headRefName,baseRefName",
|
|
243
|
+
], options);
|
|
244
|
+
return parseTaskPullRequests(output);
|
|
155
245
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clipboard-health/groundcrew",
|
|
3
|
-
"version": "4.52.
|
|
3
|
+
"version": "4.52.11",
|
|
4
4
|
"description": "Linear-driven orchestrator that launches AI coding agents in git worktrees, with workspace lifecycle and usage tracking.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent",
|
|
@@ -62,8 +62,8 @@
|
|
|
62
62
|
"lint": "oxlint --config config/oxlint.config.ts --deny-warnings --ignore-pattern 'v2/**'",
|
|
63
63
|
"markdown:lint": "markdownlint-cli2 --config config/markdownlint-cli2.jsonc",
|
|
64
64
|
"prepack": "node --run build",
|
|
65
|
-
"prepare": "husky",
|
|
66
|
-
"sync-ai-rules": "
|
|
65
|
+
"prepare": "husky && node --run sync-ai-rules",
|
|
66
|
+
"sync-ai-rules": "npm exec --yes --package=@clipboard-health/engineering-practices@latest -- engineering-practices-sync common || true",
|
|
67
67
|
"syncpack:lint": "syncpack lint --config config/syncpack.config.ts",
|
|
68
68
|
"test": "vitest run --coverage",
|
|
69
69
|
"verify": "node scripts/verifyAll.mts"
|
|
@@ -77,12 +77,11 @@
|
|
|
77
77
|
"zod": "4.6.5"
|
|
78
78
|
},
|
|
79
79
|
"devDependencies": {
|
|
80
|
-
"@clipboard-health/ai-rules": "2.50.17",
|
|
81
80
|
"@clipboard-health/oxlint-config": "1.14.3",
|
|
82
81
|
"@nx/js": "23.1.0",
|
|
83
82
|
"@tsconfig/node24": "24.0.5",
|
|
84
83
|
"@tsconfig/strictest": "2.0.8",
|
|
85
|
-
"@types/node": "26.6.
|
|
84
|
+
"@types/node": "26.6.4",
|
|
86
85
|
"@typescript/native-preview": "7.0.0-dev.20260707.2",
|
|
87
86
|
"@vitest/coverage-v8": "5.0.3",
|
|
88
87
|
"dependency-cruiser": "18.5.0",
|
|
@@ -96,7 +95,7 @@
|
|
|
96
95
|
"oxlint": "1.78.0",
|
|
97
96
|
"oxlint-tsgolint": "7.0.2003",
|
|
98
97
|
"syncpack": "15.3.3",
|
|
99
|
-
"vite": "8.3.
|
|
98
|
+
"vite": "8.3.2",
|
|
100
99
|
"vitest": "5.0.3"
|
|
101
100
|
},
|
|
102
101
|
"engines": {
|