@clipboard-health/groundcrew 4.6.0 → 4.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -16,48 +16,64 @@
16
16
  <a href="./LICENSE"><img alt="license" src="https://img.shields.io/npm/l/@clipboard-health/groundcrew?style=flat-square&label=license&color=18181b&labelColor=18181b"></a>
17
17
  </p>
18
18
 
19
- Groundcrew watches assigned tickets, creates isolated worktrees, launches agent CLIs in cmux or tmux, and leaves each ticket's work on its own branch. The longer product story is in [Tickets to pull requests while you sleep](https://www.clipboardworks.com/resources/blog/tickets-to-pull-requests-while-you-sleep).
19
+ <p align="center">
20
+ <img alt="Groundcrew picking up tickets and running coding agents in parallel" src="./static/demo.gif" width="800">
21
+ </p>
22
+
23
+ Groundcrew watches assigned tickets, creates isolated worktrees, launches agent CLIs in dedicated terminals, and leaves each ticket's work on its own PR-ready branch. For the backstory, read _[Tickets to pull requests while you sleep](https://www.clipboardworks.com/resources/blog/tickets-to-pull-requests-while-you-sleep)_.
20
24
 
21
25
  ## Why
22
26
 
23
27
  - **One worktree per ticket.** Agents work in parallel without stepping on each other.
24
- - **Linear out of the box.** Assign yourself an `agent-*` labeled issue and Groundcrew can pick it up.
25
- - **Local-first isolation.** Safehouse on macOS, Docker Sandboxes on Linux/WSL, or an explicit `none` escape hatch.
28
+ - **Pluggable ticket sources.** Linear by default; Jira and local files via [ticket sources](./docs/ticket-sources.md).
29
+ - **Local-first isolation.** Safehouse, Docker Sandboxes, or an explicit `none` escape hatch.
26
30
  - **Multi-agent routing.** Ships with `claude` and `codex`; bring your own CLI in config.
27
31
 
32
+ ## Prerequisites
33
+
34
+ `crew doctor` checks all of these, so you can install as you go.
35
+
36
+ - **Node >= 24:** [nvm](https://github.com/nvm-sh/nvm): `nvm install 24`.
37
+ - **git:** e.g., `brew install git`, `apt install git`.
38
+ - **A terminal multiplexer:** [tmux](https://github.com/tmux/tmux/wiki/Installing) (cross-platform) or [cmux](https://cmux.com/) (macOS).
39
+ - **An agent CLI:** [Claude Code](https://code.claude.com/docs/en/quickstart) and/or [Codex](https://developers.openai.com/codex/quickstart?setup=cli).
40
+ - **A sandbox runner:** [Docker Sandboxes](https://docs.docker.com/sandboxes/) (cross-platform) or [Safehouse](https://agent-safehouse.dev/) on macOS. Skip only with `--runner none`.
41
+
28
42
  ## Quickstart
29
43
 
30
44
  ```bash
31
- # 1. Install Node >= 24, git, cmux or tmux, and the agent CLI you'll use.
32
-
33
- # 2. Install groundcrew.
45
+ # 1. Install groundcrew.
34
46
  npm install -g @clipboard-health/groundcrew
35
47
 
36
- # 3. Scaffold a global config.
37
- # runner=none is the quickest path, but it runs agents unsandboxed on the host.
38
- crew init --global --project-dir ~/dev --repo OWNER/REPO --runner none --model claude
48
+ # 2. Scaffold a global config. Agents are sandboxed by default
49
+ # (Safehouse/Docker Sandboxes); add --runner none to run unsandboxed on the host.
50
+ crew init --global --project-dir ~/dev --repo OWNER/REPO --model claude
39
51
 
40
- # 4. Run the clone commands printed by `crew init`.
52
+ # 3. Run the clone commands printed by `crew init`.
41
53
 
42
- # 5. If using Linear, export your API key.
54
+ # 4. Using Linear? Export your API key. (Jira and other trackers: see Ticket Pickup.)
43
55
  export GROUNDCREW_LINEAR_API_KEY="lin_api_..."
44
56
 
45
- # 6. Verify setup, then dispatch.
57
+ # 5. Verify setup, then dispatch.
46
58
  crew doctor
47
59
  crew run --watch
48
60
  ```
49
61
 
50
- `crew init --global` writes config to `${XDG_CONFIG_HOME:-$HOME/.config}/groundcrew/`. Pass `--repo` more than once for multiple repos. Pass `--model claude` or `--model codex` to disable the missing CLI if you only have one installed.
62
+ `crew init --global` writes config to `${XDG_CONFIG_HOME:-$HOME/.config}/groundcrew/`. Pass `--repo` more than once for multiple repos. If you only have one CLI installed, pass `--model claude` (or `--model codex`) so Groundcrew disables the other model and `doctor` won't flag it as missing.
51
63
 
52
64
  ## Ticket Pickup
53
65
 
54
- For Linear, assign tickets to yourself and add an `agent-*` label:
66
+ **Not on Linear?** Use Jira or local files via [ticket sources](./docs/ticket-sources.md).
67
+
68
+ Linear works out of the box: assign tickets to yourself and add an `agent-*` label.
55
69
 
56
70
  - `agent-claude`, `agent-codex`, or `agent-<name>` routes to that model.
57
71
  - `agent-any` routes to the enabled model with the most available capacity.
58
72
  - Tickets without an `agent-*` label are ignored by `crew run`; dispatch one manually with `crew start <TICKET>`.
59
73
 
60
- Groundcrew scans `workspace.knownRepositories` to infer which repo a ticket belongs to. A ticket blocked by non-terminal blockers is skipped until those blockers are done.
74
+ Groundcrew scans `workspace.knownRepositories` to infer which repo a ticket belongs to.
75
+
76
+ A ticket blocked by non-terminal blockers is skipped until those blockers are done.
61
77
 
62
78
  ## Commands
63
79
 
@@ -91,7 +107,7 @@ export default {
91
107
  knownRepositories: ["OWNER/REPO"],
92
108
  },
93
109
  local: {
94
- runner: "none",
110
+ runner: "auto",
95
111
  },
96
112
  models: {
97
113
  default: "claude",
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAoPA,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAoCvD"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAyQA,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAwCvD"}
package/dist/cli.js CHANGED
@@ -8,7 +8,7 @@ import { resumeWorkspaceCli } from "./commands/resumeWorkspace.js";
8
8
  import { setupWorkspaceCli } from "./commands/setupWorkspace.js";
9
9
  import { statusCli } from "./commands/status.js";
10
10
  import { createDefaultUpgradeCliOptions, upgradeCli } from "./commands/upgrade.js";
11
- import { errorMessage, parseDryRunPositionals, readTicketArgument, writeError, writeOutput, } from "./lib/util.js";
11
+ import { errorMessage, parseDryRunPositionals, readEnvironmentVariable, readTicketArgument, setVerbose, writeError, writeOutput, } from "./lib/util.js";
12
12
  const REMOVED_SANDBOX_COMMAND_MESSAGE = [
13
13
  "`crew sandbox` is no longer supported.",
14
14
  "Groundcrew now launches agents inside existing sbx sandboxes but does not list, create, regenerate, authenticate, or remove them.",
@@ -176,6 +176,7 @@ function printHelp() {
176
176
  writeOutput("Options:");
177
177
  writeOutput(" -h, --help Show help");
178
178
  writeOutput(" -v, --version Print version");
179
+ writeOutput(" --verbose Show diagnostic output (or set GROUNDCREW_VERBOSE)");
179
180
  writeOutput("");
180
181
  writeOutput("Commands:");
181
182
  for (const [name, command] of visibleCommands) {
@@ -192,8 +193,27 @@ function packageMetadata() {
192
193
  function packageVersion() {
193
194
  return packageMetadata().version;
194
195
  }
196
+ const VERBOSE_FLAG = "--verbose";
197
+ function environmentVerbose() {
198
+ const raw = readEnvironmentVariable("GROUNDCREW_VERBOSE");
199
+ return raw !== undefined && raw !== "" && raw !== "0" && raw.toLowerCase() !== "false";
200
+ }
201
+ /**
202
+ * Pulls the global `--verbose` flag out of argv before subcommand dispatch so
203
+ * every command supports it and the strict per-command parsers never see it.
204
+ * GROUNDCREW_VERBOSE enables it without the flag.
205
+ */
206
+ function extractVerbose(argv) {
207
+ const commandArgv = argv.filter((argument) => argument !== VERBOSE_FLAG);
208
+ const verbose = commandArgv.length !== argv.length || environmentVerbose();
209
+ return { verbose, commandArgv };
210
+ }
195
211
  export async function run(argv) {
196
- const [subcommand, ...rest] = argv;
212
+ const { verbose, commandArgv } = extractVerbose(argv);
213
+ if (verbose) {
214
+ setVerbose(true);
215
+ }
216
+ const [subcommand, ...rest] = commandArgv;
197
217
  if (subcommand === undefined || subcommand === "-h" || subcommand === "--help") {
198
218
  printHelp();
199
219
  if (subcommand === undefined) {
@@ -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,wBAAwB,CAAC;AAChC,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,CAAC,UAAU,EAAE;QAClB,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,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnB;AAaD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,cAAc,GAAG,UAAU,CAqNjE;AAUD,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,wBAAwB,CAAC;AAChC,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,CAAC,UAAU,EAAE;QAClB,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,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnB;AAaD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,cAAc,GAAG,UAAU,CAwNjE;AAsBD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,KAAK,EAAE,GAAG,MAAM,CAQrE"}
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import { dispatchableRepository } from "../lib/repositoryValidation.js";
10
10
  import { isGroundcrewIssue, naturalIdFromCanonical, } from "../lib/ticketSource.js";
11
- import { errorMessage, log, logEvent } from "../lib/util.js";
11
+ import { errorMessage, failMark, log, logEvent } 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";
@@ -79,7 +79,7 @@ export function createDispatcher(deps) {
79
79
  });
80
80
  }
81
81
  catch (error) {
82
- log(`Failed to start ${ticketId}: ${errorMessage(error)}`);
82
+ log(`${failMark()} Failed to start ${ticketId}: ${errorMessage(error)}`);
83
83
  logEvent("dispatch", {
84
84
  outcome: "failed",
85
85
  ticket: ticketId,
@@ -145,18 +145,21 @@ export function createDispatcher(deps) {
145
145
  return;
146
146
  }
147
147
  // usage() is an HTTP call; workspaces.probe shells tmux/cmux. Kick off
148
- // usage first so the workspace probe can overlap with the in-flight request.
148
+ // usage first so any necessary workspace probe can overlap with the
149
+ // in-flight request.
149
150
  const usagePromise = usage(signal);
150
151
  // Snapshot live workspace names once per iteration so eligibility can
151
152
  // distinguish "worktree exists AND its agent is still running" (resume)
152
153
  // from "worktree exists but the workspace is gone" (ambiguous — don't
153
- // auto-recover). Done before slot-counting so a skipped stale ticket
154
- // doesn't consume an eligible slot and starve later Todo tickets.
154
+ // auto-recover). Skip the shell-out entirely for fresh-start-only ticks:
155
+ // if none of the candidates has a matching worktree, classifyRecovery()
156
+ // will never read the probe.
155
157
  let workspaceProbe;
156
158
  try {
157
- workspaceProbe = dryRun
158
- ? { kind: "ok", names: new Set() }
159
- : await workspaces.probe(config, signal);
159
+ workspaceProbe =
160
+ dryRun || !hasRecoverableCandidate(dispatchableUnblocked, worktreeEntries)
161
+ ? { kind: "ok", names: new Set() }
162
+ : await workspaces.probe(config, signal);
160
163
  }
161
164
  catch (error) {
162
165
  usagePromise.catch(() => "ignored");
@@ -196,6 +199,12 @@ export function createDispatcher(deps) {
196
199
  }
197
200
  return { runOnce };
198
201
  }
202
+ function hasRecoverableCandidate(issues, worktreeEntries) {
203
+ return issues.some((issue) => {
204
+ const naturalId = naturalIdFromCanonical(issue.id);
205
+ return worktreeEntries.some((entry) => entry.repository === issue.repository && entry.ticket === naturalId);
206
+ });
207
+ }
199
208
  function formatUsageExhaustion(exhaustion) {
200
209
  if (exhaustion.kind === "session") {
201
210
  const mins = exhaustion.resetMinutes ?? "?";
@@ -1 +1 @@
1
- {"version":3,"file":"resumeWorkspace.d.ts","sourceRoot":"","sources":["../../src/commands/resumeWorkspace.ts"],"names":[],"mappings":"AAEA,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAcnE,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC;CAChB;AA6HD,wBAAsB,eAAe,CACnC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,IAAI,CAAC,CA4Df;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":"AAEA,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAcnE,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC;CAChB;AA6HD,wBAAsB,eAAe,CACnC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,IAAI,CAAC,CA6Df;AAED,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAGtE"}
@@ -106,12 +106,13 @@ export async function resumeWorkspace(config, options) {
106
106
  if (definition === undefined) {
107
107
  throw new Error(`Unknown model: ${context.model}`);
108
108
  }
109
- const { runner, sandboxName } = await prepareAgentLaunch({
109
+ const { runner, sandboxName, ensureReady } = await prepareAgentLaunch({
110
110
  config,
111
111
  model: context.model,
112
112
  definition,
113
113
  purpose: "resumes",
114
114
  });
115
+ await ensureReady();
115
116
  const stagedPrompt = stagePromptText({
116
117
  prefix: "groundcrew-resume",
117
118
  ticket,
@@ -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;AAiBnE,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,wEAAwE;IACxE,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,aAAa,CAAC;CACxB;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,CA6Gf;AAqHD,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,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;AAiBnE,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,wEAAwE;IACxE,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,aAAa,CAAC;CACxB;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAuBD,wBAAsB,cAAc,CAClC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,qBAAqB,EAC9B,UAAU,GAAE,wBAA6B,GACxC,OAAO,CAAC,IAAI,CAAC,CA+Gf;AAyID,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GACjC,OAAO,CAAC,IAAI,CAAC,CA4Cf"}
@@ -7,7 +7,7 @@ import { buildLaunchCommand } from "../lib/launchCommand.js";
7
7
  import { recordRunState } from "../lib/runState.js";
8
8
  import { stageBuildSecrets, stagePromptFromTemplate, stageWorkspaceLaunchCommand, } from "../lib/stagedLaunch.js";
9
9
  import { naturalIdFromCanonical } from "../lib/ticketSource.js";
10
- import { errorMessage, log } from "../lib/util.js";
10
+ import { debug, errorMessage, log, okMark } from "../lib/util.js";
11
11
  import { workspaces } from "../lib/workspaces.js";
12
12
  import { isWorktreeAlreadyExistsError, worktrees } from "../lib/worktrees.js";
13
13
  function stagePrompt(input) {
@@ -31,7 +31,7 @@ export async function setupWorkspace(config, options, runOptions = {}) {
31
31
  if (!definition) {
32
32
  throw new Error(`Unknown model: ${model}`);
33
33
  }
34
- const { runner, sandboxName } = await prepareAgentLaunch({
34
+ const { runner, sandboxName, ensureReady } = await prepareAgentLaunch({
35
35
  config,
36
36
  model,
37
37
  definition,
@@ -40,11 +40,10 @@ export async function setupWorkspace(config, options, runOptions = {}) {
40
40
  });
41
41
  const spec = { repository, ticket };
42
42
  let created;
43
+ const createdPromise = signal === undefined ? worktrees.create(config, spec) : worktrees.create(config, spec, signal);
44
+ const readinessPromise = startLaunchReadiness(ensureReady);
43
45
  try {
44
- created =
45
- signal === undefined
46
- ? await worktrees.create(config, spec)
47
- : await worktrees.create(config, spec, signal);
46
+ created = await createdPromise;
48
47
  }
49
48
  catch (error) {
50
49
  if (isWorktreeAlreadyExistsError(error)) {
@@ -63,6 +62,7 @@ export async function setupWorkspace(config, options, runOptions = {}) {
63
62
  // the ticket strands forever.
64
63
  let promptDir;
65
64
  try {
65
+ await assertLaunchReady(readinessPromise);
66
66
  const ticketDetails = options.details;
67
67
  const accessHint = await workspaces.accessHint(config, ticket, signal);
68
68
  const stagedPrompt = stagePrompt({
@@ -83,7 +83,7 @@ export async function setupWorkspace(config, options, runOptions = {}) {
83
83
  sandboxName,
84
84
  });
85
85
  const launchCmd = stageWorkspaceLaunchCommand(promptDir, launchCommand);
86
- log("Opening workspace...");
86
+ debug("Opening workspace...");
87
87
  await openAgentWorkspace({
88
88
  config,
89
89
  name: ticket,
@@ -105,9 +105,9 @@ export async function setupWorkspace(config, options, runOptions = {}) {
105
105
  title: ticketDetails.title,
106
106
  ...(ticketDetails.url === undefined ? {} : { url: ticketDetails.url }),
107
107
  });
108
- log(`Workspace "${ticket}" launched (${model})`);
109
- log(` Worktree: ${launchDir}`);
110
- log(` Branch: ${branchName}`);
108
+ log(`${okMark()} "${ticket}" launched (${model}) worktree ${worktreeName}`);
109
+ debug(` Worktree: ${launchDir}`);
110
+ debug(` Branch: ${branchName}`);
111
111
  if (accessHint !== undefined) {
112
112
  logAccessHint(accessHint);
113
113
  }
@@ -130,6 +130,21 @@ export async function setupWorkspace(config, options, runOptions = {}) {
130
130
  throw error;
131
131
  }
132
132
  }
133
+ async function startLaunchReadiness(ensureReady) {
134
+ try {
135
+ await ensureReady();
136
+ return { kind: "ready" };
137
+ }
138
+ catch (error) {
139
+ return { kind: "failed", error };
140
+ }
141
+ }
142
+ async function assertLaunchReady(readinessPromise) {
143
+ const readiness = await readinessPromise;
144
+ if (readiness.kind === "failed") {
145
+ throw readiness.error;
146
+ }
147
+ }
133
148
  /**
134
149
  * Probe the workspace backend and, if a workspace for `ticket` is still
135
150
  * live, log the access hint. Used on the pre-launch error path (e.g. the
@@ -151,7 +166,7 @@ async function logAccessHintForExistingWorkspace(arguments_) {
151
166
  logAccessHint(accessHint);
152
167
  }
153
168
  function logAccessHint(accessHint) {
154
- log(` Attach: ${accessHint.command}`);
169
+ debug(` Attach: ${accessHint.command}`);
155
170
  }
156
171
  function renderWorkspaceContinuationInstruction(accessHint) {
157
172
  if (accessHint === undefined) {
@@ -1,14 +1,14 @@
1
- import { errorMessage, log, logEvent } from "../lib/util.js";
1
+ import { debug, errorMessage, log, logEvent, okMark } from "../lib/util.js";
2
2
  export function logTeardown(result) {
3
3
  if (result.workspaceProbe.kind === "unavailable" && result.workspaceProbe.error !== undefined) {
4
4
  log(`workspace list failed: ${errorMessage(result.workspaceProbe.error)}`);
5
5
  }
6
6
  for (const ticket of result.closed) {
7
- log(`Closed workspace ${ticket}`);
7
+ debug(`Closed workspace ${ticket}`);
8
8
  }
9
9
  for (const entry of result.removed) {
10
- log(`Cleanup complete for ${entry.ticket} (${entry.kind})`);
11
- log(` Worktree: ${entry.dir} (removed)`);
10
+ log(`${okMark()} Cleanup complete for ${entry.ticket} (${entry.kind})`);
11
+ debug(` Worktree: ${entry.dir} (removed)`);
12
12
  }
13
13
  for (const failure of result.failures) {
14
14
  const message = errorMessage(failure.error);
@@ -1 +1 @@
1
- {"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/linear/fetch.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/linear/fetch.ts"],"names":[],"mappings":"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"}
@@ -9,7 +9,7 @@
9
9
  * Done -> Shipped, etc.) Just Work without per-team config.
10
10
  */
11
11
  import { RepositoryResolutionError } from "../../ticketSource.js";
12
- import { log } from "../../util.js";
12
+ import { log, styleWarning } from "../../util.js";
13
13
  import { AGENT_LABEL_PREFIX, resolveModelFor, resolveRepositoryFor, } from "./parsing.js";
14
14
  export const ISSUES_PAGE_SIZE = 250;
15
15
  // `state.type` values surfaced by `fetch()`. `backlog` / `triage` are dropped
@@ -33,13 +33,13 @@ export function createBoardSource(deps) {
33
33
  };
34
34
  }
35
35
  async function verifyViewer(client) {
36
- const response = await client.client.rawRequest(`query VerifyViewer { viewer { id name email } }`);
36
+ const response = await client.client.rawRequest(`query VerifyViewer { viewer { id name } }`);
37
37
  // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- shape is fixed by our GraphQL query above
38
38
  const { viewer } = response.data;
39
39
  if (viewer === null) {
40
40
  throw new Error("Linear API did not return a viewer for this API key. Confirm LINEAR_API_KEY is set and points to a personal API key, not a workspace key.");
41
41
  }
42
- log(`Resolved Linear viewer: ${viewer.name} (${viewer.email})`);
42
+ log(`Resolved Linear viewer: ${viewer.name}`);
43
43
  }
44
44
  export function isIssueInProgress(issue) {
45
45
  return issue.stateType === "started";
@@ -158,7 +158,7 @@ function resolveAgentMetadata(arguments_) {
158
158
  }
159
159
  else {
160
160
  model = undefined;
161
- log(`WARNING: ${ticket} has an ${AGENT_LABEL_PREFIX}* label but no known repository in its description; skipping dispatch. Add one of workspace.knownRepositories to the description, or remove the ${AGENT_LABEL_PREFIX}* label: ${config.workspace.knownRepositories.join(", ")}`);
161
+ log(styleWarning(`WARNING: ${ticket} has an ${AGENT_LABEL_PREFIX}* label but no known repository in its description; skipping dispatch. Add one of workspace.knownRepositories to the description, or remove the ${AGENT_LABEL_PREFIX}* label: ${config.workspace.knownRepositories.join(", ")}`));
162
162
  }
163
163
  }
164
164
  return { repository, model };
@@ -1,4 +1,4 @@
1
- import { log } from "../../util.js";
1
+ import { debug } from "../../util.js";
2
2
  export function createLinearIssueStatusUpdater(arguments_) {
3
3
  const { client } = arguments_;
4
4
  // Positive cache only. Keyed by teamId because the in-progress-state
@@ -45,7 +45,7 @@ export function createLinearIssueStatusUpdater(arguments_) {
45
45
  throw new Error(`Could not find a workflow state with type "started" for ${issue.id} (team ${issue.teamId.length > 0 ? issue.teamId : "?"}). Confirm the team's Linear workflow has an in-progress column.`);
46
46
  }
47
47
  await client.updateIssue(issue.uuid, { stateId });
48
- log(`Marked ${issue.id} as in progress`);
48
+ debug(`Marked ${issue.id} as in progress`);
49
49
  }
50
50
  return { markInProgress };
51
51
  }
@@ -12,7 +12,7 @@
12
12
  * interpret); any other nonzero exit throws.
13
13
  */
14
14
  import { spawn } from "node:child_process";
15
- import { log } from "../../util.js";
15
+ import { debug } from "../../util.js";
16
16
  /**
17
17
  * Hard cap on captured stdout/stderr per stream. Misbehaving scripts that
18
18
  * `yes | head -c <huge>` would otherwise exhaust memory. 10 MB is enough for
@@ -96,7 +96,7 @@ export async function invokeShellCommand(args) {
96
96
  clearTimeout(timer);
97
97
  const stderrText = stderr.toString("utf8");
98
98
  if (stderrText.length > 0) {
99
- log(`[shell:${args.sourceName}] ${command}\n${stderrText.trimEnd()}`);
99
+ debug(`[shell:${args.sourceName}] ${command}\n${stderrText.trimEnd()}`);
100
100
  }
101
101
  /* v8 ignore next @preserve -- `code` is null only when the process was killed by signal; the timeout path SIGKILLs but settles via the timer rather than 'close' */
102
102
  const exitCode = code ?? 1;
@@ -2,6 +2,7 @@ import { type LocalRunner, type ModelDefinition, type ResolvedConfig } from "./c
2
2
  interface PreparedAgentLaunch {
3
3
  runner: LocalRunner;
4
4
  sandboxName: string | undefined;
5
+ ensureReady: () => Promise<void>;
5
6
  }
6
7
  export declare function prepareAgentLaunch(input: {
7
8
  config: ResolvedConfig;
@@ -1 +1 @@
1
- {"version":3,"file":"agentLaunch.d.ts","sourceRoot":"","sources":["../../src/lib/agentLaunch.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,cAAc,EACpB,MAAM,aAAa,CAAC;AAOrB,UAAU,mBAAmB;IAC3B,MAAM,EAAE,WAAW,CAAC;IACpB,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;CACjC;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,CAsD/B;AAED,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":"AAEA,OAAO,EAEL,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,cAAc,EACpB,MAAM,aAAa,CAAC;AAOrB,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;AAqBD,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"}
@@ -3,26 +3,17 @@ import { hasPreLaunchEnv, } from "./config.js";
3
3
  import { detectHostCapabilities } from "./host.js";
4
4
  import { assertLocalRunnerRequirements, resolveLocalRunner } from "./localRunner.js";
5
5
  import { sandboxNameFor } from "./sandboxName.js";
6
- import { log, sleep } from "./util.js";
6
+ import { debug, sleep } from "./util.js";
7
7
  import { workspaces } from "./workspaces.js";
8
8
  export async function prepareAgentLaunch(input) {
9
9
  const host = await detectHostCapabilities(input.signal);
10
10
  const runner = resolveLocalRunner(input.config.local.runner, host);
11
11
  assertLocalRunnerRequirements(host, runner);
12
- if (runner === "safehouse") {
13
- await ensureClearance({
14
- logger: log,
15
- ...(input.signal === undefined
16
- ? {}
17
- : {
18
- sleep: async (ms) => {
19
- await sleep(ms, input.signal);
20
- input.signal?.throwIfAborted();
21
- },
22
- }),
23
- });
24
- input.signal?.throwIfAborted();
25
- }
12
+ const ensureReady = runner === "safehouse"
13
+ ? async () => {
14
+ await ensureSafehouseClearance(input.signal);
15
+ }
16
+ : alreadyReady;
26
17
  if (runner === "sdx" && input.definition.sandbox === undefined) {
27
18
  throw new Error(`Local groundcrew ${input.purpose} with the sdx runner require a sandbox config on model '${input.model}'.`);
28
19
  }
@@ -46,7 +37,24 @@ export async function prepareAgentLaunch(input) {
46
37
  const sandboxName = runner === "sdx" && input.definition.sandbox !== undefined
47
38
  ? sandboxNameFor({ agent: input.definition.sandbox.agent })
48
39
  : undefined;
49
- return { runner, sandboxName };
40
+ return { runner, sandboxName, ensureReady };
41
+ }
42
+ async function alreadyReady() {
43
+ await Promise.resolve();
44
+ }
45
+ async function ensureSafehouseClearance(signal) {
46
+ await ensureClearance({
47
+ logger: debug,
48
+ ...(signal === undefined
49
+ ? {}
50
+ : {
51
+ sleep: async (ms) => {
52
+ await sleep(ms, signal);
53
+ signal.throwIfAborted();
54
+ },
55
+ }),
56
+ });
57
+ signal?.throwIfAborted();
50
58
  }
51
59
  export async function openAgentWorkspace(input) {
52
60
  const spec = {
@@ -4,7 +4,7 @@
4
4
  * per-workspace status pill, which `open` applies best-effort.
5
5
  */
6
6
  import { isSignalAborted, runWorkspaceCommand, } from "./workspaceAdapter.js";
7
- import { errorMessage, log } from "./util.js";
7
+ import { debug, errorMessage, log } from "./util.js";
8
8
  export const cmuxAdapter = {
9
9
  async open(spec, signal) {
10
10
  const output = await runWorkspaceCommand("cmux", [
@@ -31,7 +31,7 @@ export const cmuxAdapter = {
31
31
  // so swallow that specific gap silently; surface anything else so a real
32
32
  // regression doesn't hide behind the same swallow.
33
33
  if (!isCmuxSetStatusUnsupported(error)) {
34
- log(`cmux set-status failed for ${spec.name} (continuing): ${errorMessage(error)}`);
34
+ debug(`cmux set-status failed for ${spec.name} (continuing): ${errorMessage(error)}`);
35
35
  }
36
36
  }
37
37
  }
@@ -46,7 +46,7 @@ export const cmuxAdapter = {
46
46
  // cmux v2 `workspace.close` rejects titles, so forwarding `name`
47
47
  // would always fail. The list failure has already been logged by
48
48
  // `listCmuxRaw`; bail rather than guarantee a downstream error.
49
- log(`cmux close-workspace skipped for ${name}: list-workspaces failed, no usable id`);
49
+ debug(`cmux close-workspace skipped for ${name}: list-workspaces failed, no usable id`);
50
50
  return { kind: "unavailable" };
51
51
  }
52
52
  const match = raw.find((ws) => ws.title === name);
@@ -90,7 +90,7 @@ function parseCmuxList(output) {
90
90
  }
91
91
  const id = pickCmuxId(ws);
92
92
  if (id === undefined) {
93
- log(`cmux list-workspaces returned workspace "${ws.title}" without a usable id or ref; skipping`);
93
+ debug(`cmux list-workspaces returned workspace "${ws.title}" without a usable id or ref; skipping`);
94
94
  continue;
95
95
  }
96
96
  items.push({ title: ws.title, id });
@@ -121,7 +121,7 @@ async function listCmuxRaw(signal) {
121
121
  if (isSignalAborted(signal)) {
122
122
  throw error;
123
123
  }
124
- log(`cmux list-workspaces failed: ${errorMessage(error)}`);
124
+ debug(`cmux list-workspaces failed: ${errorMessage(error)}`);
125
125
  return undefined;
126
126
  }
127
127
  }
@@ -4,7 +4,7 @@ import { homedir } from "node:os";
4
4
  import { resolve } from "node:path";
5
5
  import { pathToFileURL } from "node:url";
6
6
  import { cosmiconfig } from "cosmiconfig";
7
- import { log, readEnvironmentVariable, setLogFile } from "./util.js";
7
+ import { debug, log, readEnvironmentVariable, setLogFile } from "./util.js";
8
8
  import { xdgConfigPath, xdgStatePath } from "./xdg.js";
9
9
  import { BUILD_SECRET_NAMES } from "./buildSecrets.js";
10
10
  export { BUILD_SECRET_NAMES } from "./buildSecrets.js";
@@ -601,7 +601,7 @@ export async function loadConfig() {
601
601
  if (isEmpty === true || !isPlainObject(userConfig)) {
602
602
  fail(`${filepath} must export a config object (e.g. \`export default { ... } satisfies Config\`)`);
603
603
  }
604
- log(`Loaded config from ${filepath}`);
604
+ debug(`Loaded config from ${filepath}`);
605
605
  // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- runtime fields are validated by applyDefaults/validate
606
606
  const resolved = applyDefaults(userConfig);
607
607
  validate(resolved);
@@ -1 +1 @@
1
- {"version":3,"file":"localRunner.d.ts","sourceRoot":"","sources":["../../src/lib/localRunner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAGlD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,kBAAkB,EAC3B,IAAI,EAAE,gBAAgB,GACrB,WAAW,CAQb;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,gBAAgB,EAAE,MAAM,EAAE,WAAW,GAAG,IAAI,CA6B/F"}
1
+ {"version":3,"file":"localRunner.d.ts","sourceRoot":"","sources":["../../src/lib/localRunner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAGlD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,kBAAkB,EAC3B,IAAI,EAAE,gBAAgB,GACrB,WAAW,CAQb;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,gBAAgB,EAAE,MAAM,EAAE,WAAW,GAAG,IAAI,CA+B/F"}
@@ -1,4 +1,4 @@
1
- import { log } from "./util.js";
1
+ import { log, styleWarning } from "./util.js";
2
2
  /**
3
3
  * Resolve `local.runner` from config + host capabilities into a concrete
4
4
  * backend. `auto` defaults to safehouse on macOS and sdx on Linux — both
@@ -47,5 +47,5 @@ export function assertLocalRunnerRequirements(host, runner) {
47
47
  return;
48
48
  }
49
49
  // runner === "none"
50
- log("WARNING: local.runner='none' — agent process will run on the host without sandboxing. Only use this when you understand the implications.");
50
+ log(styleWarning("WARNING: local.runner='none' — agent process will run on the host without sandboxing. Only use this when you understand the implications."));
51
51
  }
@@ -1,12 +1,12 @@
1
1
  import { removeRunState } from "./runState.js";
2
- import { errorMessage, log } from "./util.js";
2
+ import { debug, errorMessage } from "./util.js";
3
3
  export function recordCleanedUpRuns(config, entries) {
4
4
  for (const entry of entries) {
5
5
  try {
6
6
  removeRunState(config, entry.ticket);
7
7
  }
8
8
  catch (error) {
9
- log(`Run state cleanup failed for ${entry.ticket}: ${errorMessage(error)}`);
9
+ debug(`Run state cleanup failed for ${entry.ticket}: ${errorMessage(error)}`);
10
10
  }
11
11
  }
12
12
  }
@@ -5,7 +5,7 @@
5
5
  * Linux/WSL path where cmux is unavailable.
6
6
  */
7
7
  import { isSignalAborted, runWorkspaceCommand, } from "./workspaceAdapter.js";
8
- import { errorMessage, log, readEnvironmentVariable } from "./util.js";
8
+ import { debug, errorMessage, readEnvironmentVariable } from "./util.js";
9
9
  const TMUX_SESSION = "groundcrew";
10
10
  // `tmux new-session -d -s …` always creates one initial window. Without
11
11
  // `-n`, that window is named after the running shell (e.g. "0" / "zsh") and
@@ -49,7 +49,7 @@ export const tmuxAdapter = {
49
49
  return [];
50
50
  }
51
51
  if (probe.status === "failed") {
52
- log(`tmux list-windows failed: ${probe.reason}`);
52
+ debug(`tmux list-windows failed: ${probe.reason}`);
53
53
  // oxlint-disable-next-line unicorn/no-useless-undefined -- undefined marks the workspace backend as unavailable.
54
54
  return undefined;
55
55
  }
@@ -1 +1 @@
1
- {"version":3,"file":"usage.d.ts","sourceRoot":"","sources":["../../src/lib/usage.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAmB,cAAc,EAAE,MAAM,aAAa,CAAC;AA+BnE,UAAU,eAAe;IACvB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;AAE3D;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,EAAE,eAK7B,CAAC;AA2GF,wBAAgB,WAAW,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,EAAE,CAI5D;AAED,wBAAsB,eAAe,CACnC,MAAM,EAAE,cAAc,EACtB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,YAAY,CAAC,CA6BvB"}
1
+ {"version":3,"file":"usage.d.ts","sourceRoot":"","sources":["../../src/lib/usage.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAmB,cAAc,EAAE,MAAM,aAAa,CAAC;AA+BnE,UAAU,eAAe;IACvB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;AAE3D;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,EAAE,eAK7B,CAAC;AA2GF,wBAAgB,WAAW,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,EAAE,CAI5D;AAED,wBAAsB,eAAe,CACnC,MAAM,EAAE,cAAc,EACtB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,YAAY,CAAC,CA+BvB"}
package/dist/lib/usage.js CHANGED
@@ -6,7 +6,7 @@
6
6
  * `codexbar` itself is the user-facing inspection tool.
7
7
  */
8
8
  import { runCommandAsync } from "./commandRunner.js";
9
- import { errorMessage, log } from "./util.js";
9
+ import { debug, errorMessage } from "./util.js";
10
10
  /**
11
11
  * Synthetic snapshot used when codexbar can't be read for a model. Both
12
12
  * window fractions are pinned to Infinity so the dispatcher's
@@ -138,10 +138,12 @@ export async function getUsageByModel(config, signal) {
138
138
  }
139
139
  // Per-model failure: fail closed. A silent skip would let the
140
140
  // dispatcher spawn agents on a model whose quota we can't see —
141
- // the exact bug a usage gate is supposed to prevent. Log the
142
- // failure so operators can fix the underlying CLI, and return
143
- // a fully-exhausted snapshot so the dispatcher gates the model.
144
- log(`Usage check failed for ${model} (treating as exhausted): ${errorMessage(error)}`);
141
+ // the exact bug a usage gate is supposed to prevent. Record the
142
+ // failure (debug-tier always in the log file, console under
143
+ // --verbose) so operators can fix the underlying CLI, and return a
144
+ // fully-exhausted snapshot so the dispatcher gates the model. The
145
+ // gate itself surfaces a visible skip line via formatUsageExhaustion.
146
+ debug(`Usage check failed for ${model} (treating as exhausted): ${errorMessage(error)}`);
145
147
  out[model] = EXHAUSTED_USAGE;
146
148
  }
147
149
  }
@@ -1,9 +1,22 @@
1
1
  export declare function sleep(ms: number, signal?: AbortSignal): Promise<void>;
2
2
  export declare function writeOutput(message?: string): void;
3
3
  export declare function writeError(message: string): void;
4
+ export declare function setVerbose(value: boolean): void;
5
+ export declare function isVerbose(): boolean;
6
+ export declare function okMark(): string;
7
+ export declare function failMark(): string;
8
+ export declare function styleWarning(text: string): string;
9
+ export declare function styleDim(text: string): string;
4
10
  export declare function setLogFile(path: string | undefined): void;
5
11
  export declare function withLogOutputSuppressed<T>(operation: () => Promise<T>): Promise<T>;
12
+ /** Important tier: always on the console (dimmed timestamp) and the log file. */
6
13
  export declare function log(message: string): void;
14
+ /**
15
+ * Diagnostic tier: always tee'd to the log file, but echoed to the console only
16
+ * under --verbose. Use for mechanics (git porcelain brackets, adapter probe
17
+ * failures, "loaded config") that an operator rarely needs while watching.
18
+ */
19
+ export declare function debug(message: string): void;
7
20
  type LogEventFieldValue = boolean | number | string | readonly string[] | undefined;
8
21
  export declare function logEvent(event: string, fields: Record<string, LogEventFieldValue>): void;
9
22
  export declare function readEnvironmentVariable(name: string): string | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../src/lib/util.ts"],"names":[],"mappings":"AAGA,wBAAsB,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAoB3E;AAED,wBAAgB,WAAW,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAIlD;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAGhD;AAQD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAEzD;AAED,wBAAsB,uBAAuB,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAOxF;AAkBD,wBAAgB,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAQzC;AAED,KAAK,kBAAkB,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;AAUpF,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,GAAG,IAAI,CAcxF;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGxE;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAMzF;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,OAAO,CAAC;IAChB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,iBAAiB,CAcvF;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAcnD"}
1
+ {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../src/lib/util.ts"],"names":[],"mappings":"AAIA,wBAAsB,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAoB3E;AAED,wBAAgB,WAAW,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAIlD;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAGhD;AAQD,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAE/C;AAED,wBAAgB,SAAS,IAAI,OAAO,CAEnC;AAWD,wBAAgB,MAAM,IAAI,MAAM,CAE/B;AAED,wBAAgB,QAAQ,IAAI,MAAM,CAEjC;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE7C;AAQD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAEzD;AAED,wBAAsB,uBAAuB,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAOxF;AAuBD,iFAAiF;AACjF,wBAAgB,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAOzC;AAED;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAS3C;AAED,KAAK,kBAAkB,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;AAUpF,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,GAAG,IAAI,CAiBxF;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGxE;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAMzF;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,OAAO,CAAC;IAChB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,iBAAiB,CAcvF;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAcnD"}
package/dist/lib/util.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { appendFileSync, mkdirSync } from "node:fs";
2
2
  import { dirname } from "node:path";
3
+ import { styleText } from "node:util";
3
4
  export async function sleep(ms, signal) {
4
5
  if (signal?.aborted === true) {
5
6
  return;
@@ -30,6 +31,32 @@ export function writeError(message) {
30
31
  // oxlint-disable-next-line no-console -- Centralized CLI stderr writer.
31
32
  console.error(message);
32
33
  }
34
+ // Gates whether the diagnostic tier — debug() and logEvent() — is echoed to the
35
+ // console. Both tiers always tee to the log file regardless of this flag, so the
36
+ // full stream is never lost. The CLI arms this from `--verbose` /
37
+ // GROUNDCREW_VERBOSE before dispatching a command.
38
+ let verboseConsole = false;
39
+ export function setVerbose(value) {
40
+ verboseConsole = value;
41
+ }
42
+ export function isVerbose() {
43
+ return verboseConsole;
44
+ }
45
+ function paint(format, text) {
46
+ return styleText(format, text, { stream: process.stdout });
47
+ }
48
+ export function okMark() {
49
+ return paint("green", "✓");
50
+ }
51
+ export function failMark() {
52
+ return paint("red", "✗");
53
+ }
54
+ export function styleWarning(text) {
55
+ return paint("yellow", text);
56
+ }
57
+ export function styleDim(text) {
58
+ return paint("dim", text);
59
+ }
33
60
  // Module-scoped sink for tee-ing log()/logEvent() to disk. Unset by default
34
61
  // so tests don't write to the host filesystem; the CLI arms it after
35
62
  // loadConfig() resolves `logging.file`.
@@ -63,14 +90,33 @@ function appendLogLine(line) {
63
90
  writeError(`groundcrew: disabling file logging — could not write to ${broken}`);
64
91
  }
65
92
  }
93
+ function timestamped(message) {
94
+ const timestamp = new Date().toLocaleTimeString();
95
+ return { plain: `[${timestamp}] ${message}`, timestamp };
96
+ }
97
+ /** Important tier: always on the console (dimmed timestamp) and the log file. */
66
98
  export function log(message) {
67
99
  if (suppressedLogDepth > 0) {
68
100
  return;
69
101
  }
70
- const timestamp = new Date().toLocaleTimeString();
71
- const line = `[${timestamp}] ${message}`;
72
- writeOutput(line);
73
- appendLogLine(line);
102
+ const { plain, timestamp } = timestamped(message);
103
+ writeOutput(`${styleDim(`[${timestamp}]`)} ${message}`);
104
+ appendLogLine(plain);
105
+ }
106
+ /**
107
+ * Diagnostic tier: always tee'd to the log file, but echoed to the console only
108
+ * under --verbose. Use for mechanics (git porcelain brackets, adapter probe
109
+ * failures, "loaded config") that an operator rarely needs while watching.
110
+ */
111
+ export function debug(message) {
112
+ if (suppressedLogDepth > 0) {
113
+ return;
114
+ }
115
+ const { plain } = timestamped(message);
116
+ if (verboseConsole) {
117
+ writeOutput(styleDim(plain));
118
+ }
119
+ appendLogLine(plain);
74
120
  }
75
121
  function formatLogEventFieldValue(value) {
76
122
  const raw = Array.isArray(value) ? value.join(",") : String(value);
@@ -91,7 +137,10 @@ export function logEvent(event, fields) {
91
137
  parts.push(`${key}=${formatLogEventFieldValue(value)}`);
92
138
  }
93
139
  const line = parts.join(" ");
94
- writeOutput(line);
140
+ // Structured telemetry is diagnostic: file always, console only under --verbose.
141
+ if (verboseConsole) {
142
+ writeOutput(styleDim(line));
143
+ }
95
144
  appendLogLine(line);
96
145
  }
97
146
  export function readEnvironmentVariable(name) {
@@ -1 +1 @@
1
- {"version":3,"file":"worktrees.d.ts","sourceRoot":"","sources":["../../src/lib/worktrees.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAOH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAGlD,OAAO,EAAE,KAAK,cAAc,EAAc,MAAM,iBAAiB,CAAC;AAKlE,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC;AAElC,qBAAa,0BAA2B,SAAQ,KAAK;IACnD,SAAgB,GAAG,EAAE,MAAM,CAAC;IAE5B,YAAmB,GAAG,EAAE,MAAM,EAI7B;CACF;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,0BAA0B,CAEhG;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,sDAAsD;IACtD,MAAM,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,YAAY,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB;AAaD,iBAAS,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEnD;AA8PD,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,YAAY,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,GAAG,aAAa,EAAE,CAE7E;AAED,iBAAe,MAAM,CACnB,MAAM,EAAE,cAAc,EACtB,IAAI,EAAE,YAAY,EAClB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,aAAa,CAAC,CAQxB;AAED,iBAAe,MAAM,CACnB,MAAM,EAAE,cAAc,EACtB,KAAK,EAAE,aAAa,EACpB,OAAO,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,GAClD,OAAO,CAAC,IAAI,CAAC,CAKf;AAED,MAAM,MAAM,YAAY,GAAG,iBAAiB,GAAG,iBAAiB,CAAC;AAEjE,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,aAAa,CAAC;IACrB,IAAI,EAAE,YAAY,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,+DAA+D;IAC/D,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,sCAAsC;IACtC,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,wDAAwD;IACxD,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,cAAc,EAAE,cAAc,CAAC;CAChC;AAyBD,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;;;;;;;;GAQG;AAOH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAGlD,OAAO,EAAE,KAAK,cAAc,EAAc,MAAM,iBAAiB,CAAC;AAIlE,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC;AAElC,qBAAa,0BAA2B,SAAQ,KAAK;IACnD,SAAgB,GAAG,EAAE,MAAM,CAAC;IAE5B,YAAmB,GAAG,EAAE,MAAM,EAI7B;CACF;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,0BAA0B,CAEhG;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,sDAAsD;IACtD,MAAM,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,YAAY,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB;AAaD,iBAAS,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEnD;AAmQD,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,YAAY,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,GAAG,aAAa,EAAE,CAE7E;AAED,iBAAe,MAAM,CACnB,MAAM,EAAE,cAAc,EACtB,IAAI,EAAE,YAAY,EAClB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,aAAa,CAAC,CAQxB;AAED,iBAAe,MAAM,CACnB,MAAM,EAAE,cAAc,EACtB,KAAK,EAAE,aAAa,EACpB,OAAO,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,GAClD,OAAO,CAAC,IAAI,CAAC,CAKf;AAED,MAAM,MAAM,YAAY,GAAG,iBAAiB,GAAG,iBAAiB,CAAC;AAEjE,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,aAAa,CAAC;IACrB,IAAI,EAAE,YAAY,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,+DAA+D;IAC/D,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,sCAAsC;IACtC,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,wDAAwD;IACxD,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,cAAc,EAAE,cAAc,CAAC;CAChC;AAyBD,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"}
@@ -12,9 +12,8 @@ import { userInfo } from "node:os";
12
12
  import { isAbsolute, relative, resolve } from "node:path";
13
13
  import { runCommandAsync } from "./commandRunner.js";
14
14
  import { resolveDefaultBranch } from "./defaultBranch.js";
15
- import { errorMessage, log } from "./util.js";
15
+ import { debug, errorMessage, isVerbose } from "./util.js";
16
16
  import { workspaces } from "./workspaces.js";
17
- const LONG_RUNNING_COMMAND_OPTIONS = { stdio: "inherit", timeoutMs: 0 };
18
17
  const WORKTREE_LIST_PREFIX = "worktree ";
19
18
  export class WorktreeAlreadyExistsError extends Error {
20
19
  dir;
@@ -72,23 +71,33 @@ function basePaths(config, repository, ticket) {
72
71
  function signalProperty(signal) {
73
72
  return signal === undefined ? {} : { signal };
74
73
  }
75
- function longRunningCommandOptions(signal) {
76
- return signal === undefined
77
- ? LONG_RUNNING_COMMAND_OPTIONS
78
- : { ...LONG_RUNNING_COMMAND_OPTIONS, signal };
74
+ /**
75
+ * Runs a long-running git command (fetch, worktree add/remove/prune) with no
76
+ * timeout. Under --verbose the git porcelain streams live to the terminal;
77
+ * otherwise it is captured and discarded on success — the bracketing debug()
78
+ * lines record what ran, and a failure still carries git's stderr via the
79
+ * thrown error (see normalizeCommandError in commandRunner.ts).
80
+ */
81
+ async function runLongGitCommand(arguments_, signal) {
82
+ const signalOption = signal === undefined ? {} : { signal };
83
+ if (isVerbose()) {
84
+ await runCommandAsync("git", arguments_, { stdio: "inherit", timeoutMs: 0, ...signalOption });
85
+ return;
86
+ }
87
+ await runCommandAsync("git", arguments_, { stdio: "captured", timeoutMs: 0, ...signalOption });
79
88
  }
80
89
  async function deleteBranchBestEffort(arguments_) {
81
90
  try {
82
91
  await (arguments_.signal === undefined
83
92
  ? runCommandAsync(arguments_.cmd, arguments_.cmdArgs)
84
93
  : runCommandAsync(arguments_.cmd, arguments_.cmdArgs, { signal: arguments_.signal }));
85
- log(`Deleted branch ${arguments_.branchName}`);
94
+ debug(`Deleted branch ${arguments_.branchName}`);
86
95
  }
87
96
  catch (error) {
88
97
  if (arguments_.signal?.aborted === true) {
89
98
  throw error;
90
99
  }
91
- log(`Branch ${arguments_.branchName} cleanup skipped: ${errorMessage(error)}`);
100
+ debug(`Branch ${arguments_.branchName} cleanup skipped: ${errorMessage(error)}`);
92
101
  }
93
102
  }
94
103
  async function createWorktree(config, spec, signal) {
@@ -100,10 +109,10 @@ async function createWorktree(config, spec, signal) {
100
109
  ...signalProperty(signal),
101
110
  });
102
111
  const baseRef = `${config.git.remote}/${defaultBranch}`;
103
- log(`Fetching ${baseRef} in ${spec.repository}...`);
104
- await runCommandAsync("git", ["-C", base.repoDir, "fetch", config.git.remote, defaultBranch], longRunningCommandOptions(signal));
105
- log(`Creating worktree ${spec.repository}-${spec.ticket} (branch ${base.branchName} from ${baseRef})...`);
106
- await runCommandAsync("git", ["-C", base.repoDir, "worktree", "add", "-b", base.branchName, base.hostWorktreeDir, baseRef], longRunningCommandOptions(signal));
112
+ debug(`Fetching ${baseRef} in ${spec.repository}...`);
113
+ await runLongGitCommand(["-C", base.repoDir, "fetch", config.git.remote, defaultBranch], signal);
114
+ debug(`Creating worktree ${spec.repository}-${spec.ticket} (branch ${base.branchName} from ${baseRef})...`);
115
+ await runLongGitCommand(["-C", base.repoDir, "worktree", "add", "-b", base.branchName, base.hostWorktreeDir, baseRef], signal);
107
116
  return {
108
117
  repository: spec.repository,
109
118
  ticket: spec.ticket,
@@ -172,19 +181,20 @@ async function removeWorktree(config, entry, options) {
172
181
  const projectDir = resolve(config.workspace.projectDir);
173
182
  const repoDir = resolve(projectDir, entry.repository);
174
183
  if (existsSync(entry.dir)) {
175
- log(`Removing worktree ${entry.dir}${options.force ? " (--force)" : ""}...`);
184
+ debug(`Removing worktree ${entry.dir}${options.force ? " (--force)" : ""}...`);
176
185
  const removeArguments = ["-C", repoDir, "worktree", "remove"];
177
186
  if (options.force) {
178
187
  removeArguments.push("--force");
179
188
  }
180
189
  removeArguments.push(entry.dir);
181
190
  try {
182
- await runCommandAsync("git", removeArguments, longRunningCommandOptions(options.signal));
191
+ await runLongGitCommand(removeArguments, options.signal);
183
192
  }
184
193
  catch (error) {
185
- // git's `fatal: ...` diagnostic goes to inherited stderr, so the
186
- // captured error is just "Exit status: 128". Probe the worktree
187
- // ourselves so the failure message names the condition — dirty
194
+ // Under --verbose git's `fatal: ...` streams to the terminal rather than
195
+ // the captured error, so the failure may surface as just "Exit status:
196
+ // 128". Probe the worktree ourselves so the failure message names the
197
+ // condition either way — dirty
188
198
  // (modified/untracked files, fixable with `crew cleanup --force`) or
189
199
  // orphan (directory exists on disk but is not registered with the
190
200
  // parent repo, fixable with `crew cleanup --force` when the path still
@@ -230,8 +240,8 @@ async function removeWorktree(config, entry, options) {
230
240
  }
231
241
  }
232
242
  else {
233
- log(`Worktree directory ${entry.dir} not found, pruning stale refs...`);
234
- await runCommandAsync("git", ["-C", repoDir, "worktree", "prune"], longRunningCommandOptions(options.signal));
243
+ debug(`Worktree directory ${entry.dir} not found, pruning stale refs...`);
244
+ await runLongGitCommand(["-C", repoDir, "worktree", "prune"], options.signal);
235
245
  }
236
246
  await deleteBranchBestEffort({
237
247
  cmd: "git",
@@ -318,7 +328,7 @@ function removeOrphanWorktreeDirectory(config, entry) {
318
328
  if (targetDir !== expectedDir || !isInsideDirectory(projectDir, targetDir)) {
319
329
  throw new Error(`Refusing to force-delete ${entry.dir}: expected groundcrew worktree path ${expectedDir}.`);
320
330
  }
321
- log(`Removing orphaned worktree directory ${entry.dir} (--force)...`);
331
+ debug(`Removing orphaned worktree directory ${entry.dir} (--force)...`);
322
332
  rmSync(targetDir, { recursive: true, force: true });
323
333
  }
324
334
  function list(config) {
package/docs/commands.md CHANGED
@@ -12,24 +12,24 @@ Status is informational only. Use `crew cleanup <TICKET>` to tear down stale wor
12
12
  <summary>Sample ticket status output</summary>
13
13
 
14
14
  ```text
15
- crew status HRD-442
15
+ crew status ENG-123
16
16
  ===================
17
- ticket: hrd-442 in-progress https://linear.app/example/issue/HRD-442
17
+ ticket: eng-123 in-progress https://linear.app/example/issue/ENG-123
18
18
  title: Multi-event extractor: year inference can produce date_start > date_end
19
19
  run: running; model=claude; updated=2026-05-26T00:01:00.000Z; resumes=0
20
20
  workspace: live
21
21
 
22
22
  Worktrees
23
23
  ---------
24
- - herds-social/herds host
25
- branch: paul-hrd-442
26
- dir: /dev/workspaces/herds-social/herds-hrd-442
24
+ - acme/widgets host
25
+ branch: dev-eng-123
26
+ dir: /dev/workspaces/acme/widgets-eng-123
27
27
  git: dirty (0 modified, 1 untracked)
28
- pr: https://github.com/herds-social/herds/pull/224 (open)
28
+ pr: https://github.com/acme/widgets/pull/224 (open)
29
29
 
30
30
  Recent logs
31
31
  -----------
32
- [10:15:30] Workspace "hrd-442" launched
32
+ [10:15:30] Workspace "eng-123" launched
33
33
  ```
34
34
 
35
35
  </details>
@@ -45,8 +45,8 @@ Doctor's command introspection is intentionally shallow. It reports the resolved
45
45
  `crew start <TICKET>` launches one ticket immediately, bypassing orchestrator eligibility. Use it to dispatch a specific ticket on demand, including unlabeled tickets that `crew run` ignores.
46
46
 
47
47
  ```bash
48
- crew start HRD-442
49
- crew start HRD-442 --dry-run
48
+ crew start ENG-123
49
+ crew start ENG-123 --dry-run
50
50
  ```
51
51
 
52
52
  ## Stop
@@ -54,9 +54,9 @@ crew start HRD-442 --dry-run
54
54
  `crew stop <TICKET>` stops a live workspace pane while preserving the ticket worktree and branch. Use it when you need terminal capacity back, want to stop an agent going in the wrong direction, or need to inspect the diff before letting another agent continue.
55
55
 
56
56
  ```bash
57
- crew stop HRD-442 --reason "wrong implementation direction"
58
- crew status HRD-442
59
- crew resume HRD-442
57
+ crew stop ENG-123 --reason "wrong implementation direction"
58
+ crew status ENG-123
59
+ crew resume ENG-123
60
60
  ```
61
61
 
62
62
  The command closes the cmux/tmux workspace if present, records local run state, and never tears down the worktree. If the workspace was already gone but the worktree is still present, stop records that fact so status can show the preserved branch.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clipboard-health/groundcrew",
3
- "version": "4.6.0",
3
+ "version": "4.7.1",
4
4
  "description": "Linear-driven orchestrator that launches AI coding agents in git worktrees, with workspace lifecycle and usage tracking.",
5
5
  "keywords": [
6
6
  "agent",
@@ -82,21 +82,21 @@
82
82
  "@tsconfig/strictest": "2.0.8",
83
83
  "@types/node": "24.12.4",
84
84
  "@typescript/native-preview": "7.0.0-dev.20260522.1",
85
- "@vitest/coverage-v8": "4.1.6",
85
+ "@vitest/coverage-v8": "4.1.7",
86
86
  "cspell": "10.0.0",
87
- "dependency-cruiser": "17.4.0",
87
+ "dependency-cruiser": "17.4.2",
88
88
  "husky": "9.1.7",
89
- "jscpd": "4.2.3",
90
- "knip": "6.14.1",
89
+ "jscpd": "4.2.4",
90
+ "knip": "6.14.2",
91
91
  "lint-staged": "17.0.5",
92
92
  "markdownlint-cli2": "0.22.1",
93
93
  "nx": "22.7.2",
94
- "oxfmt": "0.50.0",
94
+ "oxfmt": "0.52.0",
95
95
  "oxlint": "1.65.0",
96
96
  "oxlint-tsgolint": "0.23.0",
97
97
  "syncpack": "15.2.0",
98
98
  "vite": "8.0.14",
99
- "vitest": "4.1.6"
99
+ "vitest": "4.1.7"
100
100
  },
101
101
  "engines": {
102
102
  "node": "24.14.1",
Binary file