@clipboard-health/groundcrew 3.4.1 → 4.0.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.
Files changed (37) hide show
  1. package/README.md +252 -298
  2. package/clearance-allow-hosts +7 -0
  3. package/crew.config.example.ts +13 -32
  4. package/dist/commands/cleaner.d.ts.map +1 -1
  5. package/dist/commands/cleaner.js +1 -5
  6. package/dist/commands/dispatcher.d.ts.map +1 -1
  7. package/dist/commands/dispatcher.js +5 -5
  8. package/dist/commands/eligibility.d.ts +1 -1
  9. package/dist/commands/eligibility.d.ts.map +1 -1
  10. package/dist/commands/eligibility.js +4 -4
  11. package/dist/commands/init.d.ts.map +1 -1
  12. package/dist/commands/init.js +2 -1
  13. package/dist/commands/setupWorkspace.d.ts.map +1 -1
  14. package/dist/commands/setupWorkspace.js +1 -2
  15. package/dist/commands/ticketDoctor.d.ts.map +1 -1
  16. package/dist/commands/ticketDoctor.js +11 -33
  17. package/dist/index.d.ts +3 -3
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +2 -2
  20. package/dist/lib/adapters/linear/factory.d.ts +10 -14
  21. package/dist/lib/adapters/linear/factory.d.ts.map +1 -1
  22. package/dist/lib/adapters/linear/factory.js +23 -63
  23. package/dist/lib/adapters/linear/schema.d.ts +3 -5
  24. package/dist/lib/adapters/linear/schema.d.ts.map +1 -1
  25. package/dist/lib/adapters/linear/schema.js +3 -5
  26. package/dist/lib/boardSource.d.ts +55 -39
  27. package/dist/lib/boardSource.d.ts.map +1 -1
  28. package/dist/lib/boardSource.js +130 -237
  29. package/dist/lib/config.d.ts +11 -70
  30. package/dist/lib/config.d.ts.map +1 -1
  31. package/dist/lib/config.js +10 -157
  32. package/dist/lib/linearIssueStatus.d.ts +0 -4
  33. package/dist/lib/linearIssueStatus.d.ts.map +1 -1
  34. package/dist/lib/linearIssueStatus.js +0 -0
  35. package/dist/lib/ticketSource.d.ts +5 -7
  36. package/dist/lib/ticketSource.d.ts.map +1 -1
  37. package/package.json +2 -2
package/README.md CHANGED
@@ -39,95 +39,109 @@ Eligibility
39
39
 
40
40
  ## Why
41
41
 
42
- - **Linear-native.** Polls a project, respects `agent-*` labels, honors blockers.
42
+ - **Linear-native.** Polls issues assigned to the API key's viewer with `agent-*` labels, honors blockers.
43
43
  - **One worktree per ticket.** Agents work in parallel without stepping on each other.
44
44
  - **Local-first sandboxing.** Safehouse on macOS, Docker Sandboxes on Linux, or an explicit `none` escape hatch.
45
- - **Multi-agent.** Ships with `claude` and `codex`; bring your own CLI by dropping a definition into `crew.config.ts`.
46
-
47
- ## Install
48
-
49
- ```bash
50
- npm install -g @clipboard-health/groundcrew
51
- ```
52
-
53
- Installs the `crew` binary. `@clipboard-health/clearance` is pulled in transitively and provides the `clearance` / `clearance-ensure` bins used by Safehouse runs.
45
+ - **Multi-agent.** Ships with `claude` and `codex`; bring your own CLI via `crew.config.ts`.
54
46
 
55
47
  ## Quickstart
56
48
 
57
- 1. **Install prereqs.** Node 24, `git`, `cmux` _or_ `tmux`, and the agent CLIs themselves (`claude`, `codex`, `cursor-agent`, …). Optional: `codexbar` for session-usage gating.
58
-
59
- 2. **Pick an isolation runner.** See [Runners](#runners) — `auto` resolves to `safehouse` on macOS and `sdx` on Linux/WSL.
60
-
61
- 3. **Create a Linear project to scope your work.** Any team works — make a project inside it and drop tickets in. The orchestrator polls by project, not by team.
62
-
63
- 4. **Configure.** Create a `crew.config.ts` you can edit:
49
+ ```bash
50
+ # 1. Install Node ≥ 24, git, cmux or tmux, and the agent CLIs you'll use (claude, codex, ...).
64
51
 
65
- ```bash
66
- # Write into the current folder:
67
- crew init && $EDITOR crew.config.ts
52
+ # 2. Install groundcrew
53
+ npm install -g @clipboard-health/groundcrew
68
54
 
69
- # ...or into the XDG config dir:
70
- crew init --global && $EDITOR "${XDG_CONFIG_HOME:-$HOME/.config}/groundcrew/crew.config.ts"
71
- ```
55
+ # 3. Scaffold a config and edit workspace.projectDir + workspace.knownRepositories
56
+ crew init && $EDITOR crew.config.ts
72
57
 
73
- `crew init` refuses to overwrite an existing config; pass `--force` to replace it, or `--dry-run` to preview the destination path.
58
+ # 4. Clone the repos referenced in your config
59
+ crew setup repos
74
60
 
75
- `crew` discovers the config via cosmiconfig project-walk, so dropping it at the root of any repo you run `crew` from works too. Any of `crew.config.{ts,mjs,js,json}`, `.crewrc{,.json,.ts}`, `.config/crew.config.{ts,json}`, or `.config/crewrc{,.json}` are recognized.
61
+ # 5. Export your Linear API key
62
+ export GROUNDCREW_LINEAR_API_KEY="lin_api_..."
76
63
 
77
- Set `linear.projects[].projectSlug` (paste the trailing slug of your Linear project URL, e.g. `ai-strategy-5152195762f3`), `workspace.projectDir`, and `workspace.knownRepositories`. Defaults cover everything else. To watch multiple projects from one `crew` instance, add more entries to `linear.projects`; they all share the same `orchestrator.maximumInProgress` budget.
64
+ # 6. Verify setup, then dispatch
65
+ crew doctor
66
+ crew run --watch
67
+ ```
78
68
 
79
- Then clone each repo before the first `crew run` groundcrew creates per-ticket worktrees from these clones, it does not auto-clone:
69
+ In Linear, assign tickets to yourself and add an `agent-*` label (`agent-claude`, `agent-codex`, or `agent-any`). Groundcrew picks them up across every team and project your API key can see.
80
70
 
81
- ```bash
82
- crew setup repos # clone all missing entries via gh
83
- crew setup repos --dry-run # preview
84
- crew setup repos owner/repo # restrict to one entry
85
- ```
71
+ `crew init --global` writes the config into `${XDG_CONFIG_HOME:-$HOME/.config}/groundcrew/` instead of the cwd. Both forms refuse to overwrite — pass `--force` to replace, `--dry-run` to preview.
86
72
 
87
- `crew setup repos` is idempotent; already-cloned repos report `[exists]`. Bare-name entries (no `owner/`) are skipped — clone them manually into `<projectDir>/<name>`.
73
+ ## Commands
88
74
 
89
- 5. **Export a Linear API key.** `crew` reads `GROUNDCREW_LINEAR_API_KEY` first, then falls back to `LINEAR_API_KEY`.
75
+ ```bash
76
+ crew init [--global | --local] [--force] [--dry-run] # create a crew.config.ts
77
+ crew doctor # check setup
78
+ crew doctor --ticket <TICKET> # diagnose a specific ticket
79
+ crew run # one-shot dispatch
80
+ crew run --watch # poll forever
81
+ crew run --ticket <TICKET> # dispatch one ticket
82
+ crew setup repos [<repo>...] [--dry-run] # clone known repos via gh
83
+ crew interrupt <TICKET> [--reason <text>] # stop workspace, keep worktree
84
+ crew resume <TICKET> # reopen a paused ticket
85
+ crew cleanup <TICKET> # tear down every worktree for a ticket
86
+ ```
90
87
 
91
- ```bash
92
- export GROUNDCREW_LINEAR_API_KEY="lin_api_..."
93
- ```
88
+ ## Configuration
94
89
 
95
- <details>
96
- <summary>Using 1Password (<code>op</code>) for the key</summary>
90
+ Two keys are required; everything else has a default.
97
91
 
98
- ```bash
99
- echo "GROUNDCREW_LINEAR_API_KEY='op://<vault>/LINEAR_API_KEY/credential'" > .env.1password
100
- op run --env-file .env.1password -- crew doctor
101
- ```
92
+ | Key | What |
93
+ | ----------------------------- | ---------------------------------------------------------------------- |
94
+ | `workspace.projectDir` | Parent dir for cloned repos and sibling ticket worktrees. |
95
+ | `workspace.knownRepositories` | Repos searched for in ticket descriptions to infer where work belongs. |
102
96
 
103
- </details>
97
+ The branch prefix (`<prefix>-<TICKET>`) is derived from `os.userInfo().username` and isn't configurable. There is no `linear` config block — 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.
104
98
 
105
- 6. **Run.**
99
+ <details>
100
+ <summary>Agent label routing</summary>
106
101
 
107
- ```bash
108
- crew doctor # check setup
109
- crew run --dry-run # preview without provisioning
110
- crew run --watch # poll forever
111
- ```
102
+ - `agent-claude`, `agent-codex`, `agent-<name>` → that model.
103
+ - `agent-any` the model with the most available session capacity.
104
+ - Unknown `agent-<name>` falls back to `models.default` with a warning.
105
+ - No `agent-*` label → ignored by `crew run`. Dispatch on demand with `crew run --ticket <TICKET>` (also falls back to `models.default`).
106
+ - Todo tickets blocked by non-terminal blockers are skipped until their blockers reach a terminal status.
112
107
 
113
- ## Secrets
108
+ Status classification uses Linear's workflow `state.type` (`unstarted`, `started`, `completed`, `canceled`, `duplicate`), so renamed status columns work without configuration. Parent issues with children are ignored — sub-issues are the work items.
114
109
 
115
- Groundcrew forwards a small allowlist of build-time secrets from your shell into the setup phase (so `npm install` can authenticate against private registries) and then strips them before the agent runs. The agent process never inherits these values in its environment.
110
+ </details>
116
111
 
117
- **Recognized names.** Defined in [`BUILD_SECRET_NAMES`](./src/lib/buildSecrets.ts):
112
+ <details>
113
+ <summary>Config discovery</summary>
118
114
 
119
- - `NPM_TOKEN`
120
- - `BUF_TOKEN`
115
+ Resolution order: `GROUNDCREW_CONFIG` → cosmiconfig project-walk from cwd (any of `crew.config.{ts,mjs,js,json}`, `.crewrc{,.json,.ts}`, `.config/crew.config.{ts,json}`, `.config/crewrc{,.json}`) → `${XDG_CONFIG_HOME:-$HOME/.config}/groundcrew/crew.config.ts` (legacy `config.ts` accepted for one release). The "Loaded config from …" line at startup tells you which won.
121
116
 
122
- Set them in the shell you run `crew` from. Anything not in this list is ignored by the secret-shuttling path.
117
+ </details>
123
118
 
124
- **Flow.** For each ticket:
119
+ <details>
120
+ <summary>Full configuration reference</summary>
125
121
 
126
- 1. If any recognized var is set and non-empty, groundcrew writes `secrets.env` (mode `0600`) into the ticket's temp prompt dir as `KEY='value'` lines — see `stageBuildSecrets` in [`src/commands/setupWorkspace.ts`](./src/commands/setupWorkspace.ts).
127
- 2. The launch script sources `secrets.env` with `set -a` so the values are exported into the setup phase only (and under `sdx`, forwarded into the sandbox via `-e KEY` flags).
128
- 3. After setup completes, the script `unset`s every name in `BUILD_SECRET_NAMES` and then `rm -rf`s the entire prompt dir (including `secrets.env`) before `exec`'ing the agent. See `sourceSecretsLine` / `unsetSecretsLine` and the `rm -rf` / `exec` lines in [`src/lib/launchCommand.ts`](./src/lib/launchCommand.ts). The rollback path on setup failure ([`src/commands/setupWorkspace.ts`](./src/commands/setupWorkspace.ts)) wipes the prompt dir too.
122
+ | Key | Default | What it does |
123
+ | --------------------------------------- | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
124
+ | `sources` | `[]` | Additional pluggable ticket sources. Extra sources are verified at startup; the built-in Linear adapter remains the dispatch read path until the canonical consumer refactor. Built-in kinds: `shell`, `linear`. |
125
+ | `git.remote` | `"origin"` | Remote used for `fetch` and as the worktree base ref. |
126
+ | `git.defaultBranch` | `"main"` | Branch fetched from `git.remote` and used as the worktree base. |
127
+ | `workspace.projectDir` | **required** | Parent dir for cloned repos and sibling ticket worktrees. |
128
+ | `workspace.knownRepositories` | **required** | Repos searched for in ticket descriptions to infer where work belongs. A ticket labeled for groundcrew (`agent-*`) fails fast when no known repo appears; unlabeled tickets are ignored. |
129
+ | `orchestrator.maximumInProgress` | `4` | Cap on in-progress tickets at once for this `crew` instance. |
130
+ | `orchestrator.pollIntervalMilliseconds` | `120_000` | Poll interval in `--watch` mode. |
131
+ | `orchestrator.sessionLimitPercentage` | `85` | Number in `(0, 100]`. A model whose codexbar session window exceeds this percentage is skipped that tick. |
132
+ | `models.default` | `"claude"` | Tiebreak for `agent-any` resolution and fallback for explicit but unknown `agent-*` labels. Also used by `crew run --ticket <TICKET>` for unlabeled tickets. `crew run` without `--ticket` ignores unlabeled tickets and does not apply this default. Must exist in `models.definitions`. |
133
+ | `models.definitions` | `{ claude, codex }` | Agent definitions. Additive merge with shipped defaults. |
134
+ | `models.definitions.<name>.cmd` | — | Shell command launched for the model. 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. |
135
+ | `models.definitions.<name>.color` | — | Color for the workspace status pill (cmux only; tmux silently drops it). |
136
+ | `models.definitions.<name>.usage` | optional | If set, codexbar usage is fetched for this model and gated by `sessionLimitPercentage`. Falls back to default when unset, with gating enabled for known models. 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. |
137
+ | `models.definitions.<name>.sandbox` | optional | Docker Sandboxes binding for the model. Required at launch when `local.runner` resolves to `sdx`. Fields: `agent` (required sbx agent name), `template`, `kits`, `setupCommand` (override for the inside-sandbox setup script). |
138
+ | `models.definitions.<name>.disabled` | optional | When set to exactly `true`, drops the named shipped default (`claude` or `codex`). Doctor skips probing it; `agent-<name>` labels fall back to `models.default` with a warning. |
139
+ | `prompts.initial` | unattended template | First message sent to the agent. Placeholders: `{{ticket}}`, `{{worktree}}`, `{{title}}`, `{{description}}`. Override this from `crew.config.ts` for team-specific statuses, tools, plugins, or review loops. |
140
+ | `workspaceKind` | `"auto"` | Terminal session manager. `"auto"` picks `cmux` when on PATH, else `tmux`. Set to `"cmux"` or `"tmux"` to fail loudly when the chosen backend is missing. |
141
+ | `local.runner` | `"auto"` | Local isolation backend. `"auto"` → `safehouse` on macOS, `sdx` on Linux/WSL. Explicit: `"safehouse"`, `"sdx"`, `"none"`. `"none"` is never picked implicitly. |
142
+ | `logging.file` | XDG state path | Append-mode log file. `log()` / `logEvent()` tee here in addition to stdout. Defaults to `${XDG_STATE_HOME:-$HOME/.local/state}/groundcrew/groundcrew.log`. |
129
143
 
130
- Net effect: by the time the agent process exists, the values are gone from the environment and the file is gone from disk.
144
+ </details>
131
145
 
132
146
  ## Runners
133
147
 
@@ -139,8 +153,6 @@ Net effect: by the time the agent process exists, the values are gone from the e
139
153
  | `sdx` | Linux / WSL | [Docker Sandboxes](https://docs.docker.com/sandboxes/) (`sbx`) — required when the agent needs `docker`. |
140
154
  | `none` | — | Unsandboxed escape hatch. Never picked implicitly; doctor warns when configured. |
141
155
 
142
- For `sdx`: each model that runs under it needs a `sandbox: { agent: "<sbx-agent>" }` block in `crew.config.ts`. Groundcrew names sandboxes `groundcrew-<agent>` (e.g. `groundcrew-claude`) and reuses one sandbox per agent across repos and tickets. First-time agent auth happens inside the sandbox the first time it launches. To bootstrap manually instead, run `sbx create --name groundcrew-<agent> <agent> <projectDir>` once.
143
-
144
156
  <details>
145
157
  <summary>Safehouse clearance allowlist</summary>
146
158
 
@@ -160,25 +172,182 @@ crew run --watch
160
172
 
161
173
  Watch `${XDG_CACHE_HOME:-$HOME/.cache}/clearance/clearance.log` for `DENY` lines and add only the domains your agents actually need.
162
174
 
175
+ `@clipboard-health/clearance` is pulled in transitively when you install groundcrew and provides the `clearance` / `clearance-ensure` bins used by Safehouse runs.
176
+
163
177
  </details>
164
178
 
165
- ## Configuration
179
+ <details>
180
+ <summary>Docker Sandboxes (sdx) setup</summary>
181
+
182
+ Each model that runs under `sdx` needs a `sandbox: { agent: "<sbx-agent>" }` block in `crew.config.ts`. Groundcrew names sandboxes `groundcrew-<agent>` (e.g. `groundcrew-claude`) and reuses one sandbox per agent across repos and tickets. First-time agent auth happens inside the sandbox the first time it launches. To bootstrap manually instead, run `sbx create --name groundcrew-<agent> <agent> <projectDir>` once.
183
+
184
+ Groundcrew auto-creates sandboxes when missing but never deletes them — they persist across tickets and `crew cleanup`. Auth state lives inside the sandbox, so deleting it forces a re-login. Manage with `sbx ls` / `sbx rm`.
185
+
186
+ </details>
187
+
188
+ ## Diagnosing tickets
189
+
190
+ `crew doctor --ticket <TICKET>` runs the full per-ticket lifecycle: pre-dispatch eligibility (Todo status, `agent-*` label, model resolution, repo mention, local clone, blockers, session usage, capacity) **and** post-dispatch local recovery (run state, host worktree, workspace pane, branches, PR). Prints a single verdict with a copy-pasteable next step.
191
+
192
+ Verdict precedence: PR outcomes (`pr-open` > `pr-merged`) → recorded failed launches → `interrupted` (concrete recoverable git work first) → `in-flight` → `recoverable` → `unresolvable` > `ineligible` > `would-dispatch` > `lost`. Exits 0 on `would-dispatch`, `pr-open`, or `pr-merged`; any other verdict exits 1. `--watch` and `--ticket` are mutually exclusive. Use `codexbar usage` to inspect session windows directly.
193
+
194
+ Flags:
195
+
196
+ - `--no-linear` — skip the Linear GraphQL call. Resolution and Eligibility sections are skipped; verdicts that need only local state (`in-flight`, `recoverable`, `pr-open`, `pr-merged`, `lost`) still fire.
197
+ - `--no-fetch` — skip the upfront `git fetch origin <branch>` before checking remote presence.
198
+
199
+ | Verdict | What to do |
200
+ | ---------------- | --------------------------------------------------------------------------------------------- |
201
+ | `pr-open` | Nothing — the PR is the source of truth. |
202
+ | `pr-merged` | Done. |
203
+ | `in-flight` | The ticket is still being worked on; the verdict line names the workspace pane to attach to. |
204
+ | `recoverable` | Run the printed `nextStep` exactly. |
205
+ | `interrupted` | Resume the preserved worktree with `crew resume <ticket>` or inspect it by hand. |
206
+ | `failed-launch` | Fix the launch failure, then run `crew resume <ticket>` or `crew cleanup <ticket>`. |
207
+ | `would-dispatch` | Pre-dispatch checks pass; the orchestrator will pick the ticket up on its next tick. |
208
+ | `ineligible` | A resolution or eligibility check failed; the reason after the colon names the failing check. |
209
+ | `unresolvable` | The Linear ticket couldn't be fetched; the reason after the colon names the error. |
210
+ | `lost` | No trace exists. Re-dispatch via `crew run --ticket <ticket>`. |
211
+
212
+ <details>
213
+ <summary>Sample output (post-dispatch)</summary>
214
+
215
+ The Workspace section appends an attach hint to the pane name when the workspace backend exposes one (e.g. `tmux attach -t <session>:<pane>` or `cmux attach <name>`), so the verdict line is immediately actionable.
216
+
217
+ ```text
218
+ groundcrew doctor --ticket HRD-442 (Multi-event extractor: year inference can produce date_start > date_end)
219
+ ────────────────────────────────────────────────────────────────────────────────────────────────────────────
220
+
221
+ Resolution
222
+ [ok] Ticket exists in Linear ("Multi-event extractor: year inference can produce date_start > date_end")
223
+ [ok] Status is Todo
224
+ (skipped — post-dispatch — pre-dispatch checks are irrelevant)
225
+
226
+ Eligibility
227
+ (skipped — post-dispatch — pre-dispatch checks are irrelevant)
228
+
229
+ Run state
230
+ [ok] Local run state (running)
231
+ [ok] Recorded model (claude)
232
+ [ok] Recorded worktree (/Users/paul/dev/groundcrew-workspaces/herds-social/herds-hrd-442)
233
+ [ok] Recorded branch (paul-hrd-442)
234
+ [ok] Resume count (0)
235
+
236
+ Worktree
237
+ [ok] Host worktree exists (/Users/paul/dev/groundcrew-workspaces/herds-social/herds-hrd-442)
238
+ [--] Working tree clean (0 modified, 1 untracked)
239
+ [ok] Branch checked out (paul-hrd-442)
240
+
241
+ Workspace
242
+ [ok] Workspace pane open (hrd-442 — attach: `tmux attach -t groundcrew:hrd-442`)
243
+
244
+ Local branch
245
+ [ok] Local branch exists (paul-hrd-442, 2 ahead / 0 behind origin/main)
246
+
247
+ Remote branch
248
+ [ok] Branch present on origin
249
+
250
+ Pull request
251
+ [ok] Open PR for this branch (#224 https://github.com/herds-social/herds/pull/224)
252
+
253
+ → pr-open: https://github.com/herds-social/herds/pull/224 (#224)
254
+ ```
255
+
256
+ </details>
257
+
258
+ ### `crew interrupt <TICKET>`
259
+
260
+ Stops a live workspace pane while preserving the ticket worktree and branch. The manual pause button for cases where you need terminal capacity back, want to stop an agent that's going in the wrong direction, or need to inspect the diff before letting another agent continue.
261
+
262
+ ```bash
263
+ crew interrupt HRD-442 --reason "wrong implementation direction"
264
+ crew doctor --ticket HRD-442
265
+ crew resume HRD-442
266
+ ```
267
+
268
+ 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, interrupt records that fact so doctor can point at the preserved branch instead of reporting a mystery ticket.
269
+
270
+ ### `crew resume <TICKET>`
271
+
272
+ Reopens an existing ticket worktree with a continuation prompt. Resume never creates a new worktree; if none exists it fails and leaves re-dispatch to `crew run --ticket <ticket>`.
273
+
274
+ 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 ticket context.
275
+
276
+ ## Secrets
277
+
278
+ Groundcrew forwards a small allowlist of build-time secrets from your shell into the setup phase (so `npm install` can authenticate against private registries) and strips them before the agent runs. The agent process never inherits these values.
279
+
280
+ Recognized names, defined in [`BUILD_SECRET_NAMES`](./src/lib/buildSecrets.ts):
281
+
282
+ - `NPM_TOKEN`
283
+ - `BUF_TOKEN`
284
+
285
+ Set them in the shell you run `crew` from. Anything not in this list is ignored.
286
+
287
+ <details>
288
+ <summary>How the secret shuttle works</summary>
289
+
290
+ For each ticket:
291
+
292
+ 1. If any recognized var is set and non-empty, groundcrew writes `secrets.env` (mode `0600`) into the ticket's temp prompt dir as `KEY='value'` lines — see `stageBuildSecrets` in [`src/commands/setupWorkspace.ts`](./src/commands/setupWorkspace.ts).
293
+ 2. The launch script sources `secrets.env` with `set -a` so the values are exported into the setup phase only (and under `sdx`, forwarded into the sandbox via `-e KEY` flags).
294
+ 3. After setup completes, the script `unset`s every name in `BUILD_SECRET_NAMES` and then `rm -rf`s the entire prompt dir (including `secrets.env`) before `exec`'ing the agent. See `sourceSecretsLine` / `unsetSecretsLine` and the `rm -rf` / `exec` lines in [`src/lib/launchCommand.ts`](./src/lib/launchCommand.ts). The rollback path on setup failure ([`src/commands/setupWorkspace.ts`](./src/commands/setupWorkspace.ts)) wipes the prompt dir too.
295
+
296
+ Net effect: by the time the agent process exists, the values are gone from the environment and the file is gone from disk.
297
+
298
+ </details>
299
+
300
+ ## Per-repo setup hook
301
+
302
+ If `.groundcrew/setup.sh` exists in the repo root, groundcrew runs `bash .groundcrew/setup.sh --deps-only` before each agent launch; otherwise nothing runs. Same convention applies inside the sdx sandbox (overridable per-model via `models.definitions.<name>.sandbox.setupCommand`). No implicit `npm install`, `uv sync`, or anything else — groundcrew is language-agnostic, so opt in by adding the script.
303
+
304
+ The `--deps-only` flag tells the script "you're being called by an automated system before an agent launches — skip anything interactive or one-time-only." The same script handles both modes; branch on `$1`:
305
+
306
+ - **With `--deps-only`**: do the cheap recurring work this worktree needs (lockfile install, generate types, etc.). No prompts, no global installs, no `nvm` / `pyenv` bootstrap.
307
+ - **Without the flag**: full interactive bootstrap. Use this when an engineer runs the script by hand for first-time onboarding, or when wiring it into another tool's SessionStart hook.
308
+
309
+ Setup failures are advisory — groundcrew logs the non-zero exit and still launches the agent so a flaky network or stale lockfile doesn't block the session.
310
+
311
+ <details>
312
+ <summary>Examples</summary>
313
+
314
+ **Python (uv):**
315
+
316
+ ```bash
317
+ #!/usr/bin/env bash
318
+ set -euo pipefail
319
+ if [ "${1:-}" = "--deps-only" ]; then
320
+ uv sync --dev
321
+ else
322
+ uv sync --dev
323
+ # ... extra one-time bootstrap (e.g., pre-commit install, db seed) ...
324
+ fi
325
+ ```
326
+
327
+ **Node (npm):**
328
+
329
+ ```bash
330
+ #!/usr/bin/env bash
331
+ set -euo pipefail
332
+ if [ "${1:-}" = "--deps-only" ]; then
333
+ npm clean-install
334
+ else
335
+ npm clean-install
336
+ # ... extra one-time bootstrap (e.g., husky install, codegen, link local packages) ...
337
+ fi
338
+ ```
166
339
 
167
- Three keys are required; everything else has a default.
340
+ **Docs-only or polyglot repo with no install step:** omit the script. With nothing at `.groundcrew/setup.sh`, groundcrew skips the hook silently.
168
341
 
169
- | Key | What |
170
- | ------------------------------- | --------------------------------------------------------------------------------------------------------- |
171
- | `linear.projects[].projectSlug` | Trailing slug of each Linear project URL to watch (e.g. `ai-strategy-5152195762f3`). One or more entries. |
172
- | `workspace.projectDir` | Parent dir for cloned repos and sibling ticket worktrees. |
173
- | `workspace.knownRepositories` | Repos searched for in ticket descriptions to infer where work belongs. |
342
+ For a comprehensive real-world example (nvm bootstrap, hash-based skip-on-no-changes caching, portable SHA-256 detection), see [this repo's own `.groundcrew/setup.sh`](./.groundcrew/setup.sh). It's also symlinked at `.claude/setup.sh` so the same script doubles as a Claude Code SessionStart hook for this repo — that symlink is local convenience, not part of groundcrew's contract.
174
343
 
175
- `crew` resolves config as: `GROUNDCREW_CONFIG` if set project-walk from cwd (cosmiconfig: `crew.config.{ts,mjs,js,json}`, `.crewrc{,.json,.ts}`, `.config/crew.config.{ts,json}`, `.config/crewrc{,.json}`) → `${XDG_CONFIG_HOME:-$HOME/.config}/groundcrew/crew.config.ts` (also accepts legacy `config.ts` for one release). The branch prefix (`<prefix>-<TICKET>`) is derived from `os.userInfo().username` not configurable.
344
+ To scaffold `.groundcrew/setup.sh` with a coding agent (Claude Code, Cursor, etc.), see [docs/setup-hook-agent-prompt.md](./docs/setup-hook-agent-prompt.md) it encodes the contract above as a copy-pasteable prompt.
176
345
 
177
- Agent selection uses Linear labels: `agent-claude`, `agent-codex`, `agent-<name>`. `crew run` without `--ticket` only fetches tickets carrying an `agent-*` label — the GraphQL query filters server-side, so unlabeled tickets are never returned by Linear and do not appear on the board. Use `crew run --ticket <TICKET>` to provision an unlabeled ticket on demand (falls back to `models.default`). `agent-any` routes to the model with the most available session capacity. Todo tickets blocked by non-terminal blockers are skipped until their blockers reach a terminal status.
346
+ </details>
178
347
 
179
- ### Pluggable ticket sources
348
+ ## Pluggable ticket sources
180
349
 
181
- `sources` declares extra ticket-system adapters. The current release verifies configured extra sources during `crew run` startup; the dispatch loop still reads Linear through `linear.projects` until the consumer refactor lands. This lets you validate shell/Jira/local-plan integrations without changing existing Linear behavior.
350
+ `sources` declares extra ticket-system adapters. The current release verifies configured extra sources during `crew run` startup; the dispatch loop still reads Linear directly through the built-in Linear adapter until the canonical consumer refactor lands. This lets you validate shell/Jira/local-plan integrations without changing existing Linear behavior.
182
351
 
183
352
  The built-in `shell` adapter runs command templates and reads JSON from stdout:
184
353
 
@@ -223,7 +392,7 @@ export default {
223
392
 
224
393
  Allowed `status` values are `todo`, `in-progress`, `in-review`, `done`, and `other`. Use `null` for `repository` or `model` when a ticket should not be groundcrew-eligible. `hasMoreBlockers` is optional and defaults to `false`; `sourceRef` is opaque data that groundcrew passes back to your writeback command.
225
394
 
226
- ### Prompt customization
395
+ ## Prompt customization
227
396
 
228
397
  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.
229
398
 
@@ -242,41 +411,7 @@ export default {
242
411
 
243
412
  This keeps package defaults portable while letting your private config reference team-specific statuses, tools, plugins, or review loops.
244
413
 
245
- <details>
246
- <summary>Full reference table</summary>
247
-
248
- | Key | Default | What it does |
249
- | --------------------------------------- | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
250
- | `linear.projects` | **required** | Non-empty array of Linear projects to watch. One `crew` instance dispatches across every entry under a shared `maximumInProgress` budget. |
251
- | `linear.projects[].projectSlug` | **required** | Linear project URL slug (e.g. `ai-strategy-5152195762f3`). The trailing 12-char hex `slugId` is what's matched against Linear's API; the leading name keeps `crew.config.ts` self-documenting and the lookup survives project renames. |
252
- | `linear.projects[].statuses.todo` | `"Todo"` | Status name picked up for new work in this project. Per-project so multi-team setups with divergent state names can coexist. |
253
- | `linear.projects[].statuses.inProgress` | `"In Progress"` | Status set after a workspace is provisioned; counts toward the shared `maximumInProgress`. |
254
- | `linear.projects[].statuses.done` | `"Done"` | Status that triggers worktree cleanup for this project. |
255
- | `linear.projects[].statuses.terminal` | `["Done"]` | Additional status names treated as terminal for cleanup and blocker checks. The project's `done` status is always included. |
256
- | `sources` | `[]` | Additional pluggable ticket sources. Extra sources are verified at startup; Linear remains the dispatch read path until the consumer refactor. Built-in kinds: `shell`, `linear`. |
257
- | `git.remote` | `"origin"` | Remote used for `fetch` and as the worktree base ref. |
258
- | `git.defaultBranch` | `"main"` | Branch fetched from `git.remote` and used as the worktree base. |
259
- | `workspace.projectDir` | **required** | Parent dir for cloned repos and sibling ticket worktrees. |
260
- | `workspace.knownRepositories` | **required** | Repos searched for in ticket descriptions to infer where work belongs. A ticket labeled for groundcrew (`agent-*`) fails fast when no known repo appears; unlabeled tickets are ignored. |
261
- | `orchestrator.maximumInProgress` | `4` | Cap on in-progress tickets at once, shared across every project in `linear.projects`. |
262
- | `orchestrator.pollIntervalMilliseconds` | `120_000` | Poll interval in `--watch` mode. |
263
- | `orchestrator.sessionLimitPercentage` | `85` | Number in `(0, 100]`. A model whose codexbar session window exceeds this percentage is skipped that tick. |
264
- | `models.default` | `"claude"` | Tiebreak for `agent-any` resolution and fallback for explicit but unknown `agent-*` labels. Also used by `crew run --ticket <TICKET>` for unlabeled tickets. `crew run` without `--ticket` ignores unlabeled tickets and does not apply this default. Must exist in `models.definitions`. |
265
- | `models.definitions` | `{ claude, codex }` | Agent definitions. Additive merge with shipped defaults. |
266
- | `models.definitions.<name>.cmd` | — | Shell command launched for the model. 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. |
267
- | `models.definitions.<name>.color` | — | Color for the workspace status pill (cmux only; tmux silently drops it). |
268
- | `models.definitions.<name>.usage` | optional | If set, codexbar usage is fetched for this model and gated by `sessionLimitPercentage`. Falls back to default when unset, with gating enabled for known models. 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. |
269
- | `models.definitions.<name>.sandbox` | optional | Docker Sandboxes binding for the model. Required at launch when `local.runner` resolves to `sdx`. Fields: `agent` (required sbx agent name), `template`, `kits`, `setupCommand` (override for the inside-sandbox setup script). |
270
- | `models.definitions.<name>.disabled` | optional | When set to exactly `true`, drops the named shipped default (`claude` or `codex`). Doctor skips probing it; `agent-<name>` labels fall back to `models.default` with a warning. |
271
- | `prompts.initial` | unattended template | First message sent to the agent. Placeholders: `{{ticket}}`, `{{worktree}}`, `{{title}}`, `{{description}}`. Override this from `crew.config.ts` for team-specific statuses, tools, plugins, or review loops. |
272
- | `workspaceKind` | `"auto"` | Terminal session manager. `"auto"` picks `cmux` when on PATH, else `tmux`. Set to `"cmux"` or `"tmux"` to fail loudly when the chosen backend is missing. |
273
- | `local.runner` | `"auto"` | Local isolation backend. `"auto"` → `safehouse` on macOS, `sdx` on Linux/WSL. Explicit: `"safehouse"`, `"sdx"`, `"none"`. `"none"` is never picked implicitly. |
274
- | `logging.file` | XDG state path | Append-mode log file. `log()` / `logEvent()` tee here in addition to stdout. Defaults to `${XDG_STATE_HOME:-$HOME/.local/state}/groundcrew/groundcrew.log`. |
275
-
276
- </details>
277
-
278
- <details>
279
- <summary>Disabling a shipped default</summary>
414
+ ## Disabling a shipped default model
280
415
 
281
416
  Groundcrew ships `claude` and `codex` as default model definitions, additively merged into every resolved config. To stop probing one:
282
417
 
@@ -306,172 +441,19 @@ Rules:
306
441
  - It cannot be combined with `cmd`, `color`, or `usage` in the same entry.
307
442
  - `models.default` must point at an enabled model.
308
443
 
309
- </details>
310
-
311
- ## Per-repo setup hook
312
-
313
- When groundcrew launches a worktree, if `.groundcrew/setup.sh` exists in the repo root it's invoked as `bash .groundcrew/setup.sh --deps-only` before the agent starts; otherwise nothing runs. The same convention applies inside the sdx sandbox (overridable per-model via `models.definitions.<name>.sandbox.setupCommand`). No implicit `npm install`, `uv sync`, or anything else — groundcrew is language-agnostic, so opt in by adding the script.
314
-
315
- ### The `--deps-only` contract
444
+ ## Using 1Password for the API key
316
445
 
317
- The flag tells the script "you're being called by an automated system before an agent launches — skip anything interactive or one-time-only." The same script handles both modes; branch on `$1`. The name is historical and Node-flavored, but the semantic is language-neutral:
318
-
319
- - **With `--deps-only`**: do the cheap recurring work this worktree needs (lockfile install, generate types, etc.). No prompts, no global installs, no `nvm` / `pyenv` bootstrap that the host should already have.
320
- - **Without the flag**: full interactive bootstrap. Use this path when an engineer runs the script by hand for first-time onboarding, or when wiring it into another tool's SessionStart hook.
321
-
322
- Setup failures are advisory — groundcrew logs the non-zero exit and still launches the agent so a flaky network or stale lockfile doesn't block the session.
323
-
324
- ### Examples
325
-
326
- **Python (uv):**
446
+ `crew` reads `GROUNDCREW_LINEAR_API_KEY` first, then falls back to `LINEAR_API_KEY`. To resolve from 1Password:
327
447
 
328
448
  ```bash
329
- #!/usr/bin/env bash
330
- set -euo pipefail
331
- if [ "${1:-}" = "--deps-only" ]; then
332
- uv sync --dev
333
- else
334
- uv sync --dev
335
- # ... extra one-time bootstrap (e.g., pre-commit install, db seed) ...
336
- fi
449
+ echo "GROUNDCREW_LINEAR_API_KEY='op://<vault>/LINEAR_API_KEY/credential'" > .env.1password
450
+ op run --env-file .env.1password -- crew doctor
337
451
  ```
338
452
 
339
- **Node (npm):**
340
-
341
- ```bash
342
- #!/usr/bin/env bash
343
- set -euo pipefail
344
- if [ "${1:-}" = "--deps-only" ]; then
345
- npm clean-install
346
- else
347
- npm clean-install
348
- # ... extra one-time bootstrap (e.g., husky install, codegen, link local packages) ...
349
- fi
350
- ```
351
-
352
- **Docs-only or polyglot repo with no install step:**
353
-
354
- Omit the script. With nothing at `.groundcrew/setup.sh`, groundcrew skips the hook silently — fine for documentation repos, polyglot monorepos where setup happens per-package, or anywhere the per-worktree work is genuinely zero.
355
-
356
- For a more comprehensive real-world example (nvm bootstrap, hash-based skip-on-no-changes caching, portable SHA-256 detection), see [this repo's own `.groundcrew/setup.sh`](./.groundcrew/setup.sh). It's also symlinked at `.claude/setup.sh` so the same script doubles as a Claude Code SessionStart hook for this repo — that symlink is local convenience, not part of groundcrew's contract.
357
-
358
- ### Generating it with an agent
359
-
360
- To have a coding agent (Claude Code, Cursor, etc.) scaffold `.groundcrew/setup.sh` for a repo you're onboarding, see [docs/setup-hook-agent-prompt.md](./docs/setup-hook-agent-prompt.md) — it encodes the contract above as a copy-pasteable prompt.
361
-
362
- ## Commands
363
-
364
- ```bash
365
- crew init [--global | --local] [--force] [--dry-run] # create a crew.config.ts
366
- crew doctor # full setup check
367
- crew doctor --ticket <TICKET> [--no-linear] [--no-fetch] # full ticket lifecycle (dispatch + recovery)
368
- crew run # one-shot dispatch
369
- crew run --watch # poll forever
370
- crew run --ticket <TICKET> # provision one ticket and exit
371
- crew setup repos [--dry-run] [<repo>...]
372
- crew interrupt <TICKET> [--reason <text>] # stop the live workspace, keep the worktree
373
- crew resume <TICKET> # reopen an existing ticket worktree
374
- crew cleanup <TICKET> # tear down every worktree carrying this ticket
375
- ```
376
-
377
- `crew doctor --ticket <TICKET>` covers the full per-ticket lifecycle: pre-dispatch eligibility (Todo status, `agent-*` label, model resolution, repository mention, local clone, blockers, model session usage, in-progress capacity) **and** post-dispatch local-state recovery (recorded run state, host worktree, workspace pane, local branch, remote branch, open PR). Verdict precedence starts with PR outcomes (`pr-open` > `pr-merged`). Recorded failed launches report before ordinary local recovery, interrupted runs report concrete recoverable git work first when it exists and otherwise report `interrupted`, and ordinary post-dispatch cases report `in-flight` before `recoverable`. If none of those apply, doctor falls through to `unresolvable` > `ineligible` > `would-dispatch` > `lost`. Exits 0 on `would-dispatch`, `pr-open`, or `pr-merged`; any other verdict exits 1. `--watch` and `--ticket` are mutually exclusive. To inspect codexbar session windows directly, run `codexbar usage`.
378
-
379
- ### `crew doctor --ticket <ticket>`
380
-
381
- Diagnose where a ticket is in its lifecycle and what to do next. Runs the same resolution and eligibility chain as the dispatcher, plus probes recorded run state, host worktree, workspace pane, local branch, remote branch, and PR; prints a single verdict with a copy-pasteable recovery step when one applies.
382
-
383
- Flags:
384
-
385
- - `--no-linear` — skip the Linear GraphQL call. Resolution and Eligibility sections are skipped; verdicts that need only local state (`in-flight`, `recoverable`, `pr-open`, `pr-merged`, `lost`) still fire.
386
- - `--no-fetch` — skip the upfront `git fetch origin <branch>` before checking remote presence.
387
-
388
- The Workspace section appends an attach hint to the pane name when the workspace backend exposes one (e.g. `tmux attach -t <session>:<pane>` or `cmux attach <name>`), so the verdict line is immediately actionable. The hero above shows a passing pre-dispatch run; here's the same command on a ticket that's already past dispatch:
389
-
390
- ```text
391
- groundcrew doctor --ticket HRD-442 (Multi-event extractor: year inference can produce date_start > date_end)
392
- ────────────────────────────────────────────────────────────────────────────────────────────────────────────
393
-
394
- Resolution
395
- [ok] Ticket exists in Linear ("Multi-event extractor: year inference can produce date_start > date_end")
396
- [ok] Status is Todo
397
- (skipped — post-dispatch — pre-dispatch checks are irrelevant)
398
-
399
- Eligibility
400
- (skipped — post-dispatch — pre-dispatch checks are irrelevant)
401
-
402
- Run state
403
- [ok] Local run state (running)
404
- [ok] Recorded model (claude)
405
- [ok] Recorded worktree (/Users/paul/dev/groundcrew-workspaces/herds-social/herds-hrd-442)
406
- [ok] Recorded branch (paul-hrd-442)
407
- [ok] Resume count (0)
408
-
409
- Worktree
410
- [ok] Host worktree exists (/Users/paul/dev/groundcrew-workspaces/herds-social/herds-hrd-442)
411
- [--] Working tree clean (0 modified, 1 untracked)
412
- [ok] Branch checked out (paul-hrd-442)
413
-
414
- Workspace
415
- [ok] Workspace pane open (hrd-442 — attach: `tmux attach -t groundcrew:hrd-442`)
416
-
417
- Local branch
418
- [ok] Local branch exists (paul-hrd-442, 2 ahead / 0 behind origin/main)
419
-
420
- Remote branch
421
- [ok] Branch present on origin
422
-
423
- Pull request
424
- [ok] Open PR for this branch (#224 https://github.com/herds-social/herds/pull/224)
425
-
426
- → pr-open: https://github.com/herds-social/herds/pull/224 (#224)
427
- ```
428
-
429
- #### Recovering a stranded ticket
430
-
431
- The verdict on the last line maps to a recovery action:
432
-
433
- | Verdict | What to do |
434
- | ---------------- | --------------------------------------------------------------------------------------------- |
435
- | `pr-open` | Nothing — the PR is the source of truth. |
436
- | `pr-merged` | Done. |
437
- | `in-flight` | The ticket is still being worked on; the verdict line names the workspace pane to attach to. |
438
- | `recoverable` | Run the printed `nextStep` exactly. |
439
- | `interrupted` | Resume the preserved worktree with `crew resume <ticket>` or inspect it by hand. |
440
- | `failed-launch` | Fix the launch failure, then run `crew resume <ticket>` or `crew cleanup <ticket>`. |
441
- | `would-dispatch` | Pre-dispatch checks pass; the orchestrator will pick the ticket up on its next tick. |
442
- | `ineligible` | A resolution or eligibility check failed; the reason after the colon names the failing check. |
443
- | `unresolvable` | The Linear ticket couldn't be fetched; the reason after the colon names the error. |
444
- | `lost` | No trace exists. Re-dispatch via `crew run --ticket <ticket>`. |
445
-
446
- ### `crew interrupt <ticket>`
447
-
448
- Stop a live workspace pane while preserving the ticket worktree and branch. This is the manual pause button for cases where you need terminal capacity back, want to stop an agent that is going in the wrong direction, or need to inspect the diff before letting another agent continue.
449
-
450
- ```bash
451
- crew interrupt HRD-442 --reason "wrong implementation direction"
452
- crew doctor --ticket HRD-442
453
- crew resume HRD-442
454
- ```
455
-
456
- The command closes the cmux/tmux workspace when it exists, records local run state under the groundcrew state directory, and never tears down the worktree. If the workspace was already gone but the worktree is still present, interrupt records that fact so doctor can point at the preserved branch instead of reporting a mystery ticket.
457
-
458
- ### `crew resume <ticket>`
459
-
460
- Reopen an existing ticket worktree with a continuation prompt. Resume never creates a new worktree; if none exists, it fails and leaves re-dispatch to `crew run --ticket <ticket>`.
461
-
462
- The resume prompt tells the agent to inspect current 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 ticket context.
463
-
464
453
  ## Troubleshooting
465
454
 
466
455
  First stop for "labeled but not on the board": `crew doctor --ticket <ticket>` lists every check the dispatcher runs and flags the failing one.
467
456
 
468
- <details>
469
- <summary>Local execution picks one of safehouse / sdx / none</summary>
470
-
471
- `local.runner: "auto"` resolves to `safehouse` on macOS and `sdx` (Docker Sandboxes) on Linux/WSL. Override with `local.runner: "safehouse" | "sdx" | "none"`. There is no per-model `isolation` knob — the runner is global. `sdx` requires a per-model `sandbox: { agent }` block so groundcrew can map the model to an sbx agent.
472
-
473
- </details>
474
-
475
457
  <details>
476
458
  <summary>Safehouse-already-wrapped commands are not re-wrapped</summary>
477
459
 
@@ -479,13 +461,6 @@ If a `models.definitions.<name>.cmd` already starts with `safehouse`, groundcrew
479
461
 
480
462
  </details>
481
463
 
482
- <details>
483
- <summary>Sandbox lifecycle is create-only</summary>
484
-
485
- Groundcrew auto-creates the sandbox for an sbx agent (`groundcrew-<agent>`) when missing, but never deletes one — sandboxes persist across tickets and across `crew cleanup`. Auth state lives inside the sandbox, so deleting it forces a re-login. Inspect or remove them manually with `sbx ls` / `sbx rm`.
486
-
487
- </details>
488
-
489
464
  <details>
490
465
  <summary>Dead tmux windows vanish by default</summary>
491
466
 
@@ -493,31 +468,10 @@ When a wrapped agent command fails (e.g. `safehouse-clearance` not found, `npm i
493
468
 
494
469
  </details>
495
470
 
496
- <details>
497
- <summary>Status names matter</summary>
498
-
499
- If your team uses `Started` instead of `In Progress`, set `linear.projects[].statuses.inProgress = "Started"` on that project's entry. Status overrides are per-project so divergent team workflows coexist.
500
-
501
- </details>
502
-
503
- <details>
504
- <summary>Leaf-only</summary>
505
-
506
- Parent issues with children are ignored — sub-issues are the work items.
507
-
508
- </details>
509
-
510
471
  <details>
511
472
  <summary>Tickets stay in-progress until something else moves them</summary>
512
473
 
513
- Groundcrew sets a ticket to `inProgress` when it provisions a workspace and never advances it. The next transition (typically "in review" when a PR opens) is left to your Linear automation rules.
514
-
515
- </details>
516
-
517
- <details>
518
- <summary>Cross-team projects need a consistent `inProgress` name per project</summary>
519
-
520
- Cross-team projects work — the orchestrator caches the in-progress state ID per `(team, statusName)` pair — but every team in a given project must use the same status name for that project's `statuses.inProgress`. If you watch two projects that share a Linear team but configure different `inProgress` names, each project's lookup is independent.
474
+ Groundcrew sets a ticket to `Started` (the first workflow state with `type === "started"` on that team) when it provisions a workspace and never advances it. The next transition (typically "In Review" when a PR opens) is left to your Linear automation rules.
521
475
 
522
476
  </details>
523
477
 
@@ -538,7 +492,7 @@ Doctor reports the resolved local runner (safehouse / sdx / none) and whether it
538
492
  <details>
539
493
  <summary>Doctor checks every enabled model</summary>
540
494
 
541
- `models.definitions` includes both shipped defaults (`claude`, `codex`) by default via additive merge. If you only intend to label tickets `agent-claude` and don't have `codex` installed, set `models.definitions.codex: { disabled: true }` (see "Disabling a shipped default" above). Without that, doctor exits non-zero on a missing `codex` binary even though `crew run` would never route to it.
495
+ `models.definitions` includes both shipped defaults (`claude`, `codex`) by default via additive merge. If you only intend to label tickets `agent-claude` and don't have `codex` installed, set `models.definitions.codex: { disabled: true }`. Without that, doctor exits non-zero on a missing `codex` binary even though `crew run` would never route to it.
542
496
 
543
497
  </details>
544
498
 
@@ -577,7 +531,7 @@ node --run crew:op -- run --watch
577
531
 
578
532
  Both forms discover config via cosmiconfig — project-walk from cwd for `crew.config.ts` and friends, then `${XDG_CONFIG_HOME:-$HOME/.config}/groundcrew/crew.config.ts` (legacy `config.ts` is still accepted for one release). Set `GROUNDCREW_CONFIG` to point elsewhere. The `crew:op` wrapper additionally reads `${XDG_CONFIG_HOME:-$HOME/.config}/groundcrew/op.env` (1Password env-file with `op://` references resolved at launch).
579
533
 
580
- Logs land in `${XDG_STATE_HOME:-$HOME/.local/state}/groundcrew/groundcrew.log` by default (override via `logging.file`). The "Loaded config from …" line at startup tells you which config won.
534
+ Logs land in `${XDG_STATE_HOME:-$HOME/.local/state}/groundcrew/groundcrew.log` by default (override via `logging.file`).
581
535
 
582
536
  Source edits in `src/**` are picked up on the next invocation. Requires Node ≥ 24.3 (native `.ts` type stripping).
583
537