@clipboard-health/groundcrew 4.24.0 → 4.24.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.
package/README.md CHANGED
@@ -95,7 +95,7 @@ crew init [--global | --local] [--force] [--dry-run] # create a crew.config.
95
95
  crew doctor # check setup
96
96
  crew task list [--source <name>] # list tasks across sources
97
97
  crew task get <TASK> [--source <name>] [--prompt] # inspect one task or its prompt
98
- crew task create "Title" --source <name> --agent <name> # create a source task
98
+ crew task create "Title" --source <name> [--agent <name>] # create a source task
99
99
  crew status [<TASK>] # inspect current state or one task
100
100
  crew run [--watch] # one-shot or --watch forever
101
101
  crew start <TASK> # provision + launch one task now
@@ -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);
@@ -1 +1 @@
1
- {"version":3,"file":"task.d.ts","sourceRoot":"","sources":["../../src/commands/task.ts"],"names":[],"mappings":"AAwmBA,wBAAsB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAe3D"}
1
+ {"version":3,"file":"task.d.ts","sourceRoot":"","sources":["../../src/commands/task.ts"],"names":[],"mappings":"AAomBA,wBAAsB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAe3D"}
@@ -1,5 +1,5 @@
1
1
  import { buildSources, sourcesFromConfig } from "../lib/buildSources.js";
2
- import { loadConfig } from "../lib/config.js";
2
+ import { AGENT_ANY_MODEL, loadConfig } from "../lib/config.js";
3
3
  import { naturalIdFromCanonical, } from "../lib/taskSource.js";
4
4
  import { writeOutput } from "../lib/util.js";
5
5
  const TASK_USAGE = `Usage: crew task <subcommand>
@@ -25,7 +25,7 @@ Options:
25
25
  --source <name> Resolve a source-native ID against a specific source.
26
26
  --json Print normalized task JSON.
27
27
  --prompt Print only the task description/prompt.`;
28
- const CREATE_USAGE = `Usage: crew task create "Short title" --source <source> --agent <agent> [options]`;
28
+ const CREATE_USAGE = `Usage: crew task create "Short title" --source <source> [--agent <agent>] [options]`;
29
29
  const CANONICAL_STATUSES = [
30
30
  "todo",
31
31
  "in-progress",
@@ -232,12 +232,9 @@ function parseCreateOptions(argv) {
232
232
  if (state.sourceName === undefined) {
233
233
  throw new Error("crew task create: --source is required");
234
234
  }
235
- if (state.agent === undefined) {
236
- throw new Error("crew task create: --agent is required");
237
- }
238
235
  const input = {
239
236
  title,
240
- agent: state.agent,
237
+ agent: state.agent ?? AGENT_ANY_MODEL,
241
238
  projects: state.projects,
242
239
  contexts: state.contexts,
243
240
  dependencies: state.dependencies,
@@ -1 +1 @@
1
- {"version":3,"file":"normalizer.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/todo-txt/normalizer.ts"],"names":[],"mappings":"AAEA,OAAO,EAAsC,KAAK,KAAK,EAAiB,MAAM,qBAAqB,CAAC;AACpG,OAAO,EAA8C,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAE9F,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;CACpB;AAkED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,cAAc,CAAC;IACvB,SAAS,EAAE,CAAC,cAAc,GAAG,IAAI,CAAC,EAAE,CAAC;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,gBAAgB,GAAG,KAAK,GAAG,SAAS,CAqD7E;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAYhE"}
1
+ {"version":3,"file":"normalizer.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/todo-txt/normalizer.ts"],"names":[],"mappings":"AAGA,OAAO,EAAsC,KAAK,KAAK,EAAiB,MAAM,qBAAqB,CAAC;AACpG,OAAO,EAA8C,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAE9F,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;CACpB;AAkED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,cAAc,CAAC;IACvB,SAAS,EAAE,CAAC,cAAc,GAAG,IAAI,CAAC,EAAE,CAAC;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,gBAAgB,GAAG,KAAK,GAAG,SAAS,CAqD7E;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAShE"}
@@ -1,4 +1,5 @@
1
1
  import path from "node:path";
2
+ import { AGENT_ANY_MODEL } from "../../config.js";
2
3
  import { toCanonicalId } from "../../taskSource.js";
3
4
  import { getMetadataAll, getMetadataFirst, hashLine } from "./parser.js";
4
5
  function derivedCanonicalStatus(parsed) {
@@ -58,7 +59,7 @@ export function normalizeToIssue(options) {
58
59
  if (id === undefined) {
59
60
  return undefined;
60
61
  }
61
- const agent = getMetadataFirst(parsed, "agent");
62
+ const agent = getMetadataFirst(parsed, "agent") ?? AGENT_ANY_MODEL;
62
63
  const status = derivedCanonicalStatus(parsed);
63
64
  const repository = getMetadataFirst(parsed, "repo") ?? defaultRepository;
64
65
  const depIds = getMetadataAll(parsed, "dep");
@@ -96,9 +97,6 @@ export function isActiveForFetch(parsed) {
96
97
  if (getMetadataFirst(parsed, "id") === undefined) {
97
98
  return false;
98
99
  }
99
- if (getMetadataFirst(parsed, "agent") === undefined) {
100
- return false;
101
- }
102
100
  const statusValue = getMetadataFirst(parsed, "status");
103
101
  return statusValue === "todo" || statusValue === "in-progress" || statusValue === "in-review";
104
102
  }
@@ -1 +1 @@
1
- {"version":3,"file":"source.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/todo-txt/source.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAKL,KAAK,UAAU,EAEhB,MAAM,qBAAqB,CAAC;AAI7B,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AA+QxD,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,oBAAoB,EAC5B,QAAQ,EAAE,cAAc,GACvB,UAAU,CA4IZ"}
1
+ {"version":3,"file":"source.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/todo-txt/source.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAKL,KAAK,UAAU,EAEhB,MAAM,qBAAqB,CAAC;AAI7B,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AA0RxD,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,oBAAoB,EAC5B,QAAQ,EAAE,cAAc,GACvB,UAAU,CA4IZ"}
@@ -8,13 +8,23 @@ import { getMetadataFirst, parseAllLines } from "./parser.js";
8
8
  import { copyPromptFile, updateTaskStatus, validateTodoFile, withLock } from "./writeback.js";
9
9
  const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
10
10
  const RECURRENCE_RE = /^\+?\d+[dwmy]$/;
11
- function readDescription(promptPath) {
11
+ function readPromptFile(promptPath) {
12
12
  try {
13
13
  return readFileSync(promptPath, "utf8");
14
14
  }
15
15
  catch {
16
- return "";
16
+ return undefined;
17
+ }
18
+ }
19
+ function descriptionFor(parsed, promptPath) {
20
+ const promptContent = readPromptFile(promptPath);
21
+ if (promptContent !== undefined && promptContent.trim().length > 0) {
22
+ return promptContent;
23
+ }
24
+ if (parsed.title.trim().length > 0) {
25
+ return `${parsed.title}\n`;
17
26
  }
27
+ return promptContent ?? "";
18
28
  }
19
29
  function fileUpdatedAt(filePath) {
20
30
  try {
@@ -51,7 +61,7 @@ function buildIssue(options) {
51
61
  }
52
62
  const promptOverride = getMetadataFirst(parsed, "prompt");
53
63
  const promptPath = promptOverride ?? `${tasksDir}/${id}.md`;
54
- const description = readDescription(promptPath);
64
+ const description = descriptionFor(parsed, promptPath);
55
65
  return normalizeToIssue({
56
66
  parsed,
57
67
  allParsed: parsedAll,
@@ -1 +1 @@
1
- {"version":3,"file":"writeback.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/todo-txt/writeback.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAExD,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;CACvB;AAoKD,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,gBAAgB,CAAC;IACtB,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ;AAED,wBAAsB,QAAQ,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAQxF;AAED,KAAK,cAAc,GAAG,aAAa,GAAG,WAAW,GAAG,MAAM,CAAC;AA6E3D,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,aAAa,EACtB,SAAS,EAAE,cAAc,GACxB,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAsElC;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAQrE;AAwFD,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CA0C7E"}
1
+ {"version":3,"file":"writeback.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/todo-txt/writeback.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAExD,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;CACvB;AAoKD,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,gBAAgB,CAAC;IACtB,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ;AAED,wBAAsB,QAAQ,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAQxF;AAED,KAAK,cAAc,GAAG,aAAa,GAAG,WAAW,GAAG,MAAM,CAAC;AA6E3D,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,aAAa,EACtB,SAAS,EAAE,cAAc,GACxB,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAsElC;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAQrE;AA4FD,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CA0C7E"}
@@ -262,16 +262,19 @@ export function copyPromptFile(oldPath, newPath) {
262
262
  // prompt file is optional — copy is best-effort
263
263
  }
264
264
  }
265
- function validatePromptFile(tasksDir, id, promptOverride, prefix, errors) {
265
+ function validatePromptFile(tasksDir, id, promptOverride, title, prefix, errors) {
266
266
  const promptPath = promptOverride ?? path.join(tasksDir, `${id}.md`);
267
+ const shouldRequirePrompt = promptOverride !== undefined || title.trim().length === 0;
267
268
  try {
268
269
  const desc = readFileSync(promptPath, "utf8");
269
- if (desc.trim().length === 0) {
270
+ if (desc.trim().length === 0 && shouldRequirePrompt) {
270
271
  errors.push(`${prefix}: empty prompt file "${promptPath}" for ready task "${id}"`);
271
272
  }
272
273
  }
273
274
  catch {
274
- errors.push(`${prefix}: missing prompt file "${promptPath}" for ready task "${id}"`);
275
+ if (shouldRequirePrompt) {
276
+ errors.push(`${prefix}: missing prompt file "${promptPath}" for ready task "${id}"`);
277
+ }
275
278
  }
276
279
  }
277
280
  function validateDepsAndDates(parsed, parsedAll, id, prefix, errors) {
@@ -308,7 +311,7 @@ function validateActiveTaskLine(parsed, parsedAll, tasksDir, id, prefix, errors)
308
311
  errors.push(`${prefix}: task "${id}" has status:todo but it is not the final token — task will not be dispatched`);
309
312
  }
310
313
  if (statusValue === "todo" && parsed.isStatusFinalToken) {
311
- validatePromptFile(tasksDir, id, parsed.metadata["prompt"]?.[0], prefix, errors);
314
+ validatePromptFile(tasksDir, id, parsed.metadata["prompt"]?.[0], parsed.title, prefix, errors);
312
315
  }
313
316
  validateDepsAndDates(parsed, parsedAll, id, prefix, errors);
314
317
  }
@@ -340,7 +343,7 @@ export function validateTodoFile(todoPath, tasksDir) {
340
343
  idsSeen.set(lower, lineNum);
341
344
  }
342
345
  }
343
- if (id === undefined || parsed.metadata["agent"] === undefined) {
346
+ if (id === undefined) {
344
347
  continue;
345
348
  }
346
349
  if (parsed.completed) {
@@ -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/docs/commands.md CHANGED
@@ -18,7 +18,7 @@ crew task get GC-20260608-001 --source todo
18
18
  crew task get todo:GC-20260608-001 --prompt
19
19
  ```
20
20
 
21
- `crew task create "Short title" --source <source> --agent <agent>` creates a task in a source that supports creation. Todo.txt creation appends the todo line, defaults to priority `A` unless `--priority` is provided, writes `.tasks/<id>.md`, and leaves `status:todo` as the final meaningful token, so no separate ready command is required.
21
+ `crew task create "Short title" --source <source> [--agent <agent>]` creates a task in a source that supports creation. When `--agent` is omitted, it defaults to `any`. Todo.txt creation appends the todo line, defaults to priority `A` unless `--priority` is provided, writes `.tasks/<id>.md`, and leaves `status:todo` as the final meaningful token, so no separate ready command is required. Hand-written todo-txt lines can omit `.tasks/<id>.md` when the line has a non-empty title; that title becomes the prompt text.
22
22
 
23
23
  ```bash
24
24
  crew task create "Fix cancellation retry race" \
@@ -78,7 +78,7 @@ export default {
78
78
  };
79
79
  ```
80
80
 
81
- Creating a todo task appends a line with `status:todo` as the final meaningful token and writes the prompt to `.tasks/<id>.md`. New todo tasks default to priority `A`; pass `--priority <letter>` to override it.
81
+ Creating a todo task appends a line with `status:todo` as the final meaningful token and writes the prompt to `.tasks/<id>.md`. New todo tasks default to priority `A`; pass `--priority <letter>` to override it. If `--agent` is omitted, the task uses `agent:any`.
82
82
 
83
83
  ```bash
84
84
  crew task create "Fix cancellation retry race" \
@@ -94,6 +94,12 @@ crew task create "Fix cancellation retry race" \
94
94
  (A) Fix cancellation retry race +marketplace @backend id:GC-20260608-001 repo:ClipboardHealth/api agent:codex status:todo
95
95
  ```
96
96
 
97
+ For hand-written todo lines, a non-empty title is enough prompt text when `.tasks/<id>.md` is absent. Omit `agent:` to default to `agent:any`:
98
+
99
+ ```txt
100
+ Say goodbye repo:ClipboardHealth/groundcrew id:GC-20260608-002 status:todo
101
+ ```
102
+
97
103
  ## Linear
98
104
 
99
105
  The built-in Linear source supports listing, getting, writeback, and task creation through `crew task create`.
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.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",
@@ -81,7 +81,7 @@
81
81
  "@nx/js": "22.7.5",
82
82
  "@tsconfig/node24": "24.0.4",
83
83
  "@tsconfig/strictest": "2.0.8",
84
- "@types/node": "25.9.1",
84
+ "@types/node": "25.9.2",
85
85
  "@typescript/native-preview": "7.0.0-dev.20260604.1",
86
86
  "@vitest/coverage-v8": "4.1.8",
87
87
  "cspell": "10.0.1",