@clipboard-health/groundcrew 4.0.2 → 4.0.3

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
@@ -83,15 +83,17 @@ In Linear, assign tickets to yourself and add an `agent-*` label (`agent-claude`
83
83
  crew init [--global | --local] [--force] [--dry-run] # create a crew.config.ts
84
84
  crew doctor # check setup
85
85
  crew status [<TICKET>] # inspect current state or one ticket
86
- crew run # one-shot dispatch
86
+ crew run # one-shot orchestration
87
87
  crew run --watch # poll forever
88
- crew run --ticket <TICKET> # dispatch one ticket
88
+ crew start <TICKET> # provision + launch one ticket now
89
89
  crew setup repos [<repo>...] [--dry-run] # clone known repos via gh
90
- crew interrupt <TICKET> [--reason <text>] # stop workspace, keep worktree
90
+ crew stop <TICKET> [--reason <text>] # stop workspace, keep worktree
91
91
  crew resume <TICKET> # reopen a paused ticket
92
92
  crew cleanup <TICKET> # tear down every worktree for a ticket
93
93
  ```
94
94
 
95
+ Deprecated aliases still work but print a warning and will be removed in the next major version: `crew interrupt` → `crew stop`, `crew run --ticket <TICKET>` → `crew start <TICKET>`, `crew doctor --ticket <TICKET>` → `crew status <TICKET>`.
96
+
95
97
  ## Configuration
96
98
 
97
99
  Two keys are required; everything else has a default.
@@ -109,7 +111,7 @@ The branch prefix (`<prefix>-<TICKET>`) is derived from `os.userInfo().username`
109
111
  - `agent-claude`, `agent-codex`, `agent-<name>` → that model.
110
112
  - `agent-any` → the model with the most available session capacity.
111
113
  - Unknown `agent-<name>` → falls back to `models.default` with a warning.
112
- - No `agent-*` label → ignored by `crew run`. Dispatch on demand with `crew run --ticket <TICKET>` (also falls back to `models.default`).
114
+ - No `agent-*` label → ignored by `crew run`. Dispatch on demand with `crew start <TICKET>` (also falls back to `models.default`).
113
115
  - Todo tickets blocked by non-terminal blockers are skipped until their blockers reach a terminal status.
114
116
 
115
117
  Status classification uses Linear's workflow `state.type` (`unstarted`, `started`, `completed`, `canceled`, `duplicate`), so renamed status columns work without configuration. Parent issues with children are ignored — sub-issues are the work items.
@@ -136,7 +138,7 @@ Resolution order: `GROUNDCREW_CONFIG` → cosmiconfig project-walk from cwd (any
136
138
  | `orchestrator.maximumInProgress` | `4` | Cap on in-progress tickets at once for this `crew` instance. |
137
139
  | `orchestrator.pollIntervalMilliseconds` | `120_000` | Poll interval in `--watch` mode. |
138
140
  | `orchestrator.sessionLimitPercentage` | `85` | Number in `(0, 100]`. A model whose codexbar session window exceeds this percentage is skipped that tick. |
139
- | `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`. |
141
+ | `models.default` | `"claude"` | Tiebreak for `agent-any` resolution and fallback for explicit but unknown `agent-*` labels. Also used by `crew start <TICKET>` for unlabeled tickets. `crew run` ignores unlabeled tickets and does not apply this default. Must exist in `models.definitions`. |
140
142
  | `models.definitions` | `{ claude, codex }` | Agent definitions. Additive merge with shipped defaults. |
141
143
  | `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. |
142
144
  | `models.definitions.<name>.color` | — | Color for the workspace status pill (cmux only; tmux silently drops it). |
@@ -234,21 +236,30 @@ In Progress (state.type=started) — Multi-event extractor: year inference can p
234
236
 
235
237
  </details>
236
238
 
237
- ### `crew interrupt <TICKET>`
239
+ ### `crew start <TICKET>`
240
+
241
+ Provisions and launches one ticket immediately, bypassing orchestrator eligibility. Use it to dispatch a specific ticket on demand — including unlabeled tickets that `crew run` ignores. (Replaces the deprecated `crew run --ticket <TICKET>`.)
242
+
243
+ ```bash
244
+ crew start HRD-442
245
+ crew start HRD-442 --dry-run
246
+ ```
247
+
248
+ ### `crew stop <TICKET>`
238
249
 
239
- Stops a live workspace pane while preserving the ticket worktree and branch. The manual pause button for cases where you need terminal capacity back, want to stop an agent that's going in the wrong direction, or need to inspect the diff before letting another agent continue.
250
+ Stops a live workspace pane while preserving the ticket worktree and branch. The manual pause button for cases where you need terminal capacity back, want to stop an agent that's going in the wrong direction, or need to inspect the diff before letting another agent continue. (Replaces the deprecated `crew interrupt <TICKET>`.)
240
251
 
241
252
  ```bash
242
- crew interrupt HRD-442 --reason "wrong implementation direction"
253
+ crew stop HRD-442 --reason "wrong implementation direction"
243
254
  crew status HRD-442
244
255
  crew resume HRD-442
245
256
  ```
246
257
 
247
- The command closes the cmux/tmux workspace if present, records local run state, and never tears down the worktree. If the workspace was already gone but the worktree is still present, interrupt records that fact so status can show the preserved branch.
258
+ The command closes the cmux/tmux workspace if present, records local run state, and never tears down the worktree. If the workspace was already gone but the worktree is still present, stop records that fact so status can show the preserved branch.
248
259
 
249
260
  ### `crew resume <TICKET>`
250
261
 
251
- Reopens an existing ticket worktree with a continuation prompt. Resume never creates a new worktree; if none exists it fails and leaves re-dispatch to `crew run --ticket <ticket>`.
262
+ Reopens an existing ticket worktree with a continuation prompt. Resume never creates a new worktree; if none exists it fails and leaves re-dispatch to `crew start <ticket>`.
252
263
 
253
264
  The resume prompt tells the agent to inspect git status and diff before editing, includes the previous interrupt reason when recorded, and reuses the recorded model, repository, branch, runner, sandbox, and workspace backend. When no run-state file exists but a worktree does, resume falls back to Linear resolution for the model and ticket context.
254
265
 
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AA6MA,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAsCvD"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAwQA,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAsCvD"}
package/dist/cli.js CHANGED
@@ -11,10 +11,17 @@ import { setupWorkspaceCli } from "./commands/setupWorkspace.js";
11
11
  import { statusCli } from "./commands/status.js";
12
12
  import { createDefaultUpgradeCliOptions, upgradeCli } from "./commands/upgrade.js";
13
13
  import { computeUpgradeNudge, defaultUpgradeCheckCachePath, fetchLatestVersion, } from "./lib/upgrade.js";
14
- import { errorMessage, readEnvironmentVariable, readTicketArgument, writeError, writeOutput, } from "./lib/util.js";
14
+ import { errorMessage, parseDryRunPositionals, readEnvironmentVariable, readTicketArgument, writeError, writeOutput, } from "./lib/util.js";
15
15
  const NUDGE_TTL_MS = 6 * 60 * 60 * 1000;
16
16
  const NUDGE_FETCH_TIMEOUT_MS = 1000;
17
17
  const requireFromCli = createRequire(import.meta.url);
18
+ /**
19
+ * Prints a deprecation warning to stderr naming the canonical command and that
20
+ * the old form is removed in the next major, then lets the caller proceed.
21
+ */
22
+ function warnDeprecated(forms) {
23
+ writeError(`crew ${forms.oldForm} is deprecated and will be removed in the next major version; use crew ${forms.newForm} instead.`);
24
+ }
18
25
  function setupUsage() {
19
26
  return "Usage: crew setup repos [--dry-run] [<repo>...]";
20
27
  }
@@ -54,6 +61,16 @@ async function runCli(argv) {
54
61
  await orchestrate({ watch, dryRun });
55
62
  return;
56
63
  }
64
+ warnDeprecated({ oldForm: "run --ticket", newForm: "start" });
65
+ await setupWorkspaceCli(ticket, { dryRun });
66
+ }
67
+ const START_USAGE = "crew start <ticket> [--dry-run]";
68
+ async function startCli(argv) {
69
+ const { dryRun, positionals } = parseDryRunPositionals(argv, START_USAGE);
70
+ const [ticket, ...extras] = positionals;
71
+ if (ticket === undefined || ticket.length === 0 || extras.length > 0) {
72
+ throw new Error(`Usage: ${START_USAGE}`);
73
+ }
57
74
  await setupWorkspaceCli(ticket, { dryRun });
58
75
  }
59
76
  async function upgradeCliInvoke(argv) {
@@ -80,7 +97,23 @@ async function maybeRunUpgradeNudge(metadata) {
80
97
  writeError(message);
81
98
  }
82
99
  }
100
+ function doctorTicketAlias(argv) {
101
+ if (argv[0] !== "--ticket") {
102
+ return undefined;
103
+ }
104
+ const ticket = readTicketArgument(argv, 0, "doctor");
105
+ if (argv.length > 2) {
106
+ throw new Error("Usage: crew status [<ticket>]");
107
+ }
108
+ return ticket;
109
+ }
83
110
  async function doctorCli(argv) {
111
+ const aliasTicket = doctorTicketAlias(argv);
112
+ if (aliasTicket !== undefined) {
113
+ warnDeprecated({ oldForm: "doctor --ticket", newForm: "status" });
114
+ await statusCli([aliasTicket]);
115
+ return;
116
+ }
84
117
  if (argv.length > 0) {
85
118
  throw new Error("Usage: crew doctor");
86
119
  }
@@ -94,10 +127,15 @@ const SUBCOMMANDS = {
94
127
  invoke: initConfigCli,
95
128
  },
96
129
  run: {
97
- summary: "Run the orchestrator (one-shot by default), or provision one ticket with --ticket",
98
- usage: "[--watch] [--dry-run] [--ticket <ticket>]",
130
+ summary: "Run the orchestrator: poll sources and start eligible tickets (one-shot by default)",
131
+ usage: "[--watch] [--dry-run]",
99
132
  invoke: runCli,
100
133
  },
134
+ start: {
135
+ summary: "Provision and launch one ticket immediately, bypassing eligibility",
136
+ usage: "<ticket> [--dry-run]",
137
+ invoke: startCli,
138
+ },
101
139
  doctor: {
102
140
  summary: "Verify host prerequisites (PATH tools, config validity, Linear reachability)",
103
141
  usage: "",
@@ -113,11 +151,20 @@ const SUBCOMMANDS = {
113
151
  usage: "[--force] <ticket>",
114
152
  invoke: cleanupWorkspaceCli,
115
153
  },
116
- interrupt: {
154
+ stop: {
117
155
  summary: "Stop a live ticket workspace while preserving its worktree",
118
156
  usage: "<ticket> [--reason <text>]",
119
157
  invoke: interruptWorkspaceCli,
120
158
  },
159
+ interrupt: {
160
+ summary: "Deprecated alias for `crew stop`",
161
+ usage: "<ticket> [--reason <text>]",
162
+ deprecated: true,
163
+ invoke: async (argv) => {
164
+ warnDeprecated({ oldForm: "interrupt", newForm: "stop" });
165
+ await interruptWorkspaceCli(argv);
166
+ },
167
+ },
121
168
  resume: {
122
169
  summary: "Reopen an existing ticket worktree with a continuation prompt",
123
170
  usage: "<ticket>",
@@ -148,6 +195,9 @@ function printHelp() {
148
195
  writeOutput("");
149
196
  writeOutput("Commands:");
150
197
  for (const [name, command] of Object.entries(SUBCOMMANDS)) {
198
+ if (command.deprecated === true) {
199
+ continue;
200
+ }
151
201
  writeOutput(` ${name.padEnd(width)} ${command.summary}`);
152
202
  writeOutput(` ${" ".repeat(width)} → crew ${name} ${command.usage}`);
153
203
  }
@@ -1 +1 @@
1
- {"version":3,"file":"interruptWorkspace.d.ts","sourceRoot":"","sources":["../../src/commands/interruptWorkspace.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAMnE,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAuGD,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,yBAAyB,GACjC,OAAO,CAAC,IAAI,CAAC,CAyBf;AAED,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAGzE"}
1
+ {"version":3,"file":"interruptWorkspace.d.ts","sourceRoot":"","sources":["../../src/commands/interruptWorkspace.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAMnE,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAqGD,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,yBAAyB,GACjC,OAAO,CAAC,IAAI,CAAC,CAyBf;AAED,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAGzE"}
@@ -15,20 +15,20 @@ function parseArguments(argv) {
15
15
  if (argument === "--reason") {
16
16
  const value = argv[index + 1];
17
17
  if (value === undefined || value.length === 0 || value.startsWith("-")) {
18
- throw new Error("crew interrupt --reason: reason text is required");
18
+ throw new Error("crew stop --reason: reason text is required");
19
19
  }
20
20
  reason = value;
21
21
  index += 1;
22
22
  continue;
23
23
  }
24
24
  if (argument.startsWith("-")) {
25
- throw new Error(`Unknown option: ${argument}\nUsage: crew interrupt <ticket> [--reason <text>]`);
25
+ throw new Error(`Unknown option: ${argument}\nUsage: crew stop <ticket> [--reason <text>]`);
26
26
  }
27
27
  positionals.push(argument);
28
28
  }
29
29
  const [ticket, ...extras] = positionals;
30
30
  if (ticket === undefined || ticket.length === 0 || extras.length > 0) {
31
- throw new Error("Usage: crew interrupt <ticket> [--reason <text>]");
31
+ throw new Error("Usage: crew stop <ticket> [--reason <text>]");
32
32
  }
33
33
  return { ticket: ticket.toLowerCase(), ...(reason === undefined ? {} : { reason }) };
34
34
  }
@@ -1 +1 @@
1
- {"version":3,"file":"setupRepos.d.ts","sourceRoot":"","sources":["../../src/commands/setupRepos.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAInE,MAAM,WAAW,iBAAiB;IAChC,gDAAgD;IAChD,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;OAIG;IACH,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1B;AAED,MAAM,MAAM,kBAAkB,GAAG,WAAW,GAAG,oBAAoB,GAAG,gBAAgB,CAAC;AAEvF,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,kBAAkB,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,kDAAkD;IAClD,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,oDAAoD;IACpD,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,4CAA4C;IAC5C,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,wEAAwE;IACxE,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,wCAAwC;IACxC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,KAAK,CAAA;KAAE,EAAE,CAAC;IACzC,mEAAmE;IACnE,SAAS,EAAE,OAAO,CAAC;CACpB;AA6JD,wBAAsB,UAAU,CAC9B,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,gBAAgB,CAAC,CA0D3B;AAwBD,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAajE"}
1
+ {"version":3,"file":"setupRepos.d.ts","sourceRoot":"","sources":["../../src/commands/setupRepos.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAInE,MAAM,WAAW,iBAAiB;IAChC,gDAAgD;IAChD,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;OAIG;IACH,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1B;AAED,MAAM,MAAM,kBAAkB,GAAG,WAAW,GAAG,oBAAoB,GAAG,gBAAgB,CAAC;AAEvF,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,kBAAkB,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,kDAAkD;IAClD,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,oDAAoD;IACpD,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,4CAA4C;IAC5C,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,wEAAwE;IACxE,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,wCAAwC;IACxC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,KAAK,CAAA;KAAE,EAAE,CAAC;IACzC,mEAAmE;IACnE,SAAS,EAAE,OAAO,CAAC;CACpB;AA6JD,wBAAsB,UAAU,CAC9B,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,gBAAgB,CAAC,CA0D3B;AAcD,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAajE"}
@@ -10,7 +10,7 @@ import { dirname, isAbsolute, relative, resolve } from "node:path";
10
10
  import { runCommandAsync } from "../lib/commandRunner.js";
11
11
  import { loadConfig } from "../lib/config.js";
12
12
  import { which } from "../lib/host.js";
13
- import { errorMessage, log, writeOutput } from "../lib/util.js";
13
+ import { errorMessage, log, parseDryRunPositionals, writeOutput } from "../lib/util.js";
14
14
  function emptyResult() {
15
15
  return {
16
16
  existing: [],
@@ -190,18 +190,7 @@ export async function setupRepos(config, options) {
190
190
  return result;
191
191
  }
192
192
  function parseArguments(argv) {
193
- let dryRun = false;
194
- const positionals = [];
195
- for (const argument of argv) {
196
- if (argument === "--dry-run") {
197
- dryRun = true;
198
- continue;
199
- }
200
- if (argument.startsWith("-")) {
201
- throw new Error(`Unknown option: ${argument}\nUsage: crew setup repos [--dry-run] [<repo>...]`);
202
- }
203
- positionals.push(argument);
204
- }
193
+ const { dryRun, positionals } = parseDryRunPositionals(argv, "crew setup repos [--dry-run] [<repo>...]");
205
194
  const options = { dryRun };
206
195
  if (positionals.length > 0) {
207
196
  options.only = positionals;
@@ -34,6 +34,17 @@ export declare function lazyLinearClient(factory: () => LinearClient): () => Lin
34
34
  * each subcommand's arg parser stays DRY.
35
35
  */
36
36
  export declare function readTicketArgument(argv: string[], index: number, command: string): string;
37
+ export interface DryRunPositionals {
38
+ dryRun: boolean;
39
+ positionals: string[];
40
+ }
41
+ /**
42
+ * Parses an argv that accepts an optional `--dry-run` flag plus free
43
+ * positionals, rejecting any other dash-prefixed token. Shared by the
44
+ * subcommands whose only flag is `--dry-run` so each parser stays DRY; pass the
45
+ * command's `usage` string for the "Unknown option" error.
46
+ */
47
+ export declare function parseDryRunPositionals(argv: string[], usage: string): DryRunPositionals;
37
48
  export declare function errorMessage(error: unknown): string;
38
49
  export {};
39
50
  //# sourceMappingURL=util.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../src/lib/util.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,wBAAsB,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAoB3E;AAED,wBAAgB,WAAW,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAIlD;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAGhD;AAQD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAEzD;AAED,wBAAsB,uBAAuB,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAOxF;AAkBD,wBAAgB,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAQzC;AAED,KAAK,kBAAkB,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;AAUpF,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,GAAG,IAAI,CAcxF;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGxE;AAED,QAAA,MAAM,sBAAsB,YAAI,2BAA2B,EAAE,gBAAgB,CAAU,CAAC;AAExF,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,sBAAsB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzE,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,kBAAkB,CAAC;CAC5B;AAED,wBAAgB,mBAAmB,IAAI,oBAAoB,GAAG,SAAS,CAQtE;AAED,wBAAgB,eAAe,IAAI,YAAY,CAQ9C;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,YAAY,GAAG,MAAM,YAAY,CAMhF;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAMzF;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAcnD"}
1
+ {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../src/lib/util.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,wBAAsB,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAoB3E;AAED,wBAAgB,WAAW,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAIlD;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAGhD;AAQD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAEzD;AAED,wBAAsB,uBAAuB,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAOxF;AAkBD,wBAAgB,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAQzC;AAED,KAAK,kBAAkB,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;AAUpF,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,GAAG,IAAI,CAcxF;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGxE;AAED,QAAA,MAAM,sBAAsB,YAAI,2BAA2B,EAAE,gBAAgB,CAAU,CAAC;AAExF,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,sBAAsB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzE,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,kBAAkB,CAAC;CAC5B;AAED,wBAAgB,mBAAmB,IAAI,oBAAoB,GAAG,SAAS,CAQtE;AAED,wBAAgB,eAAe,IAAI,YAAY,CAQ9C;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,YAAY,GAAG,MAAM,YAAY,CAMhF;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAMzF;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,OAAO,CAAC;IAChB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,iBAAiB,CAcvF;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAcnD"}
package/dist/lib/util.js CHANGED
@@ -146,6 +146,27 @@ export function readTicketArgument(argv, index, command) {
146
146
  }
147
147
  return value;
148
148
  }
149
+ /**
150
+ * Parses an argv that accepts an optional `--dry-run` flag plus free
151
+ * positionals, rejecting any other dash-prefixed token. Shared by the
152
+ * subcommands whose only flag is `--dry-run` so each parser stays DRY; pass the
153
+ * command's `usage` string for the "Unknown option" error.
154
+ */
155
+ export function parseDryRunPositionals(argv, usage) {
156
+ let dryRun = false;
157
+ const positionals = [];
158
+ for (const argument of argv) {
159
+ if (argument === "--dry-run") {
160
+ dryRun = true;
161
+ continue;
162
+ }
163
+ if (argument.startsWith("-")) {
164
+ throw new Error(`Unknown option: ${argument}\nUsage: ${usage}`);
165
+ }
166
+ positionals.push(argument);
167
+ }
168
+ return { dryRun, positionals };
169
+ }
149
170
  export function errorMessage(error) {
150
171
  if (error instanceof Error) {
151
172
  return error.message;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clipboard-health/groundcrew",
3
- "version": "4.0.2",
3
+ "version": "4.0.3",
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",