@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.
@@ -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,EACL,KAAK,UAAU,EAIhB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACvD,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,CAoQjE"}
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, } from "../lib/boardSource.js";
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 inProgressStateByTeam = new Map();
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
- teamsMissingInProgress = new Set();
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;AAa1B,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;AAkED,wBAAsB,cAAc,CAClC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,qBAAqB,EAC9B,UAAU,GAAE,wBAA6B,GACxC,OAAO,CAAC,IAAI,CAAC,CA6Ef;AAuHD,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GACjC,OAAO,CAAC,IAAI,CAAC,CAoBf"}
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 launchCmd = buildLaunchCommand({
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 = `bash ${shellSingleQuote(stageLaunchScript(promptDir, remoteLaunchCommand))}`;
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;CACzB;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,CAyCzB"}
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"}
@@ -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 { title: issue.title, description, repository, model, runner };
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.0",
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": "16.4.0",
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",