@clipboard-health/groundcrew 4.29.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.
@@ -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(),
@@ -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 = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clipboard-health/groundcrew",
3
- "version": "4.29.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",