@clipboard-health/groundcrew 4.18.0 → 4.18.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/commands/reviewer.d.ts +18 -15
- package/dist/commands/reviewer.d.ts.map +1 -1
- package/dist/commands/reviewer.js +76 -42
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +53 -3
- package/dist/lib/adapters/shell/factory.d.ts +1 -0
- package/dist/lib/adapters/shell/factory.d.ts.map +1 -1
- package/dist/lib/adapters/shell/factory.js +13 -0
- package/dist/lib/adapters/shell/schema.d.ts +2 -0
- package/dist/lib/adapters/shell/schema.d.ts.map +1 -1
- package/dist/lib/adapters/shell/schema.js +2 -0
- package/dist/lib/board.d.ts +7 -1
- package/dist/lib/board.d.ts.map +1 -1
- package/dist/lib/board.js +10 -0
- package/dist/lib/pullRequests.d.ts +2 -0
- package/dist/lib/pullRequests.d.ts.map +1 -1
- package/dist/lib/pullRequests.js +21 -15
- package/dist/lib/ticketSource.d.ts +16 -0
- package/dist/lib/ticketSource.d.ts.map +1 -1
- package/docs/ticket-sources.md +11 -7
- package/package.json +1 -1
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Per-iteration scanner that advances
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* transition frees a dispatch slot (the slot math counts only in-progress)
|
|
6
|
-
* while leaving the worktree intact for review, since the cleaner only tears
|
|
7
|
-
* down `done` tickets.
|
|
2
|
+
* Per-iteration scanner that advances a ticket based on its worktree's pull
|
|
3
|
+
* request state. Sits between the cleaner and the dispatcher in each
|
|
4
|
+
* `orchestrate()` tick.
|
|
8
5
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
6
|
+
* - An **open** PR on an **in-progress** ticket → `markInReview`: frees a
|
|
7
|
+
* dispatch slot (slot math counts only in-progress) while leaving the
|
|
8
|
+
* worktree intact for review, since the cleaner only tears down `done`.
|
|
9
|
+
* - A **merged** PR (on an in-progress or in-review ticket) → `markDone`:
|
|
10
|
+
* the work has landed, so the ticket is terminal and the cleaner tears the
|
|
11
|
+
* worktree down on a later tick. `merged` never routes to `in-review`.
|
|
14
12
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
13
|
+
* Sources that don't implement `markDone` (e.g. Linear) return `unsupported`;
|
|
14
|
+
* the reviewer logs the skip and does nothing — there is no in-review
|
|
15
|
+
* fallback. (Linear's own GitHub integration moves merged issues to Done,
|
|
16
|
+
* which groundcrew observes via `fetch()`.)
|
|
17
|
+
*
|
|
18
|
+
* The write-back lands in the ticket source, not the in-memory `BoardState`,
|
|
19
|
+
* so the dispatcher in the SAME tick still sees prior state; the slot frees on
|
|
20
|
+
* the NEXT tick's `board.fetch()`. That one-tick latency is deliberate. One
|
|
21
|
+
* per `orchestrate()`; stateless across iterations. Mirrors `Cleaner`.
|
|
19
22
|
*/
|
|
20
23
|
import type { Board } from "../lib/board.ts";
|
|
21
24
|
import type { PullRequestSummary } from "../lib/pullRequests.ts";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reviewer.d.ts","sourceRoot":"","sources":["../../src/commands/reviewer.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"reviewer.d.ts","sourceRoot":"","sources":["../../src/commands/reviewer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EACL,KAAK,UAAU,EAIhB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,UAAU,EAAE;IAC1C,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,KAAK,OAAO,CAAC,SAAS,kBAAkB,EAAE,CAAC,CAAC;AAE7C,UAAU,YAAY;IACpB,KAAK,EAAE,KAAK,CAAC;IACb,gBAAgB,EAAE,gBAAgB,CAAC;CACpC;AAED,sEAAsE;AACtE,UAAU,eAAe;IACvB,KAAK,EAAE,UAAU,CAAC;IAClB,eAAe,EAAE,SAAS,aAAa,EAAE,CAAC;IAC1C,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,QAAQ;IACvB,OAAO,CAAC,UAAU,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACrD;AA+CD,wBAAgB,cAAc,CAAC,IAAI,EAAE,YAAY,GAAG,QAAQ,CAwH3D"}
|
|
@@ -1,28 +1,47 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Per-iteration scanner that advances
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* transition frees a dispatch slot (the slot math counts only in-progress)
|
|
6
|
-
* while leaving the worktree intact for review, since the cleaner only tears
|
|
7
|
-
* down `done` tickets.
|
|
2
|
+
* Per-iteration scanner that advances a ticket based on its worktree's pull
|
|
3
|
+
* request state. Sits between the cleaner and the dispatcher in each
|
|
4
|
+
* `orchestrate()` tick.
|
|
8
5
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
6
|
+
* - An **open** PR on an **in-progress** ticket → `markInReview`: frees a
|
|
7
|
+
* dispatch slot (slot math counts only in-progress) while leaving the
|
|
8
|
+
* worktree intact for review, since the cleaner only tears down `done`.
|
|
9
|
+
* - A **merged** PR (on an in-progress or in-review ticket) → `markDone`:
|
|
10
|
+
* the work has landed, so the ticket is terminal and the cleaner tears the
|
|
11
|
+
* worktree down on a later tick. `merged` never routes to `in-review`.
|
|
14
12
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
13
|
+
* Sources that don't implement `markDone` (e.g. Linear) return `unsupported`;
|
|
14
|
+
* the reviewer logs the skip and does nothing — there is no in-review
|
|
15
|
+
* fallback. (Linear's own GitHub integration moves merged issues to Done,
|
|
16
|
+
* which groundcrew observes via `fetch()`.)
|
|
17
|
+
*
|
|
18
|
+
* The write-back lands in the ticket source, not the in-memory `BoardState`,
|
|
19
|
+
* so the dispatcher in the SAME tick still sees prior state; the slot frees on
|
|
20
|
+
* the NEXT tick's `board.fetch()`. That one-tick latency is deliberate. One
|
|
21
|
+
* per `orchestrate()`; stateless across iterations. Mirrors `Cleaner`.
|
|
19
22
|
*/
|
|
20
|
-
import { naturalIdFromCanonical } from "../lib/ticketSource.js";
|
|
23
|
+
import { naturalIdFromCanonical, } from "../lib/ticketSource.js";
|
|
21
24
|
import { debug, errorMessage, log, logEvent } from "../lib/util.js";
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
// Maps a worktree's PRs to the transition its ticket should make. A merged PR
|
|
26
|
+
// means the work landed → done; an open PR on an in-progress ticket means it's
|
|
27
|
+
// up for review. `merged` wins over `open`. An open PR on an already in-review
|
|
28
|
+
// ticket is a no-op (returns undefined). Closed-only PRs are ignored.
|
|
29
|
+
function intendedTransition(pullRequests, status) {
|
|
30
|
+
if (pullRequests.some((pr) => pr.state === "merged")) {
|
|
31
|
+
return "done";
|
|
32
|
+
}
|
|
33
|
+
if (status === "in-progress" && pullRequests.some((pr) => pr.state === "open")) {
|
|
34
|
+
return "in-review";
|
|
35
|
+
}
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
// The PR to name in logs for a transition: the merged one for `done`, the open
|
|
39
|
+
// one for `in-review`. Guaranteed to exist because intendedTransition only
|
|
40
|
+
// returns a transition when a PR of the matching state is present.
|
|
41
|
+
function pullRequestForTransition(pullRequests, transition) {
|
|
42
|
+
const state = transition === "done" ? "merged" : "open";
|
|
43
|
+
// oxlint-disable-next-line typescript/no-non-null-assertion -- intendedTransition guarantees a PR of this state exists
|
|
44
|
+
return pullRequests.find((pr) => pr.state === state);
|
|
26
45
|
}
|
|
27
46
|
function matchingWorktreeEntries(arguments_) {
|
|
28
47
|
const { issue, worktreeEntries, ticket } = arguments_;
|
|
@@ -35,12 +54,12 @@ export function createReviewer(deps) {
|
|
|
35
54
|
const { board, findPullRequests } = deps;
|
|
36
55
|
async function runOnce(arguments_) {
|
|
37
56
|
const { state, worktreeEntries, dryRun, signal } = arguments_;
|
|
38
|
-
const
|
|
39
|
-
if (
|
|
57
|
+
const candidates = state.issues.filter((issue) => issue.status === "in-progress" || issue.status === "in-review");
|
|
58
|
+
if (candidates.length === 0) {
|
|
40
59
|
return;
|
|
41
60
|
}
|
|
42
|
-
for (const issue of
|
|
43
|
-
// oxlint-disable-next-line no-await-in-loop --
|
|
61
|
+
for (const issue of candidates) {
|
|
62
|
+
// oxlint-disable-next-line no-await-in-loop -- few candidates per tick; sequential keeps gh load low.
|
|
44
63
|
await advanceIfReviewable({
|
|
45
64
|
issue,
|
|
46
65
|
worktreeEntries,
|
|
@@ -49,9 +68,10 @@ export function createReviewer(deps) {
|
|
|
49
68
|
});
|
|
50
69
|
}
|
|
51
70
|
}
|
|
52
|
-
// Idempotent after an applied transition: once advanced, the issue leaves
|
|
53
|
-
//
|
|
54
|
-
// are skipped without claiming success and may retry
|
|
71
|
+
// Idempotent after an applied transition: once advanced, the issue leaves the
|
|
72
|
+
// in-progress/in-review candidate set, so it never reaches this scan again.
|
|
73
|
+
// Unsupported writebacks are skipped without claiming success and may retry
|
|
74
|
+
// on later ticks.
|
|
55
75
|
async function advanceIfReviewable(arguments_) {
|
|
56
76
|
const { issue, worktreeEntries, dryRun, signal } = arguments_;
|
|
57
77
|
const ticket = naturalIdFromCanonical(issue.id);
|
|
@@ -59,8 +79,8 @@ export function createReviewer(deps) {
|
|
|
59
79
|
for (const entry of entries) {
|
|
60
80
|
// The injected lookup is contracted never to reject (failures resolve to
|
|
61
81
|
// []), but we still guard it so one bad lookup can never abort the tick
|
|
62
|
-
// and starve the other
|
|
63
|
-
//
|
|
82
|
+
// and starve the other candidates. A failure means "can't tell yet" →
|
|
83
|
+
// skip this worktree and retry next tick.
|
|
64
84
|
let pullRequests;
|
|
65
85
|
try {
|
|
66
86
|
// oxlint-disable-next-line no-await-in-loop -- a ticket almost always has one worktree; sequential lookups are fine.
|
|
@@ -74,47 +94,61 @@ export function createReviewer(deps) {
|
|
|
74
94
|
debug(`PR lookup failed for ${ticket} (${entry.branchName}): ${errorMessage(error)}`);
|
|
75
95
|
continue;
|
|
76
96
|
}
|
|
77
|
-
const
|
|
78
|
-
if (
|
|
97
|
+
const transition = intendedTransition(pullRequests, issue.status);
|
|
98
|
+
if (transition === undefined) {
|
|
79
99
|
continue;
|
|
80
100
|
}
|
|
101
|
+
const pullRequest = pullRequestForTransition(pullRequests, transition);
|
|
81
102
|
if (dryRun) {
|
|
82
|
-
log(`[dry-run] Would advance ${ticket} to
|
|
83
|
-
logEvent("review", {
|
|
103
|
+
log(`[dry-run] Would advance ${ticket} to ${transition} (PR ${pullRequest.url})`);
|
|
104
|
+
logEvent("review", {
|
|
105
|
+
outcome: "skipped",
|
|
106
|
+
reason: "dry_run",
|
|
107
|
+
ticket,
|
|
108
|
+
pr: pullRequest.url,
|
|
109
|
+
to: transition,
|
|
110
|
+
});
|
|
84
111
|
return;
|
|
85
112
|
}
|
|
86
|
-
// oxlint-disable-next-line no-await-in-loop -- single write-back then return; never iterates past the first
|
|
87
|
-
await advance({ issue, ticket, pullRequest
|
|
113
|
+
// oxlint-disable-next-line no-await-in-loop -- single write-back then return; never iterates past the first actionable worktree.
|
|
114
|
+
await advance({ issue, ticket, pullRequest, transition });
|
|
88
115
|
return;
|
|
89
116
|
}
|
|
90
117
|
}
|
|
91
118
|
// A writeback failure (shell/Linear error) is logged and swallowed: the
|
|
92
|
-
// ticket
|
|
119
|
+
// ticket keeps its status and is retried next tick, exactly like a failed
|
|
93
120
|
// lookup. We never let one ticket's writeback abort the others' reviews.
|
|
94
121
|
async function advance(arguments_) {
|
|
95
|
-
const { issue, ticket, pullRequest } = arguments_;
|
|
122
|
+
const { issue, ticket, pullRequest, transition } = arguments_;
|
|
96
123
|
try {
|
|
97
|
-
const result = await board.markInReview(issue);
|
|
124
|
+
const result = transition === "done" ? await board.markDone(issue) : await board.markInReview(issue);
|
|
98
125
|
if (result.outcome === "unsupported") {
|
|
99
|
-
log(`Skipped advancing ${ticket} to
|
|
126
|
+
log(`Skipped advancing ${ticket} to ${transition}: ${result.reason}`);
|
|
100
127
|
logEvent("review", {
|
|
101
128
|
outcome: "skipped",
|
|
102
129
|
reason: "unsupported",
|
|
103
130
|
ticket,
|
|
131
|
+
to: transition,
|
|
104
132
|
});
|
|
105
133
|
return;
|
|
106
134
|
}
|
|
107
|
-
log(`Advanced ${ticket} to
|
|
135
|
+
log(`Advanced ${ticket} to ${transition} (PR ${pullRequest.url})`);
|
|
108
136
|
logEvent("review", {
|
|
109
137
|
outcome: "advanced",
|
|
110
138
|
ticket,
|
|
111
139
|
pr: pullRequest.url,
|
|
112
140
|
state: pullRequest.state,
|
|
141
|
+
to: transition,
|
|
113
142
|
});
|
|
114
143
|
}
|
|
115
144
|
catch (error) {
|
|
116
|
-
log(`Failed to advance ${ticket} to
|
|
117
|
-
logEvent("review", {
|
|
145
|
+
log(`Failed to advance ${ticket} to ${transition}: ${errorMessage(error)}`);
|
|
146
|
+
logEvent("review", {
|
|
147
|
+
outcome: "failed",
|
|
148
|
+
reason: "writeback_failed",
|
|
149
|
+
ticket,
|
|
150
|
+
to: transition,
|
|
151
|
+
});
|
|
118
152
|
}
|
|
119
153
|
}
|
|
120
154
|
return { runOnce };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAIA,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAIA,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAcnE,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAoqBD,wBAAsB,MAAM,CAAC,MAAM,EAAE,cAAc,EAAE,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAU/F;AAED,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAI7D"}
|
package/dist/commands/status.js
CHANGED
|
@@ -307,7 +307,16 @@ function inventoryField(label, value) {
|
|
|
307
307
|
function formatPullRequests(prs) {
|
|
308
308
|
return prs.map((pr) => `${pr.url} (${pr.state})`).join(", ");
|
|
309
309
|
}
|
|
310
|
-
|
|
310
|
+
/**
|
|
311
|
+
* Inventory `ticket:` value: the worktree's remote canonical status. Slots are
|
|
312
|
+
* consumed solely by `in-progress` issues (see `inProgressCount`), so that one
|
|
313
|
+
* status is spelled out as `slot held` to make the otherwise-implicit rule
|
|
314
|
+
* legible on the row; every other status renders bare.
|
|
315
|
+
*/
|
|
316
|
+
function formatTicketStatus(canonicalStatus) {
|
|
317
|
+
return canonicalStatus === "in-progress" ? "in-progress (slot held)" : canonicalStatus;
|
|
318
|
+
}
|
|
319
|
+
async function writeInventoryWorktrees(config, probe, statusByTicket) {
|
|
311
320
|
writeSection("Worktrees");
|
|
312
321
|
const entries = worktrees
|
|
313
322
|
.list(config)
|
|
@@ -338,6 +347,14 @@ async function writeInventoryWorktrees(config, probe) {
|
|
|
338
347
|
writeOutput(inventoryField("title", runState.title));
|
|
339
348
|
}
|
|
340
349
|
writeOutput(inventoryField("state", inventoryStateText(runState, probe, entry.ticket, now)));
|
|
350
|
+
// `state:` is the local run lifecycle; `ticket:` is the remote status that
|
|
351
|
+
// actually drives the slot count. They're sourced independently and can
|
|
352
|
+
// legitimately disagree, so they sit adjacent. Omitted when the board fetch
|
|
353
|
+
// failed (no map) or the ticket isn't in the fetched board.
|
|
354
|
+
const ticketStatus = statusByTicket?.get(entry.ticket);
|
|
355
|
+
if (ticketStatus !== undefined) {
|
|
356
|
+
writeOutput(inventoryField("ticket", formatTicketStatus(ticketStatus)));
|
|
357
|
+
}
|
|
341
358
|
writeOutput(inventoryField("repo", entry.repository));
|
|
342
359
|
writeOutput(inventoryField("worktree", entry.dir));
|
|
343
360
|
if (accessHint !== undefined) {
|
|
@@ -432,6 +449,32 @@ async function fetchBoardForStatus(config) {
|
|
|
432
449
|
return { kind: "error", error };
|
|
433
450
|
}
|
|
434
451
|
}
|
|
452
|
+
/**
|
|
453
|
+
* Maps each fetched issue's lowercased natural id to its canonical status when
|
|
454
|
+
* exactly one fetched issue has that natural id, so the Worktrees section can
|
|
455
|
+
* show a `ticket:` field per row without guessing across sources. The key
|
|
456
|
+
* matches the lowercased `WorktreeEntry.ticket` (same join as
|
|
457
|
+
* `inProgressWithoutWorktree`). `undefined` when the board fetch failed —
|
|
458
|
+
* callers then omit the field rather than guess.
|
|
459
|
+
*/
|
|
460
|
+
function statusByWorktreeTicket(boardResult) {
|
|
461
|
+
if (boardResult.kind !== "ok") {
|
|
462
|
+
return undefined;
|
|
463
|
+
}
|
|
464
|
+
const statuses = new Map();
|
|
465
|
+
const matchCounts = new Map();
|
|
466
|
+
for (const issue of boardResult.issues) {
|
|
467
|
+
const ticket = naturalIdFromCanonical(issue.id).toLowerCase();
|
|
468
|
+
matchCounts.set(ticket, (matchCounts.get(ticket) ?? 0) + 1);
|
|
469
|
+
statuses.set(ticket, issue.status);
|
|
470
|
+
}
|
|
471
|
+
for (const [ticket, matchCount] of matchCounts) {
|
|
472
|
+
if (matchCount > 1) {
|
|
473
|
+
statuses.delete(ticket);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
return statuses;
|
|
477
|
+
}
|
|
435
478
|
function writeQueueSections(boardResult) {
|
|
436
479
|
if (boardResult.kind === "error") {
|
|
437
480
|
writeSection("Queue");
|
|
@@ -484,6 +527,9 @@ function writeInProgressIssue(issue) {
|
|
|
484
527
|
const naturalId = naturalIdFromCanonical(issue.id);
|
|
485
528
|
writeOutput(issue.url === undefined ? naturalId : `${naturalId} ${issue.url}`);
|
|
486
529
|
writeOutput(inventoryField("title", issue.title));
|
|
530
|
+
// These are all in-progress by definition, but spell out the slot-held
|
|
531
|
+
// status so every holder row reads the same whether or not it has a worktree.
|
|
532
|
+
writeOutput(inventoryField("ticket", formatTicketStatus(issue.status)));
|
|
487
533
|
if (issue.repository !== undefined) {
|
|
488
534
|
writeOutput(inventoryField("repo", issue.repository));
|
|
489
535
|
}
|
|
@@ -508,12 +554,16 @@ async function writeInventoryStatus(config) {
|
|
|
508
554
|
// Banner ("groundcrew status\n=================") dropped: the command
|
|
509
555
|
// you just ran already tells you what report you're looking at, and the
|
|
510
556
|
// section headers (`Worktrees`, `Queue`, etc.) carry the visual anchors.
|
|
557
|
+
// The board fetch runs concurrently with the probe, but we await it before
|
|
558
|
+
// rendering: each Worktrees row carries the remote ticket status, so the
|
|
559
|
+
// inventory can't print until the source resolves. A failed fetch returns
|
|
560
|
+
// quickly and still renders rows (without the `ticket:` field).
|
|
511
561
|
const boardResultPromise = fetchBoardForStatus(config);
|
|
512
562
|
const probe = await withLogOutputSuppressed(async () => await workspaces.probe(config));
|
|
513
|
-
await
|
|
563
|
+
const boardResult = await boardResultPromise;
|
|
564
|
+
await writeInventoryWorktrees(config, probe, statusByWorktreeTicket(boardResult));
|
|
514
565
|
const worktreeTickets = new Set(worktrees.list(config).map((entry) => entry.ticket));
|
|
515
566
|
writeStraySessions(probe, worktreeTickets);
|
|
516
|
-
const boardResult = await boardResultPromise;
|
|
517
567
|
writeInProgressWithoutWorktree(boardResult, worktreeTickets);
|
|
518
568
|
if (boardResult.kind === "ok") {
|
|
519
569
|
const used = inProgressCount(boardResult.issues);
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* in MVP-2.
|
|
13
13
|
* - `markInProgress` absent → silent no-op.
|
|
14
14
|
* - `markInReview` absent → reports unsupported.
|
|
15
|
+
* - `markDone` absent → reports unsupported.
|
|
15
16
|
* - `fetch` is required by the Zod schema.
|
|
16
17
|
*/
|
|
17
18
|
import type { AdapterContext } from "../../adapterDefinition.ts";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/shell/factory.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/shell/factory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAGL,KAAK,KAAK,IAAI,cAAc,EAG5B,KAAK,YAAY,EAClB,MAAM,uBAAuB,CAAC;AAG/B,OAAO,EACL,KAAK,kBAAkB,EAEvB,KAAK,UAAU,EAEhB,MAAM,aAAa,CAAC;AA+BrB,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,GAAG,cAAc,CAuB3F;AAED,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,kBAAkB,EAC1B,QAAQ,EAAE,cAAc,GACvB,YAAY,CA8Gd"}
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* in MVP-2.
|
|
13
13
|
* - `markInProgress` absent → silent no-op.
|
|
14
14
|
* - `markInReview` absent → reports unsupported.
|
|
15
|
+
* - `markDone` absent → reports unsupported.
|
|
15
16
|
* - `fetch` is required by the Zod schema.
|
|
16
17
|
*/
|
|
17
18
|
import { toCanonicalId, } from "../../ticketSource.js";
|
|
@@ -23,6 +24,7 @@ const DEFAULT_TIMEOUTS = {
|
|
|
23
24
|
resolveOne: 10_000,
|
|
24
25
|
markInProgress: 10_000,
|
|
25
26
|
markInReview: 10_000,
|
|
27
|
+
markDone: 10_000,
|
|
26
28
|
};
|
|
27
29
|
function mergeTimeouts(overrides) {
|
|
28
30
|
return {
|
|
@@ -31,6 +33,7 @@ function mergeTimeouts(overrides) {
|
|
|
31
33
|
resolveOne: overrides?.resolveOne ?? DEFAULT_TIMEOUTS.resolveOne,
|
|
32
34
|
markInProgress: overrides?.markInProgress ?? DEFAULT_TIMEOUTS.markInProgress,
|
|
33
35
|
markInReview: overrides?.markInReview ?? DEFAULT_TIMEOUTS.markInReview,
|
|
36
|
+
markDone: overrides?.markDone ?? DEFAULT_TIMEOUTS.markDone,
|
|
34
37
|
};
|
|
35
38
|
}
|
|
36
39
|
export function toCanonicalIssue(shellIssue, sourceName) {
|
|
@@ -149,5 +152,15 @@ export function createShellTicketSource(config, _context) {
|
|
|
149
152
|
await invokeWriteback(config.commands.markInReview, timeouts.markInReview, issue);
|
|
150
153
|
return { outcome: "applied" };
|
|
151
154
|
},
|
|
155
|
+
async markDone(issue) {
|
|
156
|
+
if (config.commands.markDone === undefined) {
|
|
157
|
+
return {
|
|
158
|
+
outcome: "unsupported",
|
|
159
|
+
reason: `shell source "${sourceName}" has no commands.markDone configured`,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
await invokeWriteback(config.commands.markDone, timeouts.markDone, issue);
|
|
163
|
+
return { outcome: "applied" };
|
|
164
|
+
},
|
|
152
165
|
};
|
|
153
166
|
}
|
|
@@ -89,6 +89,7 @@ export declare const shellAdapterConfigSchema: z.ZodObject<{
|
|
|
89
89
|
resolveOne: z.ZodOptional<z.ZodString>;
|
|
90
90
|
markInProgress: z.ZodOptional<z.ZodString>;
|
|
91
91
|
markInReview: z.ZodOptional<z.ZodString>;
|
|
92
|
+
markDone: z.ZodOptional<z.ZodString>;
|
|
92
93
|
}, z.core.$strip>;
|
|
93
94
|
cwd: z.ZodOptional<z.ZodString>;
|
|
94
95
|
timeouts: z.ZodOptional<z.ZodObject<{
|
|
@@ -97,6 +98,7 @@ export declare const shellAdapterConfigSchema: z.ZodObject<{
|
|
|
97
98
|
resolveOne: z.ZodOptional<z.ZodNumber>;
|
|
98
99
|
markInProgress: z.ZodOptional<z.ZodNumber>;
|
|
99
100
|
markInReview: z.ZodOptional<z.ZodNumber>;
|
|
101
|
+
markDone: z.ZodOptional<z.ZodNumber>;
|
|
100
102
|
}, z.core.$strip>>;
|
|
101
103
|
env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
102
104
|
}, z.core.$strip>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/shell/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAYxB,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAiB3B,CAAC;AAEH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAA4B,CAAC;AAEhE,eAAO,MAAM,wBAAwB
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/shell/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAYxB,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAiB3B,CAAC;AAEH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAA4B,CAAC;AAEhE,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;iBA4BnC,CAAC;AAEH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC"}
|
|
@@ -47,6 +47,7 @@ export const shellAdapterConfigSchema = z.object({
|
|
|
47
47
|
resolveOne: z.string().optional(),
|
|
48
48
|
markInProgress: z.string().optional(),
|
|
49
49
|
markInReview: z.string().optional(),
|
|
50
|
+
markDone: z.string().optional(),
|
|
50
51
|
}),
|
|
51
52
|
cwd: z.string().optional(),
|
|
52
53
|
timeouts: z
|
|
@@ -59,6 +60,7 @@ export const shellAdapterConfigSchema = z.object({
|
|
|
59
60
|
resolveOne: z.number().int().positive().optional(),
|
|
60
61
|
markInProgress: z.number().int().positive().optional(),
|
|
61
62
|
markInReview: z.number().int().positive().optional(),
|
|
63
|
+
markDone: z.number().int().positive().optional(),
|
|
62
64
|
})
|
|
63
65
|
.optional(),
|
|
64
66
|
env: z.record(z.string(), z.string()).optional(),
|
package/dist/lib/board.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* goes through this; the moment we skip the wrapper we grow Linear assumptions
|
|
5
5
|
* back into consumers.
|
|
6
6
|
*/
|
|
7
|
-
import { type BoardState, type Issue, type MarkInReviewResult, type TicketSource } from "./ticketSource.ts";
|
|
7
|
+
import { type BoardState, type Issue, type MarkDoneResult, type MarkInReviewResult, type TicketSource } from "./ticketSource.ts";
|
|
8
8
|
export interface Board {
|
|
9
9
|
verify(): Promise<void>;
|
|
10
10
|
fetch(): Promise<BoardState>;
|
|
@@ -21,6 +21,12 @@ export interface Board {
|
|
|
21
21
|
* return `unsupported` (see `TicketSource.markInReview`).
|
|
22
22
|
*/
|
|
23
23
|
markInReview(issue: Issue): Promise<MarkInReviewResult>;
|
|
24
|
+
/**
|
|
25
|
+
* Advances a ticket to done on the adapter whose `name` matches
|
|
26
|
+
* `issue.source`. Unknown source throws. Sources that don't implement the
|
|
27
|
+
* optional `markDone` return `unsupported` (see `TicketSource.markDone`).
|
|
28
|
+
*/
|
|
29
|
+
markDone(issue: Issue): Promise<MarkDoneResult>;
|
|
24
30
|
}
|
|
25
31
|
export declare function createBoard(sources: readonly TicketSource[]): Board;
|
|
26
32
|
//# sourceMappingURL=board.d.ts.map
|
package/dist/lib/board.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"board.d.ts","sourceRoot":"","sources":["../../src/lib/board.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAEL,KAAK,UAAU,EACf,KAAK,KAAK,EACV,KAAK,kBAAkB,EAEvB,KAAK,YAAY,EAClB,MAAM,mBAAmB,CAAC;AAE3B,MAAM,WAAW,KAAK;IACpB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC,CAAC;IAC7B;;;OAGG;IACH,UAAU,CAAC,oBAAoB,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC;IACrE,wFAAwF;IACxF,cAAc,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C;;;;OAIG;IACH,YAAY,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"board.d.ts","sourceRoot":"","sources":["../../src/lib/board.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAEL,KAAK,UAAU,EACf,KAAK,KAAK,EACV,KAAK,cAAc,EACnB,KAAK,kBAAkB,EAEvB,KAAK,YAAY,EAClB,MAAM,mBAAmB,CAAC;AAE3B,MAAM,WAAW,KAAK;IACpB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC,CAAC;IAC7B;;;OAGG;IACH,UAAU,CAAC,oBAAoB,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC;IACrE,wFAAwF;IACxF,cAAc,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C;;;;OAIG;IACH,YAAY,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACxD;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;CACjD;AAqBD,wBAAgB,WAAW,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,GAAG,KAAK,CAmHnE"}
|
package/dist/lib/board.js
CHANGED
|
@@ -111,6 +111,16 @@ export function createBoard(sources) {
|
|
|
111
111
|
async markInReview(issue) {
|
|
112
112
|
return await routeWriteback(byName, issue).markInReview(issue);
|
|
113
113
|
},
|
|
114
|
+
async markDone(issue) {
|
|
115
|
+
const source = routeWriteback(byName, issue);
|
|
116
|
+
if (source.markDone === undefined) {
|
|
117
|
+
return {
|
|
118
|
+
outcome: "unsupported",
|
|
119
|
+
reason: `source "${source.name}" does not support markDone`,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
return await source.markDone(issue);
|
|
123
|
+
},
|
|
114
124
|
};
|
|
115
125
|
}
|
|
116
126
|
/**
|
|
@@ -15,6 +15,8 @@ export interface PullRequestSummary {
|
|
|
15
15
|
/** Lowercased lifecycle: "open" | "merged" | "closed". */
|
|
16
16
|
state: string;
|
|
17
17
|
title: string;
|
|
18
|
+
/** PR head commit SHA used to ignore historical PRs from reused branch names. */
|
|
19
|
+
headRefOid: string;
|
|
18
20
|
}
|
|
19
21
|
interface LookupArgs {
|
|
20
22
|
/** Worktree directory; `gh` resolves the GitHub repo from its git remote. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pullRequests.d.ts","sourceRoot":"","sources":["../../src/lib/pullRequests.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;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;
|
|
1
|
+
{"version":3,"file":"pullRequests.d.ts","sourceRoot":"","sources":["../../src/lib/pullRequests.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;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;IACd,iFAAiF;IACjF,UAAU,EAAE,MAAM,CAAC;CACpB;AASD,UAAU,UAAU;IAClB,6EAA6E;IAC7E,GAAG,EAAE,MAAM,CAAC;IACZ,oCAAoC;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAmDD,wBAAsB,yBAAyB,CAC7C,UAAU,EAAE,UAAU,GACrB,OAAO,CAAC,SAAS,kBAAkB,EAAE,CAAC,CA6BxC"}
|
package/dist/lib/pullRequests.js
CHANGED
|
@@ -37,6 +37,7 @@ function parsePullRequests(output) {
|
|
|
37
37
|
number: entry.number,
|
|
38
38
|
state: STATE_MAP[entry.state] ?? entry.state.toLowerCase(),
|
|
39
39
|
title: entry.title,
|
|
40
|
+
headRefOid: entry.headRefOid,
|
|
40
41
|
});
|
|
41
42
|
}
|
|
42
43
|
return summaries;
|
|
@@ -50,27 +51,32 @@ function isRawPullRequest(value) {
|
|
|
50
51
|
return (typeof record["url"] === "string" &&
|
|
51
52
|
typeof record["number"] === "number" &&
|
|
52
53
|
typeof record["state"] === "string" &&
|
|
53
|
-
typeof record["title"] === "string"
|
|
54
|
+
typeof record["title"] === "string" &&
|
|
55
|
+
typeof record["headRefOid"] === "string");
|
|
54
56
|
}
|
|
55
57
|
export async function findPullRequestsForBranch(arguments_) {
|
|
56
58
|
const { cwd, branchName, signal } = arguments_;
|
|
59
|
+
const options = signal === undefined ? { cwd } : { cwd, signal };
|
|
57
60
|
try {
|
|
58
|
-
const output = await
|
|
59
|
-
"
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
61
|
+
const [output, currentHeadOid] = await Promise.all([
|
|
62
|
+
runCommandAsync("gh", [
|
|
63
|
+
"pr",
|
|
64
|
+
"list",
|
|
65
|
+
"--head",
|
|
66
|
+
branchName,
|
|
67
|
+
"--state",
|
|
68
|
+
"all",
|
|
69
|
+
"--limit",
|
|
70
|
+
String(GH_PR_LIST_LIMIT),
|
|
71
|
+
"--json",
|
|
72
|
+
"url,number,state,title,headRefOid",
|
|
73
|
+
], options),
|
|
74
|
+
runCommandAsync("git", ["rev-parse", "HEAD"], options),
|
|
75
|
+
]);
|
|
76
|
+
return parsePullRequests(output).filter((pr) => pr.headRefOid === currentHeadOid);
|
|
71
77
|
}
|
|
72
78
|
catch {
|
|
73
|
-
// gh not installed / not authenticated / non-GitHub remote / network
|
|
79
|
+
// gh/git not installed / not authenticated / non-GitHub remote / network
|
|
74
80
|
// error / etc. All resolve to "no PR info available" for display.
|
|
75
81
|
return [];
|
|
76
82
|
}
|
|
@@ -116,6 +116,12 @@ export type MarkInReviewResult = {
|
|
|
116
116
|
outcome: "unsupported";
|
|
117
117
|
reason: string;
|
|
118
118
|
};
|
|
119
|
+
export type MarkDoneResult = {
|
|
120
|
+
outcome: "applied";
|
|
121
|
+
} | {
|
|
122
|
+
outcome: "unsupported";
|
|
123
|
+
reason: string;
|
|
124
|
+
};
|
|
119
125
|
export interface TicketSource {
|
|
120
126
|
/** Stable identifier used as the id prefix and in log lines. Equal to the source's config `name`. */
|
|
121
127
|
readonly name: string;
|
|
@@ -136,6 +142,16 @@ export interface TicketSource {
|
|
|
136
142
|
* rather than pretending the transition happened.
|
|
137
143
|
*/
|
|
138
144
|
markInReview(issue: Issue): Promise<MarkInReviewResult>;
|
|
145
|
+
/**
|
|
146
|
+
* Optional writeback: advance a ticket to done once its PR has merged.
|
|
147
|
+
* Sources without a native/configured done transition omit this method; the
|
|
148
|
+
* Board treats an absent method as `{ outcome: "unsupported" }` so the
|
|
149
|
+
* reviewer can log the skip without claiming a transition that never
|
|
150
|
+
* happened. Linear omits it on purpose: on merge, Linear's own GitHub
|
|
151
|
+
* integration moves the issue to Done, which groundcrew then observes via
|
|
152
|
+
* `fetch()` and the cleaner tears down.
|
|
153
|
+
*/
|
|
154
|
+
markDone?(issue: Issue): Promise<MarkDoneResult>;
|
|
139
155
|
/**
|
|
140
156
|
* Optional: return parent tickets that were excluded from `fetch()` because
|
|
141
157
|
* they have sub-issues. Board surfaces these so the dispatcher can log WHY
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ticketSource.d.ts","sourceRoot":"","sources":["../../src/lib/ticketSource.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ticketSource.d.ts","sourceRoot":"","sources":["../../src/lib/ticketSource.ts"],"names":[],"mappings":"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"}
|
package/docs/ticket-sources.md
CHANGED
|
@@ -16,8 +16,9 @@ export default {
|
|
|
16
16
|
resolveOne: "~/.config/groundcrew/jira-resolve.sh ${id}",
|
|
17
17
|
markInProgress: "jira issue move ${id} 'In Progress'",
|
|
18
18
|
markInReview: "jira issue move ${id} 'In Review'",
|
|
19
|
+
markDone: "jira issue move ${id} 'Done'",
|
|
19
20
|
},
|
|
20
|
-
timeouts: { fetch: 60_000, markInReview: 15_000 },
|
|
21
|
+
timeouts: { fetch: 60_000, markInReview: 15_000, markDone: 15_000 },
|
|
21
22
|
},
|
|
22
23
|
],
|
|
23
24
|
};
|
|
@@ -26,12 +27,15 @@ export default {
|
|
|
26
27
|
`commands.fetch` must print a JSON array of issues. `commands.resolveOne`, when
|
|
27
28
|
set, must print one issue, print nothing for "not found", or exit `3` for "not
|
|
28
29
|
found". `commands.markInProgress`, when set, receives the issue's `sourceRef` as
|
|
29
|
-
JSON on stdin. `commands.markInReview`, when set, receives the same `sourceRef`
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
JSON on stdin. `commands.markInReview`, when set, receives the same `sourceRef` and is run
|
|
31
|
+
after groundcrew sees an **open** PR on the ticket's worktree branch (in-progress
|
|
32
|
+
tickets only). If omitted, groundcrew treats in-review advancement as unsupported
|
|
33
|
+
for that source and does not claim the transition succeeded. `commands.markDone`,
|
|
34
|
+
when set, receives the same `sourceRef` and is run after groundcrew sees a
|
|
35
|
+
**merged** PR on the ticket's worktree branch (a merged PR never advances to
|
|
36
|
+
in-review). If omitted, groundcrew treats done advancement as unsupported and
|
|
37
|
+
leaves the ticket for the source's own integration to close out. `${id}`,
|
|
38
|
+
`${canonicalId}`, and `${name}` placeholders are shell-quoted before substitution.
|
|
35
39
|
|
|
36
40
|
```json
|
|
37
41
|
[
|
package/package.json
CHANGED