@clipboard-health/groundcrew 1.10.1 → 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;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,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";
@@ -245,4 +246,9 @@ export async function setupWorkspaceCli(ticket, options = {}) {
245
246
  runner: resolved.runner,
246
247
  details: { title: resolved.title, description: resolved.description },
247
248
  });
249
+ await createLinearIssueStatusUpdater({ config, client }).markInProgress({
250
+ id: ticket.toLowerCase(),
251
+ uuid: resolved.uuid,
252
+ teamId: resolved.teamId,
253
+ });
248
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.1",
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",