@clipboard-health/groundcrew 2.3.0 → 2.3.4
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/LICENSE +21 -0
- package/README.md +258 -86
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +22 -10
- package/dist/commands/dispatcher.d.ts.map +1 -1
- package/dist/commands/dispatcher.js +10 -35
- package/dist/commands/doctor.d.ts +4 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +21 -1
- package/dist/commands/eligibility.d.ts +14 -0
- package/dist/commands/eligibility.d.ts.map +1 -1
- package/dist/commands/eligibility.js +44 -0
- package/dist/commands/setupWorkspace.d.ts.map +1 -1
- package/dist/commands/setupWorkspace.js +3 -1
- package/dist/commands/ticketDoctor.d.ts +48 -0
- package/dist/commands/ticketDoctor.d.ts.map +1 -0
- package/dist/commands/ticketDoctor.js +402 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/lib/boardSource.d.ts +55 -0
- package/dist/lib/boardSource.d.ts.map +1 -1
- package/dist/lib/boardSource.js +171 -26
- package/dist/lib/dockerSandbox.d.ts +6 -7
- package/dist/lib/dockerSandbox.d.ts.map +1 -1
- package/dist/lib/dockerSandbox.js +6 -6
- package/dist/lib/workspaces.d.ts.map +1 -1
- package/dist/lib/workspaces.js +9 -4
- package/dist/lib/worktrees.d.ts.map +1 -1
- package/dist/lib/worktrees.js +34 -12
- package/package.json +1 -1
- package/static/groundcrew-wordmark-dark.svg +15 -0
- package/static/groundcrew-wordmark-light.svg +15 -0
- package/static/groundcrew.svg +0 -9
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Clipboard Health
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,9 +1,48 @@
|
|
|
1
|
-
<h1 align="center">groundcrew</h1>
|
|
2
1
|
<p align="center">
|
|
3
|
-
<
|
|
2
|
+
<picture>
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="./static/groundcrew-wordmark-dark.svg">
|
|
4
|
+
<img alt="groundcrew" src="./static/groundcrew-wordmark-light.svg" height="96">
|
|
5
|
+
</picture>
|
|
4
6
|
</p>
|
|
5
7
|
|
|
6
|
-
|
|
8
|
+
<p align="center">
|
|
9
|
+
Dispatch your Linear backlog to AI coding agents. One git worktree per ticket, sandboxed by default.
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
<p align="center">
|
|
13
|
+
<a href="https://www.npmjs.com/package/@clipboard-health/groundcrew"><img alt="npm" src="https://img.shields.io/npm/v/@clipboard-health/groundcrew?style=flat-square&label=npm&color=77d94e&labelColor=18181b"></a>
|
|
14
|
+
<a href="https://www.npmjs.com/package/@clipboard-health/groundcrew"><img alt="downloads" src="https://img.shields.io/npm/dw/@clipboard-health/groundcrew?style=flat-square&label=downloads&color=18181b&labelColor=18181b"></a>
|
|
15
|
+
<a href="https://github.com/ClipboardHealth/groundcrew/actions/workflows/ci.yml"><img alt="ci" src="https://img.shields.io/github/actions/workflow/status/ClipboardHealth/groundcrew/ci.yml?style=flat-square&label=ci&color=77d94e&labelColor=18181b"></a>
|
|
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
|
+
</p>
|
|
18
|
+
|
|
19
|
+
```text
|
|
20
|
+
$ crew doctor --ticket HRD-446
|
|
21
|
+
groundcrew doctor --ticket HRD-446 (Add retry logic to the sync job)
|
|
22
|
+
────────────────────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
Resolution
|
|
25
|
+
[ok] Ticket exists in Linear ("Add retry logic to the sync job")
|
|
26
|
+
[ok] Status is Todo
|
|
27
|
+
[ok] Has agent-* label (agent-claude)
|
|
28
|
+
[ok] Model resolves from agent-* label (model "claude")
|
|
29
|
+
[ok] Description mentions known repo (owner/repo)
|
|
30
|
+
[ok] Resolved repo is cloned locally (/dev/workspaces/owner/repo)
|
|
31
|
+
|
|
32
|
+
Eligibility
|
|
33
|
+
[ok] No active blockers
|
|
34
|
+
[ok] Model "claude" usage under sessionLimitPercentage (12% (limit 85%))
|
|
35
|
+
[ok] In-progress cap not hit (2/4 used)
|
|
36
|
+
|
|
37
|
+
→ would be dispatched on next tick
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Why
|
|
41
|
+
|
|
42
|
+
- **Linear-native.** Polls a project, respects `agent-*` labels, honors blockers.
|
|
43
|
+
- **One worktree per ticket.** Agents work in parallel without stepping on each other.
|
|
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 `config.ts`.
|
|
7
46
|
|
|
8
47
|
## Install
|
|
9
48
|
|
|
@@ -11,20 +50,17 @@ Watch a Linear project and farm out ready tickets to coding-agent CLIs running i
|
|
|
11
50
|
npm install -g @clipboard-health/groundcrew
|
|
12
51
|
```
|
|
13
52
|
|
|
14
|
-
|
|
53
|
+
Installs the `crew` binary. `@clipboard-health/clearance` is pulled in transitively and provides the `clearance` / `clearance-ensure` bins used by Safehouse runs.
|
|
15
54
|
|
|
16
55
|
## Quickstart
|
|
17
56
|
|
|
18
|
-
1. **Install prereqs.** Node 24, `git`, `cmux` _or_ `tmux`, and the agent CLIs themselves (`claude`, `codex`, `cursor-agent`,
|
|
19
|
-
- **`safehouse`** (macOS default) — [Safehouse](https://agent-safehouse.dev/) on `PATH`. The fastest local backend; cannot safely give the agent Docker.
|
|
20
|
-
- **`sdx`** (Linux default, macOS opt-in) — [Docker Sandboxes](https://docs.docker.com/sandboxes/) (`sbx`) on `PATH`. Required when a ticket needs the agent to use Docker (`docker build`, `docker run`, integration tests). Each model that should run under sdx needs a `sandbox: { agent: "<sbx-agent>" }` block in `config.ts` so groundcrew knows which sbx agent to address.
|
|
21
|
-
- **`none`** — explicit unsandboxed escape hatch. Never picked implicitly; `crew doctor` warns when it is configured.
|
|
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.
|
|
22
58
|
|
|
23
|
-
|
|
59
|
+
2. **Pick an isolation runner.** See [Runners](#runners) — `auto` resolves to `safehouse` on macOS and `sdx` on Linux/WSL.
|
|
24
60
|
|
|
25
|
-
|
|
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.
|
|
26
62
|
|
|
27
|
-
|
|
63
|
+
4. **Configure.** Copy the shipped example into XDG config and edit:
|
|
28
64
|
|
|
29
65
|
```bash
|
|
30
66
|
mkdir -p "${XDG_CONFIG_HOME:-$HOME/.config}/groundcrew"
|
|
@@ -33,73 +69,91 @@ This installs the `crew` binary. `@clipboard-health/clearance` is pulled in tran
|
|
|
33
69
|
$EDITOR "${XDG_CONFIG_HOME:-$HOME/.config}/groundcrew/config.ts"
|
|
34
70
|
```
|
|
35
71
|
|
|
36
|
-
|
|
72
|
+
Set `linear.projectSlug` (paste the trailing slug of your Linear project URL, e.g. `ai-strategy-5152195762f3`), `workspace.projectDir`, and `workspace.knownRepositories`. Defaults cover everything else.
|
|
37
73
|
|
|
38
|
-
|
|
74
|
+
Then clone each repo before the first `crew run` — groundcrew creates per-ticket worktrees from these clones, it does not auto-clone:
|
|
39
75
|
|
|
40
76
|
```bash
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
Or let `crew` clone every missing `owner/repo` entry for you using your `gh` login:
|
|
46
|
-
|
|
47
|
-
```bash
|
|
48
|
-
crew setup repos # clone all missing entries
|
|
49
|
-
crew setup repos --dry-run # preview what would be cloned
|
|
77
|
+
crew setup repos # clone all missing entries via gh
|
|
78
|
+
crew setup repos --dry-run # preview
|
|
50
79
|
crew setup repos owner/repo # restrict to one entry
|
|
51
80
|
```
|
|
52
81
|
|
|
53
|
-
`crew setup repos` is idempotent
|
|
54
|
-
|
|
55
|
-
`crew` resolves the config path as: `GROUNDCREW_CONFIG` if set → `${XDG_CONFIG_HOME:-$HOME/.config}/groundcrew/config.ts` if it exists → a `config.ts` sitting next to `crew`'s own source files (only useful from a local checkout; see [Hacking on groundcrew](#hacking-on-groundcrew)). Set `GROUNDCREW_CONFIG` only when you want to override the XDG location.
|
|
82
|
+
`crew setup repos` is idempotent; already-cloned repos report `[exists]`. Bare-name entries (no `owner/`) are skipped — clone them manually into `<projectDir>/<name>`.
|
|
56
83
|
|
|
57
|
-
|
|
84
|
+
5. **Export a Linear API key.** `crew` reads `GROUNDCREW_LINEAR_API_KEY` first, then falls back to `LINEAR_API_KEY`.
|
|
58
85
|
|
|
59
86
|
```bash
|
|
60
|
-
# Direct
|
|
61
87
|
export GROUNDCREW_LINEAR_API_KEY="lin_api_..."
|
|
62
|
-
|
|
88
|
+
```
|
|
63
89
|
|
|
64
|
-
|
|
90
|
+
<details>
|
|
91
|
+
<summary>Using 1Password (<code>op</code>) for the key</summary>
|
|
92
|
+
|
|
93
|
+
```bash
|
|
65
94
|
echo "GROUNDCREW_LINEAR_API_KEY='op://<vault>/LINEAR_API_KEY/credential'" > .env.1password
|
|
66
95
|
op run --env-file .env.1password -- crew doctor
|
|
67
96
|
```
|
|
68
97
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
5. **Prepare the runner and agent auth.** Groundcrew uses a `cmux` or `tmux` workspace hosting the resolved local backend (`safehouse`, `sdx`, or `none`) plus locally authenticated agent CLIs. Setup fails fast when the resolved backend's binary or platform requirement is missing — `safehouse` requires macOS + `safehouse` on PATH; `sdx` requires macOS or Linux + `sbx` on PATH. `models.isolation` and per-model `isolation` are legacy keys and fail config validation. Per-model `sandbox` blocks are accepted again and used by the `sdx` runner.
|
|
98
|
+
</details>
|
|
72
99
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
6. **Set the clearance allowlist for local Safehouse runs.** Only applies when `local.runner` resolves to `safehouse`. Groundcrew starts `clearance` from `@clipboard-health/clearance` on `http://127.0.0.1:19999` (skipping the launch if something is already listening) and runs the agent through the bundled `safehouse-clearance` wrapper. Clearance refuses to start without an allowlist — see [its README](https://github.com/ClipboardHealth/core-utils/tree/main/packages/clearance) for the proxy's env vars, log paths, and DNS rules. The shortest path is to set the env before `crew run`:
|
|
100
|
+
6. **Run.**
|
|
76
101
|
|
|
77
102
|
```bash
|
|
78
|
-
|
|
79
|
-
crew run --
|
|
103
|
+
crew doctor # check setup
|
|
104
|
+
crew run --dry-run # preview without provisioning
|
|
105
|
+
crew run --watch # poll forever
|
|
80
106
|
```
|
|
81
107
|
|
|
82
|
-
|
|
108
|
+
## Runners
|
|
83
109
|
|
|
84
|
-
|
|
85
|
-
CLEARANCE_ALLOW_HOSTS_FILES="$(npm root -g)/@clipboard-health/groundcrew/clearance-allow-hosts:$HOME/.config/clearance/personal-allow-hosts" \
|
|
86
|
-
crew run --watch
|
|
87
|
-
```
|
|
110
|
+
`local.runner` picks the local isolation backend. `auto` resolves per platform.
|
|
88
111
|
|
|
89
|
-
|
|
112
|
+
| Runner | Default on | Backend |
|
|
113
|
+
| ----------- | ----------- | -------------------------------------------------------------------------------------------------------- |
|
|
114
|
+
| `safehouse` | macOS | [Safehouse](https://agent-safehouse.dev/) — fastest local; cannot safely give the agent Docker. |
|
|
115
|
+
| `sdx` | Linux / WSL | [Docker Sandboxes](https://docs.docker.com/sandboxes/) (`sbx`) — required when the agent needs `docker`. |
|
|
116
|
+
| `none` | — | Unsandboxed escape hatch. Never picked implicitly; doctor warns when configured. |
|
|
90
117
|
|
|
91
|
-
|
|
118
|
+
For `sdx`: each model that runs under it needs a `sandbox: { agent: "<sbx-agent>" }` block in `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.
|
|
92
119
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
crew run --dry-run
|
|
96
|
-
crew run # one-shot
|
|
97
|
-
crew run --watch # poll forever
|
|
98
|
-
```
|
|
120
|
+
<details>
|
|
121
|
+
<summary>Safehouse clearance allowlist</summary>
|
|
99
122
|
|
|
100
|
-
|
|
123
|
+
Only applies when `local.runner` resolves to `safehouse`. Groundcrew starts `clearance` on `http://127.0.0.1:19999` and runs the agent through the bundled `safehouse-clearance` wrapper. Clearance refuses to start without an allowlist — see [its README](https://github.com/ClipboardHealth/core-utils/tree/main/packages/clearance) for proxy env vars, log paths, and DNS rules. Shortest path:
|
|
101
124
|
|
|
102
|
-
|
|
125
|
+
```bash
|
|
126
|
+
CLEARANCE_ALLOW_HOSTS="api.openai.com,auth.openai.com,api.anthropic.com,mcp.linear.app,api.linear.app" \
|
|
127
|
+
crew run --watch
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Groundcrew ships a starter file covering model APIs, Linear, Notion, Slack, Datadog, GitHub, npm, and common dev tooling at `$(npm root -g)/@clipboard-health/groundcrew/clearance-allow-hosts`. Point clearance at it (and optionally a personal file) via `CLEARANCE_ALLOW_HOSTS_FILES`:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
CLEARANCE_ALLOW_HOSTS_FILES="$(npm root -g)/@clipboard-health/groundcrew/clearance-allow-hosts:$HOME/.config/clearance/personal-allow-hosts" \
|
|
134
|
+
crew run --watch
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Watch `${XDG_CACHE_HOME:-$HOME/.cache}/clearance/clearance.log` for `DENY` lines and add only the domains your agents actually need.
|
|
138
|
+
|
|
139
|
+
</details>
|
|
140
|
+
|
|
141
|
+
## Configuration
|
|
142
|
+
|
|
143
|
+
Three keys are required; everything else has a default.
|
|
144
|
+
|
|
145
|
+
| Key | What |
|
|
146
|
+
| ----------------------------- | -------------------------------------------------------------------------- |
|
|
147
|
+
| `linear.projectSlug` | Trailing slug of the Linear project URL (e.g. `ai-strategy-5152195762f3`). |
|
|
148
|
+
| `workspace.projectDir` | Parent dir for cloned repos and sibling ticket worktrees. |
|
|
149
|
+
| `workspace.knownRepositories` | Repos searched for in ticket descriptions to infer where work belongs. |
|
|
150
|
+
|
|
151
|
+
`crew` resolves config as: `GROUNDCREW_CONFIG` if set → `${XDG_CONFIG_HOME:-$HOME/.config}/groundcrew/config.ts` if it exists → a `config.ts` next to `crew`'s own source (only useful from a local checkout). The branch prefix (`<prefix>-<TICKET>`) is derived from `os.userInfo().username` — not configurable.
|
|
152
|
+
|
|
153
|
+
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.
|
|
154
|
+
|
|
155
|
+
<details>
|
|
156
|
+
<summary>Full reference table</summary>
|
|
103
157
|
|
|
104
158
|
| Key | Default | What it does |
|
|
105
159
|
| --------------------------------------- | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
@@ -121,17 +175,18 @@ Required fields are marked **required**; everything else has a default and can b
|
|
|
121
175
|
| `models.definitions.<name>.color` | — | Color for the workspace status pill (cmux only; tmux silently drops it). |
|
|
122
176
|
| `models.definitions.<name>.usage` | optional | If set, codexbar usage is fetched for this model and gated by `sessionLimitPercentage`. Omit to never gate. When `usage.codexbar.source` is omitted, groundcrew uses `auto` on macOS and `cli` elsewhere. |
|
|
123
177
|
| `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). |
|
|
124
|
-
| `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.
|
|
178
|
+
| `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. |
|
|
125
179
|
| `prompts.initial` | (template) | First message sent to the agent. Placeholders: `{{ticket}}`, `{{worktree}}`, `{{title}}`, `{{description}}`. |
|
|
126
|
-
| `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.
|
|
127
|
-
| `local.runner` | `"auto"` | Local isolation backend. `"auto"`
|
|
128
|
-
| `logging.file` | XDG state path | Append-mode log file
|
|
180
|
+
| `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. |
|
|
181
|
+
| `local.runner` | `"auto"` | Local isolation backend. `"auto"` → `safehouse` on macOS, `sdx` on Linux/WSL. Explicit: `"safehouse"`, `"sdx"`, `"none"`. `"none"` is never picked implicitly. |
|
|
182
|
+
| `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
183
|
|
|
130
|
-
|
|
184
|
+
</details>
|
|
131
185
|
|
|
132
|
-
|
|
186
|
+
<details>
|
|
187
|
+
<summary>Disabling a shipped default</summary>
|
|
133
188
|
|
|
134
|
-
Groundcrew ships `claude` and `codex` as default model definitions, additively merged into every resolved config.
|
|
189
|
+
Groundcrew ships `claude` and `codex` as default model definitions, additively merged into every resolved config. To stop probing one:
|
|
135
190
|
|
|
136
191
|
```ts
|
|
137
192
|
// config.ts
|
|
@@ -150,45 +205,158 @@ Effects:
|
|
|
150
205
|
|
|
151
206
|
- `crew doctor` does not probe the disabled model's CLI. `crew doctor || exit 1` becomes viable as a CI gate when you only have one agent installed.
|
|
152
207
|
- `agent-any` only resolves to enabled models.
|
|
153
|
-
- An `agent-<disabled>` label on a ticket
|
|
208
|
+
- An `agent-<disabled>` label on a ticket falls back to `models.default` with a warning in the log.
|
|
154
209
|
|
|
155
210
|
Rules:
|
|
156
211
|
|
|
157
|
-
- `disabled` only accepts shipped-default keys (`claude`, `codex`). A typo
|
|
212
|
+
- `disabled` only accepts shipped-default keys (`claude`, `codex`). A typo fails loudly at config load.
|
|
158
213
|
- `disabled` must be exactly the boolean `true`.
|
|
159
|
-
- It cannot be combined with `cmd`, `color`, or `usage` in the same entry
|
|
214
|
+
- It cannot be combined with `cmd`, `color`, or `usage` in the same entry.
|
|
160
215
|
- `models.default` must point at an enabled model.
|
|
161
216
|
|
|
162
|
-
|
|
217
|
+
</details>
|
|
218
|
+
|
|
219
|
+
## Commands
|
|
163
220
|
|
|
164
221
|
```bash
|
|
165
|
-
crew
|
|
222
|
+
crew doctor # full setup check
|
|
223
|
+
crew doctor --ticket <TICKET> # diagnose one ticket
|
|
224
|
+
crew run # one-shot dispatch
|
|
225
|
+
crew run --watch # poll forever
|
|
226
|
+
crew run --ticket <TICKET> # provision one ticket and exit
|
|
166
227
|
crew setup repos [--dry-run] [<repo>...]
|
|
167
|
-
crew cleanup <TICKET>
|
|
228
|
+
crew cleanup <TICKET> # tear down every worktree carrying this ticket
|
|
168
229
|
```
|
|
169
230
|
|
|
170
|
-
|
|
231
|
+
`--watch` and `--ticket` are mutually exclusive. To inspect codexbar session windows directly, run `codexbar usage`.
|
|
232
|
+
|
|
233
|
+
### `crew doctor --ticket <ticket>`
|
|
234
|
+
|
|
235
|
+
Diagnose why a ticket would or wouldn't be dispatched on the next tick. Runs the same resolution and eligibility chain as the dispatcher. Exits 0 if the ticket would dispatch, 1 otherwise. The hero above shows a passing run; here's a failing one:
|
|
236
|
+
|
|
237
|
+
```text
|
|
238
|
+
groundcrew doctor --ticket HRD-447 (Refactor auth middleware)
|
|
239
|
+
─────────────────────────────────────────────────────────────
|
|
240
|
+
|
|
241
|
+
Resolution
|
|
242
|
+
[ok] Ticket exists in Linear ("Refactor auth middleware")
|
|
243
|
+
[--] Status is Todo (current: In Progress)
|
|
244
|
+
[ok] Has agent-* label (agent-claude)
|
|
245
|
+
[ok] Model resolves from agent-* label (model "claude")
|
|
246
|
+
[ok] Description mentions known repo (owner/repo)
|
|
247
|
+
[ok] Resolved repo is cloned locally (/dev/workspaces/owner/repo)
|
|
248
|
+
|
|
249
|
+
Eligibility
|
|
250
|
+
(skipped — resolution checks failed)
|
|
251
|
+
|
|
252
|
+
→ ineligible: status is In Progress (need Todo)
|
|
253
|
+
```
|
|
171
254
|
|
|
172
|
-
##
|
|
255
|
+
## Troubleshooting
|
|
173
256
|
|
|
174
|
-
|
|
175
|
-
- **Safehouse-already-wrapped commands are not re-wrapped.** If a `models.definitions.<name>.cmd` already starts with `safehouse`, groundcrew assumes that command owns its Safehouse flags and does not add the `safehouse-clearance` wrapper a second time. Changing the proxy's allowlist after it's running requires killing the PID in `${XDG_CACHE_HOME:-$HOME/.cache}/clearance/clearance.pid` so the next launch picks up the new env.
|
|
176
|
-
- **Sandbox lifecycle is create-only.** Groundcrew auto-creates the sandbox for a `<repository, model>` pair 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`.
|
|
177
|
-
- **Dead tmux windows vanish by default.** When a wrapped agent command fails (e.g. `safehouse-clearance` not found, `npm install` crash), the tmux window closes immediately and the error scrolls into the void. Set `GROUNDCREW_KEEP_DEAD_WINDOWS=1` (any non-empty value works) in the env you launch `crew` from to flip the per-window `remain-on-exit` to `on`; the window stays open with the error visible. Close it manually with `tmux kill-window -t groundcrew:<ticket>` after diagnosis. tmux backend only.
|
|
178
|
-
- **Status names matter.** If your team uses `Started` instead of `In Progress`, set `linear.statuses.inProgress = "Started"`.
|
|
179
|
-
- **Leaf-only.** Parent issues with children are ignored — sub-issues are the work items.
|
|
180
|
-
- **Tickets stay in the in-progress status until something else moves them.** 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 team's Linear automation rules.
|
|
181
|
-
- **Project must be on a single Linear team in practice.** Cross-team projects work — the orchestrator caches the in-progress state ID per team — but every team in the project must use the same status name for `linear.statuses.inProgress`.
|
|
182
|
-
- **Claude launches in bypass-permissions mode by default.** Groundcrew creates isolated per-ticket worktrees for unattended runs, so the shipped `claude` command is `claude --permission-mode bypassPermissions` to avoid workspace-trust and tool-permission prompts blocking automation. Override `models.definitions.claude.cmd` if you want a stricter mode.
|
|
183
|
-
- **Doctor's command introspection is shallow.** Doctor reports the resolved local runner (safehouse / sdx / none) and whether its prerequisites are met, then tokenizes model `cmd` and checks the first two non-flag tokens against PATH (so `safehouse claude --foo` checks both `safehouse` and `claude`). Boolean flags without values, env-var assignments (`FOO=1`), shell pipelines, and subshells are not parsed — verify those manually. In particular, `npx -y claude` and `env FOO=1 claude` only check the wrapper, not the wrapped CLI. When `local.runner` is `"none"`, doctor surfaces a single WARNING line so the unsandboxed launch is visible at a glance.
|
|
184
|
-
- **Doctor checks every enabled model, including shipped defaults you didn't disable.** `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" under "Config reference"). Without that, doctor exits non-zero on a missing `codex` binary even though `crew run` would never route to it.
|
|
185
|
-
- **Switch to tmux if cmux is misbehaving.** Set `workspaceKind: "tmux"` to force the tmux backend when cmux's CLI/socket bridge is flaky (symptoms: `cmux --json list-workspaces` returning `Failed to write to socket (Broken pipe)` or `Socket not found at ...cmux.sock` on every tick). tmux is more reliable — just a unix socket, no GUI app — at the cost of losing cmux's status pills, notifications, and vertical-tab sidebar.
|
|
186
|
-
- **Agent CLI must accept a positional prompt.** The handoff is `<your cmd> "<prompt>"`. `claude`, `codex`, and `cursor-agent` all support this.
|
|
187
|
-
- **`crew setup repos` only auto-clones `owner/repo` entries.** Bare-name entries in `workspace.knownRepositories` (e.g. `"api"` rather than `"clipboardhealth/api"`) are skipped with a hint to clone manually — the command refuses to guess the owner. After a partial setup, the exit code is non-zero so CI gates notice; rerun is idempotent once you clone the bare ones into `<projectDir>/<name>` yourself. Adding a new repo to `knownRepositories` later? Just rerun `crew setup repos`; already-present entries report `[exists]` and are untouched.
|
|
257
|
+
First stop for "labeled but not on the board": `crew doctor --ticket <ticket>` lists every check the dispatcher runs and flags the failing one.
|
|
188
258
|
|
|
189
|
-
|
|
259
|
+
<details>
|
|
260
|
+
<summary>Local execution picks one of safehouse / sdx / none</summary>
|
|
190
261
|
|
|
191
|
-
|
|
262
|
+
`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.
|
|
263
|
+
|
|
264
|
+
</details>
|
|
265
|
+
|
|
266
|
+
<details>
|
|
267
|
+
<summary>Safehouse-already-wrapped commands are not re-wrapped</summary>
|
|
268
|
+
|
|
269
|
+
If a `models.definitions.<name>.cmd` already starts with `safehouse`, groundcrew assumes that command owns its Safehouse flags and does not add the `safehouse-clearance` wrapper a second time. Changing the proxy's allowlist after it's running requires killing the PID in `${XDG_CACHE_HOME:-$HOME/.cache}/clearance/clearance.pid` so the next launch picks up the new env.
|
|
270
|
+
|
|
271
|
+
</details>
|
|
272
|
+
|
|
273
|
+
<details>
|
|
274
|
+
<summary>Sandbox lifecycle is create-only</summary>
|
|
275
|
+
|
|
276
|
+
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`.
|
|
277
|
+
|
|
278
|
+
</details>
|
|
279
|
+
|
|
280
|
+
<details>
|
|
281
|
+
<summary>Dead tmux windows vanish by default</summary>
|
|
282
|
+
|
|
283
|
+
When a wrapped agent command fails (e.g. `safehouse-clearance` not found, `npm install` crash), the tmux window closes immediately and the error scrolls into the void. Set `GROUNDCREW_KEEP_DEAD_WINDOWS=1` in the env you launch `crew` from to flip the per-window `remain-on-exit` to `on`; the window stays open with the error visible. Close it manually with `tmux kill-window -t groundcrew:<ticket>` after diagnosis. tmux backend only.
|
|
284
|
+
|
|
285
|
+
</details>
|
|
286
|
+
|
|
287
|
+
<details>
|
|
288
|
+
<summary>Status names matter</summary>
|
|
289
|
+
|
|
290
|
+
If your team uses `Started` instead of `In Progress`, set `linear.statuses.inProgress = "Started"`.
|
|
291
|
+
|
|
292
|
+
</details>
|
|
293
|
+
|
|
294
|
+
<details>
|
|
295
|
+
<summary>Leaf-only</summary>
|
|
296
|
+
|
|
297
|
+
Parent issues with children are ignored — sub-issues are the work items.
|
|
298
|
+
|
|
299
|
+
</details>
|
|
300
|
+
|
|
301
|
+
<details>
|
|
302
|
+
<summary>Tickets stay in-progress until something else moves them</summary>
|
|
303
|
+
|
|
304
|
+
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.
|
|
305
|
+
|
|
306
|
+
</details>
|
|
307
|
+
|
|
308
|
+
<details>
|
|
309
|
+
<summary>Project must be on a single Linear team in practice</summary>
|
|
310
|
+
|
|
311
|
+
Cross-team projects work — the orchestrator caches the in-progress state ID per team — but every team in the project must use the same status name for `linear.statuses.inProgress`.
|
|
312
|
+
|
|
313
|
+
</details>
|
|
314
|
+
|
|
315
|
+
<details>
|
|
316
|
+
<summary>Claude launches in bypass-permissions mode by default</summary>
|
|
317
|
+
|
|
318
|
+
Groundcrew creates isolated per-ticket worktrees for unattended runs, so the shipped `claude` command is `claude --permission-mode bypassPermissions` to avoid workspace-trust and tool-permission prompts blocking automation. Override `models.definitions.claude.cmd` for a stricter mode.
|
|
319
|
+
|
|
320
|
+
</details>
|
|
321
|
+
|
|
322
|
+
<details>
|
|
323
|
+
<summary>Doctor's command introspection is shallow</summary>
|
|
324
|
+
|
|
325
|
+
Doctor reports the resolved local runner (safehouse / sdx / none) and whether its prerequisites are met, then tokenizes model `cmd` and checks the first two non-flag tokens against PATH. Boolean flags without values, env-var assignments (`FOO=1`), shell pipelines, and subshells are not parsed — verify those manually. When `local.runner` is `"none"`, doctor surfaces a single WARNING line.
|
|
326
|
+
|
|
327
|
+
</details>
|
|
328
|
+
|
|
329
|
+
<details>
|
|
330
|
+
<summary>Doctor checks every enabled model</summary>
|
|
331
|
+
|
|
332
|
+
`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.
|
|
333
|
+
|
|
334
|
+
</details>
|
|
335
|
+
|
|
336
|
+
<details>
|
|
337
|
+
<summary>Switch to tmux if cmux is misbehaving</summary>
|
|
338
|
+
|
|
339
|
+
Set `workspaceKind: "tmux"` to force the tmux backend when cmux's CLI/socket bridge is flaky (symptoms: `cmux --json list-workspaces` returning `Failed to write to socket (Broken pipe)` or `Socket not found at ...cmux.sock` on every tick). tmux is more reliable — just a unix socket, no GUI app — at the cost of losing cmux's status pills, notifications, and sidebar.
|
|
340
|
+
|
|
341
|
+
</details>
|
|
342
|
+
|
|
343
|
+
<details>
|
|
344
|
+
<summary>Agent CLI must accept a positional prompt</summary>
|
|
345
|
+
|
|
346
|
+
The handoff is `<your cmd> "<prompt>"`. `claude`, `codex`, and `cursor-agent` all support this.
|
|
347
|
+
|
|
348
|
+
</details>
|
|
349
|
+
|
|
350
|
+
<details>
|
|
351
|
+
<summary><code>crew setup repos</code> only auto-clones <code>owner/repo</code> entries</summary>
|
|
352
|
+
|
|
353
|
+
Bare-name entries in `workspace.knownRepositories` (e.g. `"api"` rather than `"clipboardhealth/api"`) are skipped with a hint to clone manually — the command refuses to guess the owner. After a partial setup, the exit code is non-zero so CI gates notice; rerun is idempotent once you clone the bare ones into `<projectDir>/<name>` yourself.
|
|
354
|
+
|
|
355
|
+
</details>
|
|
356
|
+
|
|
357
|
+
## Development
|
|
358
|
+
|
|
359
|
+
Clone the repo and the `crew` / `crew:op` scripts execute straight from TypeScript source — no build step needed.
|
|
192
360
|
|
|
193
361
|
```bash
|
|
194
362
|
cd ~/dev/c/groundcrew
|
|
@@ -198,8 +366,12 @@ node --run crew -- doctor
|
|
|
198
366
|
node --run crew:op -- run --watch
|
|
199
367
|
```
|
|
200
368
|
|
|
201
|
-
Both forms read `${XDG_CONFIG_HOME:-$HOME/.config}/groundcrew/config.ts` by default; 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)
|
|
369
|
+
Both forms read `${XDG_CONFIG_HOME:-$HOME/.config}/groundcrew/config.ts` by default; 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).
|
|
370
|
+
|
|
371
|
+
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.
|
|
372
|
+
|
|
373
|
+
Source edits in `src/**` are picked up on the next invocation. Requires Node ≥ 24.3 (native `.ts` type stripping).
|
|
202
374
|
|
|
203
|
-
|
|
375
|
+
## License
|
|
204
376
|
|
|
205
|
-
|
|
377
|
+
[MIT](./LICENSE)
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAyIA,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA8BvD"}
|
package/dist/cli.js
CHANGED
|
@@ -51,6 +51,24 @@ async function runCli(argv) {
|
|
|
51
51
|
}
|
|
52
52
|
await setupWorkspaceCli(ticket, { dryRun });
|
|
53
53
|
}
|
|
54
|
+
async function doctorCli(argv) {
|
|
55
|
+
let ticket;
|
|
56
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
57
|
+
const argument = argv[index];
|
|
58
|
+
if (argument === "--ticket") {
|
|
59
|
+
const value = argv[index + 1];
|
|
60
|
+
if (value === undefined || value.length === 0 || value.startsWith("-")) {
|
|
61
|
+
throw new Error("crew doctor --ticket: ticket id is required");
|
|
62
|
+
}
|
|
63
|
+
ticket = value;
|
|
64
|
+
index += 1;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
throw new Error(`crew doctor: unknown argument: ${argument}`);
|
|
68
|
+
}
|
|
69
|
+
const ok = ticket === undefined ? await doctor() : await doctor({ ticket });
|
|
70
|
+
process.exitCode = ok ? process.exitCode : 1;
|
|
71
|
+
}
|
|
54
72
|
const SUBCOMMANDS = {
|
|
55
73
|
run: {
|
|
56
74
|
summary: "Run the orchestrator (one-shot by default), or provision one ticket with --ticket",
|
|
@@ -58,13 +76,9 @@ const SUBCOMMANDS = {
|
|
|
58
76
|
invoke: runCli,
|
|
59
77
|
},
|
|
60
78
|
doctor: {
|
|
61
|
-
summary: "Verify prereqs
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
if (!ok) {
|
|
65
|
-
process.exitCode = 1;
|
|
66
|
-
}
|
|
67
|
-
},
|
|
79
|
+
summary: "Verify prereqs, or diagnose one ticket with --ticket",
|
|
80
|
+
usage: "[--ticket <ticket>]",
|
|
81
|
+
invoke: doctorCli,
|
|
68
82
|
},
|
|
69
83
|
cleanup: {
|
|
70
84
|
summary: "Tear down a worktree",
|
|
@@ -87,9 +101,7 @@ function printHelp() {
|
|
|
87
101
|
writeOutput("Commands:");
|
|
88
102
|
for (const [name, command] of Object.entries(SUBCOMMANDS)) {
|
|
89
103
|
writeOutput(` ${name.padEnd(width)} ${command.summary}`);
|
|
90
|
-
|
|
91
|
-
writeOutput(` ${" ".repeat(width)} → crew ${name} ${command.usage}`);
|
|
92
|
-
}
|
|
104
|
+
writeOutput(` ${" ".repeat(width)} → crew ${name} ${command.usage}`);
|
|
93
105
|
}
|
|
94
106
|
writeOutput("\nSee README.md for full configuration and behavior.");
|
|
95
107
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dispatcher.d.ts","sourceRoot":"","sources":["../../src/commands/dispatcher.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,EAAE,KAAK,UAAU,EAA2C,MAAM,uBAAuB,CAAC;AACjG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAGpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"dispatcher.d.ts","sourceRoot":"","sources":["../../src/commands/dispatcher.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,EAAE,KAAK,UAAU,EAA2C,MAAM,uBAAuB,CAAC;AACjG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD,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,MAAM,EAAE,YAAY,CAAC;CACtB;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;KACtB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnB;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,cAAc,GAAG,UAAU,CAuLjE"}
|
|
@@ -10,42 +10,16 @@ import { isGroundcrewIssue } from "../lib/boardSource.js";
|
|
|
10
10
|
import { createLinearIssueStatusUpdater } from "../lib/linearIssueStatus.js";
|
|
11
11
|
import { errorMessage, log, logEvent } from "../lib/util.js";
|
|
12
12
|
import { workspaces } from "../lib/workspaces.js";
|
|
13
|
-
import { classifyBlockers, classifyEligibility, } from "./eligibility.js";
|
|
13
|
+
import { classifyBlockers, classifyEligibility, classifyUsageExhaustion, } from "./eligibility.js";
|
|
14
14
|
import { setupWorkspace } from "./setupWorkspace.js";
|
|
15
|
-
const PERCENT_FRACTION_DIVISOR = 100;
|
|
16
|
-
const DAYS_PER_WEEK = 7;
|
|
17
|
-
const MINUTES_PER_DAY = 24 * 60;
|
|
18
|
-
const MINUTES_PER_WEEK = DAYS_PER_WEEK * MINUTES_PER_DAY;
|
|
19
15
|
export function createDispatcher(deps) {
|
|
20
16
|
const { config, client } = deps;
|
|
21
17
|
const issueStatusUpdater = createLinearIssueStatusUpdater({ config, client });
|
|
22
18
|
function buildExhaustedSet(usage) {
|
|
23
19
|
const exhausted = new Set();
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
exhausted.add(model);
|
|
28
|
-
const pct = (snapshot.session * PERCENT_FRACTION_DIVISOR).toFixed(0);
|
|
29
|
-
const mins = snapshot.sessionEndDuration ?? "?";
|
|
30
|
-
log(`${model} session at ${pct}% (> ${sessionLimit}%), resets in ${mins}m — skipping its tickets`);
|
|
31
|
-
}
|
|
32
|
-
// Weekly gate paces total weekly usage against day buckets from the
|
|
33
|
-
// weekly reset. Day 1's budget is available immediately after rollover,
|
|
34
|
-
// then each later day opens another 1/7 of the weekly budget. Skipped when:
|
|
35
|
-
// - weekly is null (no codexbar secondary window this tick)
|
|
36
|
-
// - weekly is non-finite (EXHAUSTED_USAGE — session gate above
|
|
37
|
-
// already pins it to Infinity)
|
|
38
|
-
// - weekEndDuration is null (can't compute where we are in week)
|
|
39
|
-
if (snapshot.weekly !== null &&
|
|
40
|
-
Number.isFinite(snapshot.weekly) &&
|
|
41
|
-
snapshot.weekEndDuration !== null) {
|
|
42
|
-
const usedPct = snapshot.weekly * PERCENT_FRACTION_DIVISOR;
|
|
43
|
-
const allowedPct = weeklyPacedBudgetPercentage(snapshot.weekEndDuration);
|
|
44
|
-
if (usedPct > allowedPct) {
|
|
45
|
-
exhausted.add(model);
|
|
46
|
-
log(`${model} weekly at ${usedPct.toFixed(1)}% (> ${allowedPct.toFixed(1)}% paced budget), resets in ${snapshot.weekEndDuration}m — skipping its tickets`);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
20
|
+
for (const exhaustion of classifyUsageExhaustion(config, usage)) {
|
|
21
|
+
exhausted.add(exhaustion.model);
|
|
22
|
+
log(formatUsageExhaustion(exhaustion));
|
|
49
23
|
}
|
|
50
24
|
return exhausted;
|
|
51
25
|
}
|
|
@@ -187,9 +161,10 @@ export function createDispatcher(deps) {
|
|
|
187
161
|
}
|
|
188
162
|
return { runOnce };
|
|
189
163
|
}
|
|
190
|
-
function
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
164
|
+
function formatUsageExhaustion(exhaustion) {
|
|
165
|
+
if (exhaustion.kind === "session") {
|
|
166
|
+
const mins = exhaustion.resetMinutes ?? "?";
|
|
167
|
+
return `${exhaustion.model} session at ${exhaustion.usedPercentage.toFixed(0)}% (> ${exhaustion.limitPercentage}%), resets in ${mins}m — skipping its tickets`;
|
|
168
|
+
}
|
|
169
|
+
return `${exhaustion.model} weekly at ${exhaustion.usedPercentage.toFixed(1)}% (> ${exhaustion.allowedPercentage.toFixed(1)}% paced budget), resets in ${exhaustion.resetMinutes}m — skipping its tickets`;
|
|
195
170
|
}
|
|
@@ -2,5 +2,8 @@
|
|
|
2
2
|
* doctor — verify groundcrew prerequisites against the resolved config.
|
|
3
3
|
* Returns true if every required check passes; false otherwise.
|
|
4
4
|
*/
|
|
5
|
-
export
|
|
5
|
+
export interface DoctorOptions {
|
|
6
|
+
ticket?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function doctor(options?: DoctorOptions): Promise<boolean>;
|
|
6
9
|
//# sourceMappingURL=doctor.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AA2BH,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAsHD,wBAAsB,MAAM,CAAC,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,OAAO,CAAC,CAK1E"}
|