@markjaquith/agency 2.17.0 → 2.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/src/cli-parser.ts CHANGED
@@ -25,6 +25,14 @@ const commonOptions = {
25
25
  silent: { type: "boolean", short: "s" },
26
26
  verbose: { type: "boolean", short: "v" },
27
27
  "no-input": { type: "boolean" },
28
+ workbase: { type: "string" },
29
+ cwd: { type: "string" },
30
+ } satisfies OptionConfig
31
+
32
+ const entitySelectorOptions = {
33
+ epic: { type: "string" },
34
+ task: { type: "string" },
35
+ phase: { type: "string" },
28
36
  } satisfies OptionConfig
29
37
 
30
38
  const outputOptions = {
@@ -86,14 +94,18 @@ const commands = {
86
94
  },
87
95
  },
88
96
  workbase: {
89
- usage: "agency workbase <add|list>",
90
- options: outputOptions,
97
+ usage: "agency workbase <add|list|remove|prune|default>",
98
+ options: {
99
+ ...outputOptions,
100
+ name: { type: "string" },
101
+ clear: { type: "boolean" },
102
+ },
91
103
  subcommands: {
92
104
  add: {
93
105
  usage: "agency workbase add <path> [--json]",
94
106
  minArgs: 1,
95
107
  maxArgs: 1,
96
- options: ["json"],
108
+ options: ["json", "name"],
97
109
  },
98
110
  list: {
99
111
  usage: "agency workbase list [--json]",
@@ -101,6 +113,25 @@ const commands = {
101
113
  maxArgs: 0,
102
114
  options: ["json"],
103
115
  },
116
+ remove: {
117
+ usage: "agency workbase remove <selector> [--json]",
118
+ minArgs: 1,
119
+ maxArgs: 1,
120
+ options: ["json"],
121
+ },
122
+ prune: {
123
+ usage: "agency workbase prune [--json]",
124
+ minArgs: 0,
125
+ maxArgs: 0,
126
+ options: ["json"],
127
+ },
128
+ default: {
129
+ usage: "agency workbase default [selector | --clear] [--json]",
130
+ minArgs: 0,
131
+ maxArgs: 1,
132
+ options: ["json", "clear"],
133
+ conflicts: [["clear", "$positional"]],
134
+ },
104
135
  },
105
136
  },
106
137
  integration: {
@@ -147,7 +178,7 @@ const commands = {
147
178
  },
148
179
  epic: {
149
180
  usage: "agency epic <create|list|show>",
150
- options: createOptions,
181
+ options: { ...createOptions, epic: { type: "string" } },
151
182
  subcommands: {
152
183
  create: {
153
184
  usage:
@@ -168,13 +199,13 @@ const commands = {
168
199
  usage: "agency epic show <id> [--json]",
169
200
  minArgs: 1,
170
201
  maxArgs: 1,
171
- options: ["json"],
202
+ options: ["json", "epic"],
172
203
  },
173
204
  },
174
205
  },
175
206
  task: {
176
207
  usage: "agency task <new|create|list|show|status>",
177
- options: taskCreateOptions,
208
+ options: { ...taskCreateOptions, task: { type: "string" } },
178
209
  subcommands: {
179
210
  new: {
180
211
  usage: "agency task new [id] [options]",
@@ -221,19 +252,23 @@ const commands = {
221
252
  usage: "agency task show <id> [--json]",
222
253
  minArgs: 1,
223
254
  maxArgs: 1,
224
- options: ["json"],
255
+ options: ["json", "task"],
225
256
  },
226
257
  status: {
227
258
  usage: "agency task status <id> <status> [--json]",
228
259
  minArgs: 2,
229
260
  maxArgs: 2,
230
- options: ["json"],
261
+ options: ["json", "task"],
231
262
  },
232
263
  },
233
264
  },
234
265
  phase: {
235
266
  usage: "agency phase <create|list|show|status>",
236
- options: phaseCreateOptions,
267
+ options: {
268
+ ...phaseCreateOptions,
269
+ task: { type: "string" },
270
+ phase: { type: "string" },
271
+ },
237
272
  subcommands: {
238
273
  create: {
239
274
  usage:
@@ -249,6 +284,8 @@ const commands = {
249
284
  "depends-on",
250
285
  "first-phase",
251
286
  "json",
287
+ "task",
288
+ "phase",
252
289
  ],
253
290
  required: ["repo", "branch", "base"],
254
291
  repeatable: ["reference", "depends-on"],
@@ -257,25 +294,29 @@ const commands = {
257
294
  usage: "agency phase list <task-id> [--json]",
258
295
  minArgs: 1,
259
296
  maxArgs: 1,
260
- options: ["json"],
297
+ options: ["json", "task"],
261
298
  },
262
299
  show: {
263
300
  usage: "agency phase show <task-id> <phase-id> [--json]",
264
301
  minArgs: 2,
265
302
  maxArgs: 2,
266
- options: ["json"],
303
+ options: ["json", "task", "phase"],
267
304
  },
268
305
  status: {
269
306
  usage: "agency phase status <task-id> <phase-id> <status> [--json]",
270
307
  minArgs: 3,
271
308
  maxArgs: 3,
272
- options: ["json"],
309
+ options: ["json", "task", "phase"],
273
310
  },
274
311
  },
275
312
  },
276
313
  claim: {
277
314
  usage: "agency claim <task-id> [phase-id] [options]",
278
- options: claimOptions,
315
+ options: {
316
+ ...claimOptions,
317
+ task: { type: "string" },
318
+ phase: { type: "string" },
319
+ },
279
320
  command: {
280
321
  usage:
281
322
  "agency claim <task-id> [phase-id] --claimant <id> --runner <id> --session-id <id> --revision <sha256> [--expires-at <timestamp>] [--json]",
@@ -288,19 +329,25 @@ const commands = {
288
329
  "revision",
289
330
  "expires-at",
290
331
  "json",
332
+ "task",
333
+ "phase",
291
334
  ],
292
335
  required: ["claimant", "runner", "session-id", "revision"],
293
336
  },
294
337
  },
295
338
  release: {
296
339
  usage: "agency release <task-id> [phase-id] [options]",
297
- options: ownedClaimOptions,
340
+ options: {
341
+ ...ownedClaimOptions,
342
+ task: { type: "string" },
343
+ phase: { type: "string" },
344
+ },
298
345
  command: {
299
346
  usage:
300
347
  "agency release <task-id> [phase-id] --session-id <id> --revision <sha256> [--json]",
301
348
  minArgs: 1,
302
349
  maxArgs: 2,
303
- options: ["session-id", "revision", "json"],
350
+ options: ["session-id", "revision", "json", "task", "phase"],
304
351
  required: ["session-id", "revision"],
305
352
  },
306
353
  },
@@ -309,13 +356,15 @@ const commands = {
309
356
  options: {
310
357
  ...ownedClaimOptions,
311
358
  outcome: { type: "string" },
359
+ task: { type: "string" },
360
+ phase: { type: "string" },
312
361
  },
313
362
  command: {
314
363
  usage:
315
364
  "agency finish <task-id> [phase-id] --session-id <id> --revision <sha256> --outcome <done|dropped> [--json]",
316
365
  minArgs: 1,
317
366
  maxArgs: 2,
318
- options: ["session-id", "revision", "outcome", "json"],
367
+ options: ["session-id", "revision", "outcome", "json", "task", "phase"],
319
368
  required: ["session-id", "revision", "outcome"],
320
369
  },
321
370
  },
@@ -336,25 +385,25 @@ const commands = {
336
385
  },
337
386
  archive: {
338
387
  usage: "agency archive <epic|task|phase>",
339
- options: outputOptions,
388
+ options: { ...outputOptions, ...entitySelectorOptions },
340
389
  subcommands: {
341
390
  epic: {
342
391
  usage: "agency archive epic <epic-id> [--json]",
343
392
  minArgs: 1,
344
393
  maxArgs: 1,
345
- options: ["json"],
394
+ options: ["json", "epic"],
346
395
  },
347
396
  task: {
348
397
  usage: "agency archive task <task-id> [--json]",
349
398
  minArgs: 1,
350
399
  maxArgs: 1,
351
- options: ["json"],
400
+ options: ["json", "task"],
352
401
  },
353
402
  phase: {
354
403
  usage: "agency archive phase <task-id> <phase-id> [--json]",
355
404
  minArgs: 2,
356
405
  maxArgs: 2,
357
- options: ["json"],
406
+ options: ["json", "task", "phase"],
358
407
  },
359
408
  },
360
409
  },
@@ -363,9 +412,9 @@ const commands = {
363
412
  "agency work [<directory-or-task-id> | --epic <epic-id>] | agency work prepare [target] [--dry-run] [--json]",
364
413
  options: {
365
414
  ...commonOptions,
415
+ ...entitySelectorOptions,
366
416
  json: { type: "boolean" },
367
417
  "dry-run": { type: "boolean" },
368
- epic: { type: "string" },
369
418
  opencode: { type: "boolean" },
370
419
  claude: { type: "boolean" },
371
420
  force: { type: "boolean" },
@@ -375,7 +424,16 @@ const commands = {
375
424
  "agency work [<directory-or-task-id> | --epic <epic-id>] | agency work prepare [target] [--dry-run] [--json]",
376
425
  minArgs: 0,
377
426
  maxArgs: 2,
378
- options: ["json", "dry-run", "epic", "opencode", "claude", "force"],
427
+ options: [
428
+ "json",
429
+ "dry-run",
430
+ "epic",
431
+ "task",
432
+ "phase",
433
+ "opencode",
434
+ "claude",
435
+ "force",
436
+ ],
379
437
  conflicts: [
380
438
  ["opencode", "claude"],
381
439
  ["epic", "$positional"],
@@ -388,6 +446,8 @@ const commands = {
388
446
  ...outputOptions,
389
447
  draft: { type: "boolean" },
390
448
  force: { type: "boolean" },
449
+ task: { type: "string" },
450
+ phase: { type: "string" },
391
451
  },
392
452
  subcommands: {
393
453
  create: {
@@ -395,7 +455,7 @@ const commands = {
395
455
  "agency pr create <task-id> [phase-id] [--draft] [--force] [--json]",
396
456
  minArgs: 1,
397
457
  maxArgs: 2,
398
- options: ["draft", "force", "json"],
458
+ options: ["draft", "force", "json", "task", "phase"],
399
459
  },
400
460
  },
401
461
  },
@@ -438,13 +498,14 @@ const commands = {
438
498
  usage: "agency context [target] [--json] [--compact]",
439
499
  options: {
440
500
  ...outputOptions,
501
+ ...entitySelectorOptions,
441
502
  compact: { type: "boolean" },
442
503
  },
443
504
  command: {
444
505
  usage: "agency context [target] [--json] [--compact]",
445
506
  minArgs: 0,
446
507
  maxArgs: 1,
447
- options: ["json", "compact"],
508
+ options: ["json", "compact", "epic", "task", "phase"],
448
509
  },
449
510
  },
450
511
  graph: {
@@ -495,6 +556,7 @@ const preCommandOptions = new Set([
495
556
  "-v",
496
557
  "--no-input",
497
558
  ])
559
+ const preCommandValueOptions = new Set(["--workbase", "--cwd"])
498
560
 
499
561
  export interface ParsedCli {
500
562
  readonly commandName?: keyof typeof commands
@@ -523,8 +585,22 @@ const usageError = (message: string, usage: string) =>
523
585
  const optionLabel = (name: string) => `--${name}`
524
586
 
525
587
  function findCommandIndex(args: readonly string[]) {
526
- for (const [index, argument] of args.entries()) {
588
+ for (let index = 0; index < args.length; index++) {
589
+ const argument = args[index]!
527
590
  if (!argument.startsWith("-")) return index
591
+ if (argument.startsWith("--workbase=") || argument.startsWith("--cwd=")) {
592
+ continue
593
+ }
594
+ if (preCommandValueOptions.has(argument)) {
595
+ if (args[index + 1] === undefined) {
596
+ throw usageError(
597
+ `Option '${argument}' expects a value.`,
598
+ "agency <command> [options]",
599
+ )
600
+ }
601
+ index++
602
+ continue
603
+ }
528
604
  if (!preCommandOptions.has(argument) && !/^-[hVsv]+$/.test(argument)) {
529
605
  throw usageError(
530
606
  `Unknown option '${argument}'.`,
@@ -535,6 +611,87 @@ function findCommandIndex(args: readonly string[]) {
535
611
  return -1
536
612
  }
537
613
 
614
+ const targetSlots = (
615
+ commandName: string,
616
+ subcommand: string | undefined,
617
+ ): readonly ("epic" | "task" | "phase")[] => {
618
+ if (commandName === "epic" && subcommand === "show") return ["epic"]
619
+ if (commandName === "task" && ["show", "status"].includes(subcommand ?? ""))
620
+ return ["task"]
621
+ if (commandName === "phase")
622
+ return subcommand === "list" ? ["task"] : ["task", "phase"]
623
+ if (["claim", "release", "finish"].includes(commandName))
624
+ return ["task", "phase"]
625
+ if (commandName === "archive")
626
+ return subcommand === "epic"
627
+ ? ["epic"]
628
+ : subcommand === "task"
629
+ ? ["task"]
630
+ : ["task", "phase"]
631
+ if (commandName === "pr" && subcommand === "create") return ["task", "phase"]
632
+ return []
633
+ }
634
+
635
+ function applyEntitySelectors(
636
+ commandName: string,
637
+ subcommand: string | undefined,
638
+ positionals: readonly string[],
639
+ values: ParsedCli["values"],
640
+ spec: LeafCommand,
641
+ ) {
642
+ const supplied = ["epic", "task", "phase"].filter(
643
+ (name) => values[name] !== undefined,
644
+ )
645
+ if (supplied.length === 0) return [...positionals]
646
+ if (values.phase !== undefined && values.task === undefined) {
647
+ throw usageError("Option '--phase' requires '--task'.", spec.usage)
648
+ }
649
+ if (values.epic !== undefined && (values.task || values.phase)) {
650
+ throw usageError(
651
+ "Option '--epic' cannot be combined with '--task' or '--phase'.",
652
+ spec.usage,
653
+ )
654
+ }
655
+ if (commandName === "work" || commandName === "context") {
656
+ if (
657
+ positionals.length >
658
+ (commandName === "work" && positionals[0] === "prepare" ? 1 : 0)
659
+ ) {
660
+ throw usageError(
661
+ "Entity selector options cannot be combined with a positional target.",
662
+ spec.usage,
663
+ )
664
+ }
665
+ return [...positionals]
666
+ }
667
+
668
+ const slots = targetSlots(commandName, subcommand)
669
+ const selectedSlots = slots.filter((slot) => values[slot] !== undefined)
670
+ if (selectedSlots.length === 0) return [...positionals]
671
+ const trailingCount = spec.maxArgs - slots.length
672
+ if (positionals.length > trailingCount) {
673
+ throw usageError(
674
+ "Entity selector options cannot be combined with positional target IDs.",
675
+ spec.usage,
676
+ )
677
+ }
678
+ const requiredSlots = slots.slice(0, spec.minArgs - trailingCount)
679
+ for (const slot of requiredSlots) {
680
+ if (values[slot] === undefined) {
681
+ throw usageError(
682
+ `Option '--${slot}' is required with explicit selectors.`,
683
+ spec.usage,
684
+ )
685
+ }
686
+ }
687
+ return [
688
+ ...slots.flatMap((slot) =>
689
+ typeof values[slot] === "string" ? [values[slot] as string] : [],
690
+ ),
691
+ ...positionals,
692
+ ]
693
+ }
694
+
538
695
  function assertNoDuplicateOptions(
539
696
  tokens: readonly { readonly kind: string; readonly name?: string }[],
540
697
  repeatable: ReadonlySet<string>,
@@ -674,7 +831,7 @@ export function parseCli(args: readonly string[]): ParsedCli {
674
831
  throw usageError(message, definition.usage)
675
832
  }
676
833
 
677
- const commandPositionals = definition.subcommands
834
+ let commandPositionals = definition.subcommands
678
835
  ? parsed.positionals.slice(1)
679
836
  : parsed.positionals
680
837
  const allowed = new Set([...commonOptionNames, ...(spec.options ?? [])])
@@ -694,6 +851,20 @@ export function parseCli(args: readonly string[]): ParsedCli {
694
851
  spec.usage,
695
852
  )
696
853
  }
854
+ for (const selector of ["workbase", "cwd", "epic", "task", "phase"]) {
855
+ if (parsed.values[selector] === "") {
856
+ throw usageError(
857
+ `Option '--${selector}' requires a non-empty value.`,
858
+ spec.usage,
859
+ )
860
+ }
861
+ }
862
+ if (parsed.values.workbase && parsed.values.cwd) {
863
+ throw usageError(
864
+ "Options '--workbase' and '--cwd' cannot be combined.",
865
+ spec.usage,
866
+ )
867
+ }
697
868
  if (parsed.values.version) {
698
869
  return {
699
870
  commandName: commandName as keyof typeof commands,
@@ -709,6 +880,14 @@ export function parseCli(args: readonly string[]): ParsedCli {
709
880
  }
710
881
  }
711
882
 
883
+ commandPositionals = applyEntitySelectors(
884
+ commandName,
885
+ subcommand,
886
+ commandPositionals,
887
+ parsed.values,
888
+ spec,
889
+ )
890
+
712
891
  if (
713
892
  commandPositionals.length < spec.minArgs ||
714
893
  commandPositionals.length > spec.maxArgs
@@ -782,7 +961,9 @@ export function parseCli(args: readonly string[]): ParsedCli {
782
961
 
783
962
  return {
784
963
  commandName: commandName as keyof typeof commands,
785
- args: parsed.positionals,
964
+ args: definition.subcommands
965
+ ? [subcommand!, ...commandPositionals]
966
+ : commandPositionals,
786
967
  values: parsed.values,
787
968
  }
788
969
  }
package/src/cli.test.ts CHANGED
@@ -217,6 +217,15 @@ describe("CLI", () => {
217
217
  await expect(access(root)).rejects.toThrow()
218
218
  })
219
219
 
220
+ test("resolves relative init paths from explicit cwd", async () => {
221
+ const parent = await createTempDir()
222
+ tempDirs.push(parent)
223
+ const result = parseJson(
224
+ await runCli(["init", "child", "--cwd", parent, "--json"], projectRoot),
225
+ )
226
+ expect(result.root).toBe(join(parent, "child"))
227
+ })
228
+
220
229
  test("refuses guided input without a TTY or with --no-input", async () => {
221
230
  for (const args of [
222
231
  ["task", "new"],
@@ -349,12 +358,93 @@ describe("CLI", () => {
349
358
  ).toEqual({
350
359
  root,
351
360
  })
352
- expect(
353
- parseJson(await runCli(["workbase", "add", root, "--json"], parent, env)),
354
- ).toEqual({ path: await realpath(root) })
361
+ const registration = parseJson(
362
+ await runCli(
363
+ [
364
+ "workbase",
365
+ "add",
366
+ "workbase",
367
+ "--cwd",
368
+ parent,
369
+ "--name",
370
+ "primary",
371
+ "--json",
372
+ ],
373
+ projectRoot,
374
+ env,
375
+ ),
376
+ )
377
+ expect(registration).toMatchObject({
378
+ name: "primary",
379
+ path: await realpath(root),
380
+ })
355
381
  expect(
356
382
  parseJson(await runCli(["workbase", "list", "--json"], parent, env)),
357
- ).toEqual([await realpath(root)])
383
+ ).toEqual({ workbases: [registration] })
384
+
385
+ await mkdir(join(root, "repos", "agency"), { recursive: true })
386
+ parseJson(
387
+ await runCli(
388
+ [
389
+ "task",
390
+ "create",
391
+ "explicit",
392
+ "--repo",
393
+ "agency",
394
+ "--workbase",
395
+ "primary",
396
+ "--no-input",
397
+ "--json",
398
+ ],
399
+ parent,
400
+ env,
401
+ ),
402
+ )
403
+ const shown = parseJson(
404
+ await runCli(
405
+ [
406
+ "task",
407
+ "show",
408
+ "--task",
409
+ "explicit",
410
+ "--workbase",
411
+ registration.id,
412
+ "--no-input",
413
+ "--json",
414
+ ],
415
+ parent,
416
+ env,
417
+ ),
418
+ )
419
+ expect(shown.id).toBe("explicit")
420
+ const inferred = parseJson(
421
+ await runCli(
422
+ ["context", "--cwd", join(root, "tasks", "explicit"), "--json"],
423
+ projectRoot,
424
+ env,
425
+ ),
426
+ )
427
+ expect(inferred.target).toMatchObject({
428
+ kind: "task",
429
+ taskId: "explicit",
430
+ })
431
+
432
+ parseJson(
433
+ await runCli(["workbase", "default", "primary", "--json"], parent, env),
434
+ )
435
+ expect(
436
+ parseJson(await runCli(["task", "list", "--json"], parent, env)),
437
+ ).toHaveLength(1)
438
+ const explicitOutside = await runCli(
439
+ ["task", "list", "--cwd", parent, "--json"],
440
+ projectRoot,
441
+ env,
442
+ )
443
+ expect(explicitOutside.exitCode).toBe(1)
444
+ expect(explicitOutside.stderr).toBe("")
445
+ expect(JSON.parse(explicitOutside.stdout).error.message).toContain(
446
+ "No Agency workbase found from",
447
+ )
358
448
  })
359
449
 
360
450
  test("exports equivalent JSON and JSONL graph contracts", async () => {
@@ -26,4 +26,7 @@ defaults to the current directory and may be an entity path or task ID.
26
26
  Options:
27
27
  --json Output a versioned machine result
28
28
  --compact Omit prose bodies and low-level Git details
29
+ --epic <id> Select an epic
30
+ --task <id> Select a task
31
+ --phase <id> Select a phase together with --task
29
32
  `
@@ -1,4 +1,5 @@
1
1
  import { Effect } from "effect"
2
+ import { resolve } from "node:path"
2
3
  import type { BaseCommandOptions } from "../utils/command"
3
4
  import { WorkbaseService } from "../services/WorkbaseService"
4
5
  import { createLoggers } from "../utils/effect"
@@ -11,8 +12,9 @@ export const init = (options: InitOptions = {}) =>
11
12
  Effect.gen(function* () {
12
13
  const workbase = yield* WorkbaseService
13
14
  const { log } = createLoggers(options)
15
+ const cwd = options.cwd ?? process.cwd()
14
16
  const root = yield* workbase.initialize(
15
- options.path ?? options.cwd ?? process.cwd(),
17
+ options.path ? resolve(cwd, options.path) : cwd,
16
18
  )
17
19
  log(
18
20
  options.json
@@ -80,6 +80,7 @@ pr: null
80
80
  : Effect.succeed(path)
81
81
  },
82
82
  listRegistered: () => Effect.succeed(["/first", "/selected"]),
83
+ getDefault: () => Effect.succeed(undefined),
83
84
  validate: (path: string) =>
84
85
  Effect.succeed({
85
86
  root: path,
@@ -116,6 +117,7 @@ pr: null
116
117
  message: "No Agency workbase found from /outside",
117
118
  }),
118
119
  listRegistered: () => Effect.succeed(["/selected"]),
120
+ getDefault: () => Effect.succeed(undefined),
119
121
  }
120
122
  const fs = {
121
123
  runCommand: () => Effect.fail(new Error("unexpected fzf probe")),
@@ -98,6 +98,7 @@ const createHarness = (options: HarnessOptions = {}) => {
98
98
  })
99
99
  : Effect.succeed("/workbase"),
100
100
  listRegistered: () => Effect.succeed(options.registeredWorkbases ?? []),
101
+ getDefault: () => Effect.succeed(undefined),
101
102
  loadConfig: () =>
102
103
  Effect.succeed({
103
104
  root: "/workbase",
@@ -297,9 +297,13 @@ export const workPrepare = (options: WorkOptions = {}) =>
297
297
  const isDirectory = yield* fs.isDirectory(targetPath)
298
298
  const root = yield* workbase.discover(isDirectory ? targetPath : cwd)
299
299
 
300
- let taskId: string | undefined
301
- let phaseId: string | undefined
302
- if (options.directory && !isDirectory) {
300
+ let taskId = options.taskId
301
+ let phaseId = options.phaseId
302
+ if (taskId) {
303
+ const task = yield* tasks.show(taskId, root)
304
+ taskId = task.id
305
+ if (phaseId) phaseId = (yield* phases.show(task.id, phaseId, root)).id
306
+ } else if (options.directory && !isDirectory) {
303
307
  const task = yield* tasks.show(options.directory, root)
304
308
  taskId = task.id
305
309
  } else {
@@ -353,12 +357,16 @@ launching an agent or changing lifecycle status. --dry-run reports planned Git
353
357
  changes without fetching, creating branches, or creating worktrees.
354
358
 
355
359
  Options:
356
- --epic <id> Work on an epic
360
+ --epic <id> Work on an epic
361
+ --task <id> Work on a task
362
+ --phase <id> Work on a phase selected with --task
363
+ --workbase <target> Select a workbase by ID, name, or path
364
+ --cwd <path> Resolve context from a specific directory
357
365
  --opencode Require OpenCode
358
366
  --claude Require Claude Code
359
367
  --force Override readiness and terminal-state guards
360
- --no-input Never open an interactive selector
368
+ --no-input Never open an interactive selector
361
369
 
362
- Without interactive input, provide a directory, task ID, or --epic and run the
363
- command from a workbase. Workbase and target selection otherwise fail.
370
+ Without interactive input, provide an explicit workbase or cwd and an entity
371
+ selector. Workbase and target selection otherwise fail.
364
372
  `