@clipboard-health/groundcrew 4.17.0 → 4.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/crew.config.example.ts +12 -4
- package/dist/cli.js +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +4 -1
- package/dist/lib/adapters/linear/schema.d.ts +1 -0
- package/dist/lib/adapters/linear/schema.d.ts.map +1 -1
- package/dist/lib/adapters/linear/schema.js +6 -0
- package/dist/lib/buildSources.d.ts +9 -3
- package/dist/lib/buildSources.d.ts.map +1 -1
- package/dist/lib/buildSources.js +53 -12
- package/dist/lib/config.d.ts +36 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +75 -4
- package/dist/lib/srtLaunch.d.ts +1 -1
- package/dist/lib/srtLaunch.d.ts.map +1 -1
- package/dist/lib/srtLaunch.js +2 -1
- package/dist/lib/worktrees.d.ts +7 -6
- package/dist/lib/worktrees.d.ts.map +1 -1
- package/dist/lib/worktrees.js +20 -20
- package/docs/configuration.md +39 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -114,6 +114,9 @@ import type { Config } from "@clipboard-health/groundcrew";
|
|
|
114
114
|
export default {
|
|
115
115
|
workspace: {
|
|
116
116
|
projectDir: "~/dev",
|
|
117
|
+
// Optional: all worktrees go here regardless of where each repo lives.
|
|
118
|
+
// worktreeDir: "~/dev/worktrees",
|
|
119
|
+
// Strings live under projectDir; use { name, projectDirOverride } to override per repo.
|
|
117
120
|
knownRepositories: ["OWNER/REPO"],
|
|
118
121
|
},
|
|
119
122
|
models: {
|
|
@@ -131,6 +134,11 @@ export default {
|
|
|
131
134
|
} satisfies Config;
|
|
132
135
|
```
|
|
133
136
|
|
|
137
|
+
Changing `workspace.worktreeDir` only affects worktrees discovered under the new
|
|
138
|
+
root. Clean up existing worktrees before switching it, or temporarily unset
|
|
139
|
+
`worktreeDir` when you need `crew cleanup` to find worktrees created beside the
|
|
140
|
+
repos.
|
|
141
|
+
|
|
134
142
|
There is no `linear` config block. Groundcrew reads `GROUNDCREW_LINEAR_API_KEY` first, then falls back to `LINEAR_API_KEY`.
|
|
135
143
|
|
|
136
144
|
## Reference
|
package/crew.config.example.ts
CHANGED
|
@@ -13,12 +13,17 @@ export default {
|
|
|
13
13
|
// Opt a ticket in: assign it to yourself and add an `agent-<model>`
|
|
14
14
|
// label (e.g. `agent-claude`, `agent-any`).
|
|
15
15
|
workspace: {
|
|
16
|
-
// Parent directory under which groundcrew clones repositories and
|
|
17
|
-
// creates per-ticket worktrees.
|
|
16
|
+
// Parent directory under which groundcrew clones repositories and (by
|
|
17
|
+
// default) creates per-ticket worktrees.
|
|
18
18
|
projectDir: "~/dev/groundcrew",
|
|
19
|
+
// Optional: collect ALL worktrees here instead of beside each repo. Useful
|
|
20
|
+
// when your repos live in more than one place. Defaults to projectDir.
|
|
21
|
+
// worktreeDir: "~/dev/worktrees",
|
|
19
22
|
// Repositories groundcrew is allowed to set up worktrees in. Add
|
|
20
|
-
// `<owner>/<repo>` or bare `<repo>`
|
|
21
|
-
// tickets to these and refuses unknown repos by default.
|
|
23
|
+
// `<owner>/<repo>` or bare `<repo>` strings; the orchestrator scopes
|
|
24
|
+
// tickets to these and refuses unknown repos by default. Use the object
|
|
25
|
+
// form to point a repo at a different parent directory:
|
|
26
|
+
// { name: "other-org/other-repo", projectDirOverride: "~/work" }
|
|
22
27
|
knownRepositories: ["your-org/your-repo"],
|
|
23
28
|
},
|
|
24
29
|
models: {
|
|
@@ -63,6 +68,9 @@ export default {
|
|
|
63
68
|
// inReview: ["Code Review"],
|
|
64
69
|
// },
|
|
65
70
|
// },
|
|
71
|
+
// // Optional: disable the built-in Linear source entirely for shell-only
|
|
72
|
+
// // setups (no Linear API key needed). Replaces the block above.
|
|
73
|
+
// // { kind: "linear", enabled: false },
|
|
66
74
|
// {
|
|
67
75
|
// kind: "shell",
|
|
68
76
|
// name: "jira",
|
package/dist/cli.js
CHANGED
|
@@ -17,7 +17,7 @@ const REMOVED_SANDBOX_COMMAND_MESSAGE = [
|
|
|
17
17
|
const requireFromCli = createRequire(import.meta.url);
|
|
18
18
|
const SETUP_REPOS_REMOVED_MESSAGE = [
|
|
19
19
|
"crew setup repos was removed.",
|
|
20
|
-
"Clone repositories manually with git clone into workspace.projectDir.",
|
|
20
|
+
"Clone repositories manually with git clone into workspace.projectDir (or each repo's workspace.knownRepositories `projectDirOverride`).",
|
|
21
21
|
"See README.md#manual-repository-bootstrap for the replacement workflow.",
|
|
22
22
|
].join(" ");
|
|
23
23
|
/**
|
|
@@ -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;AA+KH,wBAAsB,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,CAmF/C"}
|
package/dist/commands/doctor.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { existsSync, statSync } from "node:fs";
|
|
6
6
|
import { createBoard } from "../lib/board.js";
|
|
7
7
|
import { buildSources, sourcesFromConfig } from "../lib/buildSources.js";
|
|
8
|
-
import { loadConfigWithSource, } from "../lib/config.js";
|
|
8
|
+
import { loadConfigWithSource, worktreeBaseDir, } from "../lib/config.js";
|
|
9
9
|
import { detectHostCapabilities, which } from "../lib/host.js";
|
|
10
10
|
import { resolveLocalRunner } from "../lib/localRunner.js";
|
|
11
11
|
import { gatedModels } from "../lib/usage.js";
|
|
@@ -184,6 +184,9 @@ export async function doctor() {
|
|
|
184
184
|
await checkCmd("git", true, "https://git-scm.com/"),
|
|
185
185
|
...(await workspaceChecks(workspaceOutcome)),
|
|
186
186
|
checkDir(config.workspace.projectDir, "workspace.projectDir"),
|
|
187
|
+
...(config.workspace.worktreeDir === undefined
|
|
188
|
+
? []
|
|
189
|
+
: [checkDir(worktreeBaseDir(config), "workspace.worktreeDir")]),
|
|
187
190
|
localCapability,
|
|
188
191
|
];
|
|
189
192
|
const toolTargets = gatherToolTargets(config);
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { z } from "zod";
|
|
8
8
|
export declare const linearAdapterConfigSchema: z.ZodObject<{
|
|
9
9
|
kind: z.ZodLiteral<"linear">;
|
|
10
|
+
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
10
11
|
name: z.ZodOptional<z.ZodString>;
|
|
11
12
|
statuses: z.ZodOptional<z.ZodObject<{
|
|
12
13
|
inProgress: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/linear/schema.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,eAAO,MAAM,yBAAyB
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/linear/schema.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,eAAO,MAAM,yBAAyB;;;;;;;;iBAkBpC,CAAC;AAEH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC"}
|
|
@@ -8,6 +8,12 @@ import { z } from "zod";
|
|
|
8
8
|
const statusNamesSchema = z.array(z.string().trim().min(1)).min(1);
|
|
9
9
|
export const linearAdapterConfigSchema = z.object({
|
|
10
10
|
kind: z.literal("linear"),
|
|
11
|
+
/**
|
|
12
|
+
* Opt-out sentinel. Set `enabled: false` to disable the built-in Linear
|
|
13
|
+
* source entirely (no adapter is constructed, no API key required) — see
|
|
14
|
+
* `sourcesFromConfig` in buildSources.ts. Omitted/`true` keeps Linear active.
|
|
15
|
+
*/
|
|
16
|
+
enabled: z.boolean().optional(),
|
|
11
17
|
name: z
|
|
12
18
|
.string()
|
|
13
19
|
.regex(/^[a-z][a-z0-9-]*$/, "name must be kebab-case (lowercase letters, digits, hyphens)")
|
|
@@ -22,9 +22,15 @@ export declare function buildSourcesWith(registry: Record<string, AdapterDefinit
|
|
|
22
22
|
* implicit Linear source (Linear is always active under the post-#110
|
|
23
23
|
* model — viewer + agent-* label filtering happens at the GraphQL layer)
|
|
24
24
|
* and appends any user-declared `sources`. The implicit source is omitted
|
|
25
|
-
* when the user already declared a Linear source
|
|
26
|
-
*
|
|
27
|
-
* spawning a duplicate adapter.
|
|
25
|
+
* when the user already declared a Linear source — by `kind: "linear"`, or by
|
|
26
|
+
* a surviving (non-disabled) source whose runtime name is "linear" — so they
|
|
27
|
+
* can override its `name` / construction without spawning a duplicate adapter.
|
|
28
|
+
*
|
|
29
|
+
* Users opt out of Linear entirely with the sentinel
|
|
30
|
+
* `{ kind: "linear", enabled: false }`: it still counts as an explicit Linear
|
|
31
|
+
* declaration (so the implicit source is suppressed) and is itself filtered
|
|
32
|
+
* out (so no Linear adapter is constructed and no API key is required). Any
|
|
33
|
+
* other source with `enabled: false` is likewise dropped from the result.
|
|
28
34
|
*/
|
|
29
35
|
export declare function sourcesFromConfig(config: ResolvedConfig): readonly unknown[];
|
|
30
36
|
//# sourceMappingURL=buildSources.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"buildSources.d.ts","sourceRoot":"","sources":["../../src/lib/buildSources.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAEhF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAItD;;GAEG;AACH,wBAAsB,YAAY,CAChC,UAAU,EAAE,SAAS,OAAO,EAAE,EAC9B,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,YAAY,EAAE,CAAC,CAGzB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,EAC3C,UAAU,EAAE,SAAS,OAAO,EAAE,EAC9B,OAAO,EAAE,cAAc,GACtB,YAAY,EAAE,CAchB;
|
|
1
|
+
{"version":3,"file":"buildSources.d.ts","sourceRoot":"","sources":["../../src/lib/buildSources.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAEhF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAItD;;GAEG;AACH,wBAAsB,YAAY,CAChC,UAAU,EAAE,SAAS,OAAO,EAAE,EAC9B,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,YAAY,EAAE,CAAC,CAGzB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,EAC3C,UAAU,EAAE,SAAS,OAAO,EAAE,EAC9B,OAAO,EAAE,cAAc,GACtB,YAAY,EAAE,CAchB;AA6DD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,cAAc,GAAG,SAAS,OAAO,EAAE,CAa5E"}
|
package/dist/lib/buildSources.js
CHANGED
|
@@ -35,7 +35,40 @@ export function buildSourcesWith(registry, rawConfigs, context) {
|
|
|
35
35
|
const sourceShape = z.looseObject({
|
|
36
36
|
name: z.string().optional(),
|
|
37
37
|
kind: z.string().optional(),
|
|
38
|
+
enabled: z.boolean().optional(),
|
|
38
39
|
});
|
|
40
|
+
/**
|
|
41
|
+
* Read the structural `name` / `kind` / `enabled` fields off a raw source.
|
|
42
|
+
* `looseObject()` with all-optional fields only fails to parse non-object
|
|
43
|
+
* inputs (null, primitives); those are rejected downstream by the per-adapter
|
|
44
|
+
* Zod schema in buildSourcesWith, so we treat a non-object entry as an empty
|
|
45
|
+
* field set ("no opinion") rather than branching on it at every call site.
|
|
46
|
+
*/
|
|
47
|
+
function sourceFields(raw) {
|
|
48
|
+
const parsed = sourceShape.safeParse(raw);
|
|
49
|
+
/* v8 ignore next 3 @preserve -- non-object inputs never reach here in practice (see above); the guard exists only for type-narrowing. */
|
|
50
|
+
if (!parsed.success) {
|
|
51
|
+
return {};
|
|
52
|
+
}
|
|
53
|
+
return parsed.data;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* True when `raw` carries the opt-out sentinel `enabled: false`. Used to drop
|
|
57
|
+
* a source the user explicitly disabled — most importantly a
|
|
58
|
+
* `{ kind: "linear", enabled: false }` entry — so its adapter is never
|
|
59
|
+
* constructed.
|
|
60
|
+
*/
|
|
61
|
+
function isSourceDisabled(raw) {
|
|
62
|
+
return sourceFields(raw).enabled === false;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* True when `raw` declares `kind: "linear"`, regardless of `name` or `enabled`.
|
|
66
|
+
* This is what lets the `{ kind: "linear", enabled: false }` opt-out suppress
|
|
67
|
+
* the implicit Linear source even though the entry itself is filtered out.
|
|
68
|
+
*/
|
|
69
|
+
function isLinearKindSource(raw) {
|
|
70
|
+
return sourceFields(raw).kind === "linear";
|
|
71
|
+
}
|
|
39
72
|
/**
|
|
40
73
|
* True when `raw` is an explicitly-declared Linear source. Matches either a
|
|
41
74
|
* `kind: "linear"` entry — regardless of any `name` override — or any entry
|
|
@@ -50,26 +83,34 @@ const sourceShape = z.looseObject({
|
|
|
50
83
|
* downstream.
|
|
51
84
|
*/
|
|
52
85
|
function isExplicitLinearSource(raw) {
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
if (!parsed.success) {
|
|
56
|
-
return false;
|
|
57
|
-
}
|
|
58
|
-
return parsed.data.kind === "linear" || (parsed.data.name ?? parsed.data.kind) === "linear";
|
|
86
|
+
const { kind, name } = sourceFields(raw);
|
|
87
|
+
return kind === "linear" || (name ?? kind) === "linear";
|
|
59
88
|
}
|
|
60
89
|
/**
|
|
61
90
|
* Build the runtime source list from a ResolvedConfig: synthesizes the
|
|
62
91
|
* implicit Linear source (Linear is always active under the post-#110
|
|
63
92
|
* model — viewer + agent-* label filtering happens at the GraphQL layer)
|
|
64
93
|
* and appends any user-declared `sources`. The implicit source is omitted
|
|
65
|
-
* when the user already declared a Linear source
|
|
66
|
-
*
|
|
67
|
-
* spawning a duplicate adapter.
|
|
94
|
+
* when the user already declared a Linear source — by `kind: "linear"`, or by
|
|
95
|
+
* a surviving (non-disabled) source whose runtime name is "linear" — so they
|
|
96
|
+
* can override its `name` / construction without spawning a duplicate adapter.
|
|
97
|
+
*
|
|
98
|
+
* Users opt out of Linear entirely with the sentinel
|
|
99
|
+
* `{ kind: "linear", enabled: false }`: it still counts as an explicit Linear
|
|
100
|
+
* declaration (so the implicit source is suppressed) and is itself filtered
|
|
101
|
+
* out (so no Linear adapter is constructed and no API key is required). Any
|
|
102
|
+
* other source with `enabled: false` is likewise dropped from the result.
|
|
68
103
|
*/
|
|
69
104
|
export function sourcesFromConfig(config) {
|
|
70
|
-
const
|
|
105
|
+
const kept = config.sources.filter((source) => !isSourceDisabled(source));
|
|
106
|
+
// A `kind: "linear"` entry suppresses the implicit source even when it is the
|
|
107
|
+
// disabled opt-out sentinel — it's removed from `kept` above, leaving Linear
|
|
108
|
+
// off entirely. A source that's Linear only by *name* (e.g. a shell source
|
|
109
|
+
// named "linear") suppresses the implicit source only while it survives the
|
|
110
|
+
// filter, so disabling such an entry doesn't silently drop Linear.
|
|
111
|
+
const hasExplicitLinear = config.sources.some(isLinearKindSource) || kept.some(isExplicitLinearSource);
|
|
71
112
|
if (hasExplicitLinear) {
|
|
72
|
-
return
|
|
113
|
+
return kept;
|
|
73
114
|
}
|
|
74
|
-
return [{ kind: "linear" }, ...
|
|
115
|
+
return [{ kind: "linear" }, ...kept];
|
|
75
116
|
}
|
package/dist/lib/config.d.ts
CHANGED
|
@@ -127,6 +127,15 @@ type UserModelDefinition = EnabledUserModelDefinition;
|
|
|
127
127
|
* Linear's default "In Progress" / "In Review" status names disambiguate
|
|
128
128
|
* `started` workflow states; unmatched statuses fall back to `state.type`.
|
|
129
129
|
*/
|
|
130
|
+
/**
|
|
131
|
+
* A configured repository. The bare-string form keeps the repo under
|
|
132
|
+
* `workspace.projectDir`; the object form's optional `projectDirOverride`
|
|
133
|
+
* overrides that parent directory so repos can live in more than one place.
|
|
134
|
+
*/
|
|
135
|
+
export interface KnownRepository {
|
|
136
|
+
name: string;
|
|
137
|
+
projectDirOverride?: string;
|
|
138
|
+
}
|
|
130
139
|
export interface Config {
|
|
131
140
|
/**
|
|
132
141
|
* Additional pluggable ticket sources beyond the built-in Linear adapter
|
|
@@ -135,6 +144,11 @@ export interface Config {
|
|
|
135
144
|
* an external system (Jira, plan-keeper, etc.) by pointing at command
|
|
136
145
|
* templates that emit/consume JSON.
|
|
137
146
|
*
|
|
147
|
+
* The implicit Linear source can be turned off with the opt-out sentinel
|
|
148
|
+
* `{ kind: "linear", enabled: false }` — useful for shell-only setups with
|
|
149
|
+
* no Linear API key, where a failing Linear probe would otherwise take down
|
|
150
|
+
* the whole queue.
|
|
151
|
+
*
|
|
138
152
|
* Per-source Zod validation runs at `buildSources` time — config.ts only
|
|
139
153
|
* verifies the structural shape (array of objects with a string `kind`).
|
|
140
154
|
*/
|
|
@@ -151,7 +165,12 @@ export interface Config {
|
|
|
151
165
|
};
|
|
152
166
|
workspace: {
|
|
153
167
|
projectDir: string;
|
|
154
|
-
|
|
168
|
+
/**
|
|
169
|
+
* Parent directory all per-ticket worktrees are created under. Defaults
|
|
170
|
+
* to `projectDir` when unset, so single-directory setups are unchanged.
|
|
171
|
+
*/
|
|
172
|
+
worktreeDir?: string;
|
|
173
|
+
knownRepositories: (string | KnownRepository)[];
|
|
155
174
|
};
|
|
156
175
|
defaults?: {
|
|
157
176
|
hooks?: HookCommands;
|
|
@@ -216,7 +235,12 @@ export interface ResolvedConfig {
|
|
|
216
235
|
};
|
|
217
236
|
workspace: {
|
|
218
237
|
projectDir: string;
|
|
238
|
+
/** Resolved worktree root; unset means "use projectDir". */
|
|
239
|
+
worktreeDir?: string;
|
|
240
|
+
/** Repository names only — the union's `projectDirOverride`s are lifted out. */
|
|
219
241
|
knownRepositories: string[];
|
|
242
|
+
/** name -> resolved parent dir, only for entries that override projectDir. */
|
|
243
|
+
repositoryDirs?: Record<string, string>;
|
|
220
244
|
};
|
|
221
245
|
defaults: {
|
|
222
246
|
hooks: HookCommands;
|
|
@@ -250,6 +274,17 @@ export interface ResolvedConfig {
|
|
|
250
274
|
file: string;
|
|
251
275
|
};
|
|
252
276
|
}
|
|
277
|
+
/**
|
|
278
|
+
* Parent directory under which a repository's clone lives. The per-repo
|
|
279
|
+
* `projectDirOverride` wins; otherwise repos sit under `projectDir`.
|
|
280
|
+
*/
|
|
281
|
+
export declare function repositoryBaseDir(config: ResolvedConfig, repository: string): string;
|
|
282
|
+
/**
|
|
283
|
+
* Parent directory all worktrees are created under, independent of where the
|
|
284
|
+
* source repositories live. Falls back to `projectDir` when `worktreeDir` is
|
|
285
|
+
* unset.
|
|
286
|
+
*/
|
|
287
|
+
export declare function worktreeBaseDir(config: ResolvedConfig): string;
|
|
253
288
|
export type ConfigSourceKind = "env" | "project" | "xdg";
|
|
254
289
|
export interface ConfigSource {
|
|
255
290
|
kind: ConfigSourceKind;
|
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":"AAOA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AACvE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAMrE,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAEvD;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG,mBAAmB,GAAG,kBAAkB,CAAC;AAEpE,MAAM,WAAW,YAAY;IAC3B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;GAKG;AACH,eAAO,MAAM,eAAe,QAAQ,CAAC;AAErC;;;;;;GAMG;AACH,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAE5D,eAAO,MAAM,uBAAuB,EAAE,SAAS,oBAAoB,EAIzD,CAAC;AAEX;;;;;;;;GAQG;AACH,MAAM,MAAM,WAAW,GAAG,WAAW,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,CAAC;AAE/D;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,GAAG,WAAW,GAAG,MAAM,CAAC;AAEtD,eAAO,MAAM,qBAAqB,EAAE,SAAS,kBAAkB,EAMrD,CAAC;AAEX;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,+CAA+C;IAC/C,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,eAAe;IAC9B;;;;;;;OAOG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;;;;;;;OAaG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE;QACN,QAAQ,EAAE;YAAE,QAAQ,EAAE,MAAM,CAAC;YAAC,MAAM,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;KACjD,CAAC;IACF;;;;OAIG;IACH,OAAO,CAAC,EAAE,iBAAiB,CAAC;CAC7B;AAED;;;;;;;;GAQG;AACH,KAAK,SAAS,GAAG,eAAe,CAAC,OAAO,CAAC,GAAG;IAAE,QAAQ,EAAE,IAAI,CAAA;CAAE,CAAC;AAC/D,KAAK,0BAA0B,GAAG,OAAO,CAAC,IAAI,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC,GAAG;IAC1E,KAAK,CAAC,EAAE,SAAS,CAAC;CACnB,CAAC;AACF,KAAK,mBAAmB,GAAG,0BAA0B,CAAC;AAEtD;;;;;;;;;GASG;AACH,MAAM,WAAW,MAAM;IACrB
|
|
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
|
@@ -27,6 +27,21 @@ export const LOCAL_RUNNER_SETTINGS = [
|
|
|
27
27
|
"sdx",
|
|
28
28
|
"none",
|
|
29
29
|
];
|
|
30
|
+
/**
|
|
31
|
+
* Parent directory under which a repository's clone lives. The per-repo
|
|
32
|
+
* `projectDirOverride` wins; otherwise repos sit under `projectDir`.
|
|
33
|
+
*/
|
|
34
|
+
export function repositoryBaseDir(config, repository) {
|
|
35
|
+
return config.workspace.repositoryDirs?.[repository] ?? config.workspace.projectDir;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Parent directory all worktrees are created under, independent of where the
|
|
39
|
+
* source repositories live. Falls back to `projectDir` when `worktreeDir` is
|
|
40
|
+
* unset.
|
|
41
|
+
*/
|
|
42
|
+
export function worktreeBaseDir(config) {
|
|
43
|
+
return config.workspace.worktreeDir ?? config.workspace.projectDir;
|
|
44
|
+
}
|
|
30
45
|
const DEFAULT_GIT = {
|
|
31
46
|
remote: "origin",
|
|
32
47
|
defaultBranch: "main",
|
|
@@ -428,6 +443,65 @@ function normalizeSources(raw) {
|
|
|
428
443
|
// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- structural validation above guarantees array of {kind: string} entries; per-source Zod validation lives in buildSources
|
|
429
444
|
return raw;
|
|
430
445
|
}
|
|
446
|
+
/**
|
|
447
|
+
* Resolve one `knownRepositories` entry to its name and (optional) resolved
|
|
448
|
+
* base dir. Bare strings live under `projectDir`; the object form's
|
|
449
|
+
* `projectDirOverride` overrides that parent directory. This is the seam later
|
|
450
|
+
* per-repo options hang off — add new `KnownRepository` fields here.
|
|
451
|
+
*/
|
|
452
|
+
function normalizeKnownRepository(entry, index) {
|
|
453
|
+
if (typeof entry === "string") {
|
|
454
|
+
return { name: entry };
|
|
455
|
+
}
|
|
456
|
+
requireObject(entry, `workspace.knownRepositories[${index}]`);
|
|
457
|
+
requireString(entry.name, `workspace.knownRepositories[${index}].name`);
|
|
458
|
+
if (entry.projectDirOverride === undefined) {
|
|
459
|
+
return { name: entry.name };
|
|
460
|
+
}
|
|
461
|
+
requireString(entry.projectDirOverride, `workspace.knownRepositories[${index}].projectDirOverride`);
|
|
462
|
+
return { name: entry.name, projectDirOverride: expandHome(entry.projectDirOverride) };
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Flatten the loose `(string | KnownRepository)[]` union into the strict
|
|
466
|
+
* resolved shape: a `string[]` of names every downstream consumer reads, plus
|
|
467
|
+
* a separate `repositoryDirs` map holding only the entries that override
|
|
468
|
+
* `projectDir`. Types are validated here, at the resolution edge, before any
|
|
469
|
+
* `expandHome` runs (which would otherwise throw a raw TypeError on a
|
|
470
|
+
* non-string `worktreeDir`).
|
|
471
|
+
*/
|
|
472
|
+
function normalizeWorkspace(workspace) {
|
|
473
|
+
requireObject(workspace, "workspace");
|
|
474
|
+
requireString(workspace.projectDir, "workspace.projectDir");
|
|
475
|
+
// Track the first index each name was seen at so a duplicate (which would
|
|
476
|
+
// silently overwrite its `projectDirOverride` in `repositoryDirs`) fails
|
|
477
|
+
// loudly instead of resolving order-dependently.
|
|
478
|
+
const seen = new Map();
|
|
479
|
+
const repositoryDirs = {};
|
|
480
|
+
const entries = Array.isArray(workspace.knownRepositories) ? workspace.knownRepositories : [];
|
|
481
|
+
entries.forEach((entry, index) => {
|
|
482
|
+
const { name, projectDirOverride } = normalizeKnownRepository(entry, index);
|
|
483
|
+
const previous = seen.get(name);
|
|
484
|
+
if (previous !== undefined) {
|
|
485
|
+
fail(`workspace.knownRepositories[${index}] duplicates ${JSON.stringify(name)} from workspace.knownRepositories[${previous}]. Configure distinct repository names.`);
|
|
486
|
+
}
|
|
487
|
+
seen.set(name, index);
|
|
488
|
+
if (projectDirOverride !== undefined) {
|
|
489
|
+
repositoryDirs[name] = projectDirOverride;
|
|
490
|
+
}
|
|
491
|
+
});
|
|
492
|
+
const names = [...seen.keys()];
|
|
493
|
+
let worktreeDir;
|
|
494
|
+
if (workspace.worktreeDir !== undefined) {
|
|
495
|
+
requireString(workspace.worktreeDir, "workspace.worktreeDir");
|
|
496
|
+
worktreeDir = expandHome(workspace.worktreeDir);
|
|
497
|
+
}
|
|
498
|
+
return {
|
|
499
|
+
projectDir: expandHome(workspace.projectDir),
|
|
500
|
+
...(worktreeDir === undefined ? {} : { worktreeDir }),
|
|
501
|
+
knownRepositories: names,
|
|
502
|
+
...(Object.keys(repositoryDirs).length === 0 ? {} : { repositoryDirs }),
|
|
503
|
+
};
|
|
504
|
+
}
|
|
431
505
|
function applyDefaults(user) {
|
|
432
506
|
// Guard the top-level shape before reading nested fields, so a
|
|
433
507
|
// malformed runtime config produces a `groundcrew config: ...` error
|
|
@@ -458,10 +532,7 @@ function applyDefaults(user) {
|
|
|
458
532
|
...user.git,
|
|
459
533
|
...(branchPrefix === undefined ? {} : { branchPrefix }),
|
|
460
534
|
},
|
|
461
|
-
workspace:
|
|
462
|
-
projectDir: expandHome(user.workspace.projectDir),
|
|
463
|
-
knownRepositories: user.workspace.knownRepositories,
|
|
464
|
-
},
|
|
535
|
+
workspace: normalizeWorkspace(user.workspace),
|
|
465
536
|
defaults: normalizeDefaults(user.defaults),
|
|
466
537
|
orchestrator: { ...DEFAULT_ORCHESTRATOR, ...user.orchestrator },
|
|
467
538
|
models: {
|
package/dist/lib/srtLaunch.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ModelDefinition, type ResolvedConfig } from "./config.ts";
|
|
2
2
|
export interface StagedSrtLaunch {
|
|
3
3
|
/** Dedicated temp dir holding the settings files (and any relocated config home). */
|
|
4
4
|
directory: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"srtLaunch.d.ts","sourceRoot":"","sources":["../../src/lib/srtLaunch.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"srtLaunch.d.ts","sourceRoot":"","sources":["../../src/lib/srtLaunch.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,cAAc,EAAqB,MAAM,aAAa,CAAC;AAK3F,MAAM,WAAW,eAAe;IAC9B,qFAAqF;IACrF,SAAS,EAAE,MAAM,CAAC;IAClB,kFAAkF;IAClF,WAAW,EAAE,MAAM,CAAC;IACpB,4CAA4C;IAC5C,SAAS,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,iBAAiB,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CACrD;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE;IAC5C,MAAM,EAAE,cAAc,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,eAAe,CAAC;IAC5B,iFAAiF;IACjF,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,GAAG,eAAe,CA8ClB"}
|
package/dist/lib/srtLaunch.js
CHANGED
|
@@ -2,6 +2,7 @@ import { copyFileSync, existsSync, mkdirSync, mkdtempSync, writeFileSync } from
|
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { collectAllowedDomains } from "./clearanceHosts.js";
|
|
5
|
+
import { repositoryBaseDir } from "./config.js";
|
|
5
6
|
import { inferAgentCommandName } from "./launchCommand.js";
|
|
6
7
|
import { agentConfigRelocation, buildSrtSettings } from "./srtPolicy.js";
|
|
7
8
|
import { readEnvironmentVariable } from "./util.js";
|
|
@@ -27,7 +28,7 @@ import { readEnvironmentVariable } from "./util.js";
|
|
|
27
28
|
export function buildAndStageSrtLaunch(input) {
|
|
28
29
|
const agent = inferAgentCommandName(input.definition.cmd);
|
|
29
30
|
const homeDir = input.homeDir ?? os.homedir();
|
|
30
|
-
const repoDir = path.resolve(input.config.
|
|
31
|
+
const repoDir = path.resolve(repositoryBaseDir(input.config, input.repository), input.repository);
|
|
31
32
|
const base = {
|
|
32
33
|
worktreeDir: input.worktreeDir,
|
|
33
34
|
gitCommonDir: path.join(repoDir, ".git"),
|
package/dist/lib/worktrees.d.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Worktree lifecycle — manages host git worktrees for tickets.
|
|
3
3
|
*
|
|
4
|
-
* A worktree is a `git worktree add`'d
|
|
5
|
-
* `<
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* A worktree is a `git worktree add`'d directory at
|
|
5
|
+
* `<worktreeDir>/<repo>-<TICKET>/` (where `worktreeDir` defaults to
|
|
6
|
+
* `projectDir`). The source repo it is cut from may live under a different
|
|
7
|
+
* per-repo `projectDirOverride`. Callers go through the `worktrees` namespace;
|
|
8
|
+
* the module owns creation, listing, removal, and teardown (workspace-close +
|
|
9
|
+
* worktree-remove paired) so callers don't reach into git directly.
|
|
9
10
|
*/
|
|
10
|
-
import type
|
|
11
|
+
import { type ResolvedConfig } from "./config.ts";
|
|
11
12
|
import { type WorkspaceProbe } from "./workspaces.ts";
|
|
12
13
|
export type WorktreeKind = "host";
|
|
13
14
|
export declare class WorktreeAlreadyExistsError extends Error {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worktrees.d.ts","sourceRoot":"","sources":["../../src/lib/worktrees.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"worktrees.d.ts","sourceRoot":"","sources":["../../src/lib/worktrees.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAOH,OAAO,EAAE,KAAK,cAAc,EAAsC,MAAM,aAAa,CAAC;AAGtF,OAAO,EAAE,KAAK,cAAc,EAAc,MAAM,iBAAiB,CAAC;AAIlE,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC;AAElC,qBAAa,0BAA2B,SAAQ,KAAK;IACnD,SAAgB,GAAG,EAAE,MAAM,CAAC;IAE5B,YAAmB,GAAG,EAAE,MAAM,EAI7B;CACF;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,0BAA0B,CAEhG;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,sDAAsD;IACtD,MAAM,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,YAAY,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB;AAkBD,iBAAS,mBAAmB,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAE3E;AAgQD,MAAM,MAAM,iBAAiB,GACzB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GACtD;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,GACjB;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC;AAiHxB,iBAAS,IAAI,CAAC,MAAM,EAAE,cAAc,GAAG,aAAa,EAAE,CAErD;AAED,iBAAS,YAAY,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,GAAG,aAAa,EAAE,CAE7E;AAED,iBAAe,MAAM,CACnB,MAAM,EAAE,cAAc,EACtB,IAAI,EAAE,YAAY,EAClB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,aAAa,CAAC,CAQxB;AAED,iBAAe,MAAM,CACnB,MAAM,EAAE,cAAc,EACtB,KAAK,EAAE,aAAa,EACpB,OAAO,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,GAClD,OAAO,CAAC,IAAI,CAAC,CAKf;AAED,MAAM,MAAM,YAAY,GAAG,iBAAiB,GAAG,iBAAiB,CAAC;AAEjE,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,aAAa,CAAC;IACrB,IAAI,EAAE,YAAY,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,+DAA+D;IAC/D,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,sCAAsC;IACtC,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,wDAAwD;IACxD,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,cAAc,EAAE,cAAc,CAAC;CAChC;AAyBD,iBAAe,QAAQ,CACrB,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,SAAS,aAAa,EAAE,EACjC,OAAO,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,GAClD,OAAO,CAAC,cAAc,CAAC,CAiDzB;AAED,iBAAe,gBAAgB,CAAC,KAAK,EAAE;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAE7B;AAED,eAAO,MAAM,SAAS;;;;;;;;CAQrB,CAAC"}
|
package/dist/lib/worktrees.js
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Worktree lifecycle — manages host git worktrees for tickets.
|
|
3
3
|
*
|
|
4
|
-
* A worktree is a `git worktree add`'d
|
|
5
|
-
* `<
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* A worktree is a `git worktree add`'d directory at
|
|
5
|
+
* `<worktreeDir>/<repo>-<TICKET>/` (where `worktreeDir` defaults to
|
|
6
|
+
* `projectDir`). The source repo it is cut from may live under a different
|
|
7
|
+
* per-repo `projectDirOverride`. Callers go through the `worktrees` namespace;
|
|
8
|
+
* the module owns creation, listing, removal, and teardown (workspace-close +
|
|
9
|
+
* worktree-remove paired) so callers don't reach into git directly.
|
|
9
10
|
*/
|
|
10
11
|
import { existsSync, readdirSync, rmSync } from "node:fs";
|
|
11
12
|
import { userInfo } from "node:os";
|
|
12
13
|
import path from "node:path";
|
|
13
14
|
import { runCommandAsync } from "./commandRunner.js";
|
|
15
|
+
import { repositoryBaseDir, worktreeBaseDir } from "./config.js";
|
|
14
16
|
import { resolveDefaultBranch } from "./defaultBranch.js";
|
|
15
17
|
import { debug, errorMessage, isVerbose } from "./util.js";
|
|
16
18
|
import { workspaces } from "./workspaces.js";
|
|
@@ -46,7 +48,7 @@ function repoDirFor(config, repository) {
|
|
|
46
48
|
if (!config.workspace.knownRepositories.includes(repository)) {
|
|
47
49
|
throw new Error(`Repository "${repository}" is not in workspace.knownRepositories: ${config.workspace.knownRepositories.join(", ")}`);
|
|
48
50
|
}
|
|
49
|
-
const repoDir = path.resolve(config
|
|
51
|
+
const repoDir = path.resolve(repositoryBaseDir(config, repository), repository);
|
|
50
52
|
if (!existsSync(repoDir)) {
|
|
51
53
|
throw new Error(`Repository not found: ${repoDir}`);
|
|
52
54
|
}
|
|
@@ -59,12 +61,10 @@ function basePaths(config, repository, ticket) {
|
|
|
59
61
|
if (!TICKET_RE.test(ticket)) {
|
|
60
62
|
throw new Error(`Invalid ticket "${ticket}": must be a plain ticket id`);
|
|
61
63
|
}
|
|
62
|
-
const projectDir = path.resolve(config.workspace.projectDir);
|
|
63
64
|
const repoDir = repoDirFor(config, repository);
|
|
64
65
|
const hostWorktreeName = `${repository}-${ticket}`;
|
|
65
|
-
const hostWorktreeDir = path.resolve(
|
|
66
|
+
const hostWorktreeDir = path.resolve(worktreeBaseDir(config), hostWorktreeName);
|
|
66
67
|
return {
|
|
67
|
-
projectDir,
|
|
68
68
|
repoDir,
|
|
69
69
|
ticket,
|
|
70
70
|
branchName: branchNameForTicket(config, ticket),
|
|
@@ -126,17 +126,18 @@ async function createWorktree(config, spec, signal) {
|
|
|
126
126
|
};
|
|
127
127
|
}
|
|
128
128
|
function listWorktrees(config) {
|
|
129
|
-
const
|
|
129
|
+
const worktreeRoot = path.resolve(worktreeBaseDir(config));
|
|
130
130
|
const entries = [];
|
|
131
|
-
// Worktrees live at `
|
|
131
|
+
// Worktrees live at `worktreeRoot/<repository>-<ticket>`. When `repository`
|
|
132
132
|
// contains a slash (e.g. "owner/repo"), `path.resolve()` nests one level
|
|
133
|
-
// deeper, so the worktree path is `
|
|
134
|
-
// Scan each known repository's parent directory
|
|
135
|
-
// root, so nested worktrees are discovered alongside
|
|
133
|
+
// deeper, so the worktree path is `worktreeRoot/owner/repo-<ticket>`.
|
|
134
|
+
// Scan each known repository's parent directory under the worktree root
|
|
135
|
+
// rather than the root itself, so nested worktrees are discovered alongside
|
|
136
|
+
// bare ones.
|
|
136
137
|
const reposByParent = new Map();
|
|
137
138
|
for (const repository of config.workspace.knownRepositories) {
|
|
138
139
|
const lastSlash = repository.lastIndexOf("/");
|
|
139
|
-
const parentDir = lastSlash === -1 ?
|
|
140
|
+
const parentDir = lastSlash === -1 ? worktreeRoot : path.resolve(worktreeRoot, repository.slice(0, lastSlash));
|
|
140
141
|
const basename = lastSlash === -1 ? repository : repository.slice(lastSlash + 1);
|
|
141
142
|
let repoByBasename = reposByParent.get(parentDir);
|
|
142
143
|
if (repoByBasename === undefined) {
|
|
@@ -182,8 +183,7 @@ function listWorktrees(config) {
|
|
|
182
183
|
return entries;
|
|
183
184
|
}
|
|
184
185
|
async function removeWorktree(config, entry, options) {
|
|
185
|
-
const
|
|
186
|
-
const repoDir = path.resolve(projectDir, entry.repository);
|
|
186
|
+
const repoDir = path.resolve(repositoryBaseDir(config, entry.repository), entry.repository);
|
|
187
187
|
if (existsSync(entry.dir)) {
|
|
188
188
|
debug(`Removing worktree ${entry.dir}${options.force ? " (--force)" : ""}...`);
|
|
189
189
|
const removeArguments = ["-C", repoDir, "worktree", "remove"];
|
|
@@ -317,7 +317,7 @@ function describeOrphanWorktree(arguments_) {
|
|
|
317
317
|
return `directory exists but is not a registered git worktree. Run \`crew cleanup --force ${ticket}\` to remove ${dir}, or inspect it first if it may contain valuable files.`;
|
|
318
318
|
}
|
|
319
319
|
function expectedHostWorktreeDir(config, entry) {
|
|
320
|
-
return path.resolve(config
|
|
320
|
+
return path.resolve(worktreeBaseDir(config), `${entry.repository}-${entry.ticket}`);
|
|
321
321
|
}
|
|
322
322
|
function isInsideDirectory(parentDir, childDir) {
|
|
323
323
|
const childRelativePath = path.relative(parentDir, childDir);
|
|
@@ -326,10 +326,10 @@ function isInsideDirectory(parentDir, childDir) {
|
|
|
326
326
|
!path.isAbsolute(childRelativePath));
|
|
327
327
|
}
|
|
328
328
|
function removeOrphanWorktreeDirectory(config, entry) {
|
|
329
|
-
const
|
|
329
|
+
const worktreeRoot = path.resolve(worktreeBaseDir(config));
|
|
330
330
|
const expectedDir = expectedHostWorktreeDir(config, entry);
|
|
331
331
|
const targetDir = path.resolve(entry.dir);
|
|
332
|
-
if (targetDir !== expectedDir || !isInsideDirectory(
|
|
332
|
+
if (targetDir !== expectedDir || !isInsideDirectory(worktreeRoot, targetDir)) {
|
|
333
333
|
throw new Error(`Refusing to force-delete ${entry.dir}: expected groundcrew worktree path ${expectedDir}.`);
|
|
334
334
|
}
|
|
335
335
|
debug(`Removing orphaned worktree directory ${entry.dir} (--force)...`);
|
package/docs/configuration.md
CHANGED
|
@@ -4,7 +4,8 @@ Workspace settings and at least one enabled model are required; everything else
|
|
|
4
4
|
|
|
5
5
|
| Key | What |
|
|
6
6
|
| ----------------------------- | ---------------------------------------------------------------------- |
|
|
7
|
-
| `workspace.projectDir` | Parent dir for cloned repos and
|
|
7
|
+
| `workspace.projectDir` | Parent dir for cloned repos and the default ticket worktree root. |
|
|
8
|
+
| `workspace.worktreeDir` | Optional parent dir for ticket worktrees. |
|
|
8
9
|
| `workspace.knownRepositories` | Repos searched for in ticket descriptions to infer where work belongs. |
|
|
9
10
|
| `models.definitions` | Enabled model set. Built-in presets can be enabled with `{}`. |
|
|
10
11
|
|
|
@@ -12,7 +13,10 @@ The branch prefix (`<prefix>-<TICKET>`) defaults to `os.userInfo().username`; ov
|
|
|
12
13
|
|
|
13
14
|
## Repository Layout
|
|
14
15
|
|
|
15
|
-
Groundcrew never clones repositories for you. `crew init --repo OWNER/REPO`
|
|
16
|
+
Groundcrew never clones repositories for you. `crew init --repo OWNER/REPO`
|
|
17
|
+
prints the clone command to run. If you are cloning manually, clone each string
|
|
18
|
+
`workspace.knownRepositories` entry into `workspace.projectDir` using the same
|
|
19
|
+
relative path the config uses.
|
|
16
20
|
|
|
17
21
|
```bash
|
|
18
22
|
PROJECT_DIR="$HOME/dev"
|
|
@@ -20,7 +24,18 @@ mkdir -p "$PROJECT_DIR/OWNER"
|
|
|
20
24
|
git clone git@github.com:OWNER/REPO.git "$PROJECT_DIR/OWNER/REPO"
|
|
21
25
|
```
|
|
22
26
|
|
|
23
|
-
Bare-name entries have no owner, so pick the remote URL yourself and clone to
|
|
27
|
+
Bare-name entries have no owner, so pick the remote URL yourself and clone to
|
|
28
|
+
`$PROJECT_DIR/<name>`. To keep a repo clone somewhere else, use
|
|
29
|
+
`{ name: "OWNER/REPO", projectDirOverride: "~/other" }` and clone it under that
|
|
30
|
+
parent dir.
|
|
31
|
+
|
|
32
|
+
By default, ticket worktrees are created beside the repos under
|
|
33
|
+
`workspace.projectDir`. Set `workspace.worktreeDir` to collect worktrees under a
|
|
34
|
+
separate root, regardless of where each source repo clone lives. Changing
|
|
35
|
+
`workspace.worktreeDir` only affects worktrees discovered under the new root.
|
|
36
|
+
Clean up existing worktrees before switching it, or temporarily unset
|
|
37
|
+
`worktreeDir` when you need `crew cleanup` to find worktrees created beside the
|
|
38
|
+
repos.
|
|
24
39
|
|
|
25
40
|
## Config Discovery
|
|
26
41
|
|
|
@@ -68,6 +83,23 @@ export default {
|
|
|
68
83
|
|
|
69
84
|
Configured names replace the default for that status; omitted fields keep their defaults. Matching is case-insensitive and trims surrounding whitespace.
|
|
70
85
|
|
|
86
|
+
Linear is implicit-on, but you can turn it off entirely with the opt-out sentinel `{ kind: "linear", enabled: false }`. This suppresses the built-in Linear source so no adapter is constructed and no API key is required — useful for shell-only setups, where a failing Linear probe would otherwise mark the whole queue unavailable:
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
export default {
|
|
90
|
+
sources: [
|
|
91
|
+
{ kind: "linear", enabled: false },
|
|
92
|
+
{
|
|
93
|
+
kind: "shell",
|
|
94
|
+
name: "plans",
|
|
95
|
+
commands: {
|
|
96
|
+
fetch: "~/.config/groundcrew/plans-fetch.sh",
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
};
|
|
101
|
+
```
|
|
102
|
+
|
|
71
103
|
## Enabling Model Presets
|
|
72
104
|
|
|
73
105
|
Groundcrew ships built-in presets for `claude` and `codex`, but models are not enabled by default. List the models you want in `models.definitions`:
|
|
@@ -148,12 +180,13 @@ and hook contract.
|
|
|
148
180
|
|
|
149
181
|
| Key | Default | What it does |
|
|
150
182
|
| ---------------------------------------- | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
151
|
-
| `sources` | `[]` | Additional pluggable ticket sources, dispatched alongside the built-in Linear adapter. Built-in kinds: `shell`, `linear`. Declare `{ kind: "linear", statuses: { ... } }` only to override Linear status names used for `in-progress` / `in-review` disambiguation.
|
|
183
|
+
| `sources` | `[]` | Additional pluggable ticket sources, dispatched alongside the built-in Linear adapter. Built-in kinds: `shell`, `linear`. Declare `{ kind: "linear", statuses: { ... } }` only to override Linear status names used for `in-progress` / `in-review` disambiguation. Disable the implicit Linear source with `{ kind: "linear", enabled: false }` (no API key required) — useful for shell-only setups. |
|
|
152
184
|
| `git.remote` | `"origin"` | Remote used for `fetch` and as the worktree base ref. |
|
|
153
185
|
| `git.defaultBranch` | `"main"` | Branch fetched from `git.remote` and used as the worktree base. |
|
|
154
186
|
| `git.branchPrefix` | OS username | Prefix groundcrew puts before the ticket id when naming a worktree branch (`<branchPrefix>-<ticket>`). Must be a slash-free slug of letters, digits, `.`, `_`, or `-`. Defaults to the OS account username. Changing it only affects newly created worktrees; existing local branches keep their original names until cleaned up. Prefer a per-user config for personal prefixes — a committed `git.branchPrefix` gives every contributor the same branch prefix. |
|
|
155
|
-
| `workspace.projectDir` | **required** | Parent dir for cloned repos and
|
|
156
|
-
| `workspace.
|
|
187
|
+
| `workspace.projectDir` | **required** | Parent dir for cloned repos and the default ticket worktree root. |
|
|
188
|
+
| `workspace.worktreeDir` | optional | Parent dir for ticket worktrees. When unset, worktrees are created under `workspace.projectDir`. Changing this only affects worktrees discovered under the new root; clean up existing worktrees before switching it, or temporarily unset it when you need `crew cleanup` to find old worktrees. |
|
|
189
|
+
| `workspace.knownRepositories` | **required** | Repos searched for in ticket descriptions to infer where work belongs. Entries can be strings under `workspace.projectDir` or `{ name, projectDirOverride }` objects when a repo clone lives under a different parent dir. A ticket labeled for groundcrew (`agent-*`) fails fast when no known repo appears; unlabeled tickets are ignored. |
|
|
157
190
|
| `defaults.hooks.prepareWorktree` | optional | Fallback repo-preparation command used only when the worktree does not define `.groundcrew/config.json` `hooks.prepareWorktree`. The hook runs after worktree creation and before the agent starts. Repo-local config wins. |
|
|
158
191
|
| `orchestrator.maximumInProgress` | `4` | Cap on in-progress tickets at once for this `crew` instance. |
|
|
159
192
|
| `orchestrator.pollIntervalMilliseconds` | `120_000` | Poll interval in `--watch` mode. |
|
package/package.json
CHANGED