@clipboard-health/groundcrew 4.14.0 → 4.15.0
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/orchestrator.d.ts +2 -2
- package/dist/commands/orchestrator.d.ts.map +1 -1
- package/dist/commands/orchestrator.js +9 -2
- package/dist/commands/reviewer.d.ts +51 -0
- package/dist/commands/reviewer.d.ts.map +1 -0
- package/dist/commands/reviewer.js +121 -0
- package/dist/lib/adapters/linear/factory.d.ts.map +1 -1
- package/dist/lib/adapters/linear/factory.js +9 -0
- package/dist/lib/adapters/linear/writeback.d.ts +2 -0
- package/dist/lib/adapters/linear/writeback.d.ts.map +1 -1
- package/dist/lib/adapters/linear/writeback.js +11 -1
- 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 +37 -19
- 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 +11 -5
- package/dist/lib/board.d.ts.map +1 -1
- package/dist/lib/board.js +20 -9
- package/dist/lib/ticketSource.d.ts +15 -0
- package/dist/lib/ticketSource.d.ts.map +1 -1
- package/docs/ticket-sources.md +11 -2
- package/package.json +1 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* groundcrew orchestrator — polls Linear projects and spins up workspace +
|
|
3
3
|
* git-worktree pairs for ready tickets. Each tick fetches the board, runs
|
|
4
|
-
* the cleaner, and
|
|
5
|
-
* orchestrator's user-facing output.
|
|
4
|
+
* the cleaner, the reviewer, and the dispatcher; logging from those modules is
|
|
5
|
+
* the orchestrator's user-facing output.
|
|
6
6
|
*/
|
|
7
7
|
export interface OrchestratorOptions {
|
|
8
8
|
watch: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../../src/commands/orchestrator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../../src/commands/orchestrator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AA2DH,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,OAAO,CAAC;CACjB;AAiBD,wBAAsB,WAAW,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAgD7E"}
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* groundcrew orchestrator — polls Linear projects and spins up workspace +
|
|
3
3
|
* git-worktree pairs for ready tickets. Each tick fetches the board, runs
|
|
4
|
-
* the cleaner, and
|
|
5
|
-
* orchestrator's user-facing output.
|
|
4
|
+
* the cleaner, the reviewer, and the dispatcher; logging from those modules is
|
|
5
|
+
* the orchestrator's user-facing output.
|
|
6
6
|
*/
|
|
7
7
|
import { createBoard } from "../lib/board.js";
|
|
8
8
|
import { buildSources, sourcesFromConfig } from "../lib/buildSources.js";
|
|
9
9
|
import { loadConfig } from "../lib/config.js";
|
|
10
|
+
import { findPullRequestsForBranch } from "../lib/pullRequests.js";
|
|
10
11
|
import { RepositoryResolutionError } from "../lib/ticketSource.js";
|
|
11
12
|
import { getUsageByModel } from "../lib/usage.js";
|
|
12
13
|
import { errorMessage, log, sleep } from "../lib/util.js";
|
|
13
14
|
import { worktrees } from "../lib/worktrees.js";
|
|
14
15
|
import { createCleaner } from "./cleaner.js";
|
|
15
16
|
import { createDispatcher } from "./dispatcher.js";
|
|
17
|
+
import { createReviewer } from "./reviewer.js";
|
|
16
18
|
const RATE_LIMIT_DELAY_MS = 60_000;
|
|
17
19
|
const RETRY_BASE_DELAY_MS = 1000;
|
|
18
20
|
const RETRY_MAX_ATTEMPTS = 3;
|
|
@@ -72,6 +74,10 @@ export async function orchestrate(options) {
|
|
|
72
74
|
const board = createBoard(allSources);
|
|
73
75
|
await board.verify();
|
|
74
76
|
const cleaner = createCleaner({ config });
|
|
77
|
+
const reviewer = createReviewer({
|
|
78
|
+
board,
|
|
79
|
+
findPullRequests: findPullRequestsForBranch,
|
|
80
|
+
});
|
|
75
81
|
const dispatcher = createDispatcher({ config, board });
|
|
76
82
|
// Folded into the dispatcher's idle log lines in watch mode so each idle
|
|
77
83
|
// tick prints one combined line instead of "<reason>" + "Next poll in Xs".
|
|
@@ -88,6 +94,7 @@ export async function orchestrate(options) {
|
|
|
88
94
|
...(signal === undefined ? {} : { signal }),
|
|
89
95
|
};
|
|
90
96
|
await cleaner.runOnce(tickArguments);
|
|
97
|
+
await reviewer.runOnce(tickArguments);
|
|
91
98
|
await dispatcher.runOnce({
|
|
92
99
|
...tickArguments,
|
|
93
100
|
// Lazy: dispatcher only invokes this after its own early-returns, so
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-iteration scanner that advances in-progress tickets to in-review once
|
|
3
|
+
* their worktree has an open (or merged) pull request. Sits between the cleaner
|
|
4
|
+
* and the dispatcher in each `orchestrate()` tick. A successfully-applied
|
|
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.
|
|
8
|
+
*
|
|
9
|
+
* The write-back lands in the ticket source, not the in-memory `BoardState`, so
|
|
10
|
+
* the dispatcher in the SAME tick still sees the ticket as in-progress; the slot
|
|
11
|
+
* frees on the NEXT tick's `board.fetch()`. That one-tick latency is deliberate
|
|
12
|
+
* — it keeps the reviewer from mutating shared `BoardState` mid-tick. One per
|
|
13
|
+
* `orchestrate()`; stateless across iterations. Mirrors `Cleaner`.
|
|
14
|
+
*
|
|
15
|
+
* "Worktree has an open PR" is a v1 proxy for "the implementation is finished
|
|
16
|
+
* and up for review". The detection + the open/merged condition live here in
|
|
17
|
+
* the core reviewer (a push model) rather than inside any adapter, so a future
|
|
18
|
+
* per-adapter `shouldAdvanceToReview` predicate is a clean additive change.
|
|
19
|
+
*/
|
|
20
|
+
import type { Board } from "../lib/board.ts";
|
|
21
|
+
import type { PullRequestSummary } from "../lib/pullRequests.ts";
|
|
22
|
+
import { type BoardState } from "../lib/ticketSource.ts";
|
|
23
|
+
import type { WorktreeEntry } from "../lib/worktrees.ts";
|
|
24
|
+
/**
|
|
25
|
+
* Injected PR lookup. Matches `findPullRequestsForBranch`'s shape: best-effort,
|
|
26
|
+
* never rejects — a failed lookup (gh missing, unauthenticated, non-GitHub
|
|
27
|
+
* remote, network error) resolves to an empty list, indistinguishable from
|
|
28
|
+
* "no PR yet". Both outcomes mean "skip this issue, retry next tick".
|
|
29
|
+
*/
|
|
30
|
+
export type FindPullRequests = (arguments_: {
|
|
31
|
+
cwd: string;
|
|
32
|
+
branchName: string;
|
|
33
|
+
signal?: AbortSignal;
|
|
34
|
+
}) => Promise<readonly PullRequestSummary[]>;
|
|
35
|
+
interface ReviewerDeps {
|
|
36
|
+
board: Board;
|
|
37
|
+
findPullRequests: FindPullRequests;
|
|
38
|
+
}
|
|
39
|
+
/** Per-tick inputs, mirroring the other orchestrator steps' shape. */
|
|
40
|
+
interface ReviewArguments {
|
|
41
|
+
state: BoardState;
|
|
42
|
+
worktreeEntries: readonly WorktreeEntry[];
|
|
43
|
+
dryRun: boolean;
|
|
44
|
+
signal?: AbortSignal;
|
|
45
|
+
}
|
|
46
|
+
export interface Reviewer {
|
|
47
|
+
runOnce(arguments_: ReviewArguments): Promise<void>;
|
|
48
|
+
}
|
|
49
|
+
export declare function createReviewer(deps: ReviewerDeps): Reviewer;
|
|
50
|
+
export {};
|
|
51
|
+
//# sourceMappingURL=reviewer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reviewer.d.ts","sourceRoot":"","sources":["../../src/commands/reviewer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,KAAK,UAAU,EAAsC,MAAM,wBAAwB,CAAC;AAE7F,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;AAsBD,wBAAgB,cAAc,CAAC,IAAI,EAAE,YAAY,GAAG,QAAQ,CAqG3D"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-iteration scanner that advances in-progress tickets to in-review once
|
|
3
|
+
* their worktree has an open (or merged) pull request. Sits between the cleaner
|
|
4
|
+
* and the dispatcher in each `orchestrate()` tick. A successfully-applied
|
|
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.
|
|
8
|
+
*
|
|
9
|
+
* The write-back lands in the ticket source, not the in-memory `BoardState`, so
|
|
10
|
+
* the dispatcher in the SAME tick still sees the ticket as in-progress; the slot
|
|
11
|
+
* frees on the NEXT tick's `board.fetch()`. That one-tick latency is deliberate
|
|
12
|
+
* — it keeps the reviewer from mutating shared `BoardState` mid-tick. One per
|
|
13
|
+
* `orchestrate()`; stateless across iterations. Mirrors `Cleaner`.
|
|
14
|
+
*
|
|
15
|
+
* "Worktree has an open PR" is a v1 proxy for "the implementation is finished
|
|
16
|
+
* and up for review". The detection + the open/merged condition live here in
|
|
17
|
+
* the core reviewer (a push model) rather than inside any adapter, so a future
|
|
18
|
+
* per-adapter `shouldAdvanceToReview` predicate is a clean additive change.
|
|
19
|
+
*/
|
|
20
|
+
import { naturalIdFromCanonical } from "../lib/ticketSource.js";
|
|
21
|
+
import { debug, errorMessage, log, logEvent } from "../lib/util.js";
|
|
22
|
+
// A PR whose lifecycle means the work is up for (or past) review. `merged`
|
|
23
|
+
// catches a PR that merged between ticks, so we still free the slot.
|
|
24
|
+
function isReviewablePullRequest(pr) {
|
|
25
|
+
return pr.state === "open" || pr.state === "merged";
|
|
26
|
+
}
|
|
27
|
+
function matchingWorktreeEntries(arguments_) {
|
|
28
|
+
const { issue, worktreeEntries, ticket } = arguments_;
|
|
29
|
+
if (issue.repository === undefined) {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
return worktreeEntries.filter((entry) => entry.ticket === ticket && entry.repository === issue.repository);
|
|
33
|
+
}
|
|
34
|
+
export function createReviewer(deps) {
|
|
35
|
+
const { board, findPullRequests } = deps;
|
|
36
|
+
async function runOnce(arguments_) {
|
|
37
|
+
const { state, worktreeEntries, dryRun, signal } = arguments_;
|
|
38
|
+
const inProgress = state.issues.filter((issue) => issue.status === "in-progress");
|
|
39
|
+
if (inProgress.length === 0) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
for (const issue of inProgress) {
|
|
43
|
+
// oxlint-disable-next-line no-await-in-loop -- at most maximumInProgress (1-5) issues per tick; sequential keeps gh load low.
|
|
44
|
+
await advanceIfReviewable({
|
|
45
|
+
issue,
|
|
46
|
+
worktreeEntries,
|
|
47
|
+
dryRun,
|
|
48
|
+
...(signal === undefined ? {} : { signal }),
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// Idempotent after an applied transition: once advanced, the issue leaves
|
|
53
|
+
// `in-progress`, so it never reaches this scan again. Unsupported writebacks
|
|
54
|
+
// are skipped without claiming success and may retry on later ticks.
|
|
55
|
+
async function advanceIfReviewable(arguments_) {
|
|
56
|
+
const { issue, worktreeEntries, dryRun, signal } = arguments_;
|
|
57
|
+
const ticket = naturalIdFromCanonical(issue.id);
|
|
58
|
+
const entries = matchingWorktreeEntries({ issue, worktreeEntries, ticket });
|
|
59
|
+
for (const entry of entries) {
|
|
60
|
+
// The injected lookup is contracted never to reject (failures resolve to
|
|
61
|
+
// []), but we still guard it so one bad lookup can never abort the tick
|
|
62
|
+
// and starve the other in-progress issues. A failure means "can't tell
|
|
63
|
+
// yet" → skip this worktree and retry next tick.
|
|
64
|
+
let pullRequests;
|
|
65
|
+
try {
|
|
66
|
+
// oxlint-disable-next-line no-await-in-loop -- a ticket almost always has one worktree; sequential lookups are fine.
|
|
67
|
+
pullRequests = await findPullRequests({
|
|
68
|
+
cwd: entry.dir,
|
|
69
|
+
branchName: entry.branchName,
|
|
70
|
+
...(signal === undefined ? {} : { signal }),
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
debug(`PR lookup failed for ${ticket} (${entry.branchName}): ${errorMessage(error)}`);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const reviewable = pullRequests.find(isReviewablePullRequest);
|
|
78
|
+
if (reviewable === undefined) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (dryRun) {
|
|
82
|
+
log(`[dry-run] Would advance ${ticket} to in-review (PR ${reviewable.url})`);
|
|
83
|
+
logEvent("review", { outcome: "skipped", reason: "dry_run", ticket, pr: reviewable.url });
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
// oxlint-disable-next-line no-await-in-loop -- single write-back then return; never iterates past the first reviewable worktree.
|
|
87
|
+
await advance({ issue, ticket, pullRequest: reviewable });
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// A writeback failure (shell/Linear error) is logged and swallowed: the
|
|
92
|
+
// ticket stays in-progress and is retried next tick, exactly like a failed
|
|
93
|
+
// lookup. We never let one ticket's writeback abort the others' reviews.
|
|
94
|
+
async function advance(arguments_) {
|
|
95
|
+
const { issue, ticket, pullRequest } = arguments_;
|
|
96
|
+
try {
|
|
97
|
+
const result = await board.markInReview(issue);
|
|
98
|
+
if (result.outcome === "unsupported") {
|
|
99
|
+
log(`Skipped advancing ${ticket} to in-review: ${result.reason}`);
|
|
100
|
+
logEvent("review", {
|
|
101
|
+
outcome: "skipped",
|
|
102
|
+
reason: "unsupported",
|
|
103
|
+
ticket,
|
|
104
|
+
});
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
log(`Advanced ${ticket} to in-review (PR ${pullRequest.url})`);
|
|
108
|
+
logEvent("review", {
|
|
109
|
+
outcome: "advanced",
|
|
110
|
+
ticket,
|
|
111
|
+
pr: pullRequest.url,
|
|
112
|
+
state: pullRequest.state,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
log(`Failed to advance ${ticket} to in-review: ${errorMessage(error)}`);
|
|
117
|
+
logEvent("review", { outcome: "failed", reason: "writeback_failed", ticket });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return { runOnce };
|
|
121
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/linear/factory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAIL,KAAK,KAAK,IAAI,cAAc,
|
|
1
|
+
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/linear/factory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAIL,KAAK,KAAK,IAAI,cAAc,EAG5B,KAAK,YAAY,EAClB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAEvD,OAAO,EAKL,KAAK,KAAK,IAAI,WAAW,EAE1B,MAAM,YAAY,CAAC;AAGpB;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,gEAAgE;IAChE,SAAS,EAAE,MAAM,CAAC;IAClB,iGAAiG;IACjG,YAAY,EAAE,MAAM,CAAC;CACtB;AAkFD,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,GAAG,cAAc,CAuB7F;AAED,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,mBAAmB,EAC3B,OAAO,EAAE,cAAc,GACtB,YAAY,CAyFd"}
|
|
@@ -186,5 +186,14 @@ export function createLinearTicketSource(config, context) {
|
|
|
186
186
|
teamId: ref.teamId,
|
|
187
187
|
});
|
|
188
188
|
},
|
|
189
|
+
async markInReview(issue) {
|
|
190
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- by the Linear adapter's contract, every Issue it produces carries a LinearSourceRef in sourceRef
|
|
191
|
+
const ref = issue.sourceRef;
|
|
192
|
+
return await getIssueStatusUpdater().markInReview({
|
|
193
|
+
id: issue.id,
|
|
194
|
+
uuid: ref.uuid,
|
|
195
|
+
teamId: ref.teamId,
|
|
196
|
+
});
|
|
197
|
+
},
|
|
189
198
|
};
|
|
190
199
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { LinearClient } from "@linear/sdk";
|
|
2
|
+
import type { MarkInReviewResult } from "../../ticketSource.ts";
|
|
2
3
|
interface LinearIssueReference {
|
|
3
4
|
id: string;
|
|
4
5
|
uuid: string;
|
|
@@ -6,6 +7,7 @@ interface LinearIssueReference {
|
|
|
6
7
|
}
|
|
7
8
|
interface LinearIssueStatusUpdater {
|
|
8
9
|
markInProgress(issue: LinearIssueReference): Promise<void>;
|
|
10
|
+
markInReview(issue: LinearIssueReference): Promise<MarkInReviewResult>;
|
|
9
11
|
}
|
|
10
12
|
export declare function createLinearIssueStatusUpdater(arguments_: {
|
|
11
13
|
client: LinearClient;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"writeback.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/linear/writeback.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"writeback.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/linear/writeback.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAGhE,UAAU,oBAAoB;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,wBAAwB;IAChC,cAAc,CAAC,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3D,YAAY,CAAC,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;CACxE;AAaD,wBAAgB,8BAA8B,CAAC,UAAU,EAAE;IACzD,MAAM,EAAE,YAAY,CAAC;CACtB,GAAG,wBAAwB,CAuD3B"}
|
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
import { debug } from "../../util.js";
|
|
2
|
+
// Linear maps every `started` workflow state to canonical in-progress today,
|
|
3
|
+
// so it cannot prove a successful in-review transition. Report unsupported
|
|
4
|
+
// until read/write sides can distinguish "In Review" from generic started.
|
|
5
|
+
async function markInReviewUnsupported(issue) {
|
|
6
|
+
debug(`markInReview is unsupported for ${issue.id} (Linear in-review not yet implemented)`);
|
|
7
|
+
return {
|
|
8
|
+
outcome: "unsupported",
|
|
9
|
+
reason: "Linear in-review writeback is not implemented",
|
|
10
|
+
};
|
|
11
|
+
}
|
|
2
12
|
export function createLinearIssueStatusUpdater(arguments_) {
|
|
3
13
|
const { client } = arguments_;
|
|
4
14
|
// Positive cache only. Keyed by teamId because the in-progress-state
|
|
@@ -47,5 +57,5 @@ export function createLinearIssueStatusUpdater(arguments_) {
|
|
|
47
57
|
await client.updateIssue(issue.uuid, { stateId });
|
|
48
58
|
debug(`Marked ${issue.id} as in progress`);
|
|
49
59
|
}
|
|
50
|
-
return { markInProgress };
|
|
60
|
+
return { markInProgress, markInReview: markInReviewUnsupported };
|
|
51
61
|
}
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* commands should configure `resolveOne` explicitly. No cross-call cache
|
|
12
12
|
* in MVP-2.
|
|
13
13
|
* - `markInProgress` absent → silent no-op.
|
|
14
|
+
* - `markInReview` absent → reports unsupported.
|
|
14
15
|
* - `fetch` is required by the Zod schema.
|
|
15
16
|
*/
|
|
16
17
|
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;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAGL,KAAK,KAAK,IAAI,cAAc,EAE5B,KAAK,YAAY,EAClB,MAAM,uBAAuB,CAAC;AAG/B,OAAO,EACL,KAAK,kBAAkB,EAEvB,KAAK,UAAU,EAEhB,MAAM,aAAa,CAAC;AA4BrB,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,CAoGd"}
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* commands should configure `resolveOne` explicitly. No cross-call cache
|
|
12
12
|
* in MVP-2.
|
|
13
13
|
* - `markInProgress` absent → silent no-op.
|
|
14
|
+
* - `markInReview` absent → reports unsupported.
|
|
14
15
|
* - `fetch` is required by the Zod schema.
|
|
15
16
|
*/
|
|
16
17
|
import { toCanonicalId, } from "../../ticketSource.js";
|
|
@@ -21,6 +22,7 @@ const DEFAULT_TIMEOUTS = {
|
|
|
21
22
|
fetch: 30_000,
|
|
22
23
|
resolveOne: 10_000,
|
|
23
24
|
markInProgress: 10_000,
|
|
25
|
+
markInReview: 10_000,
|
|
24
26
|
};
|
|
25
27
|
function mergeTimeouts(overrides) {
|
|
26
28
|
return {
|
|
@@ -28,6 +30,7 @@ function mergeTimeouts(overrides) {
|
|
|
28
30
|
fetch: overrides?.fetch ?? DEFAULT_TIMEOUTS.fetch,
|
|
29
31
|
resolveOne: overrides?.resolveOne ?? DEFAULT_TIMEOUTS.resolveOne,
|
|
30
32
|
markInProgress: overrides?.markInProgress ?? DEFAULT_TIMEOUTS.markInProgress,
|
|
33
|
+
markInReview: overrides?.markInReview ?? DEFAULT_TIMEOUTS.markInReview,
|
|
31
34
|
};
|
|
32
35
|
}
|
|
33
36
|
export function toCanonicalIssue(shellIssue, sourceName) {
|
|
@@ -68,6 +71,30 @@ export function createShellTicketSource(config, _context) {
|
|
|
68
71
|
const parsed = shellFetchOutputSchema.parse(JSON.parse(stdout));
|
|
69
72
|
return parsed.map((si) => toCanonicalIssue(si, sourceName));
|
|
70
73
|
}
|
|
74
|
+
// Shared by markInProgress / markInReview: both pipe the canonical issue's
|
|
75
|
+
// opaque sourceRef to a status-transition script on stdin, with the natural
|
|
76
|
+
// and canonical ids substituted into the command.
|
|
77
|
+
async function invokeWriteback(command, timeoutMs, issue) {
|
|
78
|
+
if (command === undefined) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const naturalId = issue.id.startsWith(`${sourceName}:`)
|
|
82
|
+
? issue.id.slice(sourceName.length + 1)
|
|
83
|
+
: issue.id;
|
|
84
|
+
await invokeShellCommand({
|
|
85
|
+
command,
|
|
86
|
+
timeoutMs,
|
|
87
|
+
cwd: config.cwd,
|
|
88
|
+
env: config.env,
|
|
89
|
+
substitutions: {
|
|
90
|
+
id: naturalId,
|
|
91
|
+
canonicalId: issue.id,
|
|
92
|
+
name: sourceName,
|
|
93
|
+
},
|
|
94
|
+
stdin: JSON.stringify(issue.sourceRef),
|
|
95
|
+
sourceName,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
71
98
|
return {
|
|
72
99
|
name: sourceName,
|
|
73
100
|
async verify() {
|
|
@@ -110,26 +137,17 @@ export function createShellTicketSource(config, _context) {
|
|
|
110
137
|
return toCanonicalIssue(parsed, sourceName);
|
|
111
138
|
},
|
|
112
139
|
async markInProgress(issue) {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
140
|
+
await invokeWriteback(config.commands.markInProgress, timeouts.markInProgress, issue);
|
|
141
|
+
},
|
|
142
|
+
async markInReview(issue) {
|
|
143
|
+
if (config.commands.markInReview === undefined) {
|
|
144
|
+
return {
|
|
145
|
+
outcome: "unsupported",
|
|
146
|
+
reason: `shell source "${sourceName}" has no commands.markInReview configured`,
|
|
147
|
+
};
|
|
116
148
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
: issue.id;
|
|
120
|
-
await invokeShellCommand({
|
|
121
|
-
command: markCommand,
|
|
122
|
-
timeoutMs: timeouts.markInProgress,
|
|
123
|
-
cwd: config.cwd,
|
|
124
|
-
env: config.env,
|
|
125
|
-
substitutions: {
|
|
126
|
-
id: naturalId,
|
|
127
|
-
canonicalId: issue.id,
|
|
128
|
-
name: sourceName,
|
|
129
|
-
},
|
|
130
|
-
stdin: JSON.stringify(issue.sourceRef),
|
|
131
|
-
sourceName,
|
|
132
|
-
});
|
|
149
|
+
await invokeWriteback(config.commands.markInReview, timeouts.markInReview, issue);
|
|
150
|
+
return { outcome: "applied" };
|
|
133
151
|
},
|
|
134
152
|
};
|
|
135
153
|
}
|
|
@@ -88,6 +88,7 @@ export declare const shellAdapterConfigSchema: z.ZodObject<{
|
|
|
88
88
|
fetch: z.ZodString;
|
|
89
89
|
resolveOne: z.ZodOptional<z.ZodString>;
|
|
90
90
|
markInProgress: z.ZodOptional<z.ZodString>;
|
|
91
|
+
markInReview: z.ZodOptional<z.ZodString>;
|
|
91
92
|
}, z.core.$strip>;
|
|
92
93
|
cwd: z.ZodOptional<z.ZodString>;
|
|
93
94
|
timeouts: z.ZodOptional<z.ZodObject<{
|
|
@@ -95,6 +96,7 @@ export declare const shellAdapterConfigSchema: z.ZodObject<{
|
|
|
95
96
|
fetch: z.ZodOptional<z.ZodNumber>;
|
|
96
97
|
resolveOne: z.ZodOptional<z.ZodNumber>;
|
|
97
98
|
markInProgress: z.ZodOptional<z.ZodNumber>;
|
|
99
|
+
markInReview: z.ZodOptional<z.ZodNumber>;
|
|
98
100
|
}, z.core.$strip>>;
|
|
99
101
|
env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
100
102
|
}, 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;;;;;;;;;;;;;;;;;;;iBA0BnC,CAAC;AAEH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC"}
|
|
@@ -46,6 +46,7 @@ export const shellAdapterConfigSchema = z.object({
|
|
|
46
46
|
fetch: z.string(),
|
|
47
47
|
resolveOne: z.string().optional(),
|
|
48
48
|
markInProgress: z.string().optional(),
|
|
49
|
+
markInReview: z.string().optional(),
|
|
49
50
|
}),
|
|
50
51
|
cwd: z.string().optional(),
|
|
51
52
|
timeouts: z
|
|
@@ -57,6 +58,7 @@ export const shellAdapterConfigSchema = z.object({
|
|
|
57
58
|
fetch: z.number().int().positive().optional(),
|
|
58
59
|
resolveOne: z.number().int().positive().optional(),
|
|
59
60
|
markInProgress: z.number().int().positive().optional(),
|
|
61
|
+
markInReview: z.number().int().positive().optional(),
|
|
60
62
|
})
|
|
61
63
|
.optional(),
|
|
62
64
|
env: z.record(z.string(), z.string()).optional(),
|
package/dist/lib/board.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Board composer — fans `verify` / `fetch` / `resolveOne` / `markInProgress`
|
|
3
|
-
* across N `TicketSource` adapters. Even a single-source config
|
|
4
|
-
* this; the moment we skip the wrapper we grow Linear assumptions
|
|
5
|
-
* consumers.
|
|
2
|
+
* Board composer — fans `verify` / `fetch` / `resolveOne` / `markInProgress` /
|
|
3
|
+
* `markInReview` across N `TicketSource` adapters. Even a single-source config
|
|
4
|
+
* goes through this; the moment we skip the wrapper we grow Linear assumptions
|
|
5
|
+
* back into consumers.
|
|
6
6
|
*/
|
|
7
|
-
import { type BoardState, type Issue, type TicketSource } from "./ticketSource.ts";
|
|
7
|
+
import { type BoardState, type Issue, type MarkInReviewResult, type TicketSource } from "./ticketSource.ts";
|
|
8
8
|
export interface Board {
|
|
9
9
|
verify(): Promise<void>;
|
|
10
10
|
fetch(): Promise<BoardState>;
|
|
@@ -15,6 +15,12 @@ export interface Board {
|
|
|
15
15
|
resolveOne(canonicalOrNaturalId: string): Promise<Issue | undefined>;
|
|
16
16
|
/** Routes to the adapter whose `name` matches `issue.source`. Unknown source throws. */
|
|
17
17
|
markInProgress(issue: Issue): Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* Advances a ticket to in-review on the adapter whose `name` matches
|
|
20
|
+
* `issue.source`. Unknown source throws. Adapters with no in-review concept
|
|
21
|
+
* return `unsupported` (see `TicketSource.markInReview`).
|
|
22
|
+
*/
|
|
23
|
+
markInReview(issue: Issue): Promise<MarkInReviewResult>;
|
|
18
24
|
}
|
|
19
25
|
export declare function createBoard(sources: readonly TicketSource[]): Board;
|
|
20
26
|
//# 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,
|
|
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;CACzD;AAqBD,wBAAgB,WAAW,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,GAAG,KAAK,CAwGnE"}
|
package/dist/lib/board.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Board composer — fans `verify` / `fetch` / `resolveOne` / `markInProgress`
|
|
3
|
-
* across N `TicketSource` adapters. Even a single-source config
|
|
4
|
-
* this; the moment we skip the wrapper we grow Linear assumptions
|
|
5
|
-
* consumers.
|
|
2
|
+
* Board composer — fans `verify` / `fetch` / `resolveOne` / `markInProgress` /
|
|
3
|
+
* `markInReview` across N `TicketSource` adapters. Even a single-source config
|
|
4
|
+
* goes through this; the moment we skip the wrapper we grow Linear assumptions
|
|
5
|
+
* back into consumers.
|
|
6
6
|
*/
|
|
7
7
|
import { AmbiguousTicketError, } from "./ticketSource.js";
|
|
8
8
|
async function callVerify(source) {
|
|
@@ -106,11 +106,22 @@ export function createBoard(sources) {
|
|
|
106
106
|
});
|
|
107
107
|
},
|
|
108
108
|
async markInProgress(issue) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
await source.markInProgress(issue);
|
|
109
|
+
await routeWriteback(byName, issue).markInProgress(issue);
|
|
110
|
+
},
|
|
111
|
+
async markInReview(issue) {
|
|
112
|
+
return await routeWriteback(byName, issue).markInReview(issue);
|
|
114
113
|
},
|
|
115
114
|
};
|
|
116
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* Resolve the adapter that owns `issue` for a writeback, by its `source` name.
|
|
118
|
+
* Shared by `markInProgress` / `markInReview` so both route — and fail —
|
|
119
|
+
* identically. Throws when no adapter claims the source.
|
|
120
|
+
*/
|
|
121
|
+
function routeWriteback(byName, issue) {
|
|
122
|
+
const source = byName.get(issue.source);
|
|
123
|
+
if (!source) {
|
|
124
|
+
throw new Error(`unknown source "${issue.source}" for issue ${issue.id}`);
|
|
125
|
+
}
|
|
126
|
+
return source;
|
|
127
|
+
}
|
|
@@ -104,6 +104,12 @@ export interface BoardState {
|
|
|
104
104
|
/** Parent tickets skipped because they have sub-issues. */
|
|
105
105
|
parentSkips: readonly ParentSkip[];
|
|
106
106
|
}
|
|
107
|
+
export type MarkInReviewResult = {
|
|
108
|
+
outcome: "applied";
|
|
109
|
+
} | {
|
|
110
|
+
outcome: "unsupported";
|
|
111
|
+
reason: string;
|
|
112
|
+
};
|
|
107
113
|
export interface TicketSource {
|
|
108
114
|
/** Stable identifier used as the id prefix and in log lines. Equal to the source's config `name`. */
|
|
109
115
|
readonly name: string;
|
|
@@ -115,6 +121,15 @@ export interface TicketSource {
|
|
|
115
121
|
resolveOne(naturalId: string): Promise<Issue | undefined>;
|
|
116
122
|
/** Writeback. The adapter downcasts `issue.sourceRef` internally. */
|
|
117
123
|
markInProgress(issue: Issue): Promise<void>;
|
|
124
|
+
/**
|
|
125
|
+
* Writeback: advance a ticket from in-progress to in-review once its
|
|
126
|
+
* worktree has an open PR. Frees a dispatch slot without tripping the
|
|
127
|
+
* cleaner's done-only teardown, so the worktree survives for review. The
|
|
128
|
+
* adapter downcasts `issue.sourceRef` internally. Adapters with no native
|
|
129
|
+
* in-review concept (or no configured command) MUST report `unsupported`
|
|
130
|
+
* rather than pretending the transition happened.
|
|
131
|
+
*/
|
|
132
|
+
markInReview(issue: Issue): Promise<MarkInReviewResult>;
|
|
118
133
|
/**
|
|
119
134
|
* Optional: return parent tickets that were excluded from `fetch()` because
|
|
120
135
|
* 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":"AAAA;;;;;;;;GAQG;AAEH;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,aAAa,GAAG,WAAW,GAAG,MAAM,GAAG,OAAO,CAAC;AAEtF,MAAM,WAAW,OAAO;IACtB,6DAA6D;IAC7D,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,eAAe,CAAC;IACxB;;;;;;;;;;;;;OAaG;IACH,YAAY,CAAC,EAAE,SAAS,GAAG,UAAU,CAAC;IACtC;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,KAAK;IACpB,kFAAkF;IAClF,EAAE,EAAE,MAAM,CAAC;IACX,oEAAoE;IACpE,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,eAAe,CAAC;IACxB,uEAAuE;IACvE,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,uGAAuG;IACvG,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,eAAe,EAAE,OAAO,CAAC;IACzB;;;;;OAKG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,wFAAwF;IACxF,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,mEAAmE;AACnE,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC;AAE5E,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,KAAK,GAAG,KAAK,IAAI,eAAe,CAExE;AAED;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB;;;;OAIG;IACH,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,2DAA2D;IAC3D,WAAW,EAAE,SAAS,UAAU,EAAE,CAAC;CACpC;AAED,MAAM,WAAW,YAAY;IAC3B,qGAAqG;IACrG,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,8EAA8E;IAC9E,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,oFAAoF;IACpF,KAAK,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;IAC1B,0EAA0E;IAC1E,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC;IAC1D,qEAAqE;IACrE,cAAc,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;
|
|
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
|
@@ -15,14 +15,23 @@ export default {
|
|
|
15
15
|
fetch: "~/.config/groundcrew/jira-fetch.sh",
|
|
16
16
|
resolveOne: "~/.config/groundcrew/jira-resolve.sh ${id}",
|
|
17
17
|
markInProgress: "jira issue move ${id} 'In Progress'",
|
|
18
|
+
markInReview: "jira issue move ${id} 'In Review'",
|
|
18
19
|
},
|
|
19
|
-
timeouts: { fetch: 60_000 },
|
|
20
|
+
timeouts: { fetch: 60_000, markInReview: 15_000 },
|
|
20
21
|
},
|
|
21
22
|
],
|
|
22
23
|
};
|
|
23
24
|
```
|
|
24
25
|
|
|
25
|
-
`commands.fetch` must print a JSON array of issues. `commands.resolveOne`, when
|
|
26
|
+
`commands.fetch` must print a JSON array of issues. `commands.resolveOne`, when
|
|
27
|
+
set, must print one issue, print nothing for "not found", or exit `3` for "not
|
|
28
|
+
found". `commands.markInProgress`, when set, receives the issue's `sourceRef` as
|
|
29
|
+
JSON on stdin. `commands.markInReview`, when set, receives the same `sourceRef`
|
|
30
|
+
and is run after groundcrew sees an open or merged PR on the ticket's worktree
|
|
31
|
+
branch. If `commands.markInReview` is omitted, groundcrew treats in-review
|
|
32
|
+
advancement as unsupported for that source and does not claim the transition
|
|
33
|
+
succeeded. `${id}`, `${canonicalId}`, and `${name}` placeholders are shell-quoted
|
|
34
|
+
before substitution.
|
|
26
35
|
|
|
27
36
|
```json
|
|
28
37
|
[
|
package/package.json
CHANGED