@clipboard-health/groundcrew 3.1.0 → 3.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -35
- package/clearance-allow-hosts +2 -1
- package/{configExample.ts → crew.config.example.ts} +3 -3
- package/dist/lib/config.d.ts +9 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +100 -27
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -42,7 +42,7 @@ Eligibility
|
|
|
42
42
|
- **Linear-native.** Polls a project, respects `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 `config.ts`.
|
|
45
|
+
- **Multi-agent.** Ships with `claude` and `codex`; bring your own CLI by dropping a definition into `crew.config.ts`.
|
|
46
46
|
|
|
47
47
|
## Install
|
|
48
48
|
|
|
@@ -64,11 +64,13 @@ Installs the `crew` binary. `@clipboard-health/clearance` is pulled in transitiv
|
|
|
64
64
|
|
|
65
65
|
```bash
|
|
66
66
|
mkdir -p "${XDG_CONFIG_HOME:-$HOME/.config}/groundcrew"
|
|
67
|
-
cp "$(npm root -g)/@clipboard-health/groundcrew/
|
|
68
|
-
"${XDG_CONFIG_HOME:-$HOME/.config}/groundcrew/config.ts"
|
|
69
|
-
$EDITOR "${XDG_CONFIG_HOME:-$HOME/.config}/groundcrew/config.ts"
|
|
67
|
+
cp "$(npm root -g)/@clipboard-health/groundcrew/crew.config.example.ts" \
|
|
68
|
+
"${XDG_CONFIG_HOME:-$HOME/.config}/groundcrew/crew.config.ts"
|
|
69
|
+
$EDITOR "${XDG_CONFIG_HOME:-$HOME/.config}/groundcrew/crew.config.ts"
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
+
Or drop `crew.config.ts` at the root of any repo you run `crew` from — `crew` discovers it via cosmiconfig project-walk. Any of `crew.config.{ts,mjs,js,json}`, `.crewrc{,.json,.ts}`, `.config/crew.config.{ts,json}`, or `.config/crewrc{,.json}` work.
|
|
73
|
+
|
|
72
74
|
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.
|
|
73
75
|
|
|
74
76
|
Then clone each repo before the first `crew run` — groundcrew creates per-ticket worktrees from these clones, it does not auto-clone:
|
|
@@ -134,7 +136,7 @@ Net effect: by the time the agent process exists, the values are gone from the e
|
|
|
134
136
|
| `sdx` | Linux / WSL | [Docker Sandboxes](https://docs.docker.com/sandboxes/) (`sbx`) — required when the agent needs `docker`. |
|
|
135
137
|
| `none` | — | Unsandboxed escape hatch. Never picked implicitly; doctor warns when configured. |
|
|
136
138
|
|
|
137
|
-
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.
|
|
139
|
+
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.
|
|
138
140
|
|
|
139
141
|
<details>
|
|
140
142
|
<summary>Safehouse clearance allowlist</summary>
|
|
@@ -167,7 +169,7 @@ Three keys are required; everything else has a default.
|
|
|
167
169
|
| `workspace.projectDir` | Parent dir for cloned repos and sibling ticket worktrees. |
|
|
168
170
|
| `workspace.knownRepositories` | Repos searched for in ticket descriptions to infer where work belongs. |
|
|
169
171
|
|
|
170
|
-
`crew` resolves config as: `GROUNDCREW_CONFIG` if set → `${XDG_CONFIG_HOME:-$HOME/.config}/groundcrew/config.ts`
|
|
172
|
+
`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.
|
|
171
173
|
|
|
172
174
|
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.
|
|
173
175
|
|
|
@@ -180,7 +182,7 @@ For a personal workflow, keep the prompt next to your local config and load it w
|
|
|
180
182
|
```ts
|
|
181
183
|
import { readFileSync } from "node:fs";
|
|
182
184
|
|
|
183
|
-
export
|
|
185
|
+
export default {
|
|
184
186
|
// ...
|
|
185
187
|
prompts: {
|
|
186
188
|
initial: readFileSync(new URL("./initial-prompt.md", import.meta.url), "utf8"),
|
|
@@ -193,31 +195,31 @@ This keeps package defaults portable while letting your private config reference
|
|
|
193
195
|
<details>
|
|
194
196
|
<summary>Full reference table</summary>
|
|
195
197
|
|
|
196
|
-
| Key | Default | What it does
|
|
197
|
-
| --------------------------------------- | ------------------- |
|
|
198
|
-
| `linear.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 `config.ts` self-documenting and the lookup survives project renames.
|
|
199
|
-
| `linear.statuses.todo` | `"Todo"` | Status name picked up for new work.
|
|
200
|
-
| `linear.statuses.inProgress` | `"In Progress"` | Status set after a workspace is provisioned; counts toward `maximumInProgress`.
|
|
201
|
-
| `linear.statuses.done` | `"Done"` | Status that triggers worktree cleanup.
|
|
202
|
-
| `linear.statuses.terminal` | `["Done"]` | Additional status names treated as terminal for cleanup, board remaining counts, and blocker checks. The `done` status is always included.
|
|
203
|
-
| `git.remote` | `"origin"` | Remote used for `fetch` and as the worktree base ref.
|
|
204
|
-
| `git.defaultBranch` | `"main"` | Branch fetched from `git.remote` and used as the worktree base.
|
|
205
|
-
| `workspace.projectDir` | **required** | Parent dir for cloned repos and sibling ticket worktrees.
|
|
206
|
-
| `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.
|
|
207
|
-
| `orchestrator.maximumInProgress` | `4` | Cap on tickets in `linear.statuses.inProgress` at once.
|
|
208
|
-
| `orchestrator.pollIntervalMilliseconds` | `120_000` | Poll interval in `--watch` mode.
|
|
209
|
-
| `orchestrator.sessionLimitPercentage` | `85` | Number in `(0, 100]`. A model whose codexbar session window exceeds this percentage is skipped that tick.
|
|
210
|
-
| `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`.
|
|
211
|
-
| `models.definitions` | `{ claude, codex }` | Agent definitions. Additive merge with shipped defaults.
|
|
212
|
-
| `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.
|
|
213
|
-
| `models.definitions.<name>.color` | — | Color for the workspace status pill (cmux only; tmux silently drops it).
|
|
214
|
-
| `models.definitions.<name>.usage` | optional | If set, codexbar usage is fetched for this model and gated by `sessionLimitPercentage`.
|
|
215
|
-
| `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).
|
|
216
|
-
| `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.
|
|
217
|
-
| `prompts.initial` | unattended template | First message sent to the agent. Placeholders: `{{ticket}}`, `{{worktree}}`, `{{title}}`, `{{description}}`. Override this from `config.ts` for team-specific statuses, tools, plugins, or review loops.
|
|
218
|
-
| `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.
|
|
219
|
-
| `local.runner` | `"auto"` | Local isolation backend. `"auto"` → `safehouse` on macOS, `sdx` on Linux/WSL. Explicit: `"safehouse"`, `"sdx"`, `"none"`. `"none"` is never picked implicitly.
|
|
220
|
-
| `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`.
|
|
198
|
+
| Key | Default | What it does |
|
|
199
|
+
| --------------------------------------- | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
200
|
+
| `linear.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. |
|
|
201
|
+
| `linear.statuses.todo` | `"Todo"` | Status name picked up for new work. |
|
|
202
|
+
| `linear.statuses.inProgress` | `"In Progress"` | Status set after a workspace is provisioned; counts toward `maximumInProgress`. |
|
|
203
|
+
| `linear.statuses.done` | `"Done"` | Status that triggers worktree cleanup. |
|
|
204
|
+
| `linear.statuses.terminal` | `["Done"]` | Additional status names treated as terminal for cleanup, board remaining counts, and blocker checks. The `done` status is always included. |
|
|
205
|
+
| `git.remote` | `"origin"` | Remote used for `fetch` and as the worktree base ref. |
|
|
206
|
+
| `git.defaultBranch` | `"main"` | Branch fetched from `git.remote` and used as the worktree base. |
|
|
207
|
+
| `workspace.projectDir` | **required** | Parent dir for cloned repos and sibling ticket worktrees. |
|
|
208
|
+
| `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. |
|
|
209
|
+
| `orchestrator.maximumInProgress` | `4` | Cap on tickets in `linear.statuses.inProgress` at once. |
|
|
210
|
+
| `orchestrator.pollIntervalMilliseconds` | `120_000` | Poll interval in `--watch` mode. |
|
|
211
|
+
| `orchestrator.sessionLimitPercentage` | `85` | Number in `(0, 100]`. A model whose codexbar session window exceeds this percentage is skipped that tick. |
|
|
212
|
+
| `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`. |
|
|
213
|
+
| `models.definitions` | `{ claude, codex }` | Agent definitions. Additive merge with shipped defaults. |
|
|
214
|
+
| `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. |
|
|
215
|
+
| `models.definitions.<name>.color` | — | Color for the workspace status pill (cmux only; tmux silently drops it). |
|
|
216
|
+
| `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. |
|
|
217
|
+
| `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). |
|
|
218
|
+
| `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. |
|
|
219
|
+
| `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. |
|
|
220
|
+
| `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. |
|
|
221
|
+
| `local.runner` | `"auto"` | Local isolation backend. `"auto"` → `safehouse` on macOS, `sdx` on Linux/WSL. Explicit: `"safehouse"`, `"sdx"`, `"none"`. `"none"` is never picked implicitly. |
|
|
222
|
+
| `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`. |
|
|
221
223
|
|
|
222
224
|
</details>
|
|
223
225
|
|
|
@@ -227,8 +229,8 @@ This keeps package defaults portable while letting your private config reference
|
|
|
227
229
|
Groundcrew ships `claude` and `codex` as default model definitions, additively merged into every resolved config. To stop probing one:
|
|
228
230
|
|
|
229
231
|
```ts
|
|
230
|
-
// config.ts
|
|
231
|
-
export
|
|
232
|
+
// crew.config.ts
|
|
233
|
+
export default {
|
|
232
234
|
// …
|
|
233
235
|
models: {
|
|
234
236
|
default: "claude",
|
|
@@ -491,7 +493,7 @@ node --run crew -- doctor
|
|
|
491
493
|
node --run crew:op -- run --watch
|
|
492
494
|
```
|
|
493
495
|
|
|
494
|
-
Both forms
|
|
496
|
+
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).
|
|
495
497
|
|
|
496
498
|
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.
|
|
497
499
|
|
package/clearance-allow-hosts
CHANGED
|
@@ -59,9 +59,10 @@ raw.githubusercontent.com
|
|
|
59
59
|
release-assets.githubusercontent.com
|
|
60
60
|
results-receiver.actions.githubusercontent.com
|
|
61
61
|
|
|
62
|
-
# npm registry
|
|
62
|
+
# npm registry + package website
|
|
63
63
|
api.npmjs.org
|
|
64
64
|
registry.npmjs.org
|
|
65
|
+
www.npmjs.com
|
|
65
66
|
|
|
66
67
|
# Developer tooling
|
|
67
68
|
buf.build
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Config } from "./src/lib/config.js";
|
|
2
2
|
// import { readFileSync } from "node:fs";
|
|
3
3
|
|
|
4
|
-
export
|
|
4
|
+
export default {
|
|
5
5
|
linear: {
|
|
6
6
|
// Project URL slug to scope polling. Copy the trailing segment of
|
|
7
7
|
// your Linear project URL —
|
|
@@ -9,7 +9,7 @@ export const config: Config = {
|
|
|
9
9
|
// — verbatim, for example "ai-strategy-5152195762f3". The 12-char hex
|
|
10
10
|
// tail is the canonical ID groundcrew uses, so the orchestrator stays
|
|
11
11
|
// resilient across project renames and across same-name projects in
|
|
12
|
-
// different teams. The leading name segment keeps
|
|
12
|
+
// different teams. The leading name segment keeps the file
|
|
13
13
|
// self-documenting at a glance.
|
|
14
14
|
projectSlug: "your-project-name-0123456789ab",
|
|
15
15
|
// statuses: { todo: "Todo", inProgress: "In Progress", done: "Done", terminal: ["Done"] },
|
|
@@ -76,4 +76,4 @@ export const config: Config = {
|
|
|
76
76
|
// // evidence with it. Default: `${XDG_STATE_HOME:-~/.local/state}/groundcrew/groundcrew.log`.
|
|
77
77
|
// file: "~/Library/Logs/groundcrew/groundcrew.log",
|
|
78
78
|
// },
|
|
79
|
-
};
|
|
79
|
+
} satisfies Config;
|
package/dist/lib/config.d.ts
CHANGED
|
@@ -78,8 +78,16 @@ export interface ModelDefinition {
|
|
|
78
78
|
* mirrors the runtime contract: an entry is either a pure overlay
|
|
79
79
|
* (every concrete field optional, no `disabled` key) or a pure
|
|
80
80
|
* disable directive (`{ disabled: true }` and nothing else).
|
|
81
|
+
*
|
|
82
|
+
* `usage` accepts an extra `{ disabled: true }` sentinel that strips the
|
|
83
|
+
* usage block from the merged definition — the only way to opt a shipped
|
|
84
|
+
* default out of codexbar gating without disabling the model entirely.
|
|
81
85
|
*/
|
|
82
|
-
type
|
|
86
|
+
type UserUsage = ModelDefinition["usage"] | {
|
|
87
|
+
disabled: true;
|
|
88
|
+
};
|
|
89
|
+
type EnabledUserModelDefinition = Partial<Omit<ModelDefinition, "usage">> & {
|
|
90
|
+
usage?: UserUsage;
|
|
83
91
|
disabled?: never;
|
|
84
92
|
};
|
|
85
93
|
interface DisabledUserModelDefinition {
|
package/dist/lib/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/config.js
CHANGED
|
@@ -3,6 +3,7 @@ import { existsSync } from "node:fs";
|
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { resolve } from "node:path";
|
|
5
5
|
import { pathToFileURL } from "node:url";
|
|
6
|
+
import { cosmiconfig } from "cosmiconfig";
|
|
6
7
|
import { log, readEnvironmentVariable, setLogFile } from "./util.js";
|
|
7
8
|
export { BUILD_SECRET_NAMES } from "./buildSecrets.js";
|
|
8
9
|
/**
|
|
@@ -68,7 +69,8 @@ const DEFAULT_PROMPT_INITIAL = [
|
|
|
68
69
|
"3. Run the repository's documented verification command. If no documented verification exists, run the smallest relevant test suite you can find. Fix failures you introduced before continuing.",
|
|
69
70
|
"4. Review your own diff before stopping. Look for bugs, regressions, missing tests, security issues, and convention violations, then fix any issues you find.",
|
|
70
71
|
"5. If this repository uses GitHub and the `gh` CLI is available and authenticated, open a pull request. If you cannot open one, leave the branch ready and record the blocker.",
|
|
71
|
-
"6. Include
|
|
72
|
+
"6. Include `Closes {{ticket}}` in the PR description.",
|
|
73
|
+
"7. Include a short continuation note in the PR body when you know how to reattach to this workspace. For the tmux backend, use `tmux attach -t groundcrew:{{ticket}}`.",
|
|
72
74
|
"",
|
|
73
75
|
"Stop after the branch is ready or the PR is open.",
|
|
74
76
|
].join("\n");
|
|
@@ -79,10 +81,6 @@ const ALLOWED_PROMPT_PLACEHOLDERS = new Set([
|
|
|
79
81
|
"{{description}}",
|
|
80
82
|
]);
|
|
81
83
|
const PROMPT_PLACEHOLDER_RE = /{{[^{}]*}}/g;
|
|
82
|
-
// import.meta.dirname is `<package>/src/lib`; the user's `config.ts` lives
|
|
83
|
-
// at the package root (gitignored), two levels up. Last-resort fallback
|
|
84
|
-
// when neither GROUNDCREW_CONFIG nor the XDG path resolves to a file.
|
|
85
|
-
const PACKAGE_CONFIG_PATH = resolve(import.meta.dirname, "..", "..", "config.ts");
|
|
86
84
|
const PERCENT_MIN_EXCLUSIVE = 0;
|
|
87
85
|
const PERCENT_MAX = 100;
|
|
88
86
|
function xdgBase(envName, fallbackSegments) {
|
|
@@ -101,17 +99,6 @@ function xdgStatePath(...segments) {
|
|
|
101
99
|
function defaultLogFile() {
|
|
102
100
|
return xdgStatePath("groundcrew", "groundcrew.log");
|
|
103
101
|
}
|
|
104
|
-
function resolveConfigPath() {
|
|
105
|
-
const override = readEnvironmentVariable("GROUNDCREW_CONFIG");
|
|
106
|
-
if (override !== undefined && override.length > 0) {
|
|
107
|
-
return resolve(override);
|
|
108
|
-
}
|
|
109
|
-
const xdgPath = xdgConfigPath("groundcrew", "config.ts");
|
|
110
|
-
if (existsSync(xdgPath)) {
|
|
111
|
-
return xdgPath;
|
|
112
|
-
}
|
|
113
|
-
return PACKAGE_CONFIG_PATH;
|
|
114
|
-
}
|
|
115
102
|
function expandHome(p) {
|
|
116
103
|
if (p === "~") {
|
|
117
104
|
return homedir();
|
|
@@ -274,6 +261,9 @@ export function isShippedDefaultDisabled(config, name) {
|
|
|
274
261
|
return (Object.hasOwn(DEFAULT_MODEL_DEFINITIONS, name) &&
|
|
275
262
|
!Object.hasOwn(config.models.definitions, name));
|
|
276
263
|
}
|
|
264
|
+
function isUsageDisableSentinel(usage) {
|
|
265
|
+
return isPlainObject(usage) && "disabled" in usage && usage.disabled;
|
|
266
|
+
}
|
|
277
267
|
function mergeDefinitions(user) {
|
|
278
268
|
if (user !== undefined && !isPlainObject(user)) {
|
|
279
269
|
fail("models.definitions must be an object");
|
|
@@ -306,7 +296,12 @@ function mergeDefinitions(user) {
|
|
|
306
296
|
candidate.color = override.color;
|
|
307
297
|
}
|
|
308
298
|
if (override.usage !== undefined) {
|
|
309
|
-
|
|
299
|
+
if (isUsageDisableSentinel(override.usage)) {
|
|
300
|
+
delete candidate.usage;
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
candidate.usage = override.usage;
|
|
304
|
+
}
|
|
310
305
|
}
|
|
311
306
|
if (override.sandbox !== undefined) {
|
|
312
307
|
candidate.sandbox = normalizeSandbox(override.sandbox, `models.definitions.${name}.sandbox`);
|
|
@@ -463,22 +458,100 @@ function validate(config) {
|
|
|
463
458
|
validatePromptPlaceholders(config.prompts.initial);
|
|
464
459
|
requireString(config.logging.file, "logging.file");
|
|
465
460
|
}
|
|
461
|
+
const COSMICONFIG_MODULE_NAME = "crew";
|
|
462
|
+
const SEARCH_PLACES = [
|
|
463
|
+
"crew.config.ts",
|
|
464
|
+
"crew.config.mjs",
|
|
465
|
+
"crew.config.js",
|
|
466
|
+
"crew.config.json",
|
|
467
|
+
".crewrc",
|
|
468
|
+
".crewrc.json",
|
|
469
|
+
".crewrc.ts",
|
|
470
|
+
".config/crew.config.ts",
|
|
471
|
+
".config/crew.config.json",
|
|
472
|
+
".config/crewrc",
|
|
473
|
+
".config/crewrc.json",
|
|
474
|
+
];
|
|
475
|
+
// `config.ts` is the legacy single-name convention from the bespoke loader;
|
|
476
|
+
// kept for one release so existing users don't have to rename.
|
|
477
|
+
const XDG_FALLBACK_NAMES = [
|
|
478
|
+
"crew.config.ts",
|
|
479
|
+
"crew.config.mjs",
|
|
480
|
+
"crew.config.js",
|
|
481
|
+
"crew.config.json",
|
|
482
|
+
"config.ts",
|
|
483
|
+
];
|
|
484
|
+
// cosmiconfig's built-in `.ts` loader requires the `typescript` package;
|
|
485
|
+
// we already rely on Node 24's native TS-stripping for `bin/run.js`, so
|
|
486
|
+
// doing the same here keeps the dependency footprint tiny.
|
|
487
|
+
const loadExecutableModule = async (filepath) => {
|
|
488
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- runtime fields are validated by applyDefaults/validate below
|
|
489
|
+
const module_ = (await import(__rewriteRelativeImportExtension(pathToFileURL(filepath).href)));
|
|
490
|
+
if (module_.default !== undefined) {
|
|
491
|
+
return module_.default;
|
|
492
|
+
}
|
|
493
|
+
if (module_.config !== undefined) {
|
|
494
|
+
log(`Config at ${filepath} uses the legacy \`export const config\` shape. Switch to \`export default\` — the legacy form will be removed in the next major.`);
|
|
495
|
+
return module_.config;
|
|
496
|
+
}
|
|
497
|
+
return null;
|
|
498
|
+
};
|
|
499
|
+
// One explorer per process. `loadConfig` caches its resolved result via
|
|
500
|
+
// the `cached` singleton below, so cosmiconfig's internal cache state
|
|
501
|
+
// is harmless across the at-most-two calls (search + maybe load).
|
|
502
|
+
const explorer = cosmiconfig(COSMICONFIG_MODULE_NAME, {
|
|
503
|
+
searchPlaces: [...SEARCH_PLACES],
|
|
504
|
+
searchStrategy: "project",
|
|
505
|
+
loaders: {
|
|
506
|
+
".ts": loadExecutableModule,
|
|
507
|
+
".mjs": loadExecutableModule,
|
|
508
|
+
".js": loadExecutableModule,
|
|
509
|
+
},
|
|
510
|
+
});
|
|
511
|
+
async function loadAt(filepath) {
|
|
512
|
+
const result = await explorer.load(filepath);
|
|
513
|
+
if (result === null) {
|
|
514
|
+
fail(`${filepath} must export a config object (e.g. \`export default { ... } satisfies Config\`)`);
|
|
515
|
+
}
|
|
516
|
+
return result;
|
|
517
|
+
}
|
|
518
|
+
function findXdgConfigFile() {
|
|
519
|
+
return XDG_FALLBACK_NAMES.map((name) => xdgConfigPath("groundcrew", name)).find((path) => existsSync(path));
|
|
520
|
+
}
|
|
521
|
+
async function discoverUserConfig() {
|
|
522
|
+
const override = readEnvironmentVariable("GROUNDCREW_CONFIG");
|
|
523
|
+
if (override !== undefined && override.length > 0) {
|
|
524
|
+
const overridePath = resolve(override);
|
|
525
|
+
if (!existsSync(overridePath)) {
|
|
526
|
+
fail(`GROUNDCREW_CONFIG=${overridePath} not found`);
|
|
527
|
+
}
|
|
528
|
+
return await loadAt(overridePath);
|
|
529
|
+
}
|
|
530
|
+
const project = await explorer.search(process.cwd());
|
|
531
|
+
if (project !== null && project.isEmpty !== true) {
|
|
532
|
+
return project;
|
|
533
|
+
}
|
|
534
|
+
const xdgPath = findXdgConfigFile();
|
|
535
|
+
if (xdgPath !== undefined) {
|
|
536
|
+
return await loadAt(xdgPath);
|
|
537
|
+
}
|
|
538
|
+
// Throw directly so oxlint's `consistent-return` rule sees a
|
|
539
|
+
// terminating statement; it doesn't track `fail()`'s `never` return.
|
|
540
|
+
throw new Error(`groundcrew config: no crew config found. Create crew.config.ts in your project root, or ${xdgConfigPath("groundcrew", "crew.config.ts")}, or set GROUNDCREW_CONFIG.`);
|
|
541
|
+
}
|
|
466
542
|
let cached;
|
|
467
543
|
export async function loadConfig() {
|
|
468
544
|
if (cached) {
|
|
469
545
|
return cached;
|
|
470
546
|
}
|
|
471
|
-
const
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- user config is TS-typed against Config; runtime fields are validated below
|
|
477
|
-
const module_ = (await import(__rewriteRelativeImportExtension(pathToFileURL(path).href)));
|
|
478
|
-
const { config: userConfig } = module_;
|
|
479
|
-
if (!userConfig) {
|
|
480
|
-
fail(`${path} must export a named "config" object (e.g. \`export const config: Config = { ... }\`)`);
|
|
547
|
+
const result = await discoverUserConfig();
|
|
548
|
+
const { filepath, isEmpty } = result;
|
|
549
|
+
const userConfig = result.config;
|
|
550
|
+
if (isEmpty === true || !isPlainObject(userConfig)) {
|
|
551
|
+
fail(`${filepath} must export a config object (e.g. \`export default { ... } satisfies Config\`)`);
|
|
481
552
|
}
|
|
553
|
+
log(`Loaded config from ${filepath}`);
|
|
554
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- runtime fields are validated by applyDefaults/validate
|
|
482
555
|
const resolved = applyDefaults(userConfig);
|
|
483
556
|
validate(resolved);
|
|
484
557
|
setLogFile(resolved.logging.file);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clipboard-health/groundcrew",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.2",
|
|
4
4
|
"description": "Linear-driven orchestrator that launches AI coding agents in git worktrees, with workspace lifecycle and usage tracking.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"README.md",
|
|
25
25
|
"bin",
|
|
26
26
|
"clearance-allow-hosts",
|
|
27
|
-
"
|
|
27
|
+
"crew.config.example.ts",
|
|
28
28
|
"dist",
|
|
29
29
|
"static"
|
|
30
30
|
],
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"access": "public"
|
|
46
46
|
},
|
|
47
47
|
"scripts": {
|
|
48
|
-
"architecture:check": "depcruise src bin
|
|
48
|
+
"architecture:check": "depcruise src bin crew.config.example.ts vitest.config.ts",
|
|
49
49
|
"build": "tsgo --build --force tsconfig.lib.json",
|
|
50
50
|
"build:dev": "tsgo --build tsconfig.lib.json",
|
|
51
51
|
"cpd": "jscpd .",
|
|
@@ -69,6 +69,7 @@
|
|
|
69
69
|
"dependencies": {
|
|
70
70
|
"@clipboard-health/clearance": "1.0.8",
|
|
71
71
|
"@linear/sdk": "84.0.0",
|
|
72
|
+
"cosmiconfig": "9.0.1",
|
|
72
73
|
"tslib": "2.8.1"
|
|
73
74
|
},
|
|
74
75
|
"devDependencies": {
|