@clipboard-health/groundcrew 4.43.0 → 4.43.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":"setupWorkspace.d.ts","sourceRoot":"","sources":["../../src/commands/setupWorkspace.ts"],"names":[],"mappings":"AACA,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAyBnE,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,sEAAsE;IACtE,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,6FAA6F;IAC7F,2BAA2B,CAAC,EAAE,OAAO,CAAC;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAuBD,wBAAsB,cAAc,CAClC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,qBAAqB,EAC9B,UAAU,GAAE,wBAA6B,GACxC,OAAO,CAAC,IAAI,CAAC,CAqJf;AAgJD,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GACjC,OAAO,CAAC,IAAI,CAAC,CAkDf"}
1
+ {"version":3,"file":"setupWorkspace.d.ts","sourceRoot":"","sources":["../../src/commands/setupWorkspace.ts"],"names":[],"mappings":"AACA,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAyBnE,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,sEAAsE;IACtE,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,6FAA6F;IAC7F,2BAA2B,CAAC,EAAE,OAAO,CAAC;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAuBD,wBAAsB,cAAc,CAClC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,qBAAqB,EAC9B,UAAU,GAAE,wBAA6B,GACxC,OAAO,CAAC,IAAI,CAAC,CA+If;AA8MD,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GACjC,OAAO,CAAC,IAAI,CAAC,CAkDf"}
@@ -12,7 +12,7 @@ import { taskSourceWritePathsForCompletion } from "../lib/taskSourceFilesystem.j
12
12
  import { naturalIdFromCanonical } from "../lib/taskSource.js";
13
13
  import { debug, errorMessage, log, okMark } from "../lib/util.js";
14
14
  import { workspaces } from "../lib/workspaces.js";
15
- import { isWorktreeAlreadyExistsError, resolveLaunchDir, worktrees, } from "../lib/worktrees.js";
15
+ import { resolveLaunchDir, WorktreeAlreadyExistsError, worktrees, } from "../lib/worktrees.js";
16
16
  function stagePrompt(input) {
17
17
  return stagePromptFromTemplate({
18
18
  config: input.config,
@@ -41,17 +41,23 @@ export async function setupWorkspace(config, options, runOptions = {}) {
41
41
  purpose: "runs",
42
42
  ...(signal === undefined ? {} : { signal }),
43
43
  });
44
+ await preflightProvisioningGate({ config, options, signal });
44
45
  const spec = { repository, task };
45
- let created;
46
46
  const createdPromise = signal === undefined ? worktrees.create(config, spec) : worktrees.create(config, spec, signal);
47
47
  const readinessPromise = startLaunchReadiness(ensureReady);
48
+ let created;
48
49
  try {
49
50
  created = await createdPromise;
50
51
  }
51
52
  catch (error) {
52
- if (isWorktreeAlreadyExistsError(error)) {
53
- await logAccessHintForExistingWorkspace({ config, task, signal });
54
- }
53
+ // Roll the pre-flight `provisioning` row forward; the outer catch only
54
+ // fires post-create and the dispatcher just logs and moves on.
55
+ recordFailedToLaunch({
56
+ config,
57
+ options,
58
+ paths: worktrees.predictedEntry(config, repository, task),
59
+ error,
60
+ });
55
61
  throw error;
56
62
  }
57
63
  const { branchName, dir: worktreeDir } = created;
@@ -149,23 +155,61 @@ export async function setupWorkspace(config, options, runOptions = {}) {
149
155
  }
150
156
  catch (error) {
151
157
  await rollbackWorktree({ config, entry: created, promptDir, srtSettingsDir });
152
- recordRunStateBestEffort({
153
- config,
154
- task,
155
- repository,
156
- agent,
157
- worktreeDir,
158
- branchName,
159
- workspaceName: task,
160
- state: "failed-to-launch",
161
- detail: errorMessage(error),
162
- title: options.details.title,
163
- completionTaskId: options.completionTaskId ?? task,
164
- ...(options.details.url === undefined ? {} : { url: options.details.url }),
165
- });
158
+ recordFailedToLaunch({ config, options, paths: { worktreeDir, branchName }, error });
166
159
  throw error;
167
160
  }
168
161
  }
162
+ /**
163
+ * Bail out before any state-write when the worktree already exists, then
164
+ * record a "provisioning" row so `crew status` can surface the in-flight
165
+ * worktree create instead of falling back to "idle". The dispatcher
166
+ * serializes setupWorkspace calls per host, so the race against a parallel
167
+ * worktrees.create() can't realistically fire here — `worktrees.create()`
168
+ * still defends against it internally.
169
+ */
170
+ async function preflightProvisioningGate(arguments_) {
171
+ const { config, options, signal } = arguments_;
172
+ const { task, repository, agent } = options;
173
+ const existing = worktrees
174
+ .findByTask(config, task)
175
+ .find((entry) => entry.repository === repository);
176
+ if (existing !== undefined) {
177
+ await logAccessHintForExistingWorkspace({ config, task, signal });
178
+ throw new WorktreeAlreadyExistsError(existing.dir);
179
+ }
180
+ const predicted = worktrees.predictedEntry(config, repository, task);
181
+ recordRunStateBestEffort({
182
+ config,
183
+ task,
184
+ repository,
185
+ agent,
186
+ worktreeDir: predicted.worktreeDir,
187
+ branchName: predicted.branchName,
188
+ workspaceName: task,
189
+ state: "provisioning",
190
+ title: options.details.title,
191
+ completionTaskId: options.completionTaskId ?? task,
192
+ ...(options.details.url === undefined ? {} : { url: options.details.url }),
193
+ });
194
+ }
195
+ function recordFailedToLaunch(arguments_) {
196
+ const { config, options, paths, error } = arguments_;
197
+ const { task, repository, agent } = options;
198
+ recordRunStateBestEffort({
199
+ config,
200
+ task,
201
+ repository,
202
+ agent,
203
+ worktreeDir: paths.worktreeDir,
204
+ branchName: paths.branchName,
205
+ workspaceName: task,
206
+ state: "failed-to-launch",
207
+ detail: errorMessage(error),
208
+ title: options.details.title,
209
+ completionTaskId: options.completionTaskId ?? task,
210
+ ...(options.details.url === undefined ? {} : { url: options.details.url }),
211
+ });
212
+ }
169
213
  async function startLaunchReadiness(ensureReady) {
170
214
  try {
171
215
  await ensureReady();
@@ -1 +1 @@
1
- {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAIA,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAenE,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AA8qBD,wBAAsB,MAAM,CAAC,MAAM,EAAE,cAAc,EAAE,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAU/F;AAED,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAI7D"}
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAIA,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAenE,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAyrBD,wBAAsB,MAAM,CAAC,MAAM,EAAE,cAAc,EAAE,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAU/F;AAED,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAI7D"}
@@ -405,11 +405,14 @@ async function collectPullRequests(entries) {
405
405
  return [dir, result?.status === "fulfilled" ? result.value[1] : []];
406
406
  }));
407
407
  }
408
+ const ORPHANED_SESSIONS_HEADER = "Orphaned sessions (no matching worktree)";
409
+ const ORPHANED_SESSIONS_ACTION = "What to do: run 'crew stop <task>' to close the session, or 'tmux kill-session -t <task>' if no run-state exists.";
408
410
  function writeStraySessions(probe, worktreeTasks) {
409
411
  if (probe.kind === "unavailable") {
410
- // Surface probe failures so the user knows we couldn't classify strays
411
- // (silently dropping the section would hide that diagnostic).
412
- writeSection("Stray sessions");
412
+ // Surface probe failures so the user knows we couldn't classify orphans
413
+ // (silently dropping the section would hide that diagnostic). The action
414
+ // hint is omitted here — there's no row to act on.
415
+ writeSection(ORPHANED_SESSIONS_HEADER);
413
416
  writeOutput(workspaceProbeUnavailableLine(probe));
414
417
  return;
415
418
  }
@@ -417,7 +420,8 @@ function writeStraySessions(probe, worktreeTasks) {
417
420
  if (strays.length === 0) {
418
421
  return;
419
422
  }
420
- writeSection("Stray sessions");
423
+ writeSection(ORPHANED_SESSIONS_HEADER);
424
+ writeOutput(ORPHANED_SESSIONS_ACTION);
421
425
  writeOutput(strays.join("\n"));
422
426
  }
423
427
  function isTodoSourceIssue(issue) {
@@ -543,6 +547,8 @@ function writeInProgressIssue(issue) {
543
547
  writeOutput(inventoryField("repo", issue.repository));
544
548
  }
545
549
  }
550
+ const SLOT_HOLDERS_HEADER = "Slot holders with no local worktree";
551
+ const SLOT_HOLDERS_ACTION = "What to do: transition the ticket off 'in-progress' on the board, or run 'crew run <task>' to recreate the worktree locally.";
546
552
  function writeInProgressWithoutWorktree(boardResult, worktreeTasks) {
547
553
  if (boardResult.kind !== "ok") {
548
554
  return;
@@ -551,7 +557,8 @@ function writeInProgressWithoutWorktree(boardResult, worktreeTasks) {
551
557
  if (issues.length === 0) {
552
558
  return;
553
559
  }
554
- writeSection("In progress (no local worktree)");
560
+ writeSection(SLOT_HOLDERS_HEADER);
561
+ writeOutput(SLOT_HOLDERS_ACTION);
555
562
  for (const [index, issue] of issues.entries()) {
556
563
  if (index > 0) {
557
564
  writeOutput();
@@ -1,5 +1,5 @@
1
1
  import type { ResolvedConfig } from "./config.ts";
2
- export type RunLifecycleState = "running" | "interrupted" | "resumed" | "failed-to-launch";
2
+ export type RunLifecycleState = "provisioning" | "running" | "interrupted" | "resumed" | "failed-to-launch";
3
3
  export interface RunState {
4
4
  task: string;
5
5
  repository: string;
@@ -1 +1 @@
1
- {"version":3,"file":"runState.d.ts","sourceRoot":"","sources":["../../src/lib/runState.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAGlD,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,aAAa,GAAG,SAAS,GAAG,kBAAkB,CAAC;AAE3F,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,iBAAiB,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,iBAAiB,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,cAAc,CAAC;IACvB,KAAK,EAAE,aAAa,CAAC;CACtB;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,cAAc,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,WAAW,GAAG,MAAM,CAAC,CAAC,GAAG;QACrD,KAAK,EAAE,iBAAiB,CAAC;KAC1B,CAAC;CACH;AAQD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,IAAI,CAAC,cAAc,EAAE,SAAS,CAAC,GAAG,MAAM,CAEjF;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,cAAc,EAAE,SAAS,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAE1F;AAyFD,wBAAgB,YAAY,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAYvF;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,QAAQ,CA8BnE;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,QAAQ,GAAG,SAAS,CAc/E;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAEzE"}
1
+ {"version":3,"file":"runState.d.ts","sourceRoot":"","sources":["../../src/lib/runState.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAGlD,MAAM,MAAM,iBAAiB,GACzB,cAAc,GACd,SAAS,GACT,aAAa,GACb,SAAS,GACT,kBAAkB,CAAC;AAEvB,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,iBAAiB,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,iBAAiB,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,cAAc,CAAC;IACvB,KAAK,EAAE,aAAa,CAAC;CACtB;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,cAAc,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,WAAW,GAAG,MAAM,CAAC,CAAC,GAAG;QACrD,KAAK,EAAE,iBAAiB,CAAC;KAC1B,CAAC;CACH;AAQD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,IAAI,CAAC,cAAc,EAAE,SAAS,CAAC,GAAG,MAAM,CAEjF;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,cAAc,EAAE,SAAS,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAE1F;AA0FD,wBAAgB,YAAY,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAYvF;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,QAAQ,CA8BnE;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,QAAQ,GAAG,SAAS,CAc/E;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAEzE"}
@@ -22,7 +22,8 @@ function stringField(value, key) {
22
22
  return typeof field === "string" && field.length > 0 ? field : undefined;
23
23
  }
24
24
  function isRunLifecycleState(value) {
25
- return (value === "running" ||
25
+ return (value === "provisioning" ||
26
+ value === "running" ||
26
27
  value === "interrupted" ||
27
28
  value === "resumed" ||
28
29
  value === "failed-to-launch");
@@ -15,7 +15,6 @@ export declare class WorktreeAlreadyExistsError extends Error {
15
15
  readonly dir: string;
16
16
  constructor(dir: string);
17
17
  }
18
- export declare function isWorktreeAlreadyExistsError(error: unknown): error is WorktreeAlreadyExistsError;
19
18
  export interface WorktreeEntry {
20
19
  repository: string;
21
20
  /** Source task id, lowercased — e.g. "team-220" or "gc-20260608-001". */
@@ -61,6 +60,11 @@ export type WorktreeDirtiness = {
61
60
  };
62
61
  declare function list(config: ResolvedConfig): WorktreeEntry[];
63
62
  declare function findByTask(config: ResolvedConfig, task: string): WorktreeEntry[];
63
+ export interface PredictedWorktreeEntry {
64
+ branchName: string;
65
+ worktreeDir: string;
66
+ }
67
+ declare function predictedEntry(config: ResolvedConfig, repository: string, task: string): PredictedWorktreeEntry;
64
68
  declare function create(config: ResolvedConfig, spec: WorktreeSpec, signal?: AbortSignal): Promise<WorktreeEntry>;
65
69
  declare function open(config: ResolvedConfig, spec: WorktreeOpenSpec, signal?: AbortSignal): Promise<WorktreeEntry>;
66
70
  declare function remove(config: ResolvedConfig, entry: WorktreeEntry, options?: {
@@ -95,6 +99,7 @@ export declare const worktrees: {
95
99
  open: typeof open;
96
100
  list: typeof list;
97
101
  findByTask: typeof findByTask;
102
+ predictedEntry: typeof predictedEntry;
98
103
  remove: typeof remove;
99
104
  teardown: typeof teardown;
100
105
  branchNameForTask: typeof branchNameForTask;
@@ -1 +1 @@
1
- {"version":3,"file":"worktrees.d.ts","sourceRoot":"","sources":["../../src/lib/worktrees.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"worktrees.d.ts","sourceRoot":"","sources":["../../src/lib/worktrees.ts"],"names":[],"mappings":"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"}
@@ -28,9 +28,6 @@ export class WorktreeAlreadyExistsError extends Error {
28
28
  this.name = "WorktreeAlreadyExistsError";
29
29
  }
30
30
  }
31
- export function isWorktreeAlreadyExistsError(error) {
32
- return error instanceof WorktreeAlreadyExistsError;
33
- }
34
31
  function branchPrefix(config) {
35
32
  const fromConfig = config.git.branchPrefix;
36
33
  if (fromConfig !== undefined) {
@@ -192,17 +189,37 @@ async function createWorktree(config, spec, signal) {
192
189
  const base = basePaths(config, spec.repository, spec.task);
193
190
  const recipe = recipeFor(config, spec.repository);
194
191
  if (recipe.provision === undefined) {
195
- const defaultBranch = await resolveDefaultBranch({
196
- repoDir: base.repoDir,
197
- remote: config.git.remote,
198
- fallback: config.git.defaultBranch,
199
- ...signalProperty(signal),
200
- });
201
- const baseRef = `${config.git.remote}/${defaultBranch}`;
202
- debug(`Fetching ${baseRef} in ${spec.repository}...`);
203
- await runLongGitCommand(["-C", base.repoDir, "fetch", config.git.remote, defaultBranch], signal);
204
- debug(`Creating worktree ${spec.repository}-${spec.task} (branch ${base.branchName} from ${baseRef})...`);
205
- await runLongGitCommand(["-C", base.repoDir, "worktree", "add", "-b", base.branchName, base.hostWorktreeDir, baseRef], signal);
192
+ // A prior run can leave the `<prefix>-<task>` branch behind after its
193
+ // worktree directory is gone — teardown deletes the branch only
194
+ // best-effort, and operators sometimes remove the directory by hand.
195
+ // Attaching the surviving branch reuses its work instead of crashing on
196
+ // `git worktree add -b <existing branch>`.
197
+ if (await localBranchExists(base.repoDir, base.branchName, signal)) {
198
+ debug(`Branch ${base.branchName} already exists; attaching it to worktree ${spec.repository}-${spec.task}...`);
199
+ await runLongGitCommand(["-C", base.repoDir, "worktree", "add", base.hostWorktreeDir, base.branchName], signal);
200
+ }
201
+ else {
202
+ const defaultBranch = await resolveDefaultBranch({
203
+ repoDir: base.repoDir,
204
+ remote: config.git.remote,
205
+ fallback: config.git.defaultBranch,
206
+ ...signalProperty(signal),
207
+ });
208
+ const baseRef = `${config.git.remote}/${defaultBranch}`;
209
+ debug(`Fetching ${baseRef} in ${spec.repository}...`);
210
+ await runLongGitCommand(["-C", base.repoDir, "fetch", config.git.remote, defaultBranch], signal);
211
+ debug(`Creating worktree ${spec.repository}-${spec.task} (branch ${base.branchName} from ${baseRef})...`);
212
+ await runLongGitCommand([
213
+ "-C",
214
+ base.repoDir,
215
+ "worktree",
216
+ "add",
217
+ "-b",
218
+ base.branchName,
219
+ base.hostWorktreeDir,
220
+ baseRef,
221
+ ], signal);
222
+ }
206
223
  }
207
224
  else {
208
225
  const command = applySubstitutions(recipe.provision.create, provisionerSubstitutions(config, {
@@ -532,6 +549,13 @@ function list(config) {
532
549
  function findByTask(config, task) {
533
550
  return list(config).filter((entry) => entry.task === task);
534
551
  }
552
+ // Deterministic preview of where worktree create() would land. Lets callers
553
+ // record run state (e.g. "provisioning") before the worktree exists on disk,
554
+ // without duplicating the path-derivation rules in basePaths().
555
+ function predictedEntry(config, repository, task) {
556
+ const { branchName, hostWorktreeDir } = basePaths(config, repository, task);
557
+ return { branchName, worktreeDir: hostWorktreeDir };
558
+ }
535
559
  // Shared by create() and open(): reject a duplicate worktree for the same
536
560
  // task+repo before building, then assert the configured workdir materialized,
537
561
  // rolling the worktree back if it did not. The build callback owns the git
@@ -631,6 +655,7 @@ export const worktrees = {
631
655
  open,
632
656
  list,
633
657
  findByTask,
658
+ predictedEntry,
634
659
  remove,
635
660
  teardown,
636
661
  branchNameForTask,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clipboard-health/groundcrew",
3
- "version": "4.43.0",
3
+ "version": "4.43.2",
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",
@@ -83,12 +83,12 @@
83
83
  "@tsconfig/node24": "24.0.4",
84
84
  "@tsconfig/strictest": "2.0.8",
85
85
  "@types/node": "25.9.3",
86
- "@typescript/native-preview": "7.0.0-dev.20260608.1",
86
+ "@typescript/native-preview": "7.0.0-dev.20260612.1",
87
87
  "@vitest/coverage-v8": "4.1.8",
88
88
  "cspell": "10.0.1",
89
89
  "dependency-cruiser": "17.4.3",
90
90
  "husky": "9.1.7",
91
- "jscpd": "5.0.5",
91
+ "jscpd": "5.0.9",
92
92
  "knip": "6.15.0",
93
93
  "lint-staged": "17.0.7",
94
94
  "markdownlint-cli2": "0.22.1",