@clipboard-health/groundcrew 1.10.0 → 1.10.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/dispatcher.d.ts.map +1 -1
- package/dist/commands/dispatcher.js +5 -41
- package/dist/commands/setupWorkspace.d.ts.map +1 -1
- package/dist/commands/setupWorkspace.js +12 -2
- package/dist/lib/boardSource.d.ts +2 -0
- package/dist/lib/boardSource.d.ts.map +1 -1
- package/dist/lib/boardSource.js +11 -1
- package/dist/lib/linearIssueStatus.d.ts +17 -0
- package/dist/lib/linearIssueStatus.d.ts.map +1 -0
- package/dist/lib/linearIssueStatus.js +41 -0
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dispatcher.d.ts","sourceRoot":"","sources":["../../src/commands/dispatcher.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,
|
|
1
|
+
{"version":3,"file":"dispatcher.d.ts","sourceRoot":"","sources":["../../src/commands/dispatcher.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,EAAE,KAAK,UAAU,EAA2C,MAAM,uBAAuB,CAAC;AACjG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAGpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAczD,UAAU,cAAc;IACtB,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,EAAE,YAAY,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,CAAC,UAAU,EAAE;QAClB,KAAK,EAAE,UAAU,CAAC;QAClB,eAAe,EAAE,SAAS,aAAa,EAAE,CAAC;QAC1C,+FAA+F;QAC/F,KAAK,EAAE,CAAC,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;QACvD,MAAM,EAAE,OAAO,CAAC;QAChB,MAAM,CAAC,EAAE,WAAW,CAAC;KACtB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnB;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,cAAc,GAAG,UAAU,CAwNjE"}
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
* Pure verdict logic lives in `eligibility.ts`; this module is responsible
|
|
7
7
|
* for telemetry, Linear writes, and side-effecting setupWorkspace calls.
|
|
8
8
|
*/
|
|
9
|
-
import { isGroundcrewIssue
|
|
9
|
+
import { isGroundcrewIssue } from "../lib/boardSource.js";
|
|
10
|
+
import { createLinearIssueStatusUpdater } from "../lib/linearIssueStatus.js";
|
|
10
11
|
import { errorMessage, log, logEvent } from "../lib/util.js";
|
|
11
12
|
import { workspaces } from "../lib/workspaces.js";
|
|
12
13
|
import { classifyBlockers, classifyEligibility, } from "./eligibility.js";
|
|
@@ -17,8 +18,7 @@ const MINUTES_PER_DAY = 24 * 60;
|
|
|
17
18
|
const MINUTES_PER_WEEK = DAYS_PER_WEEK * MINUTES_PER_DAY;
|
|
18
19
|
export function createDispatcher(deps) {
|
|
19
20
|
const { config, client } = deps;
|
|
20
|
-
const
|
|
21
|
-
let teamsMissingInProgress = new Set();
|
|
21
|
+
const issueStatusUpdater = createLinearIssueStatusUpdater({ config, client });
|
|
22
22
|
function buildExhaustedSet(usage) {
|
|
23
23
|
const exhausted = new Set();
|
|
24
24
|
const sessionLimit = config.orchestrator.sessionLimitPercentage;
|
|
@@ -49,42 +49,6 @@ export function createDispatcher(deps) {
|
|
|
49
49
|
}
|
|
50
50
|
return exhausted;
|
|
51
51
|
}
|
|
52
|
-
async function getInProgressStateId(teamId) {
|
|
53
|
-
if (teamId.length === 0) {
|
|
54
|
-
return undefined;
|
|
55
|
-
}
|
|
56
|
-
const cached = inProgressStateByTeam.get(teamId);
|
|
57
|
-
if (cached !== undefined) {
|
|
58
|
-
return cached;
|
|
59
|
-
}
|
|
60
|
-
// Negative cache is per-iteration so a team that's fixed in Linear during
|
|
61
|
-
// a `crew watch` session auto-recovers next tick. Within one iteration,
|
|
62
|
-
// every eligible ticket in a misconfigured team would otherwise re-fetch.
|
|
63
|
-
if (teamsMissingInProgress.has(teamId)) {
|
|
64
|
-
return undefined;
|
|
65
|
-
}
|
|
66
|
-
const team = await client.team(teamId);
|
|
67
|
-
const states = await team.states();
|
|
68
|
-
const inProgress = states.nodes.find((state) => state.name === config.linear.statuses.inProgress);
|
|
69
|
-
if (inProgress?.id === undefined) {
|
|
70
|
-
teamsMissingInProgress.add(teamId);
|
|
71
|
-
return undefined;
|
|
72
|
-
}
|
|
73
|
-
inProgressStateByTeam.set(teamId, inProgress.id);
|
|
74
|
-
return inProgress.id;
|
|
75
|
-
}
|
|
76
|
-
async function markInProgress(issue) {
|
|
77
|
-
const stateId = await getInProgressStateId(issue.teamId);
|
|
78
|
-
if (stateId === undefined) {
|
|
79
|
-
// Throw rather than log+return: if we silently swallowed this, the
|
|
80
|
-
// ticket would stay Todo forever while the workspace runs, which means
|
|
81
|
-
// every iteration re-enters the recovery path and the agent never
|
|
82
|
-
// counts toward maximumInProgress.
|
|
83
|
-
throw new Error(`Could not find "${config.linear.statuses.inProgress}" state for ${issue.id} (team ${issue.teamId.length > 0 ? issue.teamId : "?"}). Verify the status name in linear.statuses.inProgress matches the team's workflow.`);
|
|
84
|
-
}
|
|
85
|
-
await client.updateIssue(issue.uuid, { stateId });
|
|
86
|
-
log(`Marked ${issue.id} as ${config.linear.statuses.inProgress}`);
|
|
87
|
-
}
|
|
88
52
|
function logSkip(verdict) {
|
|
89
53
|
log(verdict.message);
|
|
90
54
|
logEvent("dispatch", {
|
|
@@ -130,7 +94,7 @@ export function createDispatcher(deps) {
|
|
|
130
94
|
? setupWorkspace(config, setupOptions)
|
|
131
95
|
: setupWorkspace(config, setupOptions, { signal }));
|
|
132
96
|
}
|
|
133
|
-
await markInProgress(issue);
|
|
97
|
+
await issueStatusUpdater.markInProgress(issue);
|
|
134
98
|
logEvent("dispatch", {
|
|
135
99
|
outcome: recovery ? "resumed" : "started",
|
|
136
100
|
ticket: issue.id,
|
|
@@ -153,7 +117,7 @@ export function createDispatcher(deps) {
|
|
|
153
117
|
}
|
|
154
118
|
async function runOnce(arguments_) {
|
|
155
119
|
const { state, worktreeEntries, usage, dryRun, signal } = arguments_;
|
|
156
|
-
|
|
120
|
+
issueStatusUpdater.resetMissingInProgressCache();
|
|
157
121
|
const activeCount = state.issues.filter((issue) => issue.status === config.linear.statuses.inProgress).length;
|
|
158
122
|
const slots = config.orchestrator.maximumInProgress - activeCount;
|
|
159
123
|
// Narrow Todo to tickets that opted in via an `agent-*` label.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setupWorkspace.d.ts","sourceRoot":"","sources":["../../src/commands/setupWorkspace.ts"],"names":[],"mappings":"AAOA,OAAO,EAGL,KAAK,cAAc,EACnB,KAAK,eAAe,EACrB,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"setupWorkspace.d.ts","sourceRoot":"","sources":["../../src/commands/setupWorkspace.ts"],"names":[],"mappings":"AAOA,OAAO,EAGL,KAAK,cAAc,EACnB,KAAK,eAAe,EACrB,MAAM,kBAAkB,CAAC;AAc1B,UAAU,aAAa;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;CACrB;AAgBD,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB,wEAAwE;IACxE,OAAO,CAAC,EAAE,aAAa,CAAC;CACzB;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAsED,wBAAsB,cAAc,CAClC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,qBAAqB,EAC9B,UAAU,GAAE,wBAA6B,GACxC,OAAO,CAAC,IAAI,CAAC,CA8Ef;AAuHD,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GACjC,OAAO,CAAC,IAAI,CAAC,CAyBf"}
|
|
@@ -6,6 +6,7 @@ import { fetchResolvedIssue } from "../lib/boardSource.js";
|
|
|
6
6
|
import { BUILD_SECRET_NAMES, loadConfig, } from "../lib/config.js";
|
|
7
7
|
import { detectHostCapabilities } from "../lib/host.js";
|
|
8
8
|
import { buildLaunchCommand, buildRemoteLaunchCommand, shellSingleQuote, } from "../lib/launchCommand.js";
|
|
9
|
+
import { createLinearIssueStatusUpdater } from "../lib/linearIssueStatus.js";
|
|
9
10
|
import { assertLocalRunnerRequirements } from "../lib/localRunner.js";
|
|
10
11
|
import { getRemoteRunnerProvider } from "../lib/spriteRemoteRunnerProvider.js";
|
|
11
12
|
import { errorMessage, getLinearClient, log, readEnvironmentVariable } from "../lib/util.js";
|
|
@@ -54,6 +55,9 @@ function stageLaunchScript(promptDir, command) {
|
|
|
54
55
|
writeFileSync(launcherFile, `#!/usr/bin/env bash\n${command}\n`, { mode: 0o700 });
|
|
55
56
|
return launcherFile;
|
|
56
57
|
}
|
|
58
|
+
function stageWorkspaceLaunchCommand(promptDir, command) {
|
|
59
|
+
return `bash ${shellSingleQuote(stageLaunchScript(promptDir, command))}`;
|
|
60
|
+
}
|
|
57
61
|
function stagePrompt(input) {
|
|
58
62
|
const promptDir = mkdtempSync(join(tmpdir(), `groundcrew-${input.ticket}-`));
|
|
59
63
|
const promptFile = join(promptDir, "prompt.txt");
|
|
@@ -109,12 +113,13 @@ export async function setupWorkspace(config, options, runOptions = {}) {
|
|
|
109
113
|
const stagedPrompt = stagePrompt({ config, ticket, ticketDetails, worktreeName });
|
|
110
114
|
promptDir = stagedPrompt.directory;
|
|
111
115
|
const secretsFile = stageBuildSecrets(promptDir);
|
|
112
|
-
const
|
|
116
|
+
const launchCommand = buildLaunchCommand({
|
|
113
117
|
definition,
|
|
114
118
|
promptFile: stagedPrompt.file,
|
|
115
119
|
worktreeDir: launchDir,
|
|
116
120
|
secretsFile,
|
|
117
121
|
});
|
|
122
|
+
const launchCmd = stageWorkspaceLaunchCommand(promptDir, launchCommand);
|
|
118
123
|
log("Opening workspace...");
|
|
119
124
|
await workspaces.open(config, {
|
|
120
125
|
name: ticket,
|
|
@@ -171,7 +176,7 @@ async function setupRemoteWorkspace(arguments_) {
|
|
|
171
176
|
secretNames: config.remote.secretNames,
|
|
172
177
|
...(secretsFile === undefined ? {} : { secretsFile, remoteSecretsFile }),
|
|
173
178
|
});
|
|
174
|
-
const launchCmd =
|
|
179
|
+
const launchCmd = stageWorkspaceLaunchCommand(promptDir, remoteLaunchCommand);
|
|
175
180
|
log("Opening workspace...");
|
|
176
181
|
await workspaces.open(config, {
|
|
177
182
|
name: ticket,
|
|
@@ -241,4 +246,9 @@ export async function setupWorkspaceCli(ticket, options = {}) {
|
|
|
241
246
|
runner: resolved.runner,
|
|
242
247
|
details: { title: resolved.title, description: resolved.description },
|
|
243
248
|
});
|
|
249
|
+
await createLinearIssueStatusUpdater({ config, client }).markInProgress({
|
|
250
|
+
id: ticket.toLowerCase(),
|
|
251
|
+
uuid: resolved.uuid,
|
|
252
|
+
teamId: resolved.teamId,
|
|
253
|
+
});
|
|
244
254
|
}
|
|
@@ -65,11 +65,13 @@ interface BoardSourceDeps {
|
|
|
65
65
|
export declare function createBoardSource(deps: BoardSourceDeps): BoardSource;
|
|
66
66
|
export declare function isTerminalStatus(status: string, config: ResolvedConfig): boolean;
|
|
67
67
|
interface ResolvedIssue {
|
|
68
|
+
uuid: string;
|
|
68
69
|
title: string;
|
|
69
70
|
description: string;
|
|
70
71
|
repository: string;
|
|
71
72
|
model: string;
|
|
72
73
|
runner: WorkspaceRunner;
|
|
74
|
+
teamId: string;
|
|
73
75
|
}
|
|
74
76
|
/**
|
|
75
77
|
* `agent-any` collapses to `models.default` here — manual setup doesn't run
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"boardSource.d.ts","sourceRoot":"","sources":["../../src/lib/boardSource.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,EAGL,KAAK,cAAc,EACnB,KAAK,eAAe,EACrB,MAAM,aAAa,CAAC;AAMrB,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5B;AAED,MAAM,WAAW,KAAK;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,0FAA0F;IAC1F,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,0FAA0F;IAC1F,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,0FAA0F;IAC1F,MAAM,EAAE,eAAe,GAAG,SAAS,CAAC;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,eAAe,EAAE,OAAO,CAAC;CAC1B;AAED;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,eAAe,CAAC;CACzB,CAAC;AAEF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,KAAK,GAAG,KAAK,IAAI,eAAe,CAExE;AAED,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,KAAK,EAAE,CAAC;CACjB;AAED,qBAAa,yBAA0B,SAAQ,KAAK;IAClD,YAAmB,UAAU,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAA;KAAE,EAMjF;CACF;AAED,MAAM,WAAW,WAAW;IAC1B;;;OAGG;IACH,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,8DAA8D;IAC9D,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC,CAAC;CAC9B;AAED,UAAU,eAAe;IACvB,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,EAAE,YAAY,CAAC;CACtB;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,eAAe,GAAG,WAAW,CAUpE;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,GAAG,OAAO,CAEhF;AAgMD,UAAU,aAAa;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"boardSource.d.ts","sourceRoot":"","sources":["../../src/lib/boardSource.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,EAGL,KAAK,cAAc,EACnB,KAAK,eAAe,EACrB,MAAM,aAAa,CAAC;AAMrB,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5B;AAED,MAAM,WAAW,KAAK;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,0FAA0F;IAC1F,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,0FAA0F;IAC1F,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,0FAA0F;IAC1F,MAAM,EAAE,eAAe,GAAG,SAAS,CAAC;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,eAAe,EAAE,OAAO,CAAC;CAC1B;AAED;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,eAAe,CAAC;CACzB,CAAC;AAEF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,KAAK,GAAG,KAAK,IAAI,eAAe,CAExE;AAED,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,KAAK,EAAE,CAAC;CACjB;AAED,qBAAa,yBAA0B,SAAQ,KAAK;IAClD,YAAmB,UAAU,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAA;KAAE,EAMjF;CACF;AAED,MAAM,WAAW,WAAW;IAC1B;;;OAGG;IACH,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,8DAA8D;IAC9D,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC,CAAC;CAC9B;AAED,UAAU,eAAe;IACvB,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,EAAE,YAAY,CAAC;CACtB;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,eAAe,GAAG,WAAW,CAUpE;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,GAAG,OAAO,CAEhF;AAgMD,UAAU,aAAa;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,eAAe,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;CAChB;AAID;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,UAAU,EAAE;IACnD,MAAM,EAAE,YAAY,CAAC;IACrB,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC,aAAa,CAAC,CAqDzB"}
|
package/dist/lib/boardSource.js
CHANGED
|
@@ -180,8 +180,10 @@ export async function fetchResolvedIssue(arguments_) {
|
|
|
180
180
|
const { client, config, ticket } = arguments_;
|
|
181
181
|
const response = await client.client.rawRequest(`query ResolveIssue($id: String!) {
|
|
182
182
|
issue(id: $id) {
|
|
183
|
+
id
|
|
183
184
|
title
|
|
184
185
|
description
|
|
186
|
+
team { id }
|
|
185
187
|
labels(first: ${ISSUE_LABEL_PAGE_SIZE}) {
|
|
186
188
|
nodes { name }
|
|
187
189
|
}
|
|
@@ -206,7 +208,15 @@ export async function fetchResolvedIssue(arguments_) {
|
|
|
206
208
|
warnIfDisabledFallback(ticket, parsed, config);
|
|
207
209
|
const model = parsed === undefined || parsed.model === AGENT_ANY_MODEL ? config.models.default : parsed.model;
|
|
208
210
|
const runner = parsed?.runner ?? "local";
|
|
209
|
-
return {
|
|
211
|
+
return {
|
|
212
|
+
uuid: issue.id,
|
|
213
|
+
title: issue.title,
|
|
214
|
+
description,
|
|
215
|
+
repository,
|
|
216
|
+
model,
|
|
217
|
+
runner,
|
|
218
|
+
teamId: issue.team?.id ?? "",
|
|
219
|
+
};
|
|
210
220
|
}
|
|
211
221
|
function parseRepository(arguments_) {
|
|
212
222
|
const { description, config, repositoryRegex, ticket } = arguments_;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { LinearClient } from "@linear/sdk";
|
|
2
|
+
import type { ResolvedConfig } from "./config.ts";
|
|
3
|
+
interface LinearIssueReference {
|
|
4
|
+
id: string;
|
|
5
|
+
uuid: string;
|
|
6
|
+
teamId: string;
|
|
7
|
+
}
|
|
8
|
+
interface LinearIssueStatusUpdater {
|
|
9
|
+
markInProgress(issue: LinearIssueReference): Promise<void>;
|
|
10
|
+
resetMissingInProgressCache(): void;
|
|
11
|
+
}
|
|
12
|
+
export declare function createLinearIssueStatusUpdater(arguments_: {
|
|
13
|
+
config: ResolvedConfig;
|
|
14
|
+
client: LinearClient;
|
|
15
|
+
}): LinearIssueStatusUpdater;
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=linearIssueStatus.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"linearIssueStatus.d.ts","sourceRoot":"","sources":["../../src/lib/linearIssueStatus.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAGlD,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,2BAA2B,IAAI,IAAI,CAAC;CACrC;AAED,wBAAgB,8BAA8B,CAAC,UAAU,EAAE;IACzD,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,EAAE,YAAY,CAAC;CACtB,GAAG,wBAAwB,CAgD3B"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { log } from "./util.js";
|
|
2
|
+
export function createLinearIssueStatusUpdater(arguments_) {
|
|
3
|
+
const { config, client } = arguments_;
|
|
4
|
+
const inProgressStateByTeam = new Map();
|
|
5
|
+
let teamsMissingInProgress = new Set();
|
|
6
|
+
async function getInProgressStateId(teamId) {
|
|
7
|
+
if (teamId.length === 0) {
|
|
8
|
+
return undefined;
|
|
9
|
+
}
|
|
10
|
+
const cached = inProgressStateByTeam.get(teamId);
|
|
11
|
+
if (cached !== undefined) {
|
|
12
|
+
return cached;
|
|
13
|
+
}
|
|
14
|
+
// Negative cache is reset by dispatcher each iteration so a team that's
|
|
15
|
+
// fixed in Linear during a watch session auto-recovers on the next tick.
|
|
16
|
+
if (teamsMissingInProgress.has(teamId)) {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
const team = await client.team(teamId);
|
|
20
|
+
const states = await team.states();
|
|
21
|
+
const inProgress = states.nodes.find((state) => state.name === config.linear.statuses.inProgress);
|
|
22
|
+
if (inProgress?.id === undefined) {
|
|
23
|
+
teamsMissingInProgress.add(teamId);
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
inProgressStateByTeam.set(teamId, inProgress.id);
|
|
27
|
+
return inProgress.id;
|
|
28
|
+
}
|
|
29
|
+
async function markInProgress(issue) {
|
|
30
|
+
const stateId = await getInProgressStateId(issue.teamId);
|
|
31
|
+
if (stateId === undefined) {
|
|
32
|
+
throw new Error(`Could not find "${config.linear.statuses.inProgress}" state for ${issue.id} (team ${issue.teamId.length > 0 ? issue.teamId : "?"}). Verify the status name in linear.statuses.inProgress matches the team's workflow.`);
|
|
33
|
+
}
|
|
34
|
+
await client.updateIssue(issue.uuid, { stateId });
|
|
35
|
+
log(`Marked ${issue.id} as ${config.linear.statuses.inProgress}`);
|
|
36
|
+
}
|
|
37
|
+
function resetMissingInProgressCache() {
|
|
38
|
+
teamsMissingInProgress = new Set();
|
|
39
|
+
}
|
|
40
|
+
return { markInProgress, resetMissingInProgressCache };
|
|
41
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clipboard-health/groundcrew",
|
|
3
|
-
"version": "1.10.
|
|
3
|
+
"version": "1.10.2",
|
|
4
4
|
"description": "Linear-driven orchestrator that launches AI coding agents in git worktrees, with workspace lifecycle, remote runners, and usage tracking.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent",
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
"husky": "9.1.7",
|
|
85
85
|
"jscpd": "4.0.9",
|
|
86
86
|
"knip": "6.9.0",
|
|
87
|
-
"lint-staged": "
|
|
87
|
+
"lint-staged": "17.0.4",
|
|
88
88
|
"markdownlint-cli2": "0.22.1",
|
|
89
89
|
"nx": "22.7.1",
|
|
90
90
|
"oxfmt": "0.47.0",
|