@clipboard-health/groundcrew 4.28.0 → 4.30.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 CHANGED
@@ -150,35 +150,9 @@ There is no `linear` config block. Groundcrew reads `GROUNDCREW_LINEAR_API_KEY`
150
150
  - [Credentials](./docs/credentials.md): Linear API keys, 1Password, build secrets, and `preLaunch`.
151
151
  - [Prepare worktree hooks](./docs/setup-hooks.md): `.groundcrew/config.json` `hooks.prepareWorktree` for per-repo dependency setup.
152
152
  - [Task sources](./docs/task-sources.md): custom shell/Jira/local-plan adapters.
153
+ - [Development](./docs/development.md): local source workflow and README/demo asset regeneration.
153
154
  - [Troubleshooting](./docs/troubleshooting.md): common operational pitfalls and fixes.
154
155
 
155
- ## Development
156
-
157
- Clone the repo and run the CLI from TypeScript source:
158
-
159
- ```bash
160
- cd ~/dev/c/groundcrew
161
- node --run crew -- doctor
162
-
163
- # With 1Password for GROUNDCREW_LINEAR_API_KEY:
164
- node --run crew:op -- run --watch
165
- ```
166
-
167
- Both forms discover config through cosmiconfig. Source edits in `src/**` are picked up on the next invocation. Requires Node >= 24.
168
-
169
- Regenerate the README demo with VHS:
170
-
171
- ```bash
172
- ./static/render-demo.sh
173
- ```
174
-
175
- Regenerate the Slack bot avatar (also used as the emoji upload; Slack scales it down) after editing the mark:
176
-
177
- ```bash
178
- sed 's/width="120" height="120"/width="512" height="512"/' static/groundcrew-mark.svg > /tmp/mark512.svg
179
- sips -s format png /tmp/mark512.svg --out static/groundcrew-avatar.png
180
- ```
181
-
182
156
  ## License
183
157
 
184
158
  [MIT](./LICENSE)
@@ -120,6 +120,9 @@ export default {
120
120
  // // `${XDG_CONFIG_HOME:-$HOME/.config}/groundcrew/initial-prompt.md`.
121
121
  // // If you uncomment this, also uncomment the readFileSync import above.
122
122
  // initial: readFileSync(new URL("./initial-prompt.md", import.meta.url), "utf8"),
123
+ // // Or, instead of `initial`, point at a file (also works in crew.config.json).
124
+ // // Resolved relative to this config's directory; `~` and absolute paths work.
125
+ // // promptFile: "initial-prompt.md",
123
126
  // },
124
127
  //
125
128
  // // Terminal session manager. "auto" picks cmux when on PATH, else tmux.
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Shell-adapter `TaskSource` factory. Wires `invokeShellCommand` to the
3
- * four TaskSource operations and applies the ShellIssue Zod schema for
4
- * runtime validation of script stdout.
3
+ * TaskSource operations and applies the ShellIssue Zod schema for runtime
4
+ * validation of script stdout.
5
5
  *
6
6
  * Fallback behavior for omitted commands:
7
7
  * - `verify` absent → no-op (always succeeds).
@@ -13,6 +13,10 @@
13
13
  * - `markInProgress` absent → silent no-op.
14
14
  * - `markInReview` absent → reports unsupported.
15
15
  * - `markDone` absent → reports unsupported.
16
+ * - `createTask` absent → method omitted entirely (source reports it cannot
17
+ * create tasks; the optional method is attached only when configured).
18
+ * - `validate` absent → method omitted entirely (same capability-detection
19
+ * contract as createTask).
16
20
  * - `fetch` is required by the Zod schema.
17
21
  */
18
22
  import type { AdapterContext } from "../../adapterDefinition.ts";
@@ -1 +1 @@
1
- {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/shell/factory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAGL,KAAK,KAAK,IAAI,cAAc,EAG5B,KAAK,UAAU,EAChB,MAAM,qBAAqB,CAAC;AAI7B,OAAO,EACL,KAAK,kBAAkB,EAEvB,KAAK,UAAU,EAEhB,MAAM,aAAa,CAAC;AAkErB,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,GAAG,cAAc,CAuB3F;AAED,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,kBAAkB,EAC1B,QAAQ,EAAE,cAAc,GACvB,UAAU,CAyHZ"}
1
+ {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/shell/factory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAIL,KAAK,KAAK,IAAI,cAAc,EAG5B,KAAK,UAAU,EAChB,MAAM,qBAAqB,CAAC;AAI7B,OAAO,EACL,KAAK,kBAAkB,EAEvB,KAAK,UAAU,EAGhB,MAAM,aAAa,CAAC;AAyErB,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,GAAG,cAAc,CAuB3F;AA8BD,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,kBAAkB,EAC1B,QAAQ,EAAE,cAAc,GACvB,UAAU,CA6LZ"}
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Shell-adapter `TaskSource` factory. Wires `invokeShellCommand` to the
3
- * four TaskSource operations and applies the ShellIssue Zod schema for
4
- * runtime validation of script stdout.
3
+ * TaskSource operations and applies the ShellIssue Zod schema for runtime
4
+ * validation of script stdout.
5
5
  *
6
6
  * Fallback behavior for omitted commands:
7
7
  * - `verify` absent → no-op (always succeeds).
@@ -13,12 +13,16 @@
13
13
  * - `markInProgress` absent → silent no-op.
14
14
  * - `markInReview` absent → reports unsupported.
15
15
  * - `markDone` absent → reports unsupported.
16
+ * - `createTask` absent → method omitted entirely (source reports it cannot
17
+ * create tasks; the optional method is attached only when configured).
18
+ * - `validate` absent → method omitted entirely (same capability-detection
19
+ * contract as createTask).
16
20
  * - `fetch` is required by the Zod schema.
17
21
  */
18
22
  import { toCanonicalId, } from "../../taskSource.js";
19
- import { writeError } from "../../util.js";
23
+ import { errorMessage, writeError } from "../../util.js";
20
24
  import { invokeShellCommand } from "./invoke.js";
21
- import { shellFetchOutputSchema, shellIssueSchema, } from "./schema.js";
25
+ import { shellFetchOutputSchema, shellIssueSchema, shellValidateOutputSchema, } from "./schema.js";
22
26
  const DEFAULT_TIMEOUTS = {
23
27
  verify: 10_000,
24
28
  listTasks: 30_000,
@@ -26,15 +30,20 @@ const DEFAULT_TIMEOUTS = {
26
30
  markInProgress: 10_000,
27
31
  markInReview: 10_000,
28
32
  markDone: 10_000,
33
+ createTask: 30_000,
34
+ validate: 30_000,
29
35
  };
30
36
  function mergeTimeouts(overrides) {
37
+ const o = overrides ?? {};
31
38
  return {
32
- verify: overrides?.verify ?? DEFAULT_TIMEOUTS.verify,
33
- listTasks: overrides?.listTasks ?? overrides?.fetch ?? DEFAULT_TIMEOUTS.listTasks,
34
- getTask: overrides?.getTask ?? overrides?.resolveOne ?? DEFAULT_TIMEOUTS.getTask,
35
- markInProgress: overrides?.markInProgress ?? DEFAULT_TIMEOUTS.markInProgress,
36
- markInReview: overrides?.markInReview ?? DEFAULT_TIMEOUTS.markInReview,
37
- markDone: overrides?.markDone ?? DEFAULT_TIMEOUTS.markDone,
39
+ verify: o.verify ?? DEFAULT_TIMEOUTS.verify,
40
+ listTasks: o.listTasks ?? o.fetch ?? DEFAULT_TIMEOUTS.listTasks,
41
+ getTask: o.getTask ?? o.resolveOne ?? DEFAULT_TIMEOUTS.getTask,
42
+ markInProgress: o.markInProgress ?? DEFAULT_TIMEOUTS.markInProgress,
43
+ markInReview: o.markInReview ?? DEFAULT_TIMEOUTS.markInReview,
44
+ markDone: o.markDone ?? DEFAULT_TIMEOUTS.markDone,
45
+ createTask: o.createTask ?? DEFAULT_TIMEOUTS.createTask,
46
+ validate: o.validate ?? DEFAULT_TIMEOUTS.validate,
38
47
  };
39
48
  }
40
49
  function warnDuplicate(sourceName, preferred, legacy) {
@@ -85,6 +94,33 @@ export function toCanonicalIssue(shellIssue, sourceName) {
85
94
  sourceRef: shellIssue.sourceRef,
86
95
  };
87
96
  }
97
+ /**
98
+ * Flatten a CreateTaskInput into the `${...}` substitution map the createTask
99
+ * script receives. Every key is always present — absent optionals become an
100
+ * empty string — so no placeholder is ever left literally in the command.
101
+ * List fields are comma-joined into a single value.
102
+ */
103
+ function createTaskSubstitutions(input) {
104
+ return {
105
+ title: input.title,
106
+ agent: input.agent,
107
+ // Exposed under both `repo` (short form) and `repository` (matches the
108
+ // CreateTaskInput field name) so either placeholder resolves.
109
+ repo: input.repository ?? "",
110
+ repository: input.repository ?? "",
111
+ team: input.team ?? "",
112
+ id: input.id ?? "",
113
+ priority: input.priority ?? "",
114
+ due: input.due ?? "",
115
+ recurrence: input.recurrence ?? "",
116
+ promptFile: input.promptFile ?? "",
117
+ description: input.description ?? "",
118
+ edit: input.edit ? "true" : "",
119
+ projects: input.projects.join(","),
120
+ contexts: input.contexts.join(","),
121
+ dependencies: input.dependencies.join(","),
122
+ };
123
+ }
88
124
  export function createShellTaskSource(config, _context) {
89
125
  const sourceName = config.name;
90
126
  const timeouts = mergeTimeouts(config.timeouts);
@@ -149,7 +185,7 @@ export function createShellTaskSource(config, _context) {
149
185
  sourceName,
150
186
  });
151
187
  }
152
- return {
188
+ const source = {
153
189
  name: sourceName,
154
190
  async verify() {
155
191
  const verifyCommand = config.commands.verify;
@@ -198,4 +234,66 @@ export function createShellTaskSource(config, _context) {
198
234
  return { outcome: "applied" };
199
235
  },
200
236
  };
237
+ // createTask / validate are attached only when their command is configured.
238
+ // Capability detection keys off `source.createTask === undefined` /
239
+ // `source.validate === undefined`, so a slot left unset must leave the
240
+ // method genuinely absent rather than present-but-failing.
241
+ const createTaskCommand = config.commands.createTask;
242
+ if (createTaskCommand !== undefined) {
243
+ source.createTask = async (input) => {
244
+ const { stdout, exitCode } = await invokeShellCommand({
245
+ command: createTaskCommand,
246
+ timeoutMs: timeouts.createTask,
247
+ cwd: config.cwd,
248
+ env: config.env,
249
+ substitutions: createTaskSubstitutions(input),
250
+ sourceName,
251
+ });
252
+ // invokeShellCommand resolves (does not throw) on exit 3 — its "not
253
+ // found" sentinel for lookups. Creation has no not-found concept, so any
254
+ // nonzero exit is a failure: surface it rather than parse partial output.
255
+ if (exitCode === 3) {
256
+ throw new Error(`shell source "${sourceName}" createTask command exited 3 (not-found); task creation cannot signal not-found`);
257
+ }
258
+ const trimmed = stdout.trim();
259
+ if (trimmed.length === 0) {
260
+ throw new Error(`shell source "${sourceName}" createTask command produced no output (expected one ShellIssue JSON)`);
261
+ }
262
+ const parsed = shellIssueSchema.parse(JSON.parse(trimmed));
263
+ return toCanonicalIssue(parsed, sourceName);
264
+ };
265
+ }
266
+ const validateCommand = config.commands.validate;
267
+ if (validateCommand !== undefined) {
268
+ source.validate = async () => {
269
+ try {
270
+ const { stdout, exitCode } = await invokeShellCommand({
271
+ command: validateCommand,
272
+ timeoutMs: timeouts.validate,
273
+ cwd: config.cwd,
274
+ env: config.env,
275
+ sourceName,
276
+ });
277
+ // exit 3 is invoke's lookup "not found" sentinel; for validation it is
278
+ // not a meaningful success, so surface it as a failure (and validate
279
+ // never throws — return the failure as an error string).
280
+ if (exitCode === 3) {
281
+ return [
282
+ `shell source "${sourceName}" validate command exited 3 (not-found); treating as a validation failure`,
283
+ ];
284
+ }
285
+ const trimmed = stdout.trim();
286
+ if (trimmed.length === 0) {
287
+ return [];
288
+ }
289
+ return shellValidateOutputSchema.parse(JSON.parse(trimmed));
290
+ }
291
+ catch (error) {
292
+ // Contract: validate() never throws — fold a nonzero exit, timeout,
293
+ // malformed JSON, or wrong-shape payload into a single error string.
294
+ return [`shell source "${sourceName}" validate command failed: ${errorMessage(error)}`];
295
+ }
296
+ };
297
+ }
298
+ return source;
201
299
  }
@@ -80,6 +80,12 @@ export declare const shellFetchOutputSchema: z.ZodArray<z.ZodObject<{
80
80
  url: z.ZodOptional<z.ZodURL>;
81
81
  sourceRef: z.ZodUnknown;
82
82
  }, z.core.$strip>>;
83
+ /**
84
+ * Shape a `commands.validate` script must emit on stdout: a JSON array of
85
+ * human-readable error strings. An empty array (or empty stdout, handled by
86
+ * the factory) means "no problems found".
87
+ */
88
+ export declare const shellValidateOutputSchema: z.ZodArray<z.ZodString>;
83
89
  export declare const shellAdapterConfigSchema: z.ZodObject<{
84
90
  kind: z.ZodLiteral<"shell">;
85
91
  name: z.ZodString;
@@ -92,6 +98,8 @@ export declare const shellAdapterConfigSchema: z.ZodObject<{
92
98
  markInProgress: z.ZodOptional<z.ZodString>;
93
99
  markInReview: z.ZodOptional<z.ZodString>;
94
100
  markDone: z.ZodOptional<z.ZodString>;
101
+ createTask: z.ZodOptional<z.ZodString>;
102
+ validate: z.ZodOptional<z.ZodString>;
95
103
  }, z.core.$strip>;
96
104
  cwd: z.ZodOptional<z.ZodString>;
97
105
  timeouts: z.ZodOptional<z.ZodObject<{
@@ -103,6 +111,8 @@ export declare const shellAdapterConfigSchema: z.ZodObject<{
103
111
  markInProgress: z.ZodOptional<z.ZodNumber>;
104
112
  markInReview: z.ZodOptional<z.ZodNumber>;
105
113
  markDone: z.ZodOptional<z.ZodNumber>;
114
+ createTask: z.ZodOptional<z.ZodNumber>;
115
+ validate: z.ZodOptional<z.ZodNumber>;
106
116
  }, z.core.$strip>>;
107
117
  env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
108
118
  }, z.core.$strip>;
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/shell/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAYxB,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAiB3B,CAAC;AAEH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAA4B,CAAC;AAEhE,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;iBAiDnC,CAAC;AAEH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/shell/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAYxB,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAiB3B,CAAC;AAEH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAA4B,CAAC;AAEhE;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,yBAAsB,CAAC;AAE7D,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAkEnC,CAAC;AAEH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC"}
@@ -36,6 +36,12 @@ export const shellIssueSchema = z.object({
36
36
  sourceRef: z.unknown(),
37
37
  });
38
38
  export const shellFetchOutputSchema = z.array(shellIssueSchema);
39
+ /**
40
+ * Shape a `commands.validate` script must emit on stdout: a JSON array of
41
+ * human-readable error strings. An empty array (or empty stdout, handled by
42
+ * the factory) means "no problems found".
43
+ */
44
+ export const shellValidateOutputSchema = z.array(z.string());
39
45
  export const shellAdapterConfigSchema = z.object({
40
46
  kind: z.literal("shell"),
41
47
  name: z
@@ -55,6 +61,19 @@ export const shellAdapterConfigSchema = z.object({
55
61
  markInProgress: z.string().optional(),
56
62
  markInReview: z.string().optional(),
57
63
  markDone: z.string().optional(),
64
+ /**
65
+ * Create a task from a `CreateTaskInput`. Receives the input fields as
66
+ * shell-quoted `${...}` placeholders (see factory) and must print one
67
+ * ShellIssue JSON on stdout. Omitting it leaves the source unable to
68
+ * create tasks.
69
+ */
70
+ createTask: z.string().optional(),
71
+ /**
72
+ * Validate task content. Must print a JSON array of error strings on
73
+ * stdout (empty array / empty stdout = no problems). Omitting it leaves
74
+ * the source unable to validate.
75
+ */
76
+ validate: z.string().optional(),
58
77
  })
59
78
  .superRefine((commands, ctx) => {
60
79
  if (commands.listTasks === undefined && commands.fetch === undefined) {
@@ -82,6 +101,10 @@ export const shellAdapterConfigSchema = z.object({
82
101
  markInProgress: z.number().int().positive().optional(),
83
102
  markInReview: z.number().int().positive().optional(),
84
103
  markDone: z.number().int().positive().optional(),
104
+ /** Timeout for the createTask command. */
105
+ createTask: z.number().int().positive().optional(),
106
+ /** Timeout for the validate command. */
107
+ validate: z.number().int().positive().optional(),
85
108
  })
86
109
  .optional(),
87
110
  env: z.record(z.string(), z.string()).optional(),
@@ -193,7 +193,14 @@ export interface Config {
193
193
  definitions?: Record<string, UserAgentDefinition>;
194
194
  };
195
195
  prompts?: {
196
+ /** Inline initial prompt. Mutually exclusive with `promptFile`. */
196
197
  initial?: string;
198
+ /**
199
+ * Path to a UTF-8 file whose contents become the initial prompt. Resolved
200
+ * relative to the config file's directory; `~` is expanded; absolute paths
201
+ * are used as-is. Mutually exclusive with `initial`.
202
+ */
203
+ promptFile?: string;
197
204
  };
198
205
  /**
199
206
  * Terminal session manager that hosts agent processes. Defaults to
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { __rewriteRelativeImportExtension } from "tslib";
2
- import { existsSync } from "node:fs";
2
+ import { existsSync, readFileSync } from "node:fs";
3
3
  import path from "node:path";
4
4
  import { pathToFileURL } from "node:url";
5
5
  import { cosmiconfig } from "cosmiconfig";
@@ -518,7 +518,32 @@ function normalizeWorkspace(workspace) {
518
518
  ...(Object.keys(repositoryDirs).length === 0 ? {} : { repositoryDirs }),
519
519
  };
520
520
  }
521
- function applyDefaults(user) {
521
+ /**
522
+ * Resolve the initial prompt from the user's `prompts` block. `promptFile` and
523
+ * `initial` are mutually exclusive; when neither is set the built-in default is
524
+ * used. A `promptFile` is resolved relative to the config file's directory
525
+ * (matching the `.ts` `import.meta.url` behavior), with `~` expanded and
526
+ * absolute paths used as-is, then read at load time.
527
+ */
528
+ function resolveInitialPrompt(prompts, configDir) {
529
+ const { initial, promptFile } = prompts ?? {};
530
+ if (initial !== undefined && promptFile !== undefined) {
531
+ fail("prompts: set either `initial` or `promptFile`, not both");
532
+ }
533
+ if (promptFile !== undefined) {
534
+ requireString(promptFile, "prompts.promptFile");
535
+ const expanded = expandHome(promptFile);
536
+ const resolved = path.isAbsolute(expanded) ? expanded : path.resolve(configDir, expanded);
537
+ try {
538
+ return readFileSync(resolved, "utf8");
539
+ }
540
+ catch (error) {
541
+ fail(`prompts.promptFile could not be read at ${resolved}: ${String(error)}`);
542
+ }
543
+ }
544
+ return initial ?? DEFAULT_PROMPT_INITIAL;
545
+ }
546
+ function applyDefaults(user, configDir) {
522
547
  // Guard the top-level shape before reading nested fields, so a
523
548
  // malformed runtime config produces a `groundcrew config: ...` error
524
549
  // instead of a raw `TypeError: Cannot read properties of undefined`.
@@ -559,7 +584,7 @@ function applyDefaults(user) {
559
584
  definitions: mergeDefinitions(user.agents?.definitions),
560
585
  },
561
586
  prompts: {
562
- initial: user.prompts?.initial ?? DEFAULT_PROMPT_INITIAL,
587
+ initial: resolveInitialPrompt(user.prompts, configDir),
563
588
  },
564
589
  workspaceKind: normalizeWorkspaceKind(user.workspaceKind, "workspaceKind") ?? "auto",
565
590
  local: {
@@ -571,7 +596,6 @@ function applyDefaults(user) {
571
596
  };
572
597
  }
573
598
  function validatePromptPlaceholders(template) {
574
- /* v8 ignore next @preserve -- a no-placeholder prompt is unusual but tests with placeholders consistently match at least once */
575
599
  const placeholders = template.match(PROMPT_PLACEHOLDER_RE) ?? [];
576
600
  const unknown = placeholders.find((placeholder) => !ALLOWED_PROMPT_PLACEHOLDERS.has(placeholder));
577
601
  if (unknown !== undefined) {
@@ -741,7 +765,7 @@ export async function loadConfigWithSource() {
741
765
  }
742
766
  debug(`Loaded config from ${filepath}`);
743
767
  // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- runtime fields are validated by applyDefaults/validate
744
- const resolved = applyDefaults(userConfig);
768
+ const resolved = applyDefaults(userConfig, path.dirname(filepath));
745
769
  validate(resolved);
746
770
  setLogFile(resolved.logging.file);
747
771
  cached = Object.freeze({
@@ -29,11 +29,11 @@ function shellCapabilities(raw) {
29
29
  verify: commands.verify !== undefined,
30
30
  listTasks: true,
31
31
  getTask: true,
32
- createTask: false,
32
+ createTask: commands.createTask !== undefined,
33
33
  markInProgress: commands.markInProgress !== undefined,
34
34
  markInReview: commands.markInReview !== undefined,
35
35
  markDone: commands.markDone !== undefined,
36
- validate: false,
36
+ validate: commands.validate !== undefined,
37
37
  };
38
38
  }
39
39
  const TODO_TXT_CAPABILITIES = {
@@ -157,6 +157,22 @@ export default {
157
157
 
158
158
  This keeps package defaults portable while letting your private config reference team-specific statuses, tools, plugins, or review loops.
159
159
 
160
+ ### Loading the prompt from a file (`prompts.promptFile`)
161
+
162
+ `readFileSync` works for `.ts` configs but not for `crew.config.json`, which has no way to reference an external file. Set `prompts.promptFile` instead and groundcrew reads the file's contents into the initial prompt at load time:
163
+
164
+ ```json
165
+ {
166
+ "prompts": {
167
+ "promptFile": "prompt-initial.md"
168
+ }
169
+ }
170
+ ```
171
+
172
+ - The path is resolved **relative to the config file's directory** (matching the `.ts` `import.meta.url` behavior); a leading `~` is expanded and absolute paths are used as-is.
173
+ - `prompts.initial` and `prompts.promptFile` are **mutually exclusive** — setting both is a hard error. Set neither to keep the built-in default.
174
+ - Placeholder validation runs on the loaded file contents, so an unknown `{{placeholder}}` in the file fails the same way it would inline.
175
+
160
176
  ## Default Hooks
161
177
 
162
178
  Repo-local `.groundcrew/config.json` is the preferred place for
@@ -199,7 +215,8 @@ and hook contract.
199
215
  | `models.definitions.<name>.sandbox` | optional | Docker Sandboxes binding for the model. Required at launch when `local.runner` resolves to `sdx`. Field: `agent` (required sbx agent name). Groundcrew assumes the `groundcrew-<agent>` sandbox already exists. |
200
216
  | `models.definitions.<name>.preLaunch` | optional | Host-only shell snippet run before the agent exec and outside Safehouse/sdx. Exports survive into the launch shell; under the default `safehouse` runner they are only forwarded to the agent when listed via `preLaunchEnv` or when `cmd` includes its own `safehouse --env-pass=NAMES`. `{{worktree}}` is substituted. A non-zero exit aborts launch. Not supported when `local.runner` resolves to `sdx` in v1. |
201
217
  | `models.definitions.<name>.preLaunchEnv` | optional | Companion to `preLaunch`: list of env var names to append to groundcrew's `safehouse-clearance` `--env-pass=` flag, so `preLaunch` exports reach the agent without overriding `cmd` and losing the project's egress allowlist. Each entry must match `[A-Za-z_][A-Za-z0-9_]*`. Under `runner: "none"` exports already inherit and `preLaunchEnv` is a no-op. An empty array is a uniform no-op in every runner; a non-empty list is rejected when `cmd` already starts with `safehouse` or when `runner` resolves to `sdx`. |
202
- | `prompts.initial` | unattended template | First message sent to the agent: the execution wrapper around each task. The task description is the task-specific prompt. Placeholders: `{{task}}`, `{{worktree}}`, `{{title}}`, `{{description}}`. Override only to change the execution contract for every task, such as team-wide review rules or tool conventions. |
218
+ | `prompts.initial` | unattended template | First message sent to the agent: the execution wrapper around each task. The task description is the task-specific prompt. Placeholders: `{{task}}`, `{{worktree}}`, `{{title}}`, `{{description}}`. Override only to change the execution contract for every task, such as team-wide review rules or tool conventions. Mutually exclusive with `prompts.promptFile`. |
219
+ | `prompts.promptFile` | optional | Path to a UTF-8 file whose contents become `prompts.initial`, read at load time. Resolved relative to the config file's directory; `~` is expanded and absolute paths are used as-is. The JSON-friendly alternative to inlining a large prompt or `readFileSync`. Mutually exclusive with `prompts.initial`. |
203
220
  | `workspaceKind` | `"auto"` | Terminal session manager. `"auto"` picks `cmux` when on PATH, else `tmux`. Set to `"cmux"`, `"tmux"`, or `"zellij"` to fail loudly when the chosen backend is missing. |
204
221
  | `local.runner` | `"auto"` | Local isolation backend. `"auto"` uses `safehouse` on macOS and `sdx` on Linux/WSL. Explicit: `"safehouse"`, `"sdx"`, `"none"`. `"none"` is never picked implicitly. |
205
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`. |
@@ -0,0 +1,26 @@
1
+ # Development
2
+
3
+ Clone the repo and run the CLI from TypeScript source:
4
+
5
+ ```bash
6
+ cd ~/dev/c/groundcrew
7
+ node --run crew -- doctor
8
+
9
+ # With 1Password for GROUNDCREW_LINEAR_API_KEY:
10
+ node --run crew:op -- run --watch
11
+ ```
12
+
13
+ Both forms discover config through cosmiconfig. Source edits in `src/**` are picked up on the next invocation. Requires Node >= 24.
14
+
15
+ Regenerate the README demo with VHS:
16
+
17
+ ```bash
18
+ ./static/render-demo.sh
19
+ ```
20
+
21
+ Regenerate the Slack bot avatar (also used as the emoji upload; Slack scales it down) after editing the mark:
22
+
23
+ ```bash
24
+ sed 's/width="120" height="120"/width="512" height="512"/' static/groundcrew-mark.svg > /tmp/mark512.svg
25
+ sips -s format png /tmp/mark512.svg --out static/groundcrew-avatar.png
26
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clipboard-health/groundcrew",
3
- "version": "4.28.0",
3
+ "version": "4.30.0",
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",
@@ -46,24 +46,24 @@
46
46
  "access": "public"
47
47
  },
48
48
  "scripts": {
49
- "architecture:check": "depcruise src bin crew.config.example.ts vitest.config.ts",
49
+ "architecture:check": "depcruise --config config/dependencyCruiser.cjs src bin crew.config.example.ts vitest.config.ts",
50
50
  "build": "tsgo --build --force tsconfig.lib.json",
51
51
  "build:dev": "tsgo --build tsconfig.lib.json",
52
- "cpd": "jscpd .",
52
+ "cpd": "jscpd --config config/jscpd.json .",
53
53
  "crew": "node --run build:dev && node ./bin/run.js",
54
54
  "crew:op": "node --run build:dev && op run --env-file \"${XDG_CONFIG_HOME:-$HOME/.config}/groundcrew/op.env\" -- node ./bin/run.js",
55
- "format": "oxfmt",
56
- "format:check": "oxfmt --check",
57
- "hook:pre-commit": "lint-staged",
55
+ "format": "oxfmt --config config/oxfmt.json",
56
+ "format:check": "oxfmt --config config/oxfmt.json --check",
57
+ "hook:pre-commit": "lint-staged --config config/lintStaged.config.js",
58
58
  "hook:pre-push": "node --run verify",
59
- "knip": "knip",
60
- "lint": "oxlint --deny-warnings",
61
- "markdown:lint": "markdownlint-cli2",
59
+ "knip": "knip --config config/knip.json",
60
+ "lint": "oxlint --config config/oxlint.config.ts --deny-warnings",
61
+ "markdown:lint": "markdownlint-cli2 --config config/markdownlint-cli2.jsonc",
62
62
  "prepack": "node --run build",
63
63
  "prepare": "husky",
64
- "spell:check": "cspell --no-summary --no-progress --no-must-find-files",
64
+ "spell:check": "cspell --config config/cspell.json --no-summary --no-progress --no-must-find-files",
65
65
  "sync-ai-rules": "node ./node_modules/@clipboard-health/ai-rules/scripts/sync.js common",
66
- "syncpack:lint": "syncpack lint",
66
+ "syncpack:lint": "syncpack lint --config config/syncpack.config.ts",
67
67
  "test": "vitest run --coverage",
68
68
  "verify": "node scripts/verifyAll.mts"
69
69
  },