@clipboard-health/groundcrew 2.3.0 → 2.3.4

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.
@@ -69,6 +69,61 @@ interface ResolvedIssue {
69
69
  model: string;
70
70
  teamId: string;
71
71
  }
72
+ export interface RawLinearIssue {
73
+ uuid: string;
74
+ title: string;
75
+ description: string;
76
+ teamId: string;
77
+ labels: {
78
+ name: string;
79
+ }[];
80
+ /** Linear workflow state name, e.g. "Todo", "In Review". May be "" if state was null. */
81
+ stateName: string;
82
+ blockers: Blocker[];
83
+ hasMoreBlockers: boolean;
84
+ }
85
+ export declare function fetchBlockersForTicket(arguments_: {
86
+ client: LinearClient;
87
+ ticket: string;
88
+ uuid: string;
89
+ }): Promise<readonly Blocker[]>;
90
+ export declare function fetchRawLinearIssue(arguments_: {
91
+ client: LinearClient;
92
+ ticket: string;
93
+ }): Promise<RawLinearIssue>;
94
+ export declare function fetchInProgressIssueCount(arguments_: {
95
+ client: LinearClient;
96
+ config: ResolvedConfig;
97
+ }): Promise<number>;
98
+ export type RepositoryResolution = {
99
+ kind: "ok";
100
+ repository: string;
101
+ } | {
102
+ kind: "missing";
103
+ };
104
+ export declare function resolveRepositoryFor(arguments_: {
105
+ description: string | undefined;
106
+ config: ResolvedConfig;
107
+ ticket: string;
108
+ }): RepositoryResolution;
109
+ export type ModelResolution = {
110
+ kind: "matched";
111
+ model: string;
112
+ } | {
113
+ kind: "no-label";
114
+ } | {
115
+ kind: "agent-any";
116
+ } | {
117
+ kind: "disabled-fallback";
118
+ requestedModel: string;
119
+ fallbackModel: string;
120
+ };
121
+ export declare function resolveModelFor(arguments_: {
122
+ labels: {
123
+ name: string;
124
+ }[];
125
+ config: ResolvedConfig;
126
+ }): ModelResolution;
72
127
  /**
73
128
  * `agent-any` collapses to `models.default` here — manual setup doesn't run
74
129
  * the usage-gated `any` resolver, so the caller gets a concrete model name
@@ -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,EAA6C,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAM7F,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,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;CACpB,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;AAmMD,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,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,CAmDzB"}
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,EAA6C,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAM7F,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,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;CACpB,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;AA2MD,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,MAAM,CAAC;CAChB;AAKD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC3B,yFAAyF;IACzF,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,eAAe,EAAE,OAAO,CAAC;CAC1B;AAED,wBAAsB,sBAAsB,CAAC,UAAU,EAAE;IACvD,MAAM,EAAE,YAAY,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,OAAO,CAAC,SAAS,OAAO,EAAE,CAAC,CA8C9B;AAED,wBAAsB,mBAAmB,CAAC,UAAU,EAAE;IACpD,MAAM,EAAE,YAAY,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC,cAAc,CAAC,CAwD1B;AAOD,wBAAsB,yBAAyB,CAAC,UAAU,EAAE;IAC1D,MAAM,EAAE,YAAY,CAAC;IACrB,MAAM,EAAE,cAAc,CAAC;CACxB,GAAG,OAAO,CAAC,MAAM,CAAC,CAqClB;AAED,MAAM,MAAM,oBAAoB,GAAG;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC;AAE5F,wBAAgB,oBAAoB,CAAC,UAAU,EAAE;IAC/C,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,oBAAoB,CAUvB;AAED,MAAM,MAAM,eAAe,GACvB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,GACpB;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,GACrB;IAAE,IAAI,EAAE,mBAAmB,CAAC;IAAC,cAAc,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjF,wBAAgB,eAAe,CAAC,UAAU,EAAE;IAC1C,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC3B,MAAM,EAAE,cAAc,CAAC;CACxB,GAAG,eAAe,CAiBlB;AAED;;;;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,CAiCzB"}
@@ -129,9 +129,9 @@ async function fetchBoard(client, config) {
129
129
  const issues = nodes
130
130
  .filter((node) => node.children.nodes.length === 0)
131
131
  .map((node) => {
132
- const parsedAgentLabels = parseAgentLabels(node.labels.nodes, config);
133
- warnIfDisabledFallback(node.identifier, parsedAgentLabels, config);
134
- const repository = parsedAgentLabels === undefined
132
+ const modelResolution = resolveModelFor({ labels: node.labels.nodes, config });
133
+ warnIfDisabledFallback(node.identifier, modelResolution, config);
134
+ const repository = modelResolution.kind === "no-label"
135
135
  ? undefined
136
136
  : parseRepository({
137
137
  description: node.description ?? undefined,
@@ -139,6 +139,16 @@ async function fetchBoard(client, config) {
139
139
  repositoryRegex,
140
140
  ticket: node.identifier,
141
141
  });
142
+ let model;
143
+ if (modelResolution.kind === "matched") {
144
+ ({ model } = modelResolution);
145
+ }
146
+ else if (modelResolution.kind === "disabled-fallback") {
147
+ model = modelResolution.fallbackModel;
148
+ }
149
+ else if (modelResolution.kind === "agent-any") {
150
+ model = AGENT_ANY_MODEL;
151
+ }
142
152
  return {
143
153
  id: node.identifier.toLowerCase(),
144
154
  uuid: node.id,
@@ -148,7 +158,7 @@ async function fetchBoard(client, config) {
148
158
  assignee: node.assignee?.name ?? "Unassigned",
149
159
  updatedAt: node.updatedAt,
150
160
  repository,
151
- model: parsedAgentLabels?.model,
161
+ model,
152
162
  teamId: node.team?.id ?? "",
153
163
  blockers: blockersFromRelations(node.inverseRelations?.nodes ?? []),
154
164
  hasMoreBlockers: node.inverseRelations?.pageInfo.hasNextPage ?? false,
@@ -174,22 +184,64 @@ function buildRepositoryRegex(config) {
174
184
  return new RegExp(String.raw `\b(${alternation})\b`);
175
185
  }
176
186
  const ISSUE_LABEL_PAGE_SIZE = 50;
177
- /**
178
- * `agent-any` collapses to `models.default` here — manual setup doesn't run
179
- * the usage-gated `any` resolver, so the caller gets a concrete model name
180
- * instead of a sentinel that downstream code can't interpret.
181
- */
182
- export async function fetchResolvedIssue(arguments_) {
183
- const { client, config, ticket } = arguments_;
187
+ const ISSUE_RELATION_PAGE_SIZE = 50;
188
+ export async function fetchBlockersForTicket(arguments_) {
189
+ const { client, uuid } = arguments_;
190
+ const relations = [];
191
+ let after = null;
192
+ for (;;) {
193
+ // oxlint-disable-next-line no-await-in-loop -- pagination cursor depends on the previous response
194
+ const response = await client.client.rawRequest(`query IssueBlockers($id: String!, $after: String) {
195
+ issue(id: $id) {
196
+ inverseRelations(first: ${ISSUE_RELATION_PAGE_SIZE}, after: $after, includeArchived: false) {
197
+ nodes {
198
+ type
199
+ issue {
200
+ identifier
201
+ title
202
+ state { name }
203
+ }
204
+ }
205
+ pageInfo { hasNextPage endCursor }
206
+ }
207
+ }
208
+ }`, { id: uuid, after });
209
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- shape is fixed by our GraphQL query above
210
+ const { issue } = response.data;
211
+ if (issue === null) {
212
+ return [];
213
+ }
214
+ relations.push(...issue.inverseRelations.nodes);
215
+ if (!issue.inverseRelations.pageInfo.hasNextPage) {
216
+ break;
217
+ }
218
+ after = issue.inverseRelations.pageInfo.endCursor;
219
+ }
220
+ return blockersFromRelations(relations);
221
+ }
222
+ export async function fetchRawLinearIssue(arguments_) {
223
+ const { client, ticket } = arguments_;
184
224
  const response = await client.client.rawRequest(`query ResolveIssue($id: String!) {
185
225
  issue(id: $id) {
186
226
  id
187
227
  title
188
228
  description
189
229
  team { id }
230
+ state { name }
190
231
  labels(first: ${ISSUE_LABEL_PAGE_SIZE}) {
191
232
  nodes { name }
192
233
  }
234
+ inverseRelations(first: 50, includeArchived: false) {
235
+ nodes {
236
+ type
237
+ issue {
238
+ identifier
239
+ title
240
+ state { name }
241
+ }
242
+ }
243
+ pageInfo { hasNextPage }
244
+ }
193
245
  }
194
246
  }`, { id: ticket.toUpperCase() });
195
247
  // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- shape is fixed by our GraphQL query above
@@ -197,26 +249,119 @@ export async function fetchResolvedIssue(arguments_) {
197
249
  if (issue === null) {
198
250
  throw new Error(`Ticket ${ticket.toUpperCase()} not found in Linear`);
199
251
  }
200
- const description = issue.description ?? "";
201
- const repository = parseRepository({
202
- description,
252
+ return {
253
+ uuid: issue.id,
254
+ title: issue.title,
255
+ description: issue.description ?? "",
256
+ teamId: issue.team?.id ?? "",
257
+ labels: issue.labels.nodes,
258
+ stateName: issue.state?.name ?? "",
259
+ blockers: blockersFromRelations(issue.inverseRelations?.nodes ?? []),
260
+ hasMoreBlockers: issue.inverseRelations?.pageInfo.hasNextPage ?? false,
261
+ };
262
+ }
263
+ export async function fetchInProgressIssueCount(arguments_) {
264
+ const { client, config } = arguments_;
265
+ let after = null;
266
+ let count = 0;
267
+ for (;;) {
268
+ // oxlint-disable-next-line no-await-in-loop -- pagination cursor depends on the previous response
269
+ const response = await client.client.rawRequest(`query InProgressIssues($slugId: String!, $stateName: String!, $agentLabelPrefix: String!, $after: String) {
270
+ issues(
271
+ filter: {
272
+ project: { slugId: { eq: $slugId } }
273
+ state: { name: { eq: $stateName } }
274
+ labels: { some: { name: { startsWith: $agentLabelPrefix } } }
275
+ }
276
+ first: ${ISSUES_PAGE_SIZE}
277
+ after: $after
278
+ includeArchived: false
279
+ ) {
280
+ nodes { id }
281
+ pageInfo { hasNextPage endCursor }
282
+ }
283
+ }`, {
284
+ slugId: config.linear.slugId,
285
+ stateName: config.linear.statuses.inProgress,
286
+ agentLabelPrefix: AGENT_LABEL_PREFIX,
287
+ after,
288
+ });
289
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- shape is fixed by our GraphQL query above
290
+ const { issues: page } = response.data;
291
+ count += page.nodes.length;
292
+ if (!page.pageInfo.hasNextPage) {
293
+ return count;
294
+ }
295
+ after = page.pageInfo.endCursor;
296
+ }
297
+ }
298
+ export function resolveRepositoryFor(arguments_) {
299
+ const { description, config } = arguments_;
300
+ if (description === undefined || description.length === 0) {
301
+ return { kind: "missing" };
302
+ }
303
+ const repository = buildRepositoryRegex(config).exec(description)?.[1];
304
+ if (repository === undefined) {
305
+ return { kind: "missing" };
306
+ }
307
+ return { kind: "ok", repository };
308
+ }
309
+ export function resolveModelFor(arguments_) {
310
+ const { labels, config } = arguments_;
311
+ const parsed = parseAgentLabels(labels, config);
312
+ if (parsed === undefined) {
313
+ return { kind: "no-label" };
314
+ }
315
+ if (parsed.model === AGENT_ANY_MODEL) {
316
+ return { kind: "agent-any" };
317
+ }
318
+ if (parsed.disabledFallback !== undefined) {
319
+ return {
320
+ kind: "disabled-fallback",
321
+ requestedModel: parsed.disabledFallback,
322
+ fallbackModel: parsed.model,
323
+ };
324
+ }
325
+ return { kind: "matched", model: parsed.model };
326
+ }
327
+ /**
328
+ * `agent-any` collapses to `models.default` here — manual setup doesn't run
329
+ * the usage-gated `any` resolver, so the caller gets a concrete model name
330
+ * instead of a sentinel that downstream code can't interpret.
331
+ */
332
+ export async function fetchResolvedIssue(arguments_) {
333
+ const { client, config, ticket } = arguments_;
334
+ const raw = await fetchRawLinearIssue({ client, ticket });
335
+ const repositoryResolution = resolveRepositoryFor({
336
+ description: raw.description,
203
337
  config,
204
- repositoryRegex: buildRepositoryRegex(config),
205
338
  ticket: ticket.toUpperCase(),
206
339
  });
340
+ if (repositoryResolution.kind === "missing") {
341
+ throw new RepositoryResolutionError({
342
+ ticket: ticket.toUpperCase(),
343
+ repositories: config.workspace.knownRepositories,
344
+ });
345
+ }
207
346
  // Manual setup is an explicit per-ticket opt-in by the user, so an
208
347
  // unlabeled ticket still resolves to `models.default` — different from
209
348
  // the auto-pickup path, where unlabeled tickets are ignored.
210
- const parsed = parseAgentLabels(issue.labels.nodes, config);
211
- warnIfDisabledFallback(ticket, parsed, config);
212
- const model = parsed === undefined || parsed.model === AGENT_ANY_MODEL ? config.models.default : parsed.model;
349
+ const modelResolution = resolveModelFor({ labels: raw.labels, config });
350
+ warnIfDisabledFallback(ticket, modelResolution, config);
351
+ let model = config.models.default;
352
+ if (modelResolution.kind === "matched") {
353
+ ({ model } = modelResolution);
354
+ }
355
+ else if (modelResolution.kind === "disabled-fallback") {
356
+ model = modelResolution.fallbackModel;
357
+ }
213
358
  return {
214
- uuid: issue.id,
215
- title: issue.title,
216
- description,
217
- repository,
359
+ uuid: raw.uuid,
360
+ title: raw.title,
361
+ description: raw.description,
362
+ repository: repositoryResolution.repository,
218
363
  model,
219
- teamId: issue.team?.id ?? "",
364
+ teamId: raw.teamId,
220
365
  };
221
366
  }
222
367
  function parseRepository(arguments_) {
@@ -278,11 +423,11 @@ function parseAgentLabels(labels, config) {
278
423
  }
279
424
  return fallback;
280
425
  }
281
- function warnIfDisabledFallback(ticket, parsed, config) {
282
- if (parsed?.disabledFallback === undefined) {
426
+ function warnIfDisabledFallback(ticket, modelResolution, config) {
427
+ if (modelResolution.kind !== "disabled-fallback") {
283
428
  return;
284
429
  }
285
- log(`${ticket.toLowerCase()}: agent-${parsed.disabledFallback} label refers to a disabled model; falling back to models.default (${config.models.default})`);
430
+ log(`${ticket.toLowerCase()}: agent-${modelResolution.requestedModel} label refers to a disabled model; falling back to models.default (${config.models.default})`);
286
431
  }
287
432
  function blockersFromRelations(relations) {
288
433
  return relations
@@ -1,15 +1,14 @@
1
1
  import type { SandboxDefinition } from "./config.ts";
2
2
  /**
3
- * Derive a deterministic sbx sandbox name from the repository + model
4
- * tuple so `crew sandbox auth <repo>` and the subsequent `crew local`
5
- * launch agree on which sandbox to target. Lowercased and reduced to the
6
- * sbx-safe charset (`a-z0-9.+-`) so unusual repo names still round-trip
7
- * cleanly. Keep the prefix stable — doctor and teardown use it to
3
+ * Derive a deterministic sbx sandbox name from the sbx agent so every
4
+ * groundcrew model that targets the same agent reuses one sandbox across
5
+ * repositories and tickets. Lowercased and reduced to the sbx-safe
6
+ * charset (`a-z0-9.+-`) so unusual agent names still round-trip cleanly.
7
+ * Keep the `groundcrew-` prefix stable — doctor and teardown use it to
8
8
  * identify groundcrew-owned sandboxes.
9
9
  */
10
10
  export declare function sandboxNameFor(arguments_: {
11
- repository: string;
12
- model: string;
11
+ agent: string;
13
12
  }): string;
14
13
  /**
15
14
  * Probe `sbx ls` to see whether a sandbox with `sandboxName` already
@@ -1 +1 @@
1
- {"version":3,"file":"dockerSandbox.d.ts","sourceRoot":"","sources":["../../src/lib/dockerSandbox.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAErD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAMxF;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CAM/F;AAED,UAAU,sBAAsB;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,iBAAiB,CAAC;IAC3B;;;;OAIG;IACH,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;GAOG;AACH,wBAAsB,aAAa,CACjC,UAAU,EAAE,sBAAsB,EAClC,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,IAAI,CAAC,CAqBf"}
1
+ {"version":3,"file":"dockerSandbox.d.ts","sourceRoot":"","sources":["../../src/lib/dockerSandbox.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAErD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAMpE;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CAM/F;AAED,UAAU,sBAAsB;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,iBAAiB,CAAC;IAC3B;;;;OAIG;IACH,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;GAOG;AACH,wBAAsB,aAAa,CACjC,UAAU,EAAE,sBAAsB,EAClC,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,IAAI,CAAC,CAqBf"}
@@ -1,14 +1,14 @@
1
1
  import { runCommandAsync } from "./commandRunner.js";
2
2
  /**
3
- * Derive a deterministic sbx sandbox name from the repository + model
4
- * tuple so `crew sandbox auth <repo>` and the subsequent `crew local`
5
- * launch agree on which sandbox to target. Lowercased and reduced to the
6
- * sbx-safe charset (`a-z0-9.+-`) so unusual repo names still round-trip
7
- * cleanly. Keep the prefix stable — doctor and teardown use it to
3
+ * Derive a deterministic sbx sandbox name from the sbx agent so every
4
+ * groundcrew model that targets the same agent reuses one sandbox across
5
+ * repositories and tickets. Lowercased and reduced to the sbx-safe
6
+ * charset (`a-z0-9.+-`) so unusual agent names still round-trip cleanly.
7
+ * Keep the `groundcrew-` prefix stable — doctor and teardown use it to
8
8
  * identify groundcrew-owned sandboxes.
9
9
  */
10
10
  export function sandboxNameFor(arguments_) {
11
- const raw = `groundcrew-${arguments_.repository}-${arguments_.model}`.toLowerCase();
11
+ const raw = `groundcrew-${arguments_.agent}`.toLowerCase();
12
12
  return raw
13
13
  .replaceAll(/[^a-z0-9.+-]+/g, "-")
14
14
  .replaceAll(/-+/g, "-")
@@ -1 +1 @@
1
- {"version":3,"file":"workspaces.d.ts","sourceRoot":"","sources":["../../src/lib/workspaces.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACxE,OAAO,EAA0B,KAAK,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAI1E,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,MAAM,CAAC;AAE5C,MAAM,WAAW,SAAS;IACxB,2CAA2C;IAC3C,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,eAAe,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,QAAQ;IACvB,+CAA+C;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,+CAA+C;IAC/C,GAAG,EAAE,MAAM,CAAC;IACZ,qEAAqE;IACrE,OAAO,EAAE,MAAM,CAAC;IAChB,4EAA4E;IAC5E,MAAM,CAAC,EAAE,eAAe,CAAC;CAC1B;AAED;;;GAGG;AACH,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAgU7C,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,oBAAoB,CAAC;IAChC,QAAQ,EAAE,aAAa,CAAC;IACxB,yDAAyD;IACzD,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,gBAAgB;IACxB,MAAM,EAAE,cAAc,CAAC;IACvB,IAAI,EAAE,gBAAgB,CAAC;CACxB;AAED,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,gBAAgB,GAAG,mBAAmB,CAUtF;AA+ND,iBAAe,eAAe,CAC5B,MAAM,EAAE,cAAc,EACtB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,cAAc,CAAC,CAezB;AAED,eAAO,MAAM,UAAU;IACf,IAAI,SAAS,cAAc,QAAQ,QAAQ,WAAW,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvF,KAAK;IACC,KAAK,SAAS,cAAc,QAAQ,MAAM,WAAW,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAIhF,UAAU,SACN,cAAc,QAChB,MAAM,WACH,WAAW,GACnB,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC;CAI5C,CAAC"}
1
+ {"version":3,"file":"workspaces.d.ts","sourceRoot":"","sources":["../../src/lib/workspaces.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACxE,OAAO,EAA0B,KAAK,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAI1E,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,MAAM,CAAC;AAE5C,MAAM,WAAW,SAAS;IACxB,2CAA2C;IAC3C,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,eAAe,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,QAAQ;IACvB,+CAA+C;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,+CAA+C;IAC/C,GAAG,EAAE,MAAM,CAAC;IACZ,qEAAqE;IACrE,OAAO,EAAE,MAAM,CAAC;IAChB,4EAA4E;IAC5E,MAAM,CAAC,EAAE,eAAe,CAAC;CAC1B;AAED;;;GAGG;AACH,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAsU7C,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,oBAAoB,CAAC;IAChC,QAAQ,EAAE,aAAa,CAAC;IACxB,yDAAyD;IACzD,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,gBAAgB;IACxB,MAAM,EAAE,cAAc,CAAC;IACvB,IAAI,EAAE,gBAAgB,CAAC;CACxB;AAED,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,gBAAgB,GAAG,mBAAmB,CAUtF;AA+ND,iBAAe,eAAe,CAC5B,MAAM,EAAE,cAAc,EACtB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,cAAc,CAAC,CAezB;AAED,eAAO,MAAM,UAAU;IACf,IAAI,SAAS,cAAc,QAAQ,QAAQ,WAAW,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvF,KAAK;IACC,KAAK,SAAS,cAAc,QAAQ,MAAM,WAAW,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAIhF,UAAU,SACN,cAAc,QAChB,MAAM,WACH,WAAW,GACnB,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC;CAI5C,CAAC"}
@@ -179,6 +179,9 @@ async function applyCmuxStatus(workspaceId, status, signal) {
179
179
  async function closeCmuxWorkspace(workspaceId, signal) {
180
180
  await runWorkspaceCommand("cmux", ["close-workspace", "--workspace", workspaceId], signal);
181
181
  }
182
+ function isCmuxSetStatusUnsupported(error) {
183
+ return errorMessage(error).includes('unknown command "set-status"');
184
+ }
182
185
  const cmuxAdapter = {
183
186
  async open(spec, signal) {
184
187
  const inheritedRemote = await probeCurrentCmuxRemote(signal);
@@ -203,10 +206,12 @@ const cmuxAdapter = {
203
206
  await applyCmuxStatus(workspaceId, spec.status, signal);
204
207
  }
205
208
  catch (error) {
206
- // v2 cmux builds may not implement `set-status`; status pills are
207
- // a nice-to-have, not load-bearing. Log and keep the workspace
208
- // rather than tearing down a successful launch.
209
- log(`cmux set-status failed for ${spec.name} (continuing): ${errorMessage(error)}`);
209
+ // Status pills are best-effort. cmux v2+ dropped `set-status` entirely,
210
+ // so swallow that specific gap silently; surface anything else so a real
211
+ // regression doesn't hide behind the same swallow.
212
+ if (!isCmuxSetStatusUnsupported(error)) {
213
+ log(`cmux set-status failed for ${spec.name} (continuing): ${errorMessage(error)}`);
214
+ }
210
215
  }
211
216
  }
212
217
  },
@@ -1 +1 @@
1
- {"version":3,"file":"worktrees.d.ts","sourceRoot":"","sources":["../../src/lib/worktrees.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAOH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,OAAO,EAAE,KAAK,cAAc,EAAc,MAAM,iBAAiB,CAAC;AAIlE,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC;AAElC,qBAAa,0BAA2B,SAAQ,KAAK;IACnD,SAAgB,GAAG,EAAE,MAAM,CAAC;IAE5B,YAAmB,GAAG,EAAE,MAAM,EAI7B;CACF;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,0BAA0B,CAEhG;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,sDAAsD;IACtD,MAAM,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,YAAY,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB;AAoSD,iBAAS,IAAI,CAAC,MAAM,EAAE,cAAc,GAAG,aAAa,EAAE,CAErD;AAED,iBAAS,YAAY,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,GAAG,aAAa,EAAE,CAE7E;AAED,iBAAe,MAAM,CACnB,MAAM,EAAE,cAAc,EACtB,IAAI,EAAE,YAAY,EAClB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,aAAa,CAAC,CAQxB;AAED,iBAAe,MAAM,CACnB,MAAM,EAAE,cAAc,EACtB,KAAK,EAAE,aAAa,EACpB,OAAO,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,GAClD,OAAO,CAAC,IAAI,CAAC,CAKf;AAED,MAAM,MAAM,YAAY,GAAG,iBAAiB,GAAG,iBAAiB,CAAC;AAEjE,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,aAAa,CAAC;IACrB,IAAI,EAAE,YAAY,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,+DAA+D;IAC/D,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,sCAAsC;IACtC,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,wDAAwD;IACxD,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,cAAc,EAAE,cAAc,CAAC;CAChC;AAKD,iBAAe,QAAQ,CACrB,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,SAAS,aAAa,EAAE,EACjC,OAAO,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,GAClD,OAAO,CAAC,cAAc,CAAC,CAkDzB;AAED,eAAO,MAAM,SAAS;;;;;;CAMrB,CAAC"}
1
+ {"version":3,"file":"worktrees.d.ts","sourceRoot":"","sources":["../../src/lib/worktrees.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAOH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,OAAO,EAAE,KAAK,cAAc,EAAc,MAAM,iBAAiB,CAAC;AAIlE,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC;AAElC,qBAAa,0BAA2B,SAAQ,KAAK;IACnD,SAAgB,GAAG,EAAE,MAAM,CAAC;IAE5B,YAAmB,GAAG,EAAE,MAAM,EAI7B;CACF;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,0BAA0B,CAEhG;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,sDAAsD;IACtD,MAAM,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,YAAY,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB;AAoUD,iBAAS,IAAI,CAAC,MAAM,EAAE,cAAc,GAAG,aAAa,EAAE,CAErD;AAED,iBAAS,YAAY,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,GAAG,aAAa,EAAE,CAE7E;AAED,iBAAe,MAAM,CACnB,MAAM,EAAE,cAAc,EACtB,IAAI,EAAE,YAAY,EAClB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,aAAa,CAAC,CAQxB;AAED,iBAAe,MAAM,CACnB,MAAM,EAAE,cAAc,EACtB,KAAK,EAAE,aAAa,EACpB,OAAO,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,GAClD,OAAO,CAAC,IAAI,CAAC,CAKf;AAED,MAAM,MAAM,YAAY,GAAG,iBAAiB,GAAG,iBAAiB,CAAC;AAEjE,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,aAAa,CAAC;IACrB,IAAI,EAAE,YAAY,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,+DAA+D;IAC/D,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,sCAAsC;IACtC,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,wDAAwD;IACxD,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,cAAc,EAAE,cAAc,CAAC;CAChC;AAKD,iBAAe,QAAQ,CACrB,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,SAAS,aAAa,EAAE,EACjC,OAAO,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,GAClD,OAAO,CAAC,cAAc,CAAC,CAkDzB;AAED,eAAO,MAAM,SAAS;;;;;;CAMrB,CAAC"}
@@ -174,23 +174,31 @@ async function removeWorktree(config, entry, options) {
174
174
  await runCommandAsync("git", removeArguments, longRunningCommandOptions(options.signal));
175
175
  }
176
176
  catch (error) {
177
- // git's `fatal: ... use --force to delete it` line goes to inherited
178
- // stderr, so the captured error is just "Exit status: 128". Probe the
179
- // worktree ourselves so the failure message explains the condition
180
- // (modified/untracked files) and points at `crew cleanup --force`.
177
+ // git's `fatal: ...` diagnostic goes to inherited stderr, so the
178
+ // captured error is just "Exit status: 128". Probe the worktree
179
+ // ourselves so the failure message names the condition — dirty
180
+ // (modified/untracked files, fixable with `crew cleanup --force`) or
181
+ // orphan (directory exists on disk but is not registered with the
182
+ // parent repo, requires manual inspection + `rm -rf`).
181
183
  if (options.force || options.signal?.aborted === true) {
182
184
  throw error;
183
185
  }
184
186
  const dirtiness = await probeWorktreeDirtiness(entry.dir, options.signal);
185
- if (dirtiness.kind !== "dirty") {
186
- throw error;
187
+ if (dirtiness.kind === "dirty") {
188
+ throw new Error(describeDirtyWorktree({
189
+ ticket: entry.ticket,
190
+ dir: entry.dir,
191
+ modified: dirtiness.modified,
192
+ untracked: dirtiness.untracked,
193
+ }), { cause: error });
194
+ }
195
+ if (dirtiness.kind === "unknown") {
196
+ const registration = await probeWorktreeRegistration(entry.dir, options.signal);
197
+ if (registration === "orphan") {
198
+ throw new Error(describeOrphanWorktree({ dir: entry.dir }), { cause: error });
199
+ }
187
200
  }
188
- throw new Error(describeDirtyWorktree({
189
- ticket: entry.ticket,
190
- dir: entry.dir,
191
- modified: dirtiness.modified,
192
- untracked: dirtiness.untracked,
193
- }), { cause: error });
201
+ throw error;
194
202
  }
195
203
  }
196
204
  else {
@@ -243,6 +251,20 @@ function describeDirtyWorktree(arguments_) {
243
251
  const pronoun = modified + untracked === 1 ? "it" : "them";
244
252
  return `worktree has ${summary}. Run \`crew cleanup --force ${ticket}\` to discard ${pronoun}, or commit/stash in ${dir} first.`;
245
253
  }
254
+ async function probeWorktreeRegistration(worktreeDir, signal) {
255
+ let output;
256
+ try {
257
+ output = await runCommandAsync("git", ["-C", worktreeDir, "rev-parse", "--is-inside-work-tree"], signalProperty(signal));
258
+ }
259
+ catch {
260
+ return "unknown";
261
+ }
262
+ return output === "true" ? "registered" : "orphan";
263
+ }
264
+ function describeOrphanWorktree(arguments_) {
265
+ const { dir } = arguments_;
266
+ return `directory exists but is not a registered git worktree. If ${dir} has nothing of value, \`rm -rf\` ${dir} manually; otherwise inspect it before deleting.`;
267
+ }
246
268
  function list(config) {
247
269
  return listWorktrees(config);
248
270
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clipboard-health/groundcrew",
3
- "version": "2.3.0",
3
+ "version": "2.3.4",
4
4
  "description": "Linear-driven orchestrator that launches AI coding agents in git worktrees, with workspace lifecycle and usage tracking.",
5
5
  "keywords": [
6
6
  "agent",
@@ -0,0 +1,15 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 620 120" width="620" height="120" role="img" aria-label="groundcrew">
3
+ <style>
4
+ .wordmark {
5
+ font-family: ui-monospace, "SFMono-Regular", Menlo, Consolas, "Liberation Mono", "DejaVu Sans Mono", monospace;
6
+ font-size: 92px;
7
+ font-weight: 700;
8
+ letter-spacing: -0.04em;
9
+ }
10
+ </style>
11
+ <text class="wordmark" x="20" y="92" textLength="540" lengthAdjust="spacingAndGlyphs" fill="#e4e4e7">groundcrew</text>
12
+ <rect x="572" y="24" width="30" height="72" fill="#77d94e">
13
+ <animate attributeName="opacity" values="1;0" dur="1.2s" calcMode="discrete" keyTimes="0;0.5" repeatCount="indefinite"/>
14
+ </rect>
15
+ </svg>
@@ -0,0 +1,15 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 620 120" width="620" height="120" role="img" aria-label="groundcrew">
3
+ <style>
4
+ .wordmark {
5
+ font-family: ui-monospace, "SFMono-Regular", Menlo, Consolas, "Liberation Mono", "DejaVu Sans Mono", monospace;
6
+ font-size: 92px;
7
+ font-weight: 700;
8
+ letter-spacing: -0.04em;
9
+ }
10
+ </style>
11
+ <text class="wordmark" x="20" y="92" textLength="540" lengthAdjust="spacingAndGlyphs" fill="#18181b">groundcrew</text>
12
+ <rect x="572" y="24" width="30" height="72" fill="#77d94e">
13
+ <animate attributeName="opacity" values="1;0" dur="1.2s" calcMode="discrete" keyTimes="0;0.5" repeatCount="indefinite"/>
14
+ </rect>
15
+ </svg>
@@ -1,9 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
- <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
3
- <svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="225 230 575 530" width="1024.0pt" height="944.0pt">
4
- <path d="M 491.97 239.05 C 526.13 236.93 560.37 240.48 593.00 251.00 C 626.41 261.79 657.69 279.64 683.66 303.32 C 707.08 325.06 726.40 351.86 738.47 381.51 C 744.03 394.32 747.45 407.46 750.90 420.93 C 762.33 420.53 773.79 425.00 781.76 433.25 C 788.68 441.04 792.39 451.62 792.07 462.00 C 792.08 487.67 792.17 513.33 792.05 539.00 C 792.27 545.73 790.51 552.54 787.54 558.55 C 783.74 566.04 777.45 571.67 770.09 575.55 C 769.92 579.70 770.08 583.85 769.99 588.00 C 768.69 602.43 766.81 616.79 757.36 628.43 C 756.74 632.58 757.53 636.52 755.44 640.46 C 752.06 648.40 745.48 652.22 738.31 656.27 C 732.17 659.48 726.14 662.23 719.02 660.89 C 712.87 659.35 707.07 655.43 704.53 649.45 C 703.05 645.51 702.46 641.30 703.50 637.16 C 704.33 631.93 708.45 627.64 712.25 624.25 C 716.71 620.56 721.78 617.56 726.88 614.85 C 732.77 611.92 740.93 611.14 746.43 615.32 C 747.51 614.30 747.88 613.62 748.20 612.15 C 751.08 601.64 751.97 590.54 751.90 579.68 C 745.31 580.33 738.62 579.86 732.00 580.03 C 720.74 580.20 709.30 575.59 703.14 565.80 C 699.76 560.81 698.29 554.97 697.99 549.01 C 698.02 514.60 697.92 480.42 698.18 446.00 C 698.56 440.21 699.47 434.75 703.08 430.01 C 706.08 425.60 710.41 423.81 715.11 421.80 C 706.69 386.13 686.06 353.49 659.23 328.77 C 646.53 316.55 632.06 306.71 616.51 298.46 C 593.44 285.91 568.21 277.82 542.16 274.50 C 536.07 273.94 530.13 272.79 524.00 272.97 C 517.66 272.59 511.34 272.32 504.99 272.77 C 499.95 273.21 495.01 272.70 490.00 273.58 C 454.29 276.95 418.96 289.01 389.27 309.26 C 362.61 326.89 340.47 351.37 325.30 379.49 C 319.33 390.66 314.19 402.41 310.72 414.61 C 309.94 417.28 308.92 419.78 308.91 422.62 C 318.36 424.43 324.44 433.67 324.21 443.00 C 324.27 477.65 324.12 512.34 324.20 547.00 C 324.14 550.61 324.24 554.28 323.83 557.87 C 321.66 565.85 315.98 572.12 308.64 575.78 C 300.05 580.18 291.39 580.22 282.00 580.01 C 267.62 580.66 253.23 576.52 243.12 565.88 C 234.68 557.31 231.16 545.84 230.97 534.00 C 231.02 512.33 230.96 490.67 231.00 469.00 C 231.03 464.43 230.69 460.00 231.64 455.49 C 233.10 443.19 240.79 431.64 251.69 425.73 C 258.00 422.14 265.62 420.75 272.81 420.86 C 278.14 395.31 287.67 370.76 301.76 348.76 C 308.87 337.50 316.93 326.73 326.09 317.05 C 330.25 312.27 334.65 307.58 339.54 303.53 C 365.80 279.49 398.14 261.74 432.05 251.14 C 451.47 244.59 471.61 240.98 491.97 239.05 Z" fill="#78716c" />
5
- <path d="M 356.00 444.19 C 452.27 444.23 548.55 444.15 644.82 444.23 C 649.66 444.23 654.22 444.56 657.84 448.15 C 661.26 451.27 662.52 455.45 662.47 459.99 C 662.31 525.35 662.51 590.59 662.40 656.00 C 662.38 661.55 662.63 667.04 661.94 672.56 C 659.55 678.86 653.83 683.18 647.00 683.04 C 547.66 683.01 448.31 683.05 348.96 683.02 C 340.58 682.87 333.60 675.32 333.95 667.00 C 333.95 599.00 333.96 531.00 333.94 463.00 C 333.95 460.46 333.79 457.81 334.22 455.29 C 335.23 451.68 337.14 448.37 340.50 446.48 C 345.39 443.64 350.60 444.17 356.00 444.19 Z" fill="#78716c" />
6
- <path d="M 389.00 483.91 C 392.46 483.66 394.50 486.02 397.14 487.82 C 416.17 501.83 435.17 515.92 454.42 529.63 C 456.37 531.09 458.13 532.56 458.89 534.98 C 459.10 537.05 459.34 539.53 458.56 541.50 C 457.15 543.90 454.68 545.81 452.49 547.46 C 434.16 560.87 415.99 574.32 397.64 587.70 C 395.69 589.05 393.38 590.56 391.00 590.95 C 386.00 590.89 381.37 587.28 381.55 581.96 C 381.26 576.63 385.28 574.69 388.98 571.94 C 404.61 560.64 420.06 549.02 435.97 538.11 C 419.10 525.62 402.11 513.26 385.25 500.75 C 382.37 498.55 379.93 495.86 380.05 491.99 C 380.79 487.65 384.18 483.40 389.00 483.91 Z" fill="#77d94e" />
7
- <path d="M 672.78 521.32 C 677.82 520.86 682.12 520.21 686.27 523.73 C 691.33 527.85 690.31 533.17 690.62 539.00 C 690.46 604.02 690.92 668.98 690.42 734.01 C 690.17 739.13 688.58 744.11 684.46 747.42 C 681.30 750.32 677.21 751.32 673.00 751.12 C 577.34 751.12 481.66 751.13 386.00 751.08 C 377.59 751.55 369.36 743.47 369.97 735.00 C 370.03 721.31 369.99 707.63 369.97 693.94 C 460.31 694.02 550.66 693.99 641.00 693.96 C 652.00 694.60 663.63 688.89 669.36 679.38 C 672.64 674.56 672.40 668.57 672.51 663.00 C 672.55 618.67 672.51 574.33 672.55 530.00 C 672.50 527.10 672.50 524.21 672.78 521.32 Z" fill="#78716c" />
8
- <path d="M 473.99 577.90 C 490.65 577.77 507.33 577.94 524.00 577.84 C 527.49 577.86 531.25 577.49 534.68 578.19 C 539.56 579.86 541.50 585.54 539.02 590.00 C 537.21 593.42 533.72 594.68 530.03 594.77 C 512.35 594.96 494.68 594.68 477.00 594.87 C 473.37 594.93 470.04 594.29 467.64 591.35 C 465.40 589.27 465.80 585.98 466.16 583.24 C 467.44 580.11 470.43 577.60 473.99 577.90 Z" fill="#77d94e" />
9
- </svg>