@markjaquith/agency 1.11.1 → 2.1.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.
Files changed (132) hide show
  1. package/README.md +313 -117
  2. package/cli.ts +124 -420
  3. package/index.ts +1 -1
  4. package/package.json +12 -17
  5. package/skills/agency/SKILL.md +266 -0
  6. package/src/cli.test.ts +290 -0
  7. package/src/commands/description.test.ts +103 -0
  8. package/src/commands/epic.test.ts +103 -0
  9. package/src/commands/epic.ts +101 -0
  10. package/src/commands/init.test.ts +42 -290
  11. package/src/commands/init.ts +15 -123
  12. package/src/commands/phase.ts +120 -0
  13. package/src/commands/pr.test.ts +19 -170
  14. package/src/commands/pr.ts +23 -179
  15. package/src/commands/repo.test.ts +80 -0
  16. package/src/commands/repo.ts +83 -0
  17. package/src/commands/status.test.ts +35 -477
  18. package/src/commands/status.ts +24 -213
  19. package/src/commands/task-phase.test.ts +206 -0
  20. package/src/commands/task.ts +83 -1285
  21. package/src/commands/validate.test.ts +91 -0
  22. package/src/commands/validate.ts +50 -0
  23. package/src/commands/work.test.ts +326 -399
  24. package/src/commands/work.ts +153 -153
  25. package/src/services/EpicService.test.ts +71 -0
  26. package/src/services/EpicService.ts +135 -0
  27. package/src/services/FileSystemService.ts +61 -92
  28. package/src/services/PhaseService.ts +323 -0
  29. package/src/services/PullRequestService.test.ts +386 -0
  30. package/src/services/PullRequestService.ts +143 -0
  31. package/src/services/RepositoryService.test.ts +106 -0
  32. package/src/services/RepositoryService.ts +159 -0
  33. package/src/services/TaskPhaseService.test.ts +217 -0
  34. package/src/services/TaskService.ts +192 -0
  35. package/src/services/WorkbaseService.test.ts +446 -0
  36. package/src/services/WorkbaseService.ts +493 -0
  37. package/src/services/WorktreeService.test.ts +599 -0
  38. package/src/services/WorktreeService.ts +357 -0
  39. package/src/test-utils.ts +41 -438
  40. package/src/types.ts +1 -215
  41. package/src/utils/command.ts +1 -5
  42. package/src/utils/effect.test.ts +24 -0
  43. package/src/utils/effect.ts +3 -273
  44. package/src/utils/process.test.ts +0 -15
  45. package/src/utils/process.ts +1 -60
  46. package/src/workbase/frontmatter.test.ts +50 -0
  47. package/src/workbase/frontmatter.ts +77 -0
  48. package/src/workbase/repository-reference.test.ts +25 -0
  49. package/src/workbase/repository-reference.ts +18 -0
  50. package/src/workbase/schemas.test.ts +146 -0
  51. package/src/workbase/schemas.ts +78 -0
  52. package/src/workbase/work-target.test.ts +77 -0
  53. package/src/workbase/work-target.ts +180 -0
  54. package/src/workbase/worktree-command.test.ts +61 -0
  55. package/src/workbase/worktree-command.ts +52 -0
  56. package/src/commands/base.test.ts +0 -198
  57. package/src/commands/base.ts +0 -198
  58. package/src/commands/clean.test.ts +0 -299
  59. package/src/commands/clean.ts +0 -274
  60. package/src/commands/completions.test.ts +0 -51
  61. package/src/commands/completions.ts +0 -308
  62. package/src/commands/emit.integration.test.ts +0 -549
  63. package/src/commands/emit.test.ts +0 -576
  64. package/src/commands/emit.ts +0 -700
  65. package/src/commands/emitted.test.ts +0 -226
  66. package/src/commands/emitted.ts +0 -57
  67. package/src/commands/loop.test.ts +0 -621
  68. package/src/commands/loop.ts +0 -406
  69. package/src/commands/merge.integration.test.ts +0 -195
  70. package/src/commands/merge.test.ts +0 -246
  71. package/src/commands/merge.ts +0 -244
  72. package/src/commands/pull.test.ts +0 -395
  73. package/src/commands/pull.ts +0 -205
  74. package/src/commands/push.test.ts +0 -641
  75. package/src/commands/push.ts +0 -482
  76. package/src/commands/rebase.test.ts +0 -521
  77. package/src/commands/rebase.ts +0 -232
  78. package/src/commands/save.test.ts +0 -247
  79. package/src/commands/save.ts +0 -162
  80. package/src/commands/source.test.ts +0 -195
  81. package/src/commands/source.ts +0 -72
  82. package/src/commands/switch.test.ts +0 -194
  83. package/src/commands/switch.ts +0 -84
  84. package/src/commands/task-branching.test.ts +0 -633
  85. package/src/commands/task-continue.test.ts +0 -311
  86. package/src/commands/task-edit.test.ts +0 -235
  87. package/src/commands/task-main.test.ts +0 -1066
  88. package/src/commands/task-squash.test.ts +0 -266
  89. package/src/commands/tasks.test.ts +0 -391
  90. package/src/commands/tasks.ts +0 -161
  91. package/src/commands/template-delete.test.ts +0 -178
  92. package/src/commands/template-delete.ts +0 -98
  93. package/src/commands/template-list.test.ts +0 -135
  94. package/src/commands/template-list.ts +0 -87
  95. package/src/commands/template-view.test.ts +0 -158
  96. package/src/commands/template-view.ts +0 -86
  97. package/src/commands/template.test.ts +0 -32
  98. package/src/commands/template.ts +0 -96
  99. package/src/commands/use.test.ts +0 -87
  100. package/src/commands/use.ts +0 -97
  101. package/src/constants.ts +0 -10
  102. package/src/errors.ts +0 -17
  103. package/src/schemas.ts +0 -33
  104. package/src/services/AgencyMetadataService.ts +0 -311
  105. package/src/services/ClaudeService.test.ts +0 -184
  106. package/src/services/ClaudeService.ts +0 -91
  107. package/src/services/ConfigService.ts +0 -115
  108. package/src/services/FilterRepoService.ts +0 -164
  109. package/src/services/FormatterService.test.ts +0 -432
  110. package/src/services/FormatterService.ts +0 -219
  111. package/src/services/GitService.ts +0 -1187
  112. package/src/services/MockFilterRepoService.ts +0 -140
  113. package/src/services/OpencodeService.ts +0 -263
  114. package/src/services/PromptService.ts +0 -183
  115. package/src/services/TemplateService.ts +0 -81
  116. package/src/types/native-exec.d.ts +0 -8
  117. package/src/utils/colors.ts +0 -178
  118. package/src/utils/git-path.ts +0 -6
  119. package/src/utils/glob.test.ts +0 -161
  120. package/src/utils/glob.ts +0 -78
  121. package/src/utils/paths.ts +0 -51
  122. package/src/utils/pr-branch.test.ts +0 -376
  123. package/src/utils/pr-branch.ts +0 -508
  124. package/src/utils/spinner.test.ts +0 -45
  125. package/src/utils/spinner.ts +0 -96
  126. package/src/utils/task-parser.test.ts +0 -172
  127. package/src/utils/task-parser.ts +0 -93
  128. package/templates/AGENCY.md +0 -22
  129. package/templates/AGENTS.md +0 -11
  130. package/templates/CLAUDE.md +0 -3
  131. package/templates/TASK.md +0 -5
  132. package/templates/opencode.json +0 -4
package/cli.ts CHANGED
@@ -1,52 +1,36 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
3
  import { parseArgs } from "util"
4
- import { Effect, Layer } from "effect"
5
- import { clean, help as cleanHelp } from "./src/commands/clean"
4
+ import { Effect, Either, Layer } from "effect"
6
5
  import { init, help as initHelp } from "./src/commands/init"
7
- import { task, taskEdit, help as taskHelp, editHelp } from "./src/commands/task"
8
- import { tasks, help as tasksHelp } from "./src/commands/tasks"
9
- import { emit, help as emitHelp } from "./src/commands/emit"
10
- import { emitted, help as emittedHelp } from "./src/commands/emitted"
6
+ import { task, help as taskHelp } from "./src/commands/task"
11
7
  import { pr, help as prHelp } from "./src/commands/pr"
12
- import { push, help as pushHelp } from "./src/commands/push"
13
- import { pull, help as pullHelp } from "./src/commands/pull"
14
- import { rebase, help as rebaseHelp } from "./src/commands/rebase"
15
- import { base, help as baseHelp } from "./src/commands/base"
16
- import { switchBranch, help as switchHelp } from "./src/commands/switch"
17
- import { source, help as sourceHelp } from "./src/commands/source"
18
- import { merge, help as mergeHelp } from "./src/commands/merge"
19
- import { template, help as templateHelp } from "./src/commands/template"
20
8
  import { work, help as workHelp } from "./src/commands/work"
21
- import { loop, help as loopHelp } from "./src/commands/loop"
22
9
  import { status, help as statusHelp } from "./src/commands/status"
23
- import {
24
- completions,
25
- help as completionsHelp,
26
- } from "./src/commands/completions"
10
+ import { validate, help as validateHelp } from "./src/commands/validate"
11
+ import { repo, help as repoHelp } from "./src/commands/repo"
12
+ import { epic, help as epicHelp } from "./src/commands/epic"
13
+ import { phase, help as phaseHelp } from "./src/commands/phase"
27
14
  import type { Command } from "./src/types"
28
- import { setColorsEnabled } from "./src/utils/colors"
29
- import { GitService } from "./src/services/GitService"
30
- import { ConfigService } from "./src/services/ConfigService"
31
15
  import { FileSystemService } from "./src/services/FileSystemService"
32
- import { PromptService } from "./src/services/PromptService"
33
- import { TemplateService } from "./src/services/TemplateService"
34
- import { OpencodeService } from "./src/services/OpencodeService"
35
- import { ClaudeService } from "./src/services/ClaudeService"
36
- import { FilterRepoService } from "./src/services/FilterRepoService"
37
- import { FormatterService } from "./src/services/FormatterService"
16
+ import { WorkbaseService } from "./src/services/WorkbaseService"
17
+ import { RepositoryService } from "./src/services/RepositoryService"
18
+ import { EpicService } from "./src/services/EpicService"
19
+ import { TaskService } from "./src/services/TaskService"
20
+ import { PhaseService } from "./src/services/PhaseService"
21
+ import { WorktreeService } from "./src/services/WorktreeService"
22
+ import { PullRequestService } from "./src/services/PullRequestService"
38
23
 
39
24
  // Create CLI layer with all services
40
25
  const CliLayer = Layer.mergeAll(
41
- GitService.Default,
42
- ConfigService.Default,
43
26
  FileSystemService.Default,
44
- PromptService.Default,
45
- TemplateService.Default,
46
- OpencodeService.Default,
47
- ClaudeService.Default,
48
- FilterRepoService.Default,
49
- FormatterService.Default,
27
+ WorkbaseService.Default,
28
+ RepositoryService.Default,
29
+ EpicService.Default,
30
+ TaskService.Default,
31
+ PhaseService.Default,
32
+ WorktreeService.Default,
33
+ PullRequestService.Default,
50
34
  )
51
35
 
52
36
  /**
@@ -61,31 +45,28 @@ async function runCommand<E>(
61
45
  never
62
46
  >
63
47
 
64
- // Catch typed errors and convert to standard Error objects
65
- const programWithErrorHandling = Effect.catchAll(providedEffect, (error) => {
66
- // Convert typed errors to standard Error objects with clear messages
48
+ const toError = (error: unknown) => {
67
49
  if (error instanceof Error) {
68
- return Effect.fail(error)
50
+ return error
69
51
  }
70
- // Handle objects with message property (common pattern for tagged errors)
71
52
  if (
72
53
  typeof error === "object" &&
73
54
  error !== null &&
74
55
  "message" in error &&
75
56
  typeof error.message === "string"
76
57
  ) {
77
- return Effect.fail(new Error(error.message))
58
+ return new Error(error.message)
78
59
  }
79
- // Fallback: convert to string
80
- return Effect.fail(new Error(String(error)))
81
- })
60
+ return new Error(String(error))
61
+ }
82
62
 
83
- // Catch defects (unexpected crashes) and convert to errors
84
- const program = Effect.catchAllDefect(programWithErrorHandling, (defect) =>
85
- Effect.fail(defect instanceof Error ? defect : new Error(String(defect))),
63
+ const result = await Effect.runPromise(
64
+ providedEffect.pipe(
65
+ Effect.catchAllDefect((defect) => Effect.fail(toError(defect))),
66
+ Effect.either,
67
+ ),
86
68
  )
87
-
88
- await Effect.runPromise(program)
69
+ if (Either.isLeft(result)) throw toError(result.left)
89
70
  }
90
71
 
91
72
  // Read version from package.json
@@ -97,322 +78,144 @@ const VERSION = packageJson.version
97
78
  // Define commands
98
79
  const commands: Record<string, Command> = {
99
80
  init: {
100
- name: "init",
101
- description: "Initialize agency with template selection",
102
- run: async (_args: string[], options: Record<string, any>) => {
81
+ run: async (args: string[], options: Record<string, any>) => {
103
82
  if (options.help) {
104
83
  console.log(initHelp)
105
84
  return
106
85
  }
107
86
  await runCommand(
108
87
  init({
109
- template: options.template,
88
+ path: args[0],
89
+ json: options.json,
110
90
  silent: options.silent,
111
91
  verbose: options.verbose,
112
92
  }),
113
93
  )
114
94
  },
115
- help: initHelp,
116
95
  },
117
- emit: {
118
- name: "emit",
119
- description: "Emit a branch without backpack files",
96
+ epic: {
120
97
  run: async (args: string[], options: Record<string, any>) => {
121
98
  if (options.help) {
122
- console.log(emitHelp)
99
+ console.log(epicHelp)
123
100
  return
124
101
  }
125
102
  await runCommand(
126
- emit({
127
- baseBranch: args[0],
128
- emit: options.emit || options.branch,
103
+ epic({
104
+ subcommand: args[0],
105
+ args: args.slice(1),
106
+ ticketUrl: options["ticket-url"],
107
+ description: options.description,
108
+ repos: options.repo,
109
+ json: options.json,
129
110
  silent: options.silent,
130
- force: options.verbose,
131
111
  verbose: options.verbose,
132
112
  }),
133
113
  )
134
114
  },
135
- help: emitHelp,
136
- },
137
- emitted: {
138
- name: "emitted",
139
- description: "Get the name of the emitted branch",
140
- run: async (_args: string[], options: Record<string, any>) => {
141
- if (options.help) {
142
- console.log(emittedHelp)
143
- return
144
- }
145
- await runCommand(
146
- emitted({ silent: options.silent, verbose: options.verbose }),
147
- )
148
- },
149
- help: emittedHelp,
150
115
  },
151
116
  pr: {
152
- name: "pr",
153
- description: "Run gh pr with the emitted branch name",
154
- run: async (
155
- _args: string[],
156
- options: Record<string, any>,
157
- rawArgs?: string[],
158
- ) => {
117
+ run: async (args: string[], options: Record<string, any>) => {
159
118
  if (options.help) {
160
119
  console.log(prHelp)
161
120
  return
162
121
  }
163
- // Pass raw args (after filtering agency flags) directly to gh pr
164
- // This allows flags like --web to pass through without needing --
165
- const agencyFlags = ["--help", "-h", "--silent", "-s", "--verbose", "-v"]
166
- const filteredArgs = (rawArgs ?? []).filter(
167
- (arg) => !agencyFlags.includes(arg),
168
- )
169
122
  await runCommand(
170
123
  pr({
171
- args: filteredArgs,
172
- silent: options.silent,
173
- verbose: options.verbose,
174
- }),
175
- )
176
- },
177
- help: prHelp,
178
- },
179
- push: {
180
- name: "push",
181
- description: "Push the emitted branch or fall through to git push",
182
- run: async (args: string[], options: Record<string, any>) => {
183
- if (options.help) {
184
- console.log(pushHelp)
185
- return
186
- }
187
- await runCommand(
188
- push({
189
- baseBranch: args[0],
190
- gitArgs: args,
191
- emit: options.emit || options.branch,
192
- silent: options.silent,
193
- force: options.force,
194
- forceWithLease: options["force-with-lease"],
195
- noVerify: options["no-verify"],
196
- verbose: options.verbose,
197
- pr: options.pr,
198
- }),
199
- )
200
- },
201
- help: pushHelp,
202
- },
203
- pull: {
204
- name: "pull",
205
- description: "Pull commits from remote emit branch to source",
206
- run: async (_args: string[], options: Record<string, any>) => {
207
- if (options.help) {
208
- console.log(pullHelp)
209
- return
210
- }
211
- await runCommand(
212
- pull({
213
- remote: options.remote,
214
- silent: options.silent,
215
- verbose: options.verbose,
216
- }),
217
- )
218
- },
219
- help: pullHelp,
220
- },
221
- rebase: {
222
- name: "rebase",
223
- description: "Rebase source branch onto base branch",
224
- run: async (args: string[], options: Record<string, any>) => {
225
- if (options.help) {
226
- console.log(rebaseHelp)
227
- return
228
- }
229
- await runCommand(
230
- rebase({
231
- baseBranch: args[0],
232
- emit: options.emit || options.branch,
124
+ subcommand: args[0],
125
+ taskId: args[1],
126
+ phaseId: args[2],
127
+ draft: options.draft,
128
+ json: options.json,
233
129
  silent: options.silent,
234
130
  verbose: options.verbose,
235
131
  }),
236
132
  )
237
133
  },
238
- help: rebaseHelp,
239
134
  },
240
- template: {
241
- name: "template",
242
- description: "Template management commands",
135
+ phase: {
243
136
  run: async (args: string[], options: Record<string, any>) => {
244
- if (options.help) {
245
- console.log(templateHelp)
246
- return
247
- }
137
+ if (options.help) return console.log(phaseHelp)
248
138
  await runCommand(
249
- template({
139
+ phase({
250
140
  subcommand: args[0],
251
141
  args: args.slice(1),
142
+ description: options.description,
143
+ repo: options.repo?.[0],
144
+ references: options.reference,
145
+ branch: options.branch,
146
+ base: options.base,
147
+ dependsOn: options["depends-on"],
148
+ firstPhase: options["first-phase"],
149
+ json: options.json,
252
150
  silent: options.silent,
253
151
  verbose: options.verbose,
254
- template: options.template,
255
152
  }),
256
153
  )
257
154
  },
258
- help: templateHelp,
259
155
  },
260
- base: {
261
- name: "base",
262
- description: "Get or set the base branch",
156
+ repo: {
263
157
  run: async (args: string[], options: Record<string, any>) => {
264
158
  if (options.help) {
265
- console.log(baseHelp)
159
+ console.log(repoHelp)
266
160
  return
267
161
  }
268
162
  await runCommand(
269
- base({
163
+ repo({
270
164
  subcommand: args[0],
271
165
  args: args.slice(1),
272
- repo: options.repo,
273
- silent: options.silent,
274
- verbose: options.verbose,
275
- }),
276
- )
277
- },
278
- help: baseHelp,
279
- },
280
- switch: {
281
- name: "switch",
282
- description: "Toggle between source and emitted branch",
283
- run: async (_args: string[], options: Record<string, any>) => {
284
- if (options.help) {
285
- console.log(switchHelp)
286
- return
287
- }
288
- await runCommand(
289
- switchBranch({ silent: options.silent, verbose: options.verbose }),
290
- )
291
- },
292
- help: switchHelp,
293
- },
294
- source: {
295
- name: "source",
296
- description: "Switch to source branch from emitted branch",
297
- run: async (_args: string[], options: Record<string, any>) => {
298
- if (options.help) {
299
- console.log(sourceHelp)
300
- return
301
- }
302
- await runCommand(
303
- source({ silent: options.silent, verbose: options.verbose }),
304
- )
305
- },
306
- help: sourceHelp,
307
- },
308
- merge: {
309
- name: "merge",
310
- description: "Merge emitted branch into base branch",
311
- run: async (_args: string[], options: Record<string, any>) => {
312
- if (options.help) {
313
- console.log(mergeHelp)
314
- return
315
- }
316
- await runCommand(
317
- merge({
318
166
  silent: options.silent,
319
167
  verbose: options.verbose,
320
- squash: options.squash,
321
- push: options.push,
168
+ json: options.json,
322
169
  }),
323
170
  )
324
171
  },
325
- help: mergeHelp,
326
172
  },
327
173
  task: {
328
- name: "task",
329
- description: "Task management commands",
330
174
  run: async (args: string[], options: Record<string, any>) => {
331
175
  if (options.help) {
332
176
  console.log(taskHelp)
333
177
  return
334
178
  }
335
- // Initialize with optional branch name
336
- const branch = args[0] || options.emit || options.branch
337
179
  await runCommand(
338
180
  task({
339
- emit: branch,
340
- silent: options.silent,
341
- verbose: options.verbose,
342
- task: options.task,
343
- from: options.from,
344
- fromCurrent: options["from-current"],
345
- continue: options.continue,
346
- squash: options.squash,
347
- }),
348
- )
349
- },
350
- help: taskHelp,
351
- },
352
- tasks: {
353
- name: "tasks",
354
- description: "List all task branches",
355
- run: async (_args: string[], options: Record<string, any>) => {
356
- if (options.help) {
357
- console.log(tasksHelp)
358
- return
359
- }
360
- await runCommand(
361
- tasks({
362
- silent: options.silent,
363
- verbose: options.verbose,
181
+ subcommand: args[0],
182
+ args: args.slice(1),
183
+ ticketUrl: options["ticket-url"],
184
+ description: options.description,
185
+ epic: options.epic,
186
+ repo: options.repo?.[0],
187
+ references: options.reference,
188
+ branch: options.branch,
189
+ base: options.base,
190
+ multiPhase: options["multi-phase"],
364
191
  json: options.json,
365
- }),
366
- )
367
- },
368
- help: tasksHelp,
369
- },
370
- edit: {
371
- name: "edit",
372
- description: "Open TASK.md in system editor",
373
- run: async (_args: string[], options: Record<string, any>) => {
374
- if (options.help) {
375
- console.log(editHelp)
376
- return
377
- }
378
- await runCommand(
379
- taskEdit({
380
192
  silent: options.silent,
381
193
  verbose: options.verbose,
382
194
  }),
383
195
  )
384
196
  },
385
- help: editHelp,
386
197
  },
387
198
  work: {
388
- name: "work",
389
- description: "Start working on TASK.md with OpenCode",
390
199
  run: async (args: string[], options: Record<string, any>) => {
391
200
  if (options.help) {
392
201
  console.log(workHelp)
393
202
  return
394
203
  }
395
204
 
396
- // Extract extra args (anything after --)
397
- // Note: parseArgs with strict:false and allowPositionals:true will put
398
- // args after -- in the positionals array
399
- const extraArgs = args.length > 0 ? args : undefined
400
-
401
205
  await runCommand(
402
206
  work({
207
+ taskId: args[0],
208
+ phaseId: args[1],
209
+ epicId: options.epic,
403
210
  silent: options.silent,
404
211
  verbose: options.verbose,
405
212
  opencode: options.opencode,
406
213
  claude: options.claude,
407
- extraArgs,
408
214
  }),
409
215
  )
410
216
  },
411
- help: workHelp,
412
217
  },
413
218
  status: {
414
- name: "status",
415
- description: "Show agency status for this repository",
416
219
  run: async (_args: string[], options: Record<string, any>) => {
417
220
  if (options.help) {
418
221
  console.log(statusHelp)
@@ -426,66 +229,21 @@ const commands: Record<string, Command> = {
426
229
  }),
427
230
  )
428
231
  },
429
- help: statusHelp,
430
232
  },
431
- clean: {
432
- name: "clean",
433
- description: "Delete branches merged into a specified branch",
233
+ validate: {
434
234
  run: async (_args: string[], options: Record<string, any>) => {
435
235
  if (options.help) {
436
- console.log(cleanHelp)
236
+ console.log(validateHelp)
437
237
  return
438
238
  }
439
239
  await runCommand(
440
- clean({
240
+ validate({
441
241
  silent: options.silent,
442
242
  verbose: options.verbose,
443
- dryRun: options["dry-run"],
444
- mergedInto: options["merged-into"],
445
- }),
446
- )
447
- },
448
- help: cleanHelp,
449
- },
450
- loop: {
451
- name: "loop",
452
- description: "Run a Ralph Wiggum loop to complete all tasks",
453
- run: async (args: string[], options: Record<string, any>) => {
454
- if (options.help) {
455
- console.log(loopHelp)
456
- return
457
- }
458
-
459
- // Extract extra args (anything after --)
460
- // Note: parseArgs with strict:false and allowPositionals:true will put
461
- // args after -- in the positionals array
462
- const extraArgs = args.length > 0 ? args : undefined
463
-
464
- await runCommand(
465
- loop({
466
- silent: options.silent,
467
- verbose: options.verbose,
468
- maxLoops: options["max-loops"],
469
- minLoops: options["min-loops"],
470
- opencode: options.opencode,
471
- claude: options.claude,
472
- extraArgs,
243
+ json: options.json,
473
244
  }),
474
245
  )
475
246
  },
476
- help: loopHelp,
477
- },
478
- completions: {
479
- name: "completions",
480
- description: "Generate shell completion scripts",
481
- run: async (args: string[], options: Record<string, any>) => {
482
- if (options.help) {
483
- console.log(completionsHelp)
484
- return
485
- }
486
- await runCommand(completions(args[0]))
487
- },
488
- help: completionsHelp,
489
247
  },
490
248
  }
491
249
 
@@ -496,55 +254,36 @@ agency v${VERSION}
496
254
  Usage: agency <command> [options]
497
255
 
498
256
  Commands:
499
- init Initialize agency with template selection (run first)
500
- task [branch] Initialize template files on a feature branch
501
- tasks List all task branches
502
- edit Open TASK.md in system editor
503
- work Start working on TASK.md with OpenCode
504
- template Template management commands
505
- use [template] Set template for this repository
506
- save <file|dir> ... Save files/dirs to configured template
507
- list List all files in configured template
508
- view <file> View contents of a file in template
509
- delete <file> ... Delete files from configured template
510
- emit [base-branch] Emit a branch with backpack files reverted
511
- emitted Get the name of the emitted branch
512
- pr <subcommand> Run gh pr with the emitted branch name
513
- push [arguments] Push the emitted branch or fall through to git push
514
- pull Pull commits from remote emit branch to source
515
- rebase [base-branch] Rebase source branch onto base branch
516
- base Get or set the base branch
517
- set <branch> Set the base branch for the current feature branch
518
- get Get the configured base branch
519
- switch Toggle between source and emitted branch
520
- source Switch to source branch from emitted branch
521
- merge Merge emitted branch into base branch
522
- status Show agency status for this repository
523
- completions <shell> Generate bash or zsh completion scripts
257
+ init [path] Initialize an Agency workbase
258
+ epic <subcommand> Manage epics
259
+ phase <subcommand> Manage task phases
260
+ task <subcommand> Manage tasks
261
+ work [task] [phase] Work on an epic, task, or phase
262
+ pr create Create a pull request for an execution unit
263
+ repo <subcommand> Manage workbase repositories
264
+ status Show status for the current workbase
265
+ validate Validate the current workbase
524
266
 
525
267
  Global Options:
526
268
  -h, --help Show help for a command
527
- -v, --version Show version number
528
- --no-color Disable color output
269
+ -V, --version Show version number
529
270
  -s, --silent Suppress output messages
530
271
  -v, --verbose Show verbose output including detailed debugging info
531
272
 
532
273
  Examples:
533
- agency init # Initialize with template (run first)
534
- agency task my-feature # Create 'my-feature' branch from origin/main
535
- agency task --from-current # Initialize on current feature branch
536
- agency emit # Emit a branch (prompts for base branch)
537
- agency switch # Toggle between source and emitted branch
274
+ agency init # Initialize the current directory
275
+ agency task list # List tasks
276
+ agency work refresh-cli-copy # Start working on a task
538
277
 
539
278
  For more information about a command, run:
540
279
  agency <command> --help
541
280
  `)
542
281
  }
543
282
 
544
- // Parse global arguments
545
283
  try {
284
+ const args = process.argv.slice(2)
546
285
  const { values, positionals } = parseArgs({
547
- args: process.argv.slice(2),
286
+ args,
548
287
  options: {
549
288
  help: {
550
289
  type: "boolean",
@@ -552,21 +291,21 @@ try {
552
291
  },
553
292
  version: {
554
293
  type: "boolean",
555
- short: "v",
294
+ short: "V",
556
295
  },
557
- "no-color": {
296
+ silent: {
558
297
  type: "boolean",
298
+ short: "s",
299
+ },
300
+ verbose: {
301
+ type: "boolean",
302
+ short: "v",
559
303
  },
560
304
  },
561
305
  strict: false,
562
306
  allowPositionals: true,
563
307
  })
564
308
 
565
- // Handle --no-color flag
566
- if (values["no-color"]) {
567
- setColorsEnabled(false)
568
- }
569
-
570
309
  // Handle global flags
571
310
  if (values.version) {
572
311
  console.log(`v${VERSION}`)
@@ -579,13 +318,7 @@ try {
579
318
  // Show help if no command
580
319
  if (!commandName) {
581
320
  showMainHelp()
582
- process.exit(1)
583
- }
584
-
585
- // Show main help if --help with no command
586
- if (values.help && !commandName) {
587
- showMainHelp()
588
- process.exit(0)
321
+ process.exit(values.help ? 0 : 1)
589
322
  }
590
323
 
591
324
  // Check if command exists
@@ -596,8 +329,11 @@ try {
596
329
  process.exit(1)
597
330
  }
598
331
 
599
- // Parse command-specific arguments
600
- const commandArgs = process.argv.slice(3)
332
+ const commandIndex = args.indexOf(commandName)
333
+ const commandArgs = [
334
+ ...args.slice(0, commandIndex),
335
+ ...args.slice(commandIndex + 1),
336
+ ]
601
337
  const { values: cmdValues, positionals: cmdPositionals } = parseArgs({
602
338
  args: commandArgs,
603
339
  options: {
@@ -609,67 +345,36 @@ try {
609
345
  type: "boolean",
610
346
  short: "s",
611
347
  },
612
- force: {
613
- type: "boolean",
614
- short: "f",
615
- },
616
- "force-with-lease": {
617
- type: "boolean",
618
- },
619
- "no-verify": {
620
- type: "boolean",
621
- },
622
348
  verbose: {
623
349
  type: "boolean",
624
350
  short: "v",
625
351
  },
626
- template: {
627
- type: "string",
628
- short: "t",
629
- },
630
- emit: {
631
- type: "string",
632
- },
633
352
  branch: {
634
353
  type: "string",
635
354
  },
636
- task: {
637
- type: "string",
638
- },
639
- from: {
640
- type: "string",
641
- },
642
- "from-current": {
643
- type: "boolean",
644
- },
645
- continue: {
646
- type: "boolean",
647
- },
648
355
  json: {
649
356
  type: "boolean",
650
357
  },
651
- repo: {
652
- type: "boolean",
653
- },
654
- pr: {
655
- type: "boolean",
656
- },
657
- squash: {
658
- type: "boolean",
659
- },
660
- push: {
661
- type: "boolean",
358
+ "ticket-url": {
359
+ type: "string",
662
360
  },
663
- remote: {
361
+ description: {
664
362
  type: "string",
665
- short: "r",
666
363
  },
667
- "merged-into": {
364
+ repo: {
668
365
  type: "string",
366
+ multiple: true,
669
367
  },
670
- "dry-run": {
671
- type: "boolean",
368
+ reference: {
369
+ type: "string",
370
+ multiple: true,
672
371
  },
372
+ epic: { type: "string" },
373
+ base: { type: "string" },
374
+ "multi-phase": { type: "boolean" },
375
+ "depends-on": { type: "string", multiple: true },
376
+ "first-phase": { type: "string" },
377
+ draft: { type: "boolean" },
673
378
  opencode: {
674
379
  type: "boolean",
675
380
  },
@@ -681,8 +386,7 @@ try {
681
386
  allowPositionals: true,
682
387
  })
683
388
 
684
- // Run the command, passing raw args for commands that need them (like pr)
685
- await command.run(cmdPositionals, cmdValues, commandArgs)
389
+ await command.run(cmdPositionals, cmdValues)
686
390
  } catch (error) {
687
391
  if (error instanceof Error) {
688
392
  let message = error.message