@clipboard-health/groundcrew 4.34.2 → 4.35.0

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
@@ -94,6 +94,7 @@ crew source list|verify [<source>] # inspect configured ta
94
94
  crew task list [--source <name>] # list tasks across sources
95
95
  crew task get <TASK> [--source <name>] [--prompt] # inspect one task or its prompt
96
96
  crew task create "Title" --source <name> [--agent <name>] # create a source task
97
+ crew task done <TASK> [--allow-dirty] # mark a no-PR task done
97
98
  crew task validate [<source>] # validate task content
98
99
  crew status [<TASK>] # inspect current state or one task
99
100
  crew run [--watch] # one-shot or --watch forever
package/dist/cli.js CHANGED
@@ -136,8 +136,8 @@ const SUBCOMMANDS = {
136
136
  invoke: sourceCli,
137
137
  },
138
138
  task: {
139
- summary: "List, get, and create tasks across configured sources",
140
- usage: "<list|get|create> [...]",
139
+ summary: "List, get, create, and complete tasks across configured sources",
140
+ usage: "<list|get|create|done|validate> [...]",
141
141
  invoke: taskCli,
142
142
  },
143
143
  status: {
@@ -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;AAiCD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,cAAc,GAAG,UAAU,CAmOjE;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,CAoOjE;AA2BD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,KAAK,EAAE,GAAG,MAAM,CAQrE"}
@@ -70,6 +70,7 @@ export function createDispatcher(deps) {
70
70
  const setupOptions = {
71
71
  repository: issue.repository,
72
72
  task: taskId,
73
+ completionTaskId: issue.id,
73
74
  agent: issue.agent,
74
75
  details: {
75
76
  title: issue.title,
@@ -1 +1 @@
1
- {"version":3,"file":"resumeWorkspace.d.ts","sourceRoot":"","sources":["../../src/commands/resumeWorkspace.ts"],"names":[],"mappings":"AAGA,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAcnE,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;CACd;AAuID,wBAAsB,eAAe,CACnC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,IAAI,CAAC,CA2Ef;AAED,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAGtE"}
1
+ {"version":3,"file":"resumeWorkspace.d.ts","sourceRoot":"","sources":["../../src/commands/resumeWorkspace.ts"],"names":[],"mappings":"AAGA,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAenE,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;CACd;AA0ID,wBAAsB,eAAe,CACnC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,IAAI,CAAC,CA6Ef;AAED,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAGtE"}
@@ -3,9 +3,10 @@ import { getLinearClient } from "../lib/adapters/linear/client.js";
3
3
  import { isLinearEnabled } from "../lib/buildSources.js";
4
4
  import { loadConfig } from "../lib/config.js";
5
5
  import { composeAgentLaunch, openAgentWorkspace, prepareAgentLaunch } from "../lib/agentLaunch.js";
6
+ import { workerEnvironmentForTask } from "../lib/launchCommand.js";
6
7
  import { readRunState, recordRunState } from "../lib/runState.js";
7
8
  import { removeStagedPrompt, stageBuildSecrets, stagePromptText, stageWorkspaceLaunchCommand, } from "../lib/stagedLaunch.js";
8
- import { naturalIdFromCanonical } from "../lib/taskSource.js";
9
+ import { naturalIdFromCanonical, toCanonicalId } from "../lib/taskSource.js";
9
10
  import { errorMessage, log } from "../lib/util.js";
10
11
  import { workspaces } from "../lib/workspaces.js";
11
12
  import { resolveLaunchDir, worktrees } from "../lib/worktrees.js";
@@ -38,6 +39,7 @@ async function contextFromLinear(config, task, worktree) {
38
39
  worktree,
39
40
  title: resolved.title,
40
41
  description: resolved.description,
42
+ completionTaskId: toCanonicalId("linear", task),
41
43
  resumeCount: 0,
42
44
  };
43
45
  }
@@ -53,6 +55,7 @@ async function contextFromState(config, task, state, worktree) {
53
55
  worktree,
54
56
  title: details?.title ?? task.toUpperCase(),
55
57
  description: details?.description ?? "",
58
+ completionTaskId: state.completionTaskId ?? task,
56
59
  ...(state.reason === undefined ? {} : { reason: state.reason }),
57
60
  resumeCount: state.resumeCount,
58
61
  };
@@ -147,6 +150,7 @@ export async function resumeWorkspace(config, options) {
147
150
  workingDir: launchDir,
148
151
  secretsFile,
149
152
  sandboxName,
153
+ workerEnvironment: workerEnvironmentForTask(context.completionTaskId),
150
154
  }));
151
155
  const launchCmd = stageWorkspaceLaunchCommand(stagedPrompt.directory, launchCommand);
152
156
  await openAgentWorkspace({
@@ -178,6 +182,7 @@ export async function resumeWorkspace(config, options) {
178
182
  workspaceName: task,
179
183
  state: "resumed",
180
184
  resumeCount: context.resumeCount + 1,
185
+ completionTaskId: context.completionTaskId,
181
186
  ...(context.reason === undefined ? {} : { reason: context.reason }),
182
187
  },
183
188
  });
@@ -7,6 +7,8 @@ export interface TaskDetails {
7
7
  }
8
8
  export interface SetupWorkspaceOptions {
9
9
  task: string;
10
+ /** Canonical source id for worker self-completion; falls back to `task`. */
11
+ completionTaskId?: string;
10
12
  repository: string;
11
13
  agent: string;
12
14
  details: TaskDetails;
@@ -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;AAsBnE,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,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,CA0Hf;AA8ID,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GACjC,OAAO,CAAC,IAAI,CAAC,CA4Cf"}
1
+ {"version":3,"file":"setupWorkspace.d.ts","sourceRoot":"","sources":["../../src/commands/setupWorkspace.ts"],"names":[],"mappings":"AACA,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAuBnE,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,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,CA8Hf;AAgJD,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GACjC,OAAO,CAAC,IAAI,CAAC,CA6Cf"}
@@ -3,6 +3,7 @@ import { loadConfig } from "../lib/config.js";
3
3
  import { composeAgentLaunch, openAgentWorkspace, prepareAgentLaunch } from "../lib/agentLaunch.js";
4
4
  import { createBoard } from "../lib/board.js";
5
5
  import { buildSources, sourcesFromConfig } from "../lib/buildSources.js";
6
+ import { workerEnvironmentForTask } from "../lib/launchCommand.js";
6
7
  import { resolvePrepareWorktreeCommand } from "../lib/repositoryHooks.js";
7
8
  import { recordRunState } from "../lib/runState.js";
8
9
  import { stageBuildSecrets, stagePromptFromTemplate, stageWorkspaceLaunchCommand, } from "../lib/stagedLaunch.js";
@@ -80,6 +81,7 @@ export async function setupWorkspace(config, options, runOptions = {}) {
80
81
  defaultHooks: config.defaults.hooks,
81
82
  });
82
83
  const secretsFile = prepareWorktreeCommand === undefined ? undefined : stageBuildSecrets(promptDir);
84
+ const completionTaskId = options.completionTaskId ?? task;
83
85
  const { launchCommand, srtSettingsDir: stagedSrtSettingsDir } = composeAgentLaunch({
84
86
  runner,
85
87
  task,
@@ -90,6 +92,7 @@ export async function setupWorkspace(config, options, runOptions = {}) {
90
92
  secretsFile,
91
93
  prepareWorktreeCommand,
92
94
  sandboxName,
95
+ workerEnvironment: workerEnvironmentForTask(completionTaskId),
93
96
  });
94
97
  srtSettingsDir = stagedSrtSettingsDir;
95
98
  const launchCmd = stageWorkspaceLaunchCommand(promptDir, launchCommand);
@@ -113,6 +116,7 @@ export async function setupWorkspace(config, options, runOptions = {}) {
113
116
  workspaceName: task,
114
117
  state: "running",
115
118
  title: taskDetails.title,
119
+ completionTaskId,
116
120
  ...(taskDetails.url === undefined ? {} : { url: taskDetails.url }),
117
121
  });
118
122
  log(`${okMark()} "${task}" launched (${agent}) worktree ${worktreeName}`);
@@ -135,6 +139,7 @@ export async function setupWorkspace(config, options, runOptions = {}) {
135
139
  state: "failed-to-launch",
136
140
  detail: errorMessage(error),
137
141
  title: options.details.title,
142
+ completionTaskId: options.completionTaskId ?? task,
138
143
  ...(options.details.url === undefined ? {} : { url: options.details.url }),
139
144
  });
140
145
  throw error;
@@ -197,6 +202,7 @@ function recordRunStateBestEffort(arguments_) {
197
202
  workspaceName: arguments_.workspaceName,
198
203
  state: arguments_.state,
199
204
  title: arguments_.title,
205
+ completionTaskId: arguments_.completionTaskId,
200
206
  ...(arguments_.detail === undefined ? {} : { detail: arguments_.detail }),
201
207
  ...(arguments_.url === undefined ? {} : { url: arguments_.url }),
202
208
  },
@@ -275,6 +281,7 @@ export async function setupWorkspaceCli(task, options = {}) {
275
281
  const naturalId = naturalIdFromCanonical(resolved.id);
276
282
  await setupWorkspace(config, {
277
283
  task: naturalId,
284
+ completionTaskId: resolved.id,
278
285
  repository: resolved.repository,
279
286
  agent: resolved.agent,
280
287
  details: {
@@ -1 +1 @@
1
- {"version":3,"file":"task.d.ts","sourceRoot":"","sources":["../../src/commands/task.ts"],"names":[],"mappings":"AAmrBA,wBAAsB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAmB3D"}
1
+ {"version":3,"file":"task.d.ts","sourceRoot":"","sources":["../../src/commands/task.ts"],"names":[],"mappings":"AAoyBA,wBAAsB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAuB3D"}
@@ -1,13 +1,17 @@
1
+ import { createBoard } from "../lib/board.js";
1
2
  import { buildSources, sourcesFromConfig } from "../lib/buildSources.js";
2
3
  import { AGENT_ANY, loadConfig } from "../lib/config.js";
4
+ import { findPullRequestsForBranch } from "../lib/pullRequests.js";
3
5
  import { naturalIdFromCanonical, } from "../lib/taskSource.js";
4
6
  import { parseSourceFilterArgs, writeOutput } from "../lib/util.js";
7
+ import { worktrees } from "../lib/worktrees.js";
5
8
  const TASK_USAGE = `Usage: crew task <subcommand>
6
9
 
7
10
  Subcommands:
8
11
  list [options] List tasks across configured sources
9
12
  get <task-id> [options] Get one task
10
13
  create "Short title" [options] Create one task
14
+ done <task-id> [options] Mark one task done
11
15
  validate [source] [options] Validate task content`;
12
16
  const LIST_USAGE = `Usage: crew task list [options]
13
17
 
@@ -27,6 +31,7 @@ Options:
27
31
  --json Print normalized task JSON.
28
32
  --prompt Print only the task description/prompt.`;
29
33
  const CREATE_USAGE = `Usage: crew task create "Short title" --source <source> [--agent <agent>] [options]`;
34
+ const DONE_USAGE = `Usage: crew task done <task-id> [--allow-dirty]`;
30
35
  const CANONICAL_STATUSES = [
31
36
  "todo",
32
37
  "in-progress",
@@ -251,10 +256,34 @@ function parseCreateOptions(argv) {
251
256
  };
252
257
  return { title, sourceName: state.sourceName, input, json: state.json };
253
258
  }
259
+ function parseDoneOptions(argv) {
260
+ const positionals = [];
261
+ let allowDirty = false;
262
+ for (const argument of argv) {
263
+ if (argument === "--allow-dirty") {
264
+ allowDirty = true;
265
+ continue;
266
+ }
267
+ if (argument.startsWith("-")) {
268
+ throw new Error(`crew task done: unknown option: ${argument}\n${DONE_USAGE}`);
269
+ }
270
+ positionals.push(argument);
271
+ }
272
+ const [taskId, ...extras] = positionals;
273
+ if (taskId === undefined || taskId.length === 0 || extras.length > 0) {
274
+ throw new Error(DONE_USAGE);
275
+ }
276
+ return { taskId, allowDirty };
277
+ }
254
278
  async function loadTaskSources() {
255
279
  const config = await loadConfig();
256
280
  return await buildSources(sourcesFromConfig(config), { globalConfig: config });
257
281
  }
282
+ async function loadTaskBoard() {
283
+ const config = await loadConfig();
284
+ const sources = await buildSources(sourcesFromConfig(config), { globalConfig: config });
285
+ return { config, board: createBoard(sources) };
286
+ }
258
287
  function findSource(sources, sourceName) {
259
288
  const source = sources.find((candidate) => candidate.name === sourceName);
260
289
  if (source === undefined) {
@@ -472,6 +501,60 @@ async function taskCreateCli(argv) {
472
501
  }
473
502
  writeOutput(created.id);
474
503
  }
504
+ function matchingWorktreeEntries(arguments_) {
505
+ const task = naturalIdFromCanonical(arguments_.issue.id);
506
+ return worktrees
507
+ .findByTask(arguments_.config, task)
508
+ .filter((entry) => arguments_.issue.repository === undefined ||
509
+ entry.repository === arguments_.issue.repository);
510
+ }
511
+ function describeDirtiness(dirtiness) {
512
+ if (dirtiness.kind === "dirty") {
513
+ return `${dirtiness.modified} modified, ${dirtiness.untracked} untracked`;
514
+ }
515
+ return "unknown git status";
516
+ }
517
+ async function worktreeHasPullRequest(entry) {
518
+ const pullRequests = await findPullRequestsForBranch({
519
+ cwd: entry.dir,
520
+ branchName: entry.branchName,
521
+ });
522
+ return pullRequests.length > 0;
523
+ }
524
+ async function assertCanMarkDone(arguments_) {
525
+ if (arguments_.allowDirty) {
526
+ return;
527
+ }
528
+ for (const entry of matchingWorktreeEntries({
529
+ config: arguments_.config,
530
+ issue: arguments_.issue,
531
+ })) {
532
+ // oxlint-disable-next-line no-await-in-loop -- one git status per matching worktree keeps diagnostics deterministic.
533
+ const dirtiness = await worktrees.probeWorkingTree({ worktreeDir: entry.dir });
534
+ if (dirtiness.kind === "clean") {
535
+ continue;
536
+ }
537
+ // oxlint-disable-next-line no-await-in-loop -- only dirty/unknown worktrees need the PR lookup.
538
+ if (dirtiness.kind === "dirty" && (await worktreeHasPullRequest(entry))) {
539
+ continue;
540
+ }
541
+ throw new Error(`crew task done: refusing to mark ${arguments_.issue.id} done because ${entry.dir} has ${describeDirtiness(dirtiness)} and no matching PR. Commit or stash the work, open a PR, or rerun with --allow-dirty.`);
542
+ }
543
+ }
544
+ async function taskDoneCli(argv) {
545
+ const options = parseDoneOptions(argv);
546
+ const { config, board } = await loadTaskBoard();
547
+ const issue = await board.resolveOne(options.taskId);
548
+ if (issue === undefined) {
549
+ throw new Error(`Task ${options.taskId} not found across configured sources.`);
550
+ }
551
+ await assertCanMarkDone({ config, issue, allowDirty: options.allowDirty });
552
+ const result = await board.markDone(issue);
553
+ if (result.outcome === "unsupported") {
554
+ throw new Error(`crew task done: ${result.reason}`);
555
+ }
556
+ writeOutput(`Marked ${issue.id} done.`);
557
+ }
475
558
  const VALIDATE_USAGE = `Usage: crew task validate [source]
476
559
 
477
560
  Options:
@@ -536,6 +619,10 @@ export async function taskCli(argv) {
536
619
  await taskCreateCli(rest);
537
620
  return;
538
621
  }
622
+ if (verb === "done") {
623
+ await taskDoneCli(rest);
624
+ return;
625
+ }
539
626
  if (verb === "validate") {
540
627
  await taskValidateCli(rest);
541
628
  return;
@@ -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;AAEjE,OAAO,EAKL,KAAK,UAAU,EAEhB,MAAM,qBAAqB,CAAC;AAI7B,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAoUxD,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,oBAAoB,EAC5B,OAAO,EAAE,cAAc,GACtB,UAAU,CAqKZ"}
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;AAEjE,OAAO,EAKL,KAAK,UAAU,EAEhB,MAAM,qBAAqB,CAAC;AAK7B,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AA8VxD,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,oBAAoB,EAC5B,OAAO,EAAE,cAAc,GACtB,UAAU,CA2KZ"}
@@ -3,6 +3,7 @@ import { appendFileSync, mkdirSync, readFileSync, statSync, writeFileSync } from
3
3
  import path from "node:path";
4
4
  import { AGENT_ANY } from "../../config.js";
5
5
  import { toCanonicalId, } from "../../taskSource.js";
6
+ import { formatKnownRepositories } from "../../repositoryValidation.js";
6
7
  import { readEnvironmentVariable } from "../../util.js";
7
8
  import { isActiveForFetch, normalizeToIssue } from "./normalizer.js";
8
9
  import { DATE_RE, getMetadataFirst, parseAllLines } from "./parser.js";
@@ -161,6 +162,18 @@ function assertNewId(id, parsedAll) {
161
162
  throw new Error(`todo-txt: task id "${id}" already exists`);
162
163
  }
163
164
  }
165
+ function assertCreateRepository(arguments_) {
166
+ const { defaultRepository, input, knownRepositories, sourceName } = arguments_;
167
+ const repository = input.repository ?? defaultRepository;
168
+ /* v8 ignore else @preserve -- both missing and resolved repository paths are covered; full-suite V8 coverage remaps the synthetic else inconsistently. */
169
+ if (repository === undefined) {
170
+ throw new Error(`todo-txt: --repo is required unless source "${sourceName}" configures defaultRepository. Known repositories: ${formatKnownRepositories(knownRepositories)}`);
171
+ }
172
+ /* v8 ignore else @preserve -- both known and unknown repository paths are covered; full-suite V8 coverage remaps the synthetic else inconsistently. */
173
+ if (!knownRepositories.includes(repository)) {
174
+ throw new Error(`todo-txt: repository "${repository}" is not in workspace.knownRepositories: ${formatKnownRepositories(knownRepositories)}`);
175
+ }
176
+ }
164
177
  function buildTodoLine(id, input) {
165
178
  const title = input.title.trim();
166
179
  /* v8 ignore else @preserve -- no explicit else branch; full-suite V8 coverage remaps the synthetic else inconsistently. */
@@ -172,12 +185,14 @@ function buildTodoLine(id, input) {
172
185
  throw new Error("todo-txt: title must be a single line");
173
186
  }
174
187
  const tokens = [];
175
- const priority = input.priority ?? "A";
176
- /* v8 ignore else @preserve -- no explicit else branch; full-suite V8 coverage remaps the synthetic else inconsistently. */
177
- if (!/^[A-Z]$/.test(priority)) {
178
- throw new Error("todo-txt: priority must be a single uppercase letter");
188
+ /* v8 ignore else @preserve -- both priority-present and priority-absent creation paths are covered; full-suite V8 coverage remaps the synthetic else inconsistently. */
189
+ if (input.priority !== undefined) {
190
+ /* v8 ignore else @preserve -- no explicit else branch; full-suite V8 coverage remaps the synthetic else inconsistently. */
191
+ if (!/^[A-Z]$/.test(input.priority)) {
192
+ throw new Error("todo-txt: priority must be a single uppercase letter");
193
+ }
194
+ tokens.push(`(${input.priority})`);
179
195
  }
180
- tokens.push(`(${priority})`);
181
196
  tokens.push(title);
182
197
  for (const rawProject of input.projects) {
183
198
  const project = normalizeProject(rawProject);
@@ -369,6 +384,12 @@ export function createTodoTxtTaskSource(config, context) {
369
384
  const id = input.id ?? nextGeneratedId(config, parsedAll);
370
385
  assertCreateId(id);
371
386
  assertNewId(id, parsedAll);
387
+ assertCreateRepository({
388
+ defaultRepository: config.defaultRepository,
389
+ input,
390
+ knownRepositories: context.globalConfig.workspace.knownRepositories,
391
+ sourceName,
392
+ });
372
393
  const promptPath = path.join(tasksDir, `${id}.md`);
373
394
  const promptContent = promptContentFor(input);
374
395
  const line = buildTodoLine(id, input);
@@ -1,4 +1,5 @@
1
1
  import { type LocalRunner, type AgentDefinition, type ResolvedConfig } from "./config.ts";
2
+ import { type WorkerEnvironment } from "./launchCommand.ts";
2
3
  /**
3
4
  * Stage any srt settings and build the workspace launch command — the assembly
4
5
  * shared verbatim by `setupWorkspace` (fresh runs) and `resumeWorkspace`
@@ -16,6 +17,7 @@ export declare function composeAgentLaunch(input: {
16
17
  secretsFile?: string | undefined;
17
18
  prepareWorktreeCommand?: string | undefined;
18
19
  sandboxName?: string | undefined;
20
+ workerEnvironment?: WorkerEnvironment | undefined;
19
21
  }): {
20
22
  launchCommand: string;
21
23
  srtSettingsDir: string | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"agentLaunch.d.ts","sourceRoot":"","sources":["../../src/lib/agentLaunch.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,cAAc,EACpB,MAAM,aAAa,CAAC;AASrB;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE;IACxC,MAAM,EAAE,WAAW,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,eAAe,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,sBAAsB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5C,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAClC,GAAG;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CA0BhE;AAsBD,UAAU,mBAAmB;IAC3B,MAAM,EAAE,WAAW,CAAC;IACpB,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAClC;AAED,wBAAsB,kBAAkB,CAAC,KAAK,EAAE;IAC9C,MAAM,EAAE,cAAc,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,eAAe,CAAC;IAC5B,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CA+C/B;AAwBD,wBAAsB,kBAAkB,CAAC,KAAK,EAAE;IAC9C,MAAM,EAAE,cAAc,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,GAAG,OAAO,CAAC,IAAI,CAAC,CAUhB"}
1
+ {"version":3,"file":"agentLaunch.d.ts","sourceRoot":"","sources":["../../src/lib/agentLaunch.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,cAAc,EACpB,MAAM,aAAa,CAAC;AAErB,OAAO,EAAsB,KAAK,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAOhF;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE;IACxC,MAAM,EAAE,WAAW,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,eAAe,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,sBAAsB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5C,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,iBAAiB,CAAC,EAAE,iBAAiB,GAAG,SAAS,CAAC;CACnD,GAAG;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CA2BhE;AAsBD,UAAU,mBAAmB;IAC3B,MAAM,EAAE,WAAW,CAAC;IACpB,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAClC;AAED,wBAAsB,kBAAkB,CAAC,KAAK,EAAE;IAC9C,MAAM,EAAE,cAAc,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,eAAe,CAAC;IAC5B,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAqD/B;AAwBD,wBAAsB,kBAAkB,CAAC,KAAK,EAAE;IAC9C,MAAM,EAAE,cAAc,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,GAAG,OAAO,CAAC,IAAI,CAAC,CAUhB"}
@@ -36,6 +36,7 @@ export function composeAgentLaunch(input) {
36
36
  srtAgentSettingsFile: staged?.agentFile,
37
37
  srtSettingsDir: staged?.directory,
38
38
  srtAgentConfigDirEnv: staged?.agentConfigDirEnv,
39
+ workerEnvironment: input.workerEnvironment,
39
40
  safehouseAddDirs: input.runner === "safehouse" ? resolveSafehouseAddDirs(input.worktreeDir) : undefined,
40
41
  });
41
42
  return { launchCommand, srtSettingsDir: staged?.directory };
@@ -88,6 +89,10 @@ export async function prepareAgentLaunch(input) {
88
89
  throw new Error(`Local groundcrew ${input.purpose} on agent '${input.agent}' cannot inject preLaunchEnv when 'cmd' already starts with 'safehouse'. ` +
89
90
  "Your cmd owns the wrap, so add the names to its own '--env-pass=' flag, or drop the 'safehouse' prefix from 'cmd' to let groundcrew compose the flag for you.");
90
91
  }
92
+ if (runner === "safehouse" && /^safehouse(?:\s|$)/.test(input.definition.cmd)) {
93
+ throw new Error(`Local groundcrew ${input.purpose} on agent '${input.agent}' cannot inject worker self-completion env when 'cmd' already starts with 'safehouse'. ` +
94
+ "Your cmd owns the wrap, so add GROUNDCREW_TASK_ID,GROUNDCREW_COMPLETE to its own '--env-pass=' flag, or drop the 'safehouse' prefix from 'cmd' to let groundcrew compose the flag for you.");
95
+ }
91
96
  const sandboxName = runner === "sdx" && input.definition.sandbox !== undefined
92
97
  ? sandboxNameFor({ agent: input.definition.sandbox.agent })
93
98
  : undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AACvE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AACrE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAO1E,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAEvD;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG,mBAAmB,GAAG,kBAAkB,GAAG,oBAAoB,CAAC;AAE3F,MAAM,WAAW,YAAY;IAC3B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;GAKG;AACH,eAAO,MAAM,SAAS,QAAQ,CAAC;AAE/B;;;;;;;GAOG;AACH,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEvE,eAAO,MAAM,uBAAuB,EAAE,SAAS,oBAAoB,EAKzD,CAAC;AAEX;;;;;;;;GAQG;AACH,MAAM,MAAM,WAAW,GAAG,WAAW,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,CAAC;AAE/D;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,GAAG,WAAW,GAAG,MAAM,CAAC;AAEtD,eAAO,MAAM,qBAAqB,EAAE,SAAS,kBAAkB,EAMrD,CAAC;AAEX;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,+CAA+C;IAC/C,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,eAAe;IAC9B;;;;;;;OAOG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;;;;;;;OAaG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE;QACN,QAAQ,EAAE;YAAE,QAAQ,EAAE,MAAM,CAAC;YAAC,MAAM,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;KACjD,CAAC;IACF;;;;OAIG;IACH,OAAO,CAAC,EAAE,iBAAiB,CAAC;CAC7B;AAED;;;;;;;;GAQG;AACH,KAAK,SAAS,GAAG,eAAe,CAAC,OAAO,CAAC,GAAG;IAAE,QAAQ,EAAE,IAAI,CAAA;CAAE,CAAC;AAC/D,KAAK,0BAA0B,GAAG,OAAO,CAAC,IAAI,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC,GAAG;IAC1E,KAAK,CAAC,EAAE,SAAS,CAAC;CACnB,CAAC;AACF,KAAK,mBAAmB,GAAG,0BAA0B,CAAC;AAEtD;;;;;;;;;GASG;AACH;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,yDAAyD;IACzD,MAAM,EAAE,MAAM,CAAC;IACf,4DAA4D;IAC5D,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,eAAe;IAC9B,oFAAoF;IACpF,IAAI,EAAE,MAAM,CAAC;IACb,6FAA6F;IAC7F,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,2HAA2H;IAC3H,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAC7B;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,MAAM;IACrB;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,EAAE,YAAY,EAAE,CAAC;IACzB,GAAG,CAAC,EAAE;QACJ,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB;;;;WAIG;QACH,YAAY,CAAC,EAAE,MAAM,CAAC;KACvB,CAAC;IACF,SAAS,EAAE;QACT,UAAU,EAAE,MAAM,CAAC;QACnB;;;WAGG;QACH,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,iBAAiB,EAAE,CAAC,MAAM,GAAG,eAAe,CAAC,EAAE,CAAC;KACjD,CAAC;IACF,QAAQ,CAAC,EAAE;QACT,KAAK,CAAC,EAAE,YAAY,CAAC;KACtB,CAAC;IACF,YAAY,CAAC,EAAE;QACb,iBAAiB,CAAC,EAAE,MAAM,CAAC;QAC3B,wBAAwB,CAAC,EAAE,MAAM,CAAC;QAClC,sBAAsB,CAAC,EAAE,MAAM,CAAC;KACjC,CAAC;IACF,MAAM,CAAC,EAAE;QACP,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB;;;;;WAKG;QACH,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;KACnD,CAAC;IACF,OAAO,CAAC,EAAE;QACR,mEAAmE;QACnE,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB;;;;WAIG;QACH,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;IACF;;;;OAIG;IACH,aAAa,CAAC,EAAE,oBAAoB,CAAC;IACrC;;;;OAIG;IACH,KAAK,CAAC,EAAE;QACN,MAAM,CAAC,EAAE,kBAAkB,CAAC;KAC7B,CAAC;IACF,OAAO,CAAC,EAAE;QACR;;;;;WAKG;QACH,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B;;;;;OAKG;IACH,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,GAAG,EAAE;QACH,MAAM,EAAE,MAAM,CAAC;QACf,aAAa,EAAE,MAAM,CAAC;QACtB,YAAY,CAAC,EAAE,MAAM,CAAC;KACvB,CAAC;IACF,SAAS,EAAE;QACT,UAAU,EAAE,MAAM,CAAC;QACnB,4DAA4D;QAC5D,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,0EAA0E;QAC1E,iBAAiB,EAAE,MAAM,EAAE,CAAC;QAC5B,6EAA6E;QAC7E,YAAY,EAAE,eAAe,EAAE,CAAC;QAChC,8EAA8E;QAC9E,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KACzC,CAAC;IACF,QAAQ,EAAE;QACR,KAAK,EAAE,YAAY,CAAC;KACrB,CAAC;IACF,YAAY,EAAE;QACZ,iBAAiB,EAAE,MAAM,CAAC;QAC1B,wBAAwB,EAAE,MAAM,CAAC;QACjC,sBAAsB,EAAE,MAAM,CAAC;KAChC,CAAC;IACF,MAAM,EAAE;QACN,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;KAC9C,CAAC;IACF,OAAO,EAAE;QACP,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IACF;;;OAGG;IACH,aAAa,EAAE,oBAAoB,CAAC;IACpC;;;;OAIG;IACH,KAAK,EAAE;QACL,MAAM,EAAE,kBAAkB,CAAC;KAC5B,CAAC;IACF,OAAO,EAAE;QACP,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;CACH;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAEpF;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAE9D;AAED,MAAM,MAAM,gBAAgB,GAAG,KAAK,GAAG,SAAS,GAAG,KAAK,CAAC;AAEzD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,gBAAgB,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,QAAQ,CAAC,cAAc,CAAC,CAAC;IACjC,MAAM,EAAE,QAAQ,CAAC,YAAY,CAAC,CAAC;CAChC;AA+MD;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,UAAU,EAAE,IAAI,CAAC,eAAe,EAAE,cAAc,CAAC,GAAG,OAAO,CAE1F;AA6FD;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,IAAI,CAAC,cAAc,EAAE,QAAQ,CAAC,EACtC,IAAI,EAAE,MAAM,GACX,OAAO,CAKT;AAukBD,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,CA2B5E;AAED,wBAAsB,UAAU,IAAI,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC,CAGpE"}
@@ -108,6 +108,7 @@ const DEFAULT_PROMPT_INITIAL = [
108
108
  "2. Implement the smallest sensible change that completes the task.",
109
109
  "3. Run the repo's documented verification command. If no documented command exists, run the smallest relevant test suite you can find and fix failures you introduced before continuing.",
110
110
  "4. Follow the task description for output. If no output instructions exist, open a PR with `Closes {{task}}` in the description. If you cannot open one, leave the branch ready and record the blocker.",
111
+ "5. If the requested work is complete, no PR is needed, and any dirty worktree state is expected or explicitly allowed, run the command in `GROUNDCREW_COMPLETE` to mark the task done.",
111
112
  ].join("\n");
112
113
  const ALLOWED_PROMPT_PLACEHOLDERS = new Set([
113
114
  "{{task}}",
@@ -37,6 +37,10 @@ export declare function isEnvironmentAssignment(token: string): boolean;
37
37
  * profile; srt uses it to pick the agent's credential profile in `srtPolicy`.
38
38
  */
39
39
  export declare function inferAgentCommandName(agentCmd: string): string;
40
+ declare const WORKER_ENVIRONMENT_NAMES: readonly ["GROUNDCREW_TASK_ID", "GROUNDCREW_COMPLETE"];
41
+ type WorkerEnvironmentName = (typeof WORKER_ENVIRONMENT_NAMES)[number];
42
+ export type WorkerEnvironment = Readonly<Record<WorkerEnvironmentName, string>>;
43
+ export declare function workerEnvironmentForTask(taskId: string): WorkerEnvironment;
40
44
  interface LaunchCommandArguments {
41
45
  definition: AgentDefinition;
42
46
  promptFile: string;
@@ -111,6 +115,11 @@ interface LaunchCommandArguments {
111
115
  * pre-existing behavior). Only consumed by the safehouse wrap.
112
116
  */
113
117
  safehouseAddDirs?: readonly string[] | undefined;
118
+ /**
119
+ * Groundcrew-managed task metadata exposed to the launched worker. Forwarded
120
+ * to the agent process, not the prepareWorktree hook.
121
+ */
122
+ workerEnvironment?: WorkerEnvironment | undefined;
114
123
  }
115
124
  /**
116
125
  * Build the shell command that runs inside the workspace. The prompt is
@@ -1 +1 @@
1
- {"version":3,"file":"launchCommand.d.ts","sourceRoot":"","sources":["../../src/lib/launchCommand.ts"],"names":[],"mappings":"AAIA,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,eAAe,EACrB,MAAM,aAAa,CAAC;AAIrB,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE9C;;;;;;;;;GASG;AACH,wBAAgB,6BAA6B,CAAC,OAAO,GAAE,MAAwB,GAAG,MAAM,CAcvF;AAID;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,MAAwB,GAAG,MAAM,CAgB3E;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GAAG,MAAM,CAMvF;AAsMD,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE9D;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CA8B9D;AAED,UAAU,sBAAsB;IAC9B,UAAU,EAAE,eAAe,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC;;;;OAIG;IACH,sBAAsB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5C;;;;OAIG;IACH,MAAM,EAAE,WAAW,CAAC;IACpB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC;;;;OAIG;IACH,sBAAsB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5C;;;OAGG;IACH,oBAAoB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC;;;;;;;OAOG;IACH,oBAAoB,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;IACnE;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;CAClD;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,sBAAsB,GAAG,MAAM,CA6B7E"}
1
+ {"version":3,"file":"launchCommand.d.ts","sourceRoot":"","sources":["../../src/lib/launchCommand.ts"],"names":[],"mappings":"AAIA,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,eAAe,EACrB,MAAM,aAAa,CAAC;AAIrB,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE9C;;;;;;;;;GASG;AACH,wBAAgB,6BAA6B,CAAC,OAAO,GAAE,MAAwB,GAAG,MAAM,CAcvF;AAID;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,MAAwB,GAAG,MAAM,CAgB3E;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GAAG,MAAM,CAMvF;AAsMD,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE9D;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CA8B9D;AAED,QAAA,MAAM,wBAAwB,YAAI,oBAAoB,EAAE,qBAAqB,CAAU,CAAC;AAExF,KAAK,qBAAqB,GAAG,CAAC,OAAO,wBAAwB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEvE,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC,MAAM,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC,CAAC;AAEhF,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,MAAM,GAAG,iBAAiB,CAK1E;AAsBD,UAAU,sBAAsB;IAC9B,UAAU,EAAE,eAAe,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC;;;;OAIG;IACH,sBAAsB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5C;;;;OAIG;IACH,MAAM,EAAE,WAAW,CAAC;IACpB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC;;;;OAIG;IACH,sBAAsB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5C;;;OAGG;IACH,oBAAoB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC;;;;;;;OAOG;IACH,oBAAoB,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;IACnE;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IACjD;;;OAGG;IACH,iBAAiB,CAAC,EAAE,iBAAiB,GAAG,SAAS,CAAC;CACnD;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,sBAAsB,GAAG,MAAM,CAkC7E"}
@@ -247,6 +247,26 @@ export function inferAgentCommandName(agentCmd) {
247
247
  }
248
248
  return commandName;
249
249
  }
250
+ const WORKER_ENVIRONMENT_NAMES = ["GROUNDCREW_TASK_ID", "GROUNDCREW_COMPLETE"];
251
+ export function workerEnvironmentForTask(taskId) {
252
+ return {
253
+ GROUNDCREW_TASK_ID: taskId,
254
+ GROUNDCREW_COMPLETE: `crew task done ${taskId}`,
255
+ };
256
+ }
257
+ function workerEnvironmentNames(workerEnvironment) {
258
+ return workerEnvironment === undefined ? [] : WORKER_ENVIRONMENT_NAMES;
259
+ }
260
+ function workerEnvironmentExports(workerEnvironment) {
261
+ if (workerEnvironment === undefined) {
262
+ return [];
263
+ }
264
+ return WORKER_ENVIRONMENT_NAMES.map((name) => `export ${name}=${shellSingleQuote(workerEnvironment[name])}`);
265
+ }
266
+ function envPassFlag(names) {
267
+ const uniqueNames = [...new Set(names)];
268
+ return uniqueNames.length === 0 ? "" : `--env-pass=${uniqueNames.join(",")} `;
269
+ }
250
270
  /**
251
271
  * Build the shell command that runs inside the workspace. The prompt is
252
272
  * staged in a temp file (so backticks/quotes/$ in the description survive),
@@ -277,6 +297,9 @@ export function buildLaunchCommand(arguments_) {
277
297
  // inject into. Fail loudly instead of silently dropping the contract.
278
298
  throw new Error("preLaunchEnv cannot be injected when `cmd` starts with `safehouse` — your cmd owns the wrap, so add the names to its own `--env-pass=` flag, or drop the `safehouse` prefix from `cmd` to let groundcrew compose the flag for you.");
279
299
  }
300
+ if (arguments_.workerEnvironment !== undefined && arguments_.runner === "safehouse") {
301
+ throw new Error("workerEnvironment cannot be injected when `cmd` starts with `safehouse` — your cmd owns the wrap, so add GROUNDCREW_TASK_ID,GROUNDCREW_COMPLETE to its own `--env-pass=` flag, or drop the `safehouse` prefix from `cmd` to let groundcrew compose the flag for you.");
302
+ }
280
303
  return buildUnwrappedHostLaunchCommand(arguments_);
281
304
  }
282
305
  /**
@@ -315,6 +338,7 @@ function buildUnwrappedHostLaunchCommand(arguments_) {
315
338
  if (arguments_.secretsFile !== undefined) {
316
339
  lines.push(unsetSecretsLine());
317
340
  }
341
+ lines.push(...workerEnvironmentExports(arguments_.workerEnvironment));
318
342
  lines.push(...preLaunchPromptAndExec({
319
343
  definition: arguments_.definition,
320
344
  worktreeDir: arguments_.worktreeDir,
@@ -365,8 +389,10 @@ function buildSafehouseLaunchCommand(arguments_) {
365
389
  // credentials out of the profile-neutral prepare phase — see PR #128.
366
390
  // Trailing space keeps each flag separated from the next argv token.
367
391
  const prepareWorktreeEnvPassFlag = arguments_.secretsFile === undefined ? "" : `--env-pass=${BUILD_SECRET_NAMES.join(",")} `;
368
- const preLaunchEnvNames = arguments_.definition.preLaunchEnv ?? [];
369
- const agentEnvPassFlag = preLaunchEnvNames.length === 0 ? "" : `--env-pass=${preLaunchEnvNames.join(",")} `;
392
+ const agentEnvPassFlag = envPassFlag([
393
+ ...(arguments_.definition.preLaunchEnv ?? []),
394
+ ...workerEnvironmentNames(arguments_.workerEnvironment),
395
+ ]);
370
396
  // safehouse reads colon-separated paths from `--add-dirs`; both wraps get the
371
397
  // same grant so the prepareWorktree hook and the agent can each reach git.
372
398
  // Quote the whole value so shell-special chars survive; the trailing space
@@ -398,7 +424,7 @@ function buildSafehouseLaunchCommand(arguments_) {
398
424
  if (arguments_.secretsFile !== undefined) {
399
425
  lines.push(unsetSecretsLine());
400
426
  }
401
- lines.push(`_safehouse_shim_dir=$(mktemp -d "\${TMPDIR:-/tmp}/groundcrew-safehouse-XXXXXX")`, shimAndPromptTrap, `_safehouse_shim="$_safehouse_shim_dir/${safehouseCommandName}"`, `ln -s /bin/sh "$_safehouse_shim"`,
427
+ lines.push(...workerEnvironmentExports(arguments_.workerEnvironment), `_safehouse_shim_dir=$(mktemp -d "\${TMPDIR:-/tmp}/groundcrew-safehouse-XXXXXX")`, shimAndPromptTrap, `_safehouse_shim="$_safehouse_shim_dir/${safehouseCommandName}"`, `ln -s /bin/sh "$_safehouse_shim"`,
402
428
  // Safehouse selects an agent profile from the wrapped command's basename.
403
429
  // Running the real launch chain as `sh -c` would make it see `sh`, so use
404
430
  // an agent-named symlink to /bin/sh. This preserves per-agent profile
@@ -492,7 +518,10 @@ function buildSrtLaunchCommand(arguments_) {
492
518
  const agentConfigDirAssignment = arguments_.srtAgentConfigDirEnv === undefined
493
519
  ? ""
494
520
  : ` ${arguments_.srtAgentConfigDirEnv.name}=${shellSingleQuote(arguments_.srtAgentConfigDirEnv.value)}`;
495
- const agentWrap = `env -i ${baseline}${agentConfigDirAssignment}${srtForwardedEnv(arguments_.definition.preLaunchEnv ?? [])} ${agentTarget}`;
521
+ const agentWrap = `env -i ${baseline}${agentConfigDirAssignment}${srtForwardedEnv([
522
+ ...(arguments_.definition.preLaunchEnv ?? []),
523
+ ...workerEnvironmentNames(arguments_.workerEnvironment),
524
+ ])} ${agentTarget}`;
496
525
  // One EXIT trap wipes both the settings dir and the prompt dir, covering
497
526
  // every failure window between here and the post-wrap cleanup.
498
527
  const cleanup = `rm -rf ${shellSingleQuote(arguments_.srtSettingsDir)}; rm -rf ${shellSingleQuote(promptDir)}`;
@@ -510,7 +539,7 @@ function buildSrtLaunchCommand(arguments_) {
510
539
  if (prepareWorktreeCommand !== undefined) {
511
540
  lines.push(`${prepareWrap} sh -c ${shellSingleQuote(prepareWorktreeCommand)}`);
512
541
  }
513
- lines.push(`{ ${agentWrap} sh -c ${shellSingleQuote(agentCommand)} sh "$_p"; _srt_status=$?; rm -rf ${shellSingleQuote(arguments_.srtSettingsDir)}; trap - EXIT; exit "$_srt_status"; }`);
542
+ lines.push(...workerEnvironmentExports(arguments_.workerEnvironment), `{ ${agentWrap} sh -c ${shellSingleQuote(agentCommand)} sh "$_p"; _srt_status=$?; rm -rf ${shellSingleQuote(arguments_.srtSettingsDir)}; trap - EXIT; exit "$_srt_status"; }`);
514
543
  return lines.join(" && ");
515
544
  }
516
545
  function buildSdxLaunchCommand(arguments_) {
@@ -527,15 +556,17 @@ function buildSdxLaunchCommand(arguments_) {
527
556
  if (arguments_.secretsFile !== undefined) {
528
557
  innerParts.push(unsetSecretsLine());
529
558
  }
559
+ innerParts.push(...workerEnvironmentExports(arguments_.workerEnvironment));
530
560
  innerParts.push(agentCommand);
531
561
  const innerCommand = innerParts.join("; ");
532
562
  // Passthrough form (`-e KEY` without `=VALUE`): sbx reads each value
533
563
  // from its own env at invocation time — populated by sourceSecretsLine
534
564
  // a few lines up. Avoids `-e KEY="$KEY"`, which would embed the value
535
565
  // in argv and break on `"`, `$`, or backticks in the token.
536
- const sbxEnvironmentFlags = arguments_.secretsFile === undefined
566
+ const sbxEnvironmentNames = arguments_.secretsFile === undefined ? [] : BUILD_SECRET_NAMES;
567
+ const sbxEnvironmentFlags = sbxEnvironmentNames.length === 0
537
568
  ? ""
538
- : `${BUILD_SECRET_NAMES.map((name) => `-e ${name}`).join(" ")} `;
569
+ : `${sbxEnvironmentNames.map((name) => `-e ${name}`).join(" ")} `;
539
570
  const lines = [trapCleanupLine(promptDir)];
540
571
  if (arguments_.secretsFile !== undefined) {
541
572
  lines.push(sourceSecretsLine(arguments_.secretsFile));
@@ -26,6 +26,11 @@ export interface RunState {
26
26
  * just the task id.
27
27
  */
28
28
  url?: string;
29
+ /**
30
+ * Canonical source-prefixed id used for no-PR self-completion. Cached so
31
+ * resumed workers keep the same completion target as the original launch.
32
+ */
33
+ completionTaskId?: string;
29
34
  }
30
35
  export interface RunStateDraft {
31
36
  task: string;
@@ -40,6 +45,7 @@ export interface RunStateDraft {
40
45
  resumeCount?: number;
41
46
  title?: string;
42
47
  url?: string;
48
+ completionTaskId?: string;
43
49
  }
44
50
  export interface RecordRunStateInput {
45
51
  config: ResolvedConfig;
@@ -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;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
+ {"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;CAC3B;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;CAC3B;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;AAqFD,wBAAgB,YAAY,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAYvF;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,QAAQ,CA4BnE;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"}
@@ -44,6 +44,7 @@ function parseRunState(value) {
44
44
  const detail = stringField(value, "detail");
45
45
  const title = stringField(value, "title");
46
46
  const url = stringField(value, "url");
47
+ const completionTaskId = stringField(value, "completionTaskId");
47
48
  if (task === undefined ||
48
49
  repository === undefined ||
49
50
  agent === undefined ||
@@ -73,6 +74,7 @@ function parseRunState(value) {
73
74
  ...(detail === undefined ? {} : { detail }),
74
75
  ...(title === undefined ? {} : { title }),
75
76
  ...(url === undefined ? {} : { url }),
77
+ ...(completionTaskId === undefined ? {} : { completionTaskId }),
76
78
  };
77
79
  }
78
80
  function writeState(config, state) {
@@ -105,6 +107,7 @@ export function recordRunState(input) {
105
107
  // transitions.
106
108
  const title = input.state.title ?? existing?.title;
107
109
  const url = input.state.url ?? existing?.url;
110
+ const completionTaskId = input.state.completionTaskId ?? existing?.completionTaskId;
108
111
  const state = {
109
112
  task: taskKey(input.state.task),
110
113
  repository: input.state.repository,
@@ -120,6 +123,7 @@ export function recordRunState(input) {
120
123
  ...(input.state.detail === undefined ? {} : { detail: input.state.detail }),
121
124
  ...(title === undefined ? {} : { title }),
122
125
  ...(url === undefined ? {} : { url }),
126
+ ...(completionTaskId === undefined ? {} : { completionTaskId }),
123
127
  };
124
128
  writeState(input.config, state);
125
129
  return state;
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. 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.
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 requires `--repo <repo>` unless the source configures `defaultRepository`, appends the todo line, writes `.tasks/<id>.md`, and leaves `status:todo` as the final meaningful token, so no separate ready command is required. Pass `--priority <letter>` to add a todo.txt priority marker. 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" \
@@ -41,6 +41,44 @@ crew task create "Fix cancellation retry race" \
41
41
  --description "Investigate retry handling."
42
42
  ```
43
43
 
44
+ `crew task done <task-id>` marks one task done through its source adapter. Use
45
+ it for completed work that intentionally does not produce a PR. The command
46
+ resolves canonical IDs such as `todo:flaky-triage-1` directly, or natural IDs
47
+ when they match exactly one configured source. Sources without a done writeback
48
+ return an unsupported error.
49
+
50
+ Groundcrew checks matching local worktrees before marking a task done. Clean
51
+ worktrees, and tasks with no local worktree, are allowed. A dirty worktree with
52
+ no matching PR is refused by default so later cleanup does not discard
53
+ uncommitted work; pass `--allow-dirty` only when that dirty state is expected.
54
+
55
+ ```bash
56
+ # PR-producing tasks usually complete automatically:
57
+ # open PR -> in-review, merged PR -> done.
58
+
59
+ # Manual completion for no-PR operational work:
60
+ crew task done todo:flaky-triage-1
61
+
62
+ # Explicit override when a no-PR task intentionally leaves local changes:
63
+ crew task done todo:docs-refresh-1 --allow-dirty
64
+ ```
65
+
66
+ For recurring no-PR tasks, keep recurrence in the source and complete the
67
+ current task with `crew task done`. The todo-txt source marks the current line
68
+ done and schedules the next `status:todo` recurrence itself.
69
+
70
+ ```bash
71
+ crew task create "Run flaky triage sweep" \
72
+ --source todo \
73
+ --agent codex \
74
+ --repo ClipboardHealth/groundcrew \
75
+ --id flaky-triage-1 \
76
+ --rec 2h \
77
+ --description "Triage the flaky queue. No PR is needed when the queue is updated."
78
+
79
+ crew task done todo:flaky-triage-1
80
+ ```
81
+
44
82
  ## Status
45
83
 
46
84
  `crew status <TASK>` prints a read-only snapshot for one task: cached title and URL when present, recorded run state, live workspace presence, matching worktrees, git dirtiness, PR links for matching branches, recent log lines when present, and the task status from the configured task source.
@@ -57,7 +95,7 @@ crew status ENG-123
57
95
  ===================
58
96
  task: eng-123 in-progress https://linear.app/example/issue/ENG-123
59
97
  title: Multi-event extractor: year inference can produce date_start > date_end
60
- run: running; model=claude; updated=2026-05-26T00:01:00.000Z; resumes=0
98
+ run: running; agent=claude; updated=2026-05-26T00:01:00.000Z; resumes=0
61
99
  workspace: live
62
100
 
63
101
  Worktrees
@@ -77,9 +115,9 @@ Recent logs
77
115
 
78
116
  ## Doctor
79
117
 
80
- `crew doctor` checks host prerequisites only: config validity, task-source reachability, required binaries on PATH, workspace backend availability, `workspace.projectDir`, local runner capability, and enabled model commands.
118
+ `crew doctor` checks host prerequisites only: config validity, task-source reachability, required binaries on PATH, workspace backend availability, `workspace.projectDir`, local runner capability, and enabled agent commands.
81
119
 
82
- Doctor's command introspection is intentionally shallow. It reports the resolved local runner and tokenizes each model `cmd`, then checks the first two non-flag tokens against PATH. Boolean flags without values, env-var assignments, shell pipelines, and subshells are not parsed.
120
+ Doctor's command introspection is intentionally shallow. It reports the resolved local runner and tokenizes each agent `cmd`, then checks the first two non-flag tokens against PATH. Boolean flags without values, env-var assignments, shell pipelines, and subshells are not parsed.
83
121
 
84
122
  ## Start
85
123
 
@@ -106,4 +144,4 @@ The command closes the cmux/tmux/zellij workspace if present, records local run
106
144
 
107
145
  `crew resume <TASK>` reopens an existing task worktree with a continuation prompt. Resume never creates a new worktree; if none exists it fails and leaves re-dispatch to `crew start <task>`.
108
146
 
109
- The resume prompt tells the agent to inspect git status and diff before editing, includes the previous interrupt reason when recorded, and reuses the recorded model, repository, branch, runner, sandbox, and workspace backend. When no run-state file exists but a worktree does, resume falls back to Linear resolution for the model and task context.
147
+ The resume prompt tells the agent to inspect git status and diff before editing, includes the previous interrupt reason when recorded, and reuses the recorded agent, repository, branch, runner, sandbox, and workspace backend. When no run-state file exists but a worktree does, resume falls back to Linear resolution for the agent and task context.
@@ -1,13 +1,13 @@
1
1
  # Configuration
2
2
 
3
- Workspace settings and at least one enabled model are required; everything else has a default.
3
+ Workspace settings and at least one enabled agent are required; everything else has a default.
4
4
 
5
5
  | Key | What |
6
6
  | ----------------------------- | -------------------------------------------------------------------- |
7
7
  | `workspace.projectDir` | Parent dir for cloned repos and the default task worktree root. |
8
8
  | `workspace.worktreeDir` | Optional parent dir for task worktrees. |
9
9
  | `workspace.knownRepositories` | Repos searched for in task descriptions to infer where work belongs. |
10
- | `models.definitions` | Enabled model set. Built-in presets can be enabled with `{}`. |
10
+ | `agents.definitions` | Enabled agent set. Built-in presets can be enabled with `{}`. |
11
11
 
12
12
  The branch prefix (`<prefix>-<TASK>`) defaults to `os.userInfo().username`; override it with `git.branchPrefix` (see the full reference below). Changing it only affects newly created worktrees; existing local branches keep their original names until cleaned up. Groundcrew picks up every issue assigned to your API key's viewer that carries an `agent-*` label across every visible team and project, governed by a single `orchestrator.maximumInProgress` budget.
13
13
 
@@ -92,11 +92,11 @@ The "Loaded config from ..." line at startup tells you which config won.
92
92
 
93
93
  ## Agent Label Routing
94
94
 
95
- - `agent-claude`, `agent-codex`, `agent-<name>` routes to that enabled model.
96
- - `agent-any` routes to the model with the most session headroom, after skipping models over their session limit or weekly paced budget.
97
- - Unknown `agent-<name>` falls back to `models.default`.
98
- - A built-in `agent-<name>` label whose model is not enabled falls back to `models.default` with a warning.
99
- - No `agent-*` label is ignored by `crew run`. Dispatch on demand with `crew start <TASK>`, which falls back to `models.default`.
95
+ - `agent-claude`, `agent-codex`, `agent-<name>` routes to that enabled agent.
96
+ - `agent-any` routes to the agent with the most session headroom, after skipping agents over their session limit or weekly paced budget.
97
+ - Unknown `agent-<name>` falls back to `agents.default`.
98
+ - A built-in `agent-<name>` label whose agent is not enabled falls back to `agents.default` with a warning.
99
+ - No `agent-*` label is ignored by `crew run`. Dispatch on demand with `crew start <TASK>`, which falls back to `agents.default`.
100
100
  - Todo tasks blocked by non-terminal blockers are skipped until their blockers reach a terminal status.
101
101
 
102
102
  Status classification uses Linear's default status names `In Progress` and `In Review` to disambiguate multiple `started` workflow states. Statuses that do not match those names fall back to Linear's workflow `state.type` (`unstarted`, `started`, `completed`, `canceled`, `duplicate`), so broad lifecycle classification still works without configuration. Parent issues with children are ignored; sub-issues are the work items.
@@ -136,13 +136,13 @@ export default {
136
136
  };
137
137
  ```
138
138
 
139
- ## Enabling Model Presets
139
+ ## Enabling Agent Presets
140
140
 
141
- Groundcrew ships built-in presets for `claude` and `codex`, but models are not enabled by default. List the models you want in `models.definitions`:
141
+ Groundcrew ships built-in presets for `claude` and `codex`, but agents are not enabled by default. List the agents you want in `agents.definitions`:
142
142
 
143
143
  ```ts
144
144
  export default {
145
- models: {
145
+ agents: {
146
146
  default: "claude",
147
147
  definitions: {
148
148
  claude: {},
@@ -155,7 +155,7 @@ To keep both shipped presets enabled:
155
155
 
156
156
  ```ts
157
157
  export default {
158
- models: {
158
+ agents: {
159
159
  default: "claude",
160
160
  definitions: {
161
161
  claude: {},
@@ -167,15 +167,15 @@ export default {
167
167
 
168
168
  Rules:
169
169
 
170
- - `models.definitions` is the enabled model set; `crew doctor` only probes listed models.
170
+ - `agents.definitions` is the enabled agent set; `crew doctor` only probes listed agents.
171
171
  - Built-in entries can be `{}` or partial overrides such as `{ cmd: "..." }`.
172
- - Custom model names must provide `cmd` and `color`.
173
- - `models.default` must point at an enabled model.
174
- - Legacy model entries like `codex: { disabled: true }` are rejected with migration guidance; remove unwanted entries instead.
172
+ - Custom agent names must provide `cmd` and `color`.
173
+ - `agents.default` must point at an enabled agent.
174
+ - Legacy agent entries like `codex: { disabled: true }` are rejected with migration guidance; remove unwanted entries instead.
175
175
 
176
176
  ## Prompt Customization
177
177
 
178
- Groundcrew ships one model-agnostic unattended prompt by default. It tells the agent to make reasonable assumptions, follow repository instructions, run documented verification, review its diff, open a PR when GitHub/`gh` is available, and include a workspace continuation hint when known.
178
+ Groundcrew ships one agent-agnostic unattended prompt by default. It tells the agent to make reasonable assumptions, follow repository instructions, run documented verification, review its diff, open a PR when GitHub/`gh` is available, and include a workspace continuation hint when known.
179
179
 
180
180
  This prompt describes how the agent works, not what it does. The task is the task description, which groundcrew passes through unchanged. Keep source-specific instructions, acceptance criteria, links, and output requirements in the task body. Override `prompts.initial` only to change the execution contract for every dispatched task — team-wide review rules, required verification, local tool conventions — not to encode behavior for a single task type.
181
181
 
@@ -242,15 +242,15 @@ and hook contract.
242
242
  | `defaults.hooks.prepareWorktree` | optional | Fallback repo-preparation command used only when the worktree does not define `.groundcrew/config.json` `hooks.prepareWorktree`. The hook runs after worktree creation and before the agent starts. Repo-local config wins. |
243
243
  | `orchestrator.maximumInProgress` | `4` | Cap on in-progress tasks at once for this `crew` instance. |
244
244
  | `orchestrator.pollIntervalMilliseconds` | `120_000` | Poll interval in `--watch` mode. |
245
- | `orchestrator.sessionLimitPercentage` | `85` | Number in `(0, 100]`. A model whose codexbar session window exceeds this percentage is skipped that tick. Models are also skipped when codexbar reports weekly usage over the current weekly paced budget. |
246
- | `models.default` | `"claude"` | Tiebreak for `agent-any` resolution and fallback for explicit but unknown `agent-*` labels. Also used by `crew start <TASK>` for unlabeled tasks. `crew run` ignores unlabeled tasks and does not apply this default. Must exist in `models.definitions`. If you enable only `codex`, set `default: "codex"`. |
247
- | `models.definitions` | **required** | Enabled model set. Built-in keys (`claude`, `codex`) can use `{}` to opt into the shipped preset. Custom model names must provide `cmd` and `color`. |
248
- | `models.definitions.<name>.cmd` | preset for built-ins | Shell command launched for the model. Required for custom models. Runs in the worktree through the resolved `local.runner`. `{{worktree}}` is replaced before launch; `{{sandbox}}` expands to the sbx sandbox name under the sdx runner and an empty string otherwise. |
249
- | `models.definitions.<name>.color` | preset for built-ins | Color for the workspace status pill (cmux only; tmux and zellij silently drop it). Required for custom models. |
250
- | `models.definitions.<name>.usage` | preset for built-ins | If set, codexbar usage is fetched for this model and gated by `sessionLimitPercentage` plus the weekly paced budget when codexbar exposes a weekly window. When `usage.codexbar.source` is omitted, groundcrew uses `oauth` for Codex/Claude on macOS, `auto` for other macOS providers, and `cli` elsewhere. Set to `{ disabled: true }` to disable usage gating while keeping the model enabled. |
251
- | `models.definitions.<name>.sandbox` | optional | Docker Sandboxes binding for the model. Required at launch when `local.runner` resolves to `sdx`. Field: `agent` (required sbx agent name). Groundcrew assumes the `groundcrew-<agent>` sandbox already exists. |
252
- | `models.definitions.<name>.preLaunch` | optional | Host-only shell snippet run before the agent exec and outside Safehouse/sdx. Exports survive into the launch shell; under the default `safehouse` runner they are only forwarded to the agent when listed via `preLaunchEnv` or when `cmd` includes its own `safehouse --env-pass=NAMES`. `{{worktree}}` is substituted. A non-zero exit aborts launch. Not supported when `local.runner` resolves to `sdx` in v1. |
253
- | `models.definitions.<name>.preLaunchEnv` | optional | Companion to `preLaunch`: list of env var names to append to groundcrew's `safehouse-clearance` `--env-pass=` flag, so `preLaunch` exports reach the agent without overriding `cmd` and losing the project's egress allowlist. Each entry must match `[A-Za-z_][A-Za-z0-9_]*`. Under `runner: "none"` exports already inherit and `preLaunchEnv` is a no-op. An empty array is a uniform no-op in every runner; a non-empty list is rejected when `cmd` already starts with `safehouse` or when `runner` resolves to `sdx`. |
245
+ | `orchestrator.sessionLimitPercentage` | `85` | Number in `(0, 100]`. An agent whose codexbar session window exceeds this percentage is skipped that tick. Agents are also skipped when codexbar reports weekly usage over the current weekly paced budget. |
246
+ | `agents.default` | `"claude"` | Tiebreak for `agent-any` resolution and fallback for explicit but unknown `agent-*` labels. Also used by `crew start <TASK>` for unlabeled tasks. `crew run` ignores unlabeled tasks and does not apply this default. Must exist in `agents.definitions`. If you enable only `codex`, set `default: "codex"`. |
247
+ | `agents.definitions` | **required** | Enabled agent set. Built-in keys (`claude`, `codex`) can use `{}` to opt into the shipped preset. Custom agent names must provide `cmd` and `color`. |
248
+ | `agents.definitions.<name>.cmd` | preset for built-ins | Shell command launched for the agent. Required for custom agents. Runs in the worktree through the resolved `local.runner`. `{{worktree}}` is replaced before launch; `{{sandbox}}` expands to the sbx sandbox name under the sdx runner and an empty string otherwise. |
249
+ | `agents.definitions.<name>.color` | preset for built-ins | Color for the workspace status pill (cmux only; tmux and zellij silently drop it). Required for custom agents. |
250
+ | `agents.definitions.<name>.usage` | preset for built-ins | If set, codexbar usage is fetched for this agent and gated by `sessionLimitPercentage` plus the weekly paced budget when codexbar exposes a weekly window. When `usage.codexbar.source` is omitted, groundcrew uses `oauth` for Codex/Claude on macOS, `auto` for other macOS providers, and `cli` elsewhere. Set to `{ disabled: true }` to disable usage gating while keeping the agent enabled. |
251
+ | `agents.definitions.<name>.sandbox` | optional | Docker Sandboxes binding for the agent. Required at launch when `local.runner` resolves to `sdx`. Field: `agent` (required sbx agent name). Groundcrew assumes the `groundcrew-<agent>` sandbox already exists. |
252
+ | `agents.definitions.<name>.preLaunch` | optional | Host-only shell snippet run before the agent exec and outside Safehouse/sdx. Exports survive into the launch shell; under the default `safehouse` runner they are only forwarded to the agent when listed via `preLaunchEnv` or when `cmd` includes its own `safehouse --env-pass=NAMES`. `{{worktree}}` is substituted. A non-zero exit aborts launch. Not supported when `local.runner` resolves to `sdx` in v1. |
253
+ | `agents.definitions.<name>.preLaunchEnv` | optional | Companion to `preLaunch`: list of env var names to append to groundcrew's `safehouse-clearance` `--env-pass=` flag, so `preLaunch` exports reach the agent without overriding `cmd` and losing the project's egress allowlist. Each entry must match `[A-Za-z_][A-Za-z0-9_]*`. Under `runner: "none"` exports already inherit and `preLaunchEnv` is a no-op. An empty array is a uniform no-op in every runner; a non-empty list is rejected when `cmd` already starts with `safehouse` or when `runner` resolves to `sdx`. |
254
254
  | `prompts.initial` | unattended template | First message sent to the agent: the execution wrapper around each task. The task description is the task-specific prompt. Placeholders: `{{task}}`, `{{worktree}}`, `{{title}}`, `{{description}}`. Override only to change the execution contract for every task, such as team-wide review rules or tool conventions. Mutually exclusive with `prompts.promptFile`. |
255
255
  | `prompts.promptFile` | optional | Path to a UTF-8 file whose contents become `prompts.initial`, read at load time. Resolved relative to the config file's directory; `~` is expanded and absolute paths are used as-is. The JSON-friendly alternative to inlining a large prompt or `readFileSync`. Mutually exclusive with `prompts.initial`. |
256
256
  | `workspaceKind` | `"auto"` | Terminal session manager. `"auto"` picks `cmux` when on PATH, else `tmux`. Set to `"cmux"`, `"tmux"`, or `"zellij"` to fail loudly when the chosen backend is missing. |
@@ -52,7 +52,7 @@ The "preLaunch never sees build secrets" contract is enforced differently per ru
52
52
  Under the default `safehouse` runner, the agent runs under a sanitized env allowlist. Exports from `preLaunch` land in the launch shell but are stripped before reaching the agent unless they are forwarded. `preLaunchEnv` is the supported way to forward them:
53
53
 
54
54
  ```ts
55
- models: {
55
+ agents: {
56
56
  definitions: {
57
57
  claude: {
58
58
  preLaunch: "SESSION_TOKEN=$(your-mint-command) && export SESSION_TOKEN",
@@ -69,7 +69,7 @@ Under `runner: "none"`, exports flow through unchanged and `preLaunchEnv` is a n
69
69
  <details>
70
70
  <summary>Manual fallback when <code>cmd</code> brings its own <code>safehouse</code> wrap</summary>
71
71
 
72
- If your `cmd` already starts with `safehouse`, groundcrew will not auto-compose `--env-pass=` for you and a non-empty `preLaunchEnv` is rejected at launch. Add the names to your own `cmd` instead. This opts the model out of groundcrew's default `safehouse-clearance` wrap, so re-supply `--append-profile` / `--env` yourself if you need it:
72
+ If your `cmd` already starts with `safehouse`, groundcrew will not auto-compose `--env-pass=` for you and a non-empty `preLaunchEnv` is rejected at launch. Add the names to your own `cmd` instead. This opts the agent out of groundcrew's default `safehouse-clearance` wrap, so re-supply `--append-profile` / `--env` yourself if you need it:
73
73
 
74
74
  ```ts
75
75
  claude: {
package/docs/runners.md CHANGED
@@ -49,7 +49,7 @@ Groundcrew generates a per-launch policy itself (Safehouse's `.sb` profiles have
49
49
 
50
50
  - **Reads**: the home region (`/Users` on macOS, `/home`+`/root`+`/mnt` on Linux — `/mnt` covers WSL's Windows drive mounts) is denied, then the worktree, the repo's git metadata, the language toolchains needed to run the agent, and the agent's own config dirs (`~/.claude`, `~/.codex`, …) are re-opened. On macOS the user keychain dir (`~/Library/Keychains`) is also re-opened read-only so keychain-authenticated agents (claude) can sign in under the home mask. The agent cannot read `~/.ssh`, `~/.aws`, shell history, or unrelated repos.
51
51
  - **Writes**: allow-only, and the host-CLI persistence vector (planting hooks, `mcpServers`, `commands/`, `plugins/`, … that run on the user's next host invocation) is closed per agent. **claude** keeps a writable `~/.claude` (its Bash tool needs scratch/session state there) but every fixed-path executable/instruction surface — `~/.claude.json` (`mcpServers`), `settings.json` and its hooks, `commands/`, `agents/`, `plugins/`, `skills/`, `statusline.sh`, `CLAUDE.md`, the bundled `chrome` binary, `.git/{hooks,config}` — is denied; claude tolerates those write denials. **codex** hard-fails with a read-only home, so it is pointed at a per-launch relocated config dir (`CODEX_HOME`) seeded with its credentials, leaving the real `~/.codex` entirely unwritten. The git common dir is granted as a **narrow allowlist** of only what `status/diff/add/commit/push/gc` write (`objects`, `refs`, `logs`, `packed-refs`, this worktree's gitdir, …) — never wholesale, so the repo `config`/`hooks`, the per-worktree gitdir redirection files, and **sibling worktree gitdirs** stay unwritable. Global toolchain bins (`~/.cargo/bin`, global `node_modules`, the npx cache, …) are never writable either.
52
- - **Environment**: each `srt` invocation runs under a sanitized env (`env -i` + a benign baseline). Unlike safehouse and sdx, the `srt` CLI inherits the host env, so without this an ambient `AWS_*`, `GITHUB_TOKEN`, etc. would reach the agent and bypass the read mask. Credentials the agent legitimately needs from the environment must be forwarded explicitly via the model's `preLaunchEnv` (the same opt-in pass-list safehouse uses).
52
+ - **Environment**: each `srt` invocation runs under a sanitized env (`env -i` + a benign baseline). Unlike safehouse and sdx, the `srt` CLI inherits the host env, so without this an ambient `AWS_*`, `GITHUB_TOKEN`, etc. would reach the agent and bypass the read mask. Credentials the agent legitimately needs from the environment must be forwarded explicitly via the agent's `preLaunchEnv` (the same opt-in pass-list safehouse uses).
53
53
  - **Network**: allow-only, **reused from the same Clearance allowlist** (`CLEARANCE_ALLOW_HOSTS` / `CLEARANCE_ALLOW_HOSTS_FILES`, including the shipped `clearance-allow-hosts`) so there is one source of truth. Local binding and unix sockets stay off (never the Docker socket).
54
54
 
55
55
  ### Linux / WSL prerequisites
@@ -76,7 +76,7 @@ sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
76
76
 
77
77
  ## Docker Sandboxes Setup
78
78
 
79
- Each model that runs under `sdx` needs a `sandbox: { agent: "<sbx-agent>" }` block in `crew.config.ts`. Groundcrew addresses the sandbox as `groundcrew-<agent>` and reuses one existing sandbox per agent across repos and tasks.
79
+ Each agent that runs under `sdx` needs a `sandbox: { agent: "<sbx-agent>" }` block in `crew.config.ts`. Groundcrew addresses the sandbox as `groundcrew-<agent>` and reuses one existing sandbox per agent across repos and tasks.
80
80
 
81
81
  First-time setup is manual:
82
82
 
@@ -86,4 +86,4 @@ sbx exec -it groundcrew-claude claude auth login
86
86
  sbx exec -it groundcrew-claude gh auth login
87
87
  ```
88
88
 
89
- Replace `claude` with the sbx agent for the model and `<projectDir>` with `workspace.projectDir` from `crew.config.ts`. Manage lifecycle and auth with `sbx` directly (`sbx ls`, `sbx exec`, `sbx rm`). Groundcrew does not create, authenticate, regenerate, list, or remove sandboxes.
89
+ Replace `claude` with the sbx agent name for your agent and `<projectDir>` with `workspace.projectDir` from `crew.config.ts`. Manage lifecycle and auth with `sbx` directly (`sbx ls`, `sbx exec`, `sbx rm`). Groundcrew does not create, authenticate, regenerate, list, or remove sandboxes.
@@ -35,9 +35,15 @@ tasks only). If omitted, groundcrew treats in-review advancement as unsupported
35
35
  for that source and does not claim the transition succeeded. `commands.markDone`,
36
36
  when set, receives the same `sourceRef` and is run after groundcrew sees a
37
37
  **merged** PR on the task's worktree branch (a merged PR never advances to
38
- in-review). If omitted, groundcrew treats done advancement as unsupported and
39
- leaves the task for the source's own integration to close out. `${id}`,
40
- `${canonicalId}`, and `${name}` placeholders are shell-quoted before substitution.
38
+ in-review), or when a human or worker runs `crew task done <task-id>` for
39
+ completed no-PR work. If omitted, groundcrew treats done advancement as
40
+ unsupported and leaves the task for the source's own integration to close out.
41
+ `${id}`, `${canonicalId}`, and `${name}` placeholders are shell-quoted before substitution.
42
+
43
+ Workers receive `GROUNDCREW_TASK_ID` and `GROUNDCREW_COMPLETE` in their launch
44
+ environment. The default prompt tells them to run `GROUNDCREW_COMPLETE` only
45
+ when the requested work is complete, no PR is needed, and any dirty worktree
46
+ state is expected or explicitly allowed.
41
47
 
42
48
  ```json
43
49
  [
@@ -78,7 +84,7 @@ export default {
78
84
  };
79
85
  ```
80
86
 
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`.
87
+ Creating a todo task appends a line with `status:todo` as the final meaningful token and writes the prompt to `.tasks/<id>.md`. Pass `--repo <repo>` unless the source configures `defaultRepository`. Pass `--priority <letter>` to add a todo.txt priority marker. If `--agent` is omitted, the task uses `agent:any`.
82
88
 
83
89
  ```bash
84
90
  crew task create "Fix cancellation retry race" \
@@ -91,7 +97,7 @@ crew task create "Fix cancellation retry race" \
91
97
  ```
92
98
 
93
99
  ```txt
94
- (A) Fix cancellation retry race +marketplace @backend id:GC-20260608-001 repo:ClipboardHealth/api agent:codex status:todo
100
+ Fix cancellation retry race +marketplace @backend id:GC-20260608-001 repo:ClipboardHealth/api agent:codex status:todo
95
101
  ```
96
102
 
97
103
  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`:
@@ -2,12 +2,12 @@
2
2
 
3
3
  First stop for "what exists locally right now": `crew status <task>` shows the task's worktrees, workspace presence, run state, logs, and task-source status. Use `crew doctor` when you need to verify host setup.
4
4
 
5
- ## Missing Model CLI
5
+ ## Missing Agent CLI
6
6
 
7
- `crew doctor` probes every model listed in `models.definitions`. If you do not have `codex` installed, initialize with `crew init --model claude` or leave `codex` out of the enabled model set:
7
+ `crew doctor` probes every agent listed in `agents.definitions`. If you do not have `codex` installed, initialize with `crew init --agent claude` or leave `codex` out of the enabled agent set:
8
8
 
9
9
  ```ts
10
- models: {
10
+ agents: {
11
11
  default: "claude",
12
12
  definitions: {
13
13
  claude: {},
@@ -19,7 +19,7 @@ If `codex: {}` is listed, doctor expects the `codex` CLI to be installed because
19
19
 
20
20
  ## Safehouse-Wrapped Commands Are Not Re-Wrapped
21
21
 
22
- If a `models.definitions.<name>.cmd` already starts with `safehouse`, groundcrew assumes that command owns its Safehouse flags and does not add the `safehouse-clearance` wrapper a second time. Changing the proxy's allowlist after it is running requires killing the PID in `${XDG_CACHE_HOME:-$HOME/.cache}/clearance/clearance.pid` so the next launch picks up the new env.
22
+ If a `agents.definitions.<name>.cmd` already starts with `safehouse`, groundcrew assumes that command owns its Safehouse flags and does not add the `safehouse-clearance` wrapper a second time. Changing the proxy's allowlist after it is running requires killing the PID in `${XDG_CACHE_HOME:-$HOME/.cache}/clearance/clearance.pid` so the next launch picks up the new env.
23
23
 
24
24
  ## Dead Tmux Windows Vanish By Default
25
25
 
@@ -31,13 +31,15 @@ This applies to the tmux backend only.
31
31
 
32
32
  Groundcrew marks a task `In Progress` when it provisions a workspace. When a PR opens on that worktree branch, the reviewer pass attempts to mark the task `In Review`. Linear's default `In Review` status works out of the box; if your team renamed it, configure `sources: [{ kind: "linear", statuses: { inReview: ["Code Review"] } }]`.
33
33
 
34
+ If the task intentionally has no PR, mark it complete with `crew task done <task-id>`. Groundcrew refuses dirty matching worktrees with no PR unless you pass `--allow-dirty`, so inspect or commit/stash unexpected changes first. For todo-txt tasks with `rec:`, this completion path also lets the source schedule the next recurrence.
35
+
34
36
  ## Claude Launches In Auto Mode By Default
35
37
 
36
- Groundcrew creates isolated per-task worktrees for unattended runs, so the shipped `claude` command is `claude --permission-mode auto` to let Claude proceed without stopping for clarifying questions while keeping its built-in safety prompts intact. Override `models.definitions.claude.cmd` for `bypassPermissions` if you need to suppress tool-permission prompts entirely, or for a stricter mode.
38
+ Groundcrew creates isolated per-task worktrees for unattended runs, so the shipped `claude` command is `claude --permission-mode auto` to let Claude proceed without stopping for clarifying questions while keeping its built-in safety prompts intact. Override `agents.definitions.claude.cmd` for `bypassPermissions` if you need to suppress tool-permission prompts entirely, or for a stricter mode.
37
39
 
38
40
  ## Doctor's Command Introspection Is Shallow
39
41
 
40
- Doctor reports the resolved local runner and whether its prerequisites are met, then tokenizes model `cmd` and checks the first two non-flag tokens against PATH. Boolean flags without values, env-var assignments (`FOO=1`), shell pipelines, and subshells are not parsed. When `local.runner` is `"none"`, doctor surfaces a single WARNING line.
42
+ Doctor reports the resolved local runner and whether its prerequisites are met, then tokenizes agent `cmd` and checks the first two non-flag tokens against PATH. Boolean flags without values, env-var assignments (`FOO=1`), shell pipelines, and subshells are not parsed. When `local.runner` is `"none"`, doctor surfaces a single WARNING line.
41
43
 
42
44
  ## Switch To Tmux If Cmux Is Misbehaving
43
45
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clipboard-health/groundcrew",
3
- "version": "4.34.2",
3
+ "version": "4.35.0",
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",
@@ -77,7 +77,7 @@
77
77
  },
78
78
  "devDependencies": {
79
79
  "@clipboard-health/ai-rules": "2.27.0",
80
- "@clipboard-health/oxlint-config": "1.10.14",
80
+ "@clipboard-health/oxlint-config": "1.10.21",
81
81
  "@nx/js": "22.7.5",
82
82
  "@tsconfig/node24": "24.0.4",
83
83
  "@tsconfig/strictest": "2.0.8",