@clipboard-health/groundcrew 4.24.0 → 4.24.1

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,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD,OAAO,EACL,KAAK,UAAU,EAGf,KAAK,KAAK,EAEX,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAGpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAWzD,UAAU,cAAc;IACtB,MAAM,EAAE,cAAc,CAAC;IACvB,KAAK,EAAE,KAAK,CAAC;CACd;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,CAAC,UAAU,EAAE;QACpB,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;QACrB;;;;WAIG;QACH,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACrB;AAaD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,cAAc,GAAG,UAAU,CAyNjE;AA2BD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,KAAK,EAAE,GAAG,MAAM,CAQrE"}
1
+ {"version":3,"file":"dispatcher.d.ts","sourceRoot":"","sources":["../../src/commands/dispatcher.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD,OAAO,EACL,KAAK,UAAU,EAGf,KAAK,KAAK,EAEX,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAGpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAWzD,UAAU,cAAc;IACtB,MAAM,EAAE,cAAc,CAAC;IACvB,KAAK,EAAE,KAAK,CAAC;CACd;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,CAAC,UAAU,EAAE;QACpB,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;QACrB;;;;WAIG;QACH,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACrB;AAiCD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,cAAc,GAAG,UAAU,CAmOjE;AA2BD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,KAAK,EAAE,GAAG,MAAM,CAQrE"}
@@ -6,9 +6,9 @@
6
6
  * Pure verdict logic lives in `eligibility.ts`; this module is responsible
7
7
  * for telemetry, writeback via Board, and side-effecting setupWorkspace calls.
8
8
  */
9
- import { dispatchableRepository } from "../lib/repositoryValidation.js";
9
+ import { dispatchableRepository, formatKnownRepositories } from "../lib/repositoryValidation.js";
10
10
  import { isGroundcrewIssue, naturalIdFromCanonical, } from "../lib/taskSource.js";
11
- import { errorMessage, failMark, log, logEvent } from "../lib/util.js";
11
+ import { errorMessage, failMark, log, logEvent, styleWarning } from "../lib/util.js";
12
12
  import { workspaces } from "../lib/workspaces.js";
13
13
  import { classifyBlockers, classifyEligibility, classifyUsageExhaustion, } from "./eligibility.js";
14
14
  import { setupWorkspace } from "./setupWorkspace.js";
@@ -22,6 +22,17 @@ function logSkip(verdict) {
22
22
  model: verdict.model,
23
23
  });
24
24
  }
25
+ function logMissingRepositorySkip(issue, model, knownRepositories) {
26
+ const task = naturalIdFromCanonical(issue.id);
27
+ log(styleWarning(`WARNING: ${issue.id} has agent "${model}" but no repository; skipping dispatch. Add --repo <repo> when creating the task, add repo:<repo> to the task line, or set defaultRepository on source "${issue.source}". Known repositories: ${formatKnownRepositories(knownRepositories)}`));
28
+ logEvent("dispatch", {
29
+ outcome: "skipped",
30
+ reason: "missing_repository",
31
+ task,
32
+ source: issue.source,
33
+ model,
34
+ });
35
+ }
25
36
  export function createDispatcher(deps) {
26
37
  const { config, board } = deps;
27
38
  function buildExhaustedSet(usage) {
@@ -109,20 +120,31 @@ export function createDispatcher(deps) {
109
120
  .toSorted((a, b) => a.id.localeCompare(b.id));
110
121
  const activeCount = active.length;
111
122
  const slots = config.orchestrator.maximumInProgress - activeCount;
112
- // Narrow Todo to tasks that opted in via an `agent-*` label.
113
- // Unlabeled tasks are not groundcrew's concern even when in Todo.
123
+ const rawTodo = state.issues.filter((issue) => issue.status === "todo");
124
+ for (const issue of rawTodo) {
125
+ const { model, repository } = issue;
126
+ if (model !== undefined && repository === undefined) {
127
+ logMissingRepositorySkip(issue, model, config.workspace.knownRepositories);
128
+ }
129
+ }
130
+ // Narrow queued work to tasks that opted in with an agent and resolved a
131
+ // repository. Unlabeled tasks are not groundcrew's concern.
114
132
  // Sort by priority so higher-priority tasks fill slots first.
115
- const todo = state.issues
116
- .filter((issue) => issue.status === "todo" && isGroundcrewIssue(issue))
133
+ const todo = rawTodo
134
+ .filter((issue) => isGroundcrewIssue(issue))
117
135
  .toSorted((a, b) => prioritySortKey(a.priority) - prioritySortKey(b.priority));
118
136
  if (slots <= 0) {
119
137
  log(`At capacity (${activeCount}/${config.orchestrator.maximumInProgress})${formatActiveSlotList(active)}, no new work to start${idleSuffix}`);
120
138
  return;
121
139
  }
122
- if (todo.length === 0) {
140
+ if (rawTodo.length === 0) {
123
141
  log(`No Todo tasks to pick up${idleSuffix}`);
124
142
  return;
125
143
  }
144
+ if (todo.length === 0) {
145
+ log(`No eligible Todo tasks after agent/repository filtering${idleSuffix}`);
146
+ return;
147
+ }
126
148
  // Run the blocker pre-pass first so an all-blocked board short-circuits
127
149
  // before the codexbar HTTP call and the cmux/tmux shell-out fire.
128
150
  const { unblocked, skips: blockerSkips } = classifyBlockers(todo);
@@ -9,5 +9,6 @@
9
9
  * the tick across N sources.
10
10
  */
11
11
  import type { Issue } from "./taskSource.ts";
12
+ export declare function formatKnownRepositories(knownRepositories: readonly string[]): string;
12
13
  export declare function dispatchableRepository(issue: Issue, knownRepositories: readonly string[], log: (message: string) => void): string | undefined;
13
14
  //# sourceMappingURL=repositoryValidation.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"repositoryValidation.d.ts","sourceRoot":"","sources":["../../src/lib/repositoryValidation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAE7C,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,KAAK,EACZ,iBAAiB,EAAE,SAAS,MAAM,EAAE,EACpC,GAAG,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAC7B,MAAM,GAAG,SAAS,CAWpB"}
1
+ {"version":3,"file":"repositoryValidation.d.ts","sourceRoot":"","sources":["../../src/lib/repositoryValidation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAE7C,wBAAgB,uBAAuB,CAAC,iBAAiB,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAEpF;AAED,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,KAAK,EACZ,iBAAiB,EAAE,SAAS,MAAM,EAAE,EACpC,GAAG,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAC7B,MAAM,GAAG,SAAS,CAWpB"}
@@ -8,12 +8,15 @@
8
8
  * the MVP-2 plan): one badly-labelled task should not throw and abort
9
9
  * the tick across N sources.
10
10
  */
11
+ export function formatKnownRepositories(knownRepositories) {
12
+ return knownRepositories.join(", ") || "(none)";
13
+ }
11
14
  export function dispatchableRepository(issue, knownRepositories, log) {
12
15
  if (issue.repository === undefined) {
13
16
  return undefined;
14
17
  }
15
18
  if (!knownRepositories.includes(issue.repository)) {
16
- log(`issue ${issue.id} references unknown repository ${issue.repository}; configured workspace.knownRepositories: ${knownRepositories.join(", ") || "(none)"}`);
19
+ log(`issue ${issue.id} references unknown repository ${issue.repository}; configured workspace.knownRepositories: ${formatKnownRepositories(knownRepositories)}`);
17
20
  return undefined;
18
21
  }
19
22
  return issue.repository;
@@ -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;AAElD,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;CACd;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;CACd;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;AAaD,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;AAmFD,wBAAgB,YAAY,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAYvF;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,QAAQ,CA0BnE;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,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;CACd;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;CACd;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;AAmFD,wBAAgB,YAAY,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAYvF;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,QAAQ,CA0BnE;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,13 +1,9 @@
1
1
  import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
2
2
  import path from "node:path";
3
- const TASK_RE = /^[a-z][\da-z]*-\d+$/;
3
+ import { normalizePlainTaskId } from "./taskId.js";
4
4
  const RUN_STATE_DIRECTORY_NAME = "runs";
5
5
  function taskKey(task) {
6
- const normalized = task.toLowerCase();
7
- if (!TASK_RE.test(normalized)) {
8
- throw new Error(`Invalid task "${task}": must be a plain task id`);
9
- }
10
- return normalized;
6
+ return normalizePlainTaskId(task);
11
7
  }
12
8
  export function runStateDirectory(config) {
13
9
  return path.resolve(path.dirname(config.logging.file), RUN_STATE_DIRECTORY_NAME);
@@ -0,0 +1,4 @@
1
+ export declare function isPlainTaskId(task: string): boolean;
2
+ export declare function assertPlainTaskId(task: string): void;
3
+ export declare function normalizePlainTaskId(task: string): string;
4
+ //# sourceMappingURL=taskId.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"taskId.d.ts","sourceRoot":"","sources":["../../src/lib/taskId.ts"],"names":[],"mappings":"AAMA,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEnD;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAIpD;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMzD"}
@@ -0,0 +1,19 @@
1
+ const PLAIN_TASK_ID_RE = /^[a-z][\da-z]*(?:-[\da-z]+)*-\d+$/;
2
+ function invalidPlainTaskIdError(task) {
3
+ return new Error(`Invalid task "${task}": must be a plain task id`);
4
+ }
5
+ export function isPlainTaskId(task) {
6
+ return PLAIN_TASK_ID_RE.test(task);
7
+ }
8
+ export function assertPlainTaskId(task) {
9
+ if (!isPlainTaskId(task)) {
10
+ throw invalidPlainTaskIdError(task);
11
+ }
12
+ }
13
+ export function normalizePlainTaskId(task) {
14
+ const normalized = task.toLowerCase();
15
+ if (!isPlainTaskId(normalized)) {
16
+ throw invalidPlainTaskIdError(task);
17
+ }
18
+ return normalized;
19
+ }
@@ -18,7 +18,7 @@ export declare class WorktreeAlreadyExistsError extends Error {
18
18
  export declare function isWorktreeAlreadyExistsError(error: unknown): error is WorktreeAlreadyExistsError;
19
19
  export interface WorktreeEntry {
20
20
  repository: string;
21
- /** Linear task id, lowercased — e.g. "team-220". */
21
+ /** Source task id, lowercased — e.g. "team-220" or "gc-20260608-001". */
22
22
  task: string;
23
23
  /** Slash-free `<prefix>-<task>`. */
24
24
  branchName: string;
@@ -1 +1 @@
1
- {"version":3,"file":"worktrees.d.ts","sourceRoot":"","sources":["../../src/lib/worktrees.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAOH,OAAO,EAAE,KAAK,cAAc,EAAsC,MAAM,aAAa,CAAC;AAGtF,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,oDAAoD;IACpD,IAAI,EAAE,MAAM,CAAC;IACb,oCAAoC;IACpC,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,IAAI,EAAE,MAAM,CAAC;CACd;AAkBD,iBAAS,iBAAiB,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAEvE;AAgQD,MAAM,MAAM,iBAAiB,GACzB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GACtD;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,GACjB;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC;AAiHxB,iBAAS,IAAI,CAAC,MAAM,EAAE,cAAc,GAAG,aAAa,EAAE,CAErD;AAED,iBAAS,UAAU,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,aAAa,EAAE,CAEzE;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,2DAA2D;IAC3D,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;AAuBD,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,CAiDzB;AAED,iBAAe,gBAAgB,CAAC,KAAK,EAAE;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAE7B;AAED,eAAO,MAAM,SAAS;;;;;;;;CAQrB,CAAC"}
1
+ {"version":3,"file":"worktrees.d.ts","sourceRoot":"","sources":["../../src/lib/worktrees.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAOH,OAAO,EAAE,KAAK,cAAc,EAAsC,MAAM,aAAa,CAAC;AAItF,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,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;IACb,oCAAoC;IACpC,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,IAAI,EAAE,MAAM,CAAC;CACd;AAeD,iBAAS,iBAAiB,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAEvE;AA4QD,MAAM,MAAM,iBAAiB,GACzB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GACtD;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,GACjB;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC;AAiHxB,iBAAS,IAAI,CAAC,MAAM,EAAE,cAAc,GAAG,aAAa,EAAE,CAErD;AAED,iBAAS,UAAU,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,aAAa,EAAE,CAEzE;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,2DAA2D;IAC3D,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;AAuBD,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,CAiDzB;AAED,iBAAe,gBAAgB,CAAC,KAAK,EAAE;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAE7B;AAED,eAAO,MAAM,SAAS;;;;;;;;CAQrB,CAAC"}
@@ -14,6 +14,7 @@ import path from "node:path";
14
14
  import { runCommandAsync } from "./commandRunner.js";
15
15
  import { repositoryBaseDir, worktreeBaseDir } from "./config.js";
16
16
  import { resolveDefaultBranch } from "./defaultBranch.js";
17
+ import { assertPlainTaskId, isPlainTaskId } from "./taskId.js";
17
18
  import { debug, errorMessage, isVerbose } from "./util.js";
18
19
  import { workspaces } from "./workspaces.js";
19
20
  const WORKTREE_LIST_PREFIX = "worktree ";
@@ -28,8 +29,6 @@ export class WorktreeAlreadyExistsError extends Error {
28
29
  export function isWorktreeAlreadyExistsError(error) {
29
30
  return error instanceof WorktreeAlreadyExistsError;
30
31
  }
31
- const TASK_RE = /^[a-z][\da-z]*-\d+$/;
32
- const TASK_DIR_RE = /^(?<repoBasename>.+)-(?<task>[a-z][\da-z]*-\d+)$/;
33
32
  function branchPrefix(config) {
34
33
  const fromConfig = config.git.branchPrefix;
35
34
  if (fromConfig !== undefined) {
@@ -58,9 +57,7 @@ function basePaths(config, repository, task) {
58
57
  // Tasks must match the same shape the worktree discovery regexes use,
59
58
  // so create()/list()/findByTask() agree on what's a valid worktree.
60
59
  // This also rejects traversal tokens before they reach path.resolve().
61
- if (!TASK_RE.test(task)) {
62
- throw new Error(`Invalid task "${task}": must be a plain task id`);
63
- }
60
+ assertPlainTaskId(task);
64
61
  const repoDir = repoDirFor(config, repository);
65
62
  const hostWorktreeName = `${repository}-${task}`;
66
63
  const hostWorktreeDir = path.resolve(worktreeBaseDir(config), hostWorktreeName);
@@ -75,6 +72,22 @@ function basePaths(config, repository, task) {
75
72
  function signalProperty(signal) {
76
73
  return signal === undefined ? {} : { signal };
77
74
  }
75
+ function parseWorktreeDirectoryName(directoryName, repositoryEntriesByLongestName) {
76
+ // Match the longest repository basename first so overlapping names like
77
+ // "repo-a" and "repo-a-admin" parse to the intended repository.
78
+ for (const [repositoryBaseName, repository] of repositoryEntriesByLongestName) {
79
+ const worktreePrefix = `${repositoryBaseName}-`;
80
+ if (!directoryName.startsWith(worktreePrefix)) {
81
+ continue;
82
+ }
83
+ const task = directoryName.slice(worktreePrefix.length);
84
+ if (!isPlainTaskId(task)) {
85
+ continue;
86
+ }
87
+ return { repository, task };
88
+ }
89
+ return undefined;
90
+ }
78
91
  /**
79
92
  * Runs a long-running git command (fetch, worktree add/remove/prune) with no
80
93
  * timeout. Under --verbose the git porcelain streams live to the terminal;
@@ -147,6 +160,7 @@ function listWorktrees(config) {
147
160
  repoByBasename.set(basename, repository);
148
161
  }
149
162
  for (const [parentDir, repoByBasename] of reposByParent) {
163
+ const repositoryEntriesByLongestName = [...repoByBasename.entries()].toSorted(([nameA], [nameB]) => nameB.length - nameA.length);
150
164
  let children;
151
165
  try {
152
166
  children = readdirSync(parentDir, { withFileTypes: true });
@@ -158,23 +172,14 @@ function listWorktrees(config) {
158
172
  if (!entry.isDirectory()) {
159
173
  continue;
160
174
  }
161
- const match = TASK_DIR_RE.exec(entry.name);
162
- if (!match) {
163
- continue;
164
- }
165
- const [, repoBasename, task] = match;
166
- /* v8 ignore next 3 @preserve -- TASK_DIR_RE always captures both groups when it matches */
167
- if (repoBasename === undefined || task === undefined) {
168
- continue;
169
- }
170
- const repository = repoByBasename.get(repoBasename);
171
- if (repository === undefined) {
175
+ const parsed = parseWorktreeDirectoryName(entry.name, repositoryEntriesByLongestName);
176
+ if (parsed === undefined) {
172
177
  continue;
173
178
  }
174
179
  entries.push({
175
- repository,
176
- task,
177
- branchName: branchNameForTask(config, task),
180
+ repository: parsed.repository,
181
+ task: parsed.task,
182
+ branchName: branchNameForTask(config, parsed.task),
178
183
  dir: path.resolve(parentDir, entry.name),
179
184
  kind: "host",
180
185
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clipboard-health/groundcrew",
3
- "version": "4.24.0",
3
+ "version": "4.24.1",
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",