@markjaquith/agency 2.20.0 → 2.22.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
@@ -408,6 +408,9 @@ agency epic create <id> --ticket-url <url> [--description <text>] [--json]
408
408
  --repo <alias>:<ref> [--repo <alias>:<ref>...]
409
409
  agency epic list [filters] [--json]
410
410
  agency epic show <id> [--json]
411
+ agency epic update <id> [--ticket-url <url>] [--description <text>]
412
+ [--clear-description] [--repo <alias>:<ref>...] [--json]
413
+ agency epic rename <id> <new-id> [--json]
411
414
  ```
412
415
 
413
416
  Creating a task with `--epic <id>` adds the task to the epic and writes the task
@@ -463,8 +466,18 @@ Inspect tasks:
463
466
  agency task list [filters] [--json]
464
467
  agency task show <id> [--json]
465
468
  agency task status <id> <open|done|dropped> [--json]
469
+ agency task update <id> [metadata options] [--json]
470
+ agency task rename <id> <new-id> [--json]
471
+ agency task move <id> (--epic <epic-id> | --no-epic) [--json]
472
+ agency task dependency <add|remove> <task-id> <dependency-id> [--json]
466
473
  ```
467
474
 
475
+ Task updates can replace or clear descriptions, tickets, repository references,
476
+ and pull request URLs, or replace writable repository, branch, and base metadata.
477
+ Execution metadata changes refuse to run while code is materialized. Moving a
478
+ task with scoped incoming or outgoing dependencies also refuses until those
479
+ dependencies are removed.
480
+
468
481
  To add a phase to an existing single-phase task, name the phase that will own
469
482
  the task's current execution fields with `--first-phase`:
470
483
 
@@ -490,8 +503,18 @@ agency phase create <task-id> <phase-id>
490
503
  agency phase list <task-id> [filters] [--json]
491
504
  agency phase show <task-id> <phase-id> [--json]
492
505
  agency phase status <task-id> <phase-id> <open|done|dropped> [--json]
506
+ agency phase update <task-id> <phase-id> [metadata options] [--json]
507
+ agency phase rename <task-id> <phase-id> <new-id> [--json]
508
+ agency phase dependency <add|remove> <task-id> <phase-id> <dependency-id>
509
+ [--json]
493
510
  ```
494
511
 
512
+ Dependency additions append without reordering existing declarations and reject
513
+ unknown IDs, self-dependencies, and cycles. Rename operations update structured
514
+ references as one rollback-capable mutation and refuse when a materialized
515
+ worktree would make the directory move unsafe. Mutation JSON includes changed
516
+ paths and the focused validation scope.
517
+
495
518
  Single-phase tasks and phases store status in YAML. New execution units start
496
519
  `open`, and `agency work` marks the selected execution unit `working` immediately
497
520
  before launch. Use claims for coordinated ownership and the status subcommands
package/cli.ts CHANGED
@@ -38,6 +38,7 @@ import { GraphService } from "./src/services/GraphService"
38
38
  import { ClaimService } from "./src/services/ClaimService"
39
39
  import { SyncService } from "./src/services/SyncService"
40
40
  import { ReadinessService } from "./src/services/ReadinessService"
41
+ import { GraphMutationService } from "./src/services/GraphMutationService"
41
42
  import {
42
43
  claimCommand,
43
44
  claimHelp,
@@ -68,6 +69,7 @@ const CliLayer = Layer.mergeAll(
68
69
  ClaimService.Default,
69
70
  SyncService.Default,
70
71
  ReadinessService.Default,
72
+ GraphMutationService.Default,
71
73
  )
72
74
 
73
75
  /**
@@ -235,6 +237,7 @@ const commands: Record<string, Command> = {
235
237
  args: args.slice(1),
236
238
  ticketUrl: options["ticket-url"],
237
239
  description: options.description,
240
+ clearDescription: options["clear-description"],
238
241
  repos: options.repo,
239
242
  json: options.json,
240
243
  statuses: options.status,
@@ -278,10 +281,14 @@ const commands: Record<string, Command> = {
278
281
  subcommand: args[0],
279
282
  args: args.slice(1),
280
283
  description: options.description,
284
+ clearDescription: options["clear-description"],
281
285
  repo: options.repo?.[0],
282
286
  references: options.reference,
283
287
  branch: options.branch,
284
288
  base: options.base,
289
+ clearReferences: options["clear-references"],
290
+ prUrl: options["pr-url"],
291
+ clearPr: options["clear-pr"],
285
292
  dependsOn: options["depends-on"],
286
293
  firstPhase: options["first-phase"],
287
294
  json: options.json,
@@ -381,12 +388,18 @@ const commands: Record<string, Command> = {
381
388
  subcommand: args[0],
382
389
  args: args.slice(1),
383
390
  ticketUrl: options["ticket-url"],
391
+ clearTicket: options["clear-ticket"],
384
392
  description: options.description,
393
+ clearDescription: options["clear-description"],
385
394
  epic: options.epic,
386
395
  repo: options.repo?.[0],
387
396
  references: options.reference,
388
397
  branch: options.branch,
389
398
  base: options.base,
399
+ clearReferences: options["clear-references"],
400
+ prUrl: options["pr-url"],
401
+ clearPr: options["clear-pr"],
402
+ noEpic: options["no-epic"],
390
403
  multiPhase: options["multi-phase"],
391
404
  json: options.json,
392
405
  statuses: options.status,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markjaquith/agency",
3
- "version": "2.20.0",
3
+ "version": "2.22.0",
4
4
  "description": "Manage agentic work across repositories with durable workbases",
5
5
  "keywords": [
6
6
  "agents",
@@ -387,7 +387,51 @@ describe("strict CLI parsing", () => {
387
387
  )
388
388
  expectUsageError(
389
389
  ["task", "crate"],
390
- "agency task <new|create|list|show|status>",
390
+ "agency task <new|create|list|show|status|update|rename|move|dependency>",
391
+ )
392
+ })
393
+
394
+ test("parses graph mutation commands and rejects unsafe ambiguity", () => {
395
+ expect(
396
+ parseCli([
397
+ "task",
398
+ "update",
399
+ "example",
400
+ "--description",
401
+ "Revised",
402
+ "--reference",
403
+ "docs:main",
404
+ "--reference",
405
+ "api:main",
406
+ "--clear-pr",
407
+ ]),
408
+ ).toMatchObject({
409
+ args: ["update", "example"],
410
+ values: {
411
+ description: "Revised",
412
+ reference: ["docs:main", "api:main"],
413
+ "clear-pr": true,
414
+ },
415
+ })
416
+ expect(
417
+ parseCli(["task", "move", "example", "--no-epic"]).values,
418
+ ).toMatchObject({
419
+ "no-epic": true,
420
+ })
421
+ expect(parseCli(["phase", "rename", "task", "old", "new"]).args).toEqual([
422
+ "rename",
423
+ "task",
424
+ "old",
425
+ "new",
426
+ ])
427
+ expect(() => parseCli(["task", "update", "example"])).toThrow(
428
+ "At least one update option",
429
+ )
430
+ expect(() =>
431
+ parseCli(["task", "move", "example", "--epic", "one", "--no-epic"]),
432
+ ).toThrow("cannot be combined")
433
+ expect(() => parseCli(["task", "dependency", "replace", "a", "b"])).toThrow(
434
+ "must be 'add' or 'remove'",
391
435
  )
392
436
  })
393
437
 
package/src/cli-parser.ts CHANGED
@@ -90,6 +90,15 @@ const phaseCreateOptions = {
90
90
  "first-phase": { type: "string" },
91
91
  } satisfies OptionConfig
92
92
 
93
+ const mutationOptions = {
94
+ "clear-description": { type: "boolean" },
95
+ "clear-ticket": { type: "boolean" },
96
+ "clear-references": { type: "boolean" },
97
+ "pr-url": { type: "string" },
98
+ "clear-pr": { type: "boolean" },
99
+ "no-epic": { type: "boolean" },
100
+ } satisfies OptionConfig
101
+
93
102
  const claimOptions = {
94
103
  ...outputOptions,
95
104
  claimant: { type: "string" },
@@ -200,8 +209,13 @@ const commands = {
200
209
  },
201
210
  },
202
211
  epic: {
203
- usage: "agency epic <create|list|show>",
204
- options: { ...createOptions, ...viewOptions, epic: { type: "string" } },
212
+ usage: "agency epic <create|list|show|update|rename>",
213
+ options: {
214
+ ...createOptions,
215
+ ...viewOptions,
216
+ ...mutationOptions,
217
+ epic: { type: "string" },
218
+ },
205
219
  subcommands: {
206
220
  create: {
207
221
  usage:
@@ -226,11 +240,38 @@ const commands = {
226
240
  maxArgs: 1,
227
241
  options: ["json", "epic"],
228
242
  },
243
+ update: {
244
+ usage: "agency epic update <id> [options] [--json]",
245
+ minArgs: 1,
246
+ maxArgs: 1,
247
+ options: [
248
+ "description",
249
+ "clear-description",
250
+ "ticket-url",
251
+ "repo",
252
+ "json",
253
+ ],
254
+ repeatable: ["repo"],
255
+ conflicts: [["description", "clear-description"]],
256
+ },
257
+ rename: {
258
+ usage: "agency epic rename <id> <new-id> [--json]",
259
+ minArgs: 2,
260
+ maxArgs: 2,
261
+ options: ["json"],
262
+ },
229
263
  },
230
264
  },
231
265
  task: {
232
- usage: "agency task <new|create|list|show|status>",
233
- options: { ...taskCreateOptions, ...viewOptions, task: { type: "string" } },
266
+ usage:
267
+ "agency task <new|create|list|show|status|update|rename|move|dependency>",
268
+ options: {
269
+ ...taskCreateOptions,
270
+ ...viewOptions,
271
+ ...mutationOptions,
272
+ "pr-url": { type: "string" },
273
+ task: { type: "string" },
274
+ },
234
275
  subcommands: {
235
276
  new: {
236
277
  usage: "agency task new [id] [options]",
@@ -287,13 +328,61 @@ const commands = {
287
328
  maxArgs: 2,
288
329
  options: ["json", "task"],
289
330
  },
331
+ update: {
332
+ usage: "agency task update <id> [options] [--json]",
333
+ minArgs: 1,
334
+ maxArgs: 1,
335
+ options: [
336
+ "ticket-url",
337
+ "clear-ticket",
338
+ "description",
339
+ "clear-description",
340
+ "repo",
341
+ "reference",
342
+ "clear-references",
343
+ "branch",
344
+ "base",
345
+ "pr-url",
346
+ "clear-pr",
347
+ "json",
348
+ ],
349
+ repeatable: ["reference"],
350
+ conflicts: [
351
+ ["ticket-url", "clear-ticket"],
352
+ ["description", "clear-description"],
353
+ ["reference", "clear-references"],
354
+ ["pr-url", "clear-pr"],
355
+ ],
356
+ },
357
+ rename: {
358
+ usage: "agency task rename <id> <new-id> [--json]",
359
+ minArgs: 2,
360
+ maxArgs: 2,
361
+ options: ["json"],
362
+ },
363
+ move: {
364
+ usage: "agency task move <id> (--epic <id> | --no-epic) [--json]",
365
+ minArgs: 1,
366
+ maxArgs: 1,
367
+ options: ["epic", "no-epic", "json"],
368
+ conflicts: [["epic", "no-epic"]],
369
+ },
370
+ dependency: {
371
+ usage:
372
+ "agency task dependency <add|remove> <task-id> <dependency-id> [--json]",
373
+ minArgs: 3,
374
+ maxArgs: 3,
375
+ options: ["json"],
376
+ },
290
377
  },
291
378
  },
292
379
  phase: {
293
- usage: "agency phase <create|list|show|status>",
380
+ usage: "agency phase <create|list|show|status|update|rename|dependency>",
294
381
  options: {
295
382
  ...phaseCreateOptions,
296
383
  ...viewOptions,
384
+ ...mutationOptions,
385
+ "pr-url": { type: "string" },
297
386
  task: { type: "string" },
298
387
  phase: { type: "string" },
299
388
  },
@@ -338,6 +427,42 @@ const commands = {
338
427
  maxArgs: 3,
339
428
  options: ["json", "task", "phase"],
340
429
  },
430
+ update: {
431
+ usage: "agency phase update <task-id> <phase-id> [options] [--json]",
432
+ minArgs: 2,
433
+ maxArgs: 2,
434
+ options: [
435
+ "description",
436
+ "clear-description",
437
+ "repo",
438
+ "reference",
439
+ "clear-references",
440
+ "branch",
441
+ "base",
442
+ "pr-url",
443
+ "clear-pr",
444
+ "json",
445
+ ],
446
+ repeatable: ["reference"],
447
+ conflicts: [
448
+ ["description", "clear-description"],
449
+ ["reference", "clear-references"],
450
+ ["pr-url", "clear-pr"],
451
+ ],
452
+ },
453
+ rename: {
454
+ usage: "agency phase rename <task-id> <phase-id> <new-id> [--json]",
455
+ minArgs: 3,
456
+ maxArgs: 3,
457
+ options: ["json"],
458
+ },
459
+ dependency: {
460
+ usage:
461
+ "agency phase dependency <add|remove> <task-id> <phase-id> <dependency-id> [--json]",
462
+ minArgs: 4,
463
+ maxArgs: 4,
464
+ options: ["json"],
465
+ },
341
466
  },
342
467
  },
343
468
  claim: {
@@ -977,6 +1102,34 @@ export function parseCli(args: readonly string[]): ParsedCli {
977
1102
  ) {
978
1103
  validateTaskCreate(parsed.values, spec, subcommand === "create")
979
1104
  }
1105
+ if (["task", "phase"].includes(commandName) && subcommand === "dependency") {
1106
+ if (!["add", "remove"].includes(commandPositionals[0] ?? "")) {
1107
+ throw usageError(
1108
+ "Dependency operation must be 'add' or 'remove'.",
1109
+ spec.usage,
1110
+ )
1111
+ }
1112
+ }
1113
+ if (commandName === "task" && subcommand === "move") {
1114
+ if (
1115
+ parsed.values.epic === undefined &&
1116
+ parsed.values["no-epic"] === undefined
1117
+ ) {
1118
+ throw usageError(
1119
+ "One of '--epic' or '--no-epic' is required.",
1120
+ spec.usage,
1121
+ )
1122
+ }
1123
+ }
1124
+ if (
1125
+ ["epic", "task", "phase"].includes(commandName) &&
1126
+ subcommand === "update"
1127
+ ) {
1128
+ const mutationNames = (spec.options ?? []).filter((name) => name !== "json")
1129
+ if (!mutationNames.some((name) => parsed.values[name] !== undefined)) {
1130
+ throw usageError("At least one update option is required.", spec.usage)
1131
+ }
1132
+ }
980
1133
  if (commandName === "graph") {
981
1134
  validateGraphOptions(parsed.values, spec)
982
1135
  }
package/src/cli.test.ts CHANGED
@@ -166,6 +166,68 @@ describe("CLI", () => {
166
166
  expect(finished.claim).toMatchObject({ state: "finished", outcome: "done" })
167
167
  })
168
168
 
169
+ test("routes graph mutations through structured output", async () => {
170
+ const root = await createTempDir()
171
+ tempDirs.push(root)
172
+ await Bun.write(join(root, "agency.json"), '{"version":2}\n')
173
+ await mkdir(join(root, "repos", "agency"), { recursive: true })
174
+ parseJson(
175
+ await runCli(
176
+ [
177
+ "epic",
178
+ "create",
179
+ "delivery",
180
+ "--ticket-url",
181
+ "https://example.com/delivery",
182
+ "--repo",
183
+ "agency:main",
184
+ "--json",
185
+ ],
186
+ root,
187
+ ),
188
+ )
189
+ for (const id of ["first", "second"]) {
190
+ parseJson(
191
+ await runCli(
192
+ [
193
+ "task",
194
+ "create",
195
+ id,
196
+ "--repo",
197
+ "agency",
198
+ "--epic",
199
+ "delivery",
200
+ "--json",
201
+ ],
202
+ root,
203
+ ),
204
+ )
205
+ }
206
+
207
+ const updated = parseJson(
208
+ await runCli(
209
+ ["task", "update", "second", "--description", "Revised", "--json"],
210
+ root,
211
+ ),
212
+ )
213
+ expect(updated).toMatchObject({
214
+ operation: "task.update",
215
+ entity: { kind: "task", id: "second" },
216
+ validation: { valid: true, scope: ["tasks/second/TASK.md"] },
217
+ })
218
+
219
+ const dependency = parseJson(
220
+ await runCli(
221
+ ["task", "dependency", "add", "second", "first", "--json"],
222
+ root,
223
+ ),
224
+ )
225
+ expect(dependency).toMatchObject({
226
+ operation: "task.dependency.add",
227
+ changedPaths: ["epics/delivery/EPIC.md"],
228
+ })
229
+ })
230
+
169
231
  test("emits one versioned error envelope for usage and command failures", async () => {
170
232
  const usage = await runCli(["unknown", "--json"])
171
233
  expect(usage.exitCode).toBe(1)
@@ -5,12 +5,14 @@ import { createLoggers } from "../utils/effect"
5
5
  import { formatTable } from "../utils/table"
6
6
  import { getWorkViews } from "../work-view"
7
7
  import { parseRepositoryReferences } from "../workbase/repository-reference"
8
+ import { GraphMutationService } from "../services/GraphMutationService"
8
9
 
9
10
  interface EpicOptions extends BaseCommandOptions {
10
11
  readonly subcommand?: string
11
12
  readonly args: readonly string[]
12
13
  readonly ticketUrl?: string
13
14
  readonly description?: string
15
+ readonly clearDescription?: boolean
14
16
  readonly repos?: readonly string[]
15
17
  readonly json?: boolean
16
18
  readonly statuses?: readonly string[]
@@ -23,6 +25,7 @@ interface EpicOptions extends BaseCommandOptions {
23
25
  export const epic = (options: EpicOptions) =>
24
26
  Effect.gen(function* () {
25
27
  const epics = yield* EpicService
28
+ const mutations = yield* GraphMutationService
26
29
  const { log } = createLoggers(options)
27
30
  const cwd = options.cwd ?? process.cwd()
28
31
 
@@ -105,10 +108,49 @@ export const epic = (options: EpicOptions) =>
105
108
  return
106
109
  }
107
110
 
111
+ case "update": {
112
+ const id = options.args[0]
113
+ if (!id) return yield* Effect.fail(new Error("Epic ID is required"))
114
+ const output = yield* mutations.updateEpic(
115
+ id,
116
+ {
117
+ description: options.clearDescription ? null : options.description,
118
+ ticketUrl: options.ticketUrl,
119
+ repos:
120
+ options.repos === undefined
121
+ ? undefined
122
+ : parseRepositoryReferences(options.repos),
123
+ },
124
+ cwd,
125
+ )
126
+ log(
127
+ options.json
128
+ ? JSON.stringify(output, null, 2)
129
+ : `Updated epic '${id}'`,
130
+ )
131
+ return
132
+ }
133
+
134
+ case "rename": {
135
+ const [id, newId] = options.args
136
+ if (!id || !newId) {
137
+ return yield* Effect.fail(
138
+ new Error("Epic ID and new ID are required"),
139
+ )
140
+ }
141
+ const output = yield* mutations.renameEpic(id, newId, cwd)
142
+ log(
143
+ options.json
144
+ ? JSON.stringify(output, null, 2)
145
+ : `Renamed epic '${id}' to '${newId}'`,
146
+ )
147
+ return
148
+ }
149
+
108
150
  default:
109
151
  return yield* Effect.fail(
110
152
  new Error(
111
- "Subcommand is required. Available subcommands: create, list, show",
153
+ "Subcommand is required. Available subcommands: create, list, show, update, rename",
112
154
  ),
113
155
  )
114
156
  }
@@ -121,12 +163,20 @@ Subcommands:
121
163
  create <id> Create an epic
122
164
  list List epics
123
165
  show <id> Show an epic
166
+ update <id> Update epic metadata
167
+ rename <id> <new-id> Rename an epic and update task references
124
168
 
125
169
  Create options:
126
170
  --ticket-url <url> External ticket URL
127
171
  --description <text> Short description of the epic
128
172
  --repo <alias>:<ref> Read-only repository reference; repeatable
129
173
 
174
+ Update options:
175
+ --ticket-url <url> Replace the external ticket URL
176
+ --description <text> Replace the description
177
+ --clear-description Remove the description
178
+ --repo <alias>:<ref> Replace repository references; repeatable
179
+
130
180
  Options:
131
181
  --json Output results as JSON
132
182
  --status <status> Filter list by status; repeatable
@@ -5,15 +5,20 @@ import { createLoggers } from "../utils/effect"
5
5
  import { formatTable } from "../utils/table"
6
6
  import { getWorkViews } from "../work-view"
7
7
  import { parseRepositoryReferences } from "../workbase/repository-reference"
8
+ import { GraphMutationService } from "../services/GraphMutationService"
8
9
 
9
10
  interface PhaseOptions extends BaseCommandOptions {
10
11
  readonly subcommand?: string
11
12
  readonly args: readonly string[]
12
13
  readonly description?: string
14
+ readonly clearDescription?: boolean
13
15
  readonly repo?: string
14
16
  readonly references?: readonly string[]
15
17
  readonly branch?: string
16
18
  readonly base?: string
19
+ readonly clearReferences?: boolean
20
+ readonly prUrl?: string
21
+ readonly clearPr?: boolean
17
22
  readonly dependsOn?: readonly string[]
18
23
  readonly firstPhase?: string
19
24
  readonly json?: boolean
@@ -27,6 +32,7 @@ interface PhaseOptions extends BaseCommandOptions {
27
32
  export const phase = (options: PhaseOptions) =>
28
33
  Effect.gen(function* () {
29
34
  const phases = yield* PhaseService
35
+ const mutations = yield* GraphMutationService
30
36
  const { log } = createLoggers(options)
31
37
  const cwd = options.cwd ?? process.cwd()
32
38
  const [taskId, phaseId] = options.args
@@ -153,10 +159,84 @@ export const phase = (options: PhaseOptions) =>
153
159
  )
154
160
  return
155
161
  }
162
+ case "update": {
163
+ if (!taskId || !phaseId) {
164
+ return yield* Effect.fail(
165
+ new Error("Task ID and phase ID are required"),
166
+ )
167
+ }
168
+ const output = yield* mutations.updatePhase(
169
+ taskId,
170
+ phaseId,
171
+ {
172
+ description: options.clearDescription ? null : options.description,
173
+ repo: options.repo,
174
+ repos: options.clearReferences
175
+ ? null
176
+ : options.references === undefined
177
+ ? undefined
178
+ : parseRepositoryReferences(options.references),
179
+ branch: options.branch,
180
+ base: options.base,
181
+ pr: options.clearPr ? null : options.prUrl,
182
+ },
183
+ cwd,
184
+ )
185
+ log(
186
+ options.json
187
+ ? JSON.stringify(output, null, 2)
188
+ : `Updated phase '${phaseId}'`,
189
+ )
190
+ return
191
+ }
192
+ case "rename": {
193
+ const newId = options.args[2]
194
+ if (!taskId || !phaseId || !newId) {
195
+ return yield* Effect.fail(
196
+ new Error("Task ID, phase ID, and new ID are required"),
197
+ )
198
+ }
199
+ const output = yield* mutations.renamePhase(taskId, phaseId, newId, cwd)
200
+ log(
201
+ options.json
202
+ ? JSON.stringify(output, null, 2)
203
+ : `Renamed phase '${phaseId}' to '${newId}'`,
204
+ )
205
+ return
206
+ }
207
+ case "dependency": {
208
+ const [operation, dependencyTaskId, dependencyPhaseId, dependencyId] =
209
+ options.args
210
+ if (
211
+ (operation !== "add" && operation !== "remove") ||
212
+ !dependencyTaskId ||
213
+ !dependencyPhaseId ||
214
+ !dependencyId
215
+ ) {
216
+ return yield* Effect.fail(
217
+ new Error(
218
+ "Usage: agency phase dependency <add|remove> <task-id> <phase-id> <dependency-id>",
219
+ ),
220
+ )
221
+ }
222
+ const output = yield* mutations.mutatePhaseDependency(
223
+ operation,
224
+ dependencyTaskId,
225
+ dependencyPhaseId,
226
+ dependencyId,
227
+ cwd,
228
+ )
229
+ log(
230
+ options.json
231
+ ? JSON.stringify(output, null, 2)
232
+ : `${operation === "add" ? "Added" : "Removed"} dependency '${dependencyId}' ${operation === "add" ? "to" : "from"} phase '${dependencyPhaseId}'`,
233
+ )
234
+ return
235
+ }
156
236
  default:
157
237
  return yield* Effect.fail(
158
238
  new Error(
159
- "Subcommand is required. Available: create, list, show, status",
239
+ "Subcommand is required. Available: create, list, show, status, update, rename, dependency",
160
240
  ),
161
241
  )
162
242
  }
@@ -171,6 +251,11 @@ Subcommands:
171
251
  show <task> <phase> Show a phase
172
252
  status <task> <phase> <status>
173
253
  Set open, done, or dropped
254
+ update <task> <phase> Update phase metadata
255
+ rename <task> <phase> <new-id>
256
+ Rename a phase and update dependencies
257
+ dependency <operation> <task> <phase> <dependency>
258
+ Add or remove a phase dependency
174
259
 
175
260
  Create options:
176
261
  --description <text> Short description of the phase
@@ -182,6 +267,14 @@ Create options:
182
267
  --depends-on <id> Phase dependency; repeatable
183
268
  --first-phase <id> Existing execution phase ID when converting a task
184
269
 
270
+ Update options:
271
+ --description <text> / --clear-description
272
+ --repo <alias> Replace the writable repository
273
+ --reference <alias>:<ref> / --clear-references
274
+ --branch <name> Replace the working branch
275
+ --base <name> Replace the base branch
276
+ --pr-url <url> / --clear-pr
277
+
185
278
  Options:
186
279
  --json Output results as JSON
187
280
  --status <status> Filter list by status; repeatable
@@ -33,8 +33,8 @@ export const pr = (options: PrOptions) =>
33
33
  export const help = `
34
34
  Usage: agency pr create <task-id> [phase-id]
35
35
 
36
- Push the execution branch, create a GitHub pull request, and update its
37
- task or phase document.
36
+ Push the execution branch, create a pull request with the delivery provider,
37
+ and update its task or phase document.
38
38
 
39
39
  Options:
40
40
  --draft Create a draft pull request