@markjaquith/agency 2.52.1 → 2.52.2
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/cli-main.ts +854 -0
- package/cli.ts +15 -850
- package/package.json +2 -1
- package/src/services/TaskService.ts +1 -1
- package/src/services/VcsMigrationService.test.ts +46 -0
- package/src/services/VcsMigrationService.ts +35 -21
- package/src/services/VersionControlService.ts +9 -3
- package/src/services/WorktreeService.ts +234 -40
- package/src/vcs-status-fast.ts +315 -0
- package/src/workbase/opencode-plugin-file.ts +31 -4
package/cli-main.ts
ADDED
|
@@ -0,0 +1,854 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import { Effect, Either, Layer } from "effect"
|
|
4
|
+
import { join, resolve } from "node:path"
|
|
5
|
+
import { parseCli } from "./src/cli-parser"
|
|
6
|
+
import { init, help as initHelp } from "./src/commands/init"
|
|
7
|
+
import { task, help as taskHelp } from "./src/commands/task"
|
|
8
|
+
import { pr, help as prHelp } from "./src/commands/pr"
|
|
9
|
+
import { work, workPrepare, help as workHelp } from "./src/commands/work"
|
|
10
|
+
import { worktree, help as worktreeHelp } from "./src/commands/worktree"
|
|
11
|
+
import { status, help as statusHelp } from "./src/commands/status"
|
|
12
|
+
import { doctor, help as doctorHelp } from "./src/commands/doctor"
|
|
13
|
+
import { validate, help as validateHelp } from "./src/commands/validate"
|
|
14
|
+
import { context, help as contextHelp } from "./src/commands/context"
|
|
15
|
+
import { graph, help as graphHelp } from "./src/commands/graph"
|
|
16
|
+
import { next, help as nextHelp } from "./src/commands/next"
|
|
17
|
+
import { sync, help as syncHelp } from "./src/commands/sync"
|
|
18
|
+
import { repo, help as repoHelp } from "./src/commands/repo"
|
|
19
|
+
import { epic, help as epicHelp } from "./src/commands/epic"
|
|
20
|
+
import { phase, help as phaseHelp } from "./src/commands/phase"
|
|
21
|
+
import { archive, help as archiveHelp } from "./src/commands/archive"
|
|
22
|
+
import { restore, help as restoreHelp } from "./src/commands/restore"
|
|
23
|
+
import { workbase, help as workbaseHelp } from "./src/commands/workbase"
|
|
24
|
+
import {
|
|
25
|
+
integration,
|
|
26
|
+
help as integrationHelp,
|
|
27
|
+
} from "./src/commands/integration"
|
|
28
|
+
import type { Command } from "./src/types"
|
|
29
|
+
import { FileSystemService } from "./src/services/FileSystemService"
|
|
30
|
+
import { WorkbaseService } from "./src/services/WorkbaseService"
|
|
31
|
+
import { RepositoryService } from "./src/services/RepositoryService"
|
|
32
|
+
import { EpicService } from "./src/services/EpicService"
|
|
33
|
+
import { TaskService } from "./src/services/TaskService"
|
|
34
|
+
import { PhaseService } from "./src/services/PhaseService"
|
|
35
|
+
import { WorktreeService } from "./src/services/WorktreeService"
|
|
36
|
+
import { PullRequestService } from "./src/services/PullRequestService"
|
|
37
|
+
import { ArchiveService } from "./src/services/ArchiveService"
|
|
38
|
+
import { IntegrationService } from "./src/services/IntegrationService"
|
|
39
|
+
import { ContextService } from "./src/services/ContextService"
|
|
40
|
+
import { GraphService } from "./src/services/GraphService"
|
|
41
|
+
import { ClaimService } from "./src/services/ClaimService"
|
|
42
|
+
import { SyncService } from "./src/services/SyncService"
|
|
43
|
+
import { ReadinessService } from "./src/services/ReadinessService"
|
|
44
|
+
import { GraphMutationService } from "./src/services/GraphMutationService"
|
|
45
|
+
import { DoctorService } from "./src/services/DoctorService"
|
|
46
|
+
import { ReviewService } from "./src/services/ReviewService"
|
|
47
|
+
import {
|
|
48
|
+
GitVersionControlService,
|
|
49
|
+
JjVersionControlService,
|
|
50
|
+
VersionControlService,
|
|
51
|
+
} from "./src/services/VersionControlService"
|
|
52
|
+
import { review, help as reviewHelp } from "./src/commands/review"
|
|
53
|
+
import { vcs, help as vcsHelp } from "./src/commands/vcs"
|
|
54
|
+
import { VcsMigrationService } from "./src/services/VcsMigrationService"
|
|
55
|
+
import {
|
|
56
|
+
claimCommand,
|
|
57
|
+
claimHelp,
|
|
58
|
+
releaseHelp,
|
|
59
|
+
finishHelp,
|
|
60
|
+
} from "./src/commands/claim"
|
|
61
|
+
import {
|
|
62
|
+
collectCommandResult,
|
|
63
|
+
errorEnvelope,
|
|
64
|
+
successEnvelope,
|
|
65
|
+
writeEnvelope,
|
|
66
|
+
} from "./src/protocol"
|
|
67
|
+
|
|
68
|
+
// Create CLI layer with all services
|
|
69
|
+
const CliLayer = Layer.mergeAll(
|
|
70
|
+
FileSystemService.Default,
|
|
71
|
+
WorkbaseService.Default,
|
|
72
|
+
GitVersionControlService.Default,
|
|
73
|
+
JjVersionControlService.Default,
|
|
74
|
+
VersionControlService.Default,
|
|
75
|
+
VcsMigrationService.Default,
|
|
76
|
+
RepositoryService.Default,
|
|
77
|
+
EpicService.Default,
|
|
78
|
+
TaskService.Default,
|
|
79
|
+
PhaseService.Default,
|
|
80
|
+
WorktreeService.Default,
|
|
81
|
+
PullRequestService.Default,
|
|
82
|
+
ArchiveService.Default,
|
|
83
|
+
IntegrationService.Default,
|
|
84
|
+
ContextService.Default,
|
|
85
|
+
GraphService.Default,
|
|
86
|
+
ClaimService.Default,
|
|
87
|
+
SyncService.Default,
|
|
88
|
+
ReadinessService.Default,
|
|
89
|
+
GraphMutationService.Default,
|
|
90
|
+
DoctorService.Default,
|
|
91
|
+
ReviewService.Default,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Run a command Effect with all services provided
|
|
96
|
+
*/
|
|
97
|
+
async function runEffect<A, E>(effect: Effect.Effect<A, E, any>): Promise<A> {
|
|
98
|
+
const providedEffect = Effect.provide(effect, CliLayer) as Effect.Effect<
|
|
99
|
+
A,
|
|
100
|
+
E,
|
|
101
|
+
never
|
|
102
|
+
>
|
|
103
|
+
|
|
104
|
+
const toError = (error: unknown) => {
|
|
105
|
+
if (error instanceof Error) {
|
|
106
|
+
return error
|
|
107
|
+
}
|
|
108
|
+
if (
|
|
109
|
+
typeof error === "object" &&
|
|
110
|
+
error !== null &&
|
|
111
|
+
"message" in error &&
|
|
112
|
+
typeof error.message === "string"
|
|
113
|
+
) {
|
|
114
|
+
return new Error(error.message)
|
|
115
|
+
}
|
|
116
|
+
return new Error(String(error))
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const result = await Effect.runPromise(
|
|
120
|
+
providedEffect.pipe(
|
|
121
|
+
Effect.catchAllDefect((defect) => Effect.fail(toError(defect))),
|
|
122
|
+
Effect.either,
|
|
123
|
+
),
|
|
124
|
+
)
|
|
125
|
+
if (Either.isLeft(result)) throw result.left
|
|
126
|
+
return result.right
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const runCommand = <E>(effect: Effect.Effect<void, E, any>) => runEffect(effect)
|
|
130
|
+
|
|
131
|
+
const resolveInvocationCwd = (
|
|
132
|
+
commandName: string,
|
|
133
|
+
options: Record<string, any>,
|
|
134
|
+
) =>
|
|
135
|
+
runEffect(
|
|
136
|
+
Effect.gen(function* () {
|
|
137
|
+
if (
|
|
138
|
+
options.help ||
|
|
139
|
+
commandName === "init" ||
|
|
140
|
+
commandName === "workbase"
|
|
141
|
+
) {
|
|
142
|
+
return resolve(options.cwd ?? process.cwd())
|
|
143
|
+
}
|
|
144
|
+
const workbases = yield* WorkbaseService
|
|
145
|
+
if (options.workbase) {
|
|
146
|
+
return yield* workbases.resolveRegistered(options.workbase)
|
|
147
|
+
}
|
|
148
|
+
if (options.cwd) {
|
|
149
|
+
const selectedCwd = resolve(options.cwd)
|
|
150
|
+
yield* workbases.discover(selectedCwd)
|
|
151
|
+
return selectedCwd
|
|
152
|
+
}
|
|
153
|
+
return yield* workbases.discover(process.cwd()).pipe(
|
|
154
|
+
Effect.as(process.cwd()),
|
|
155
|
+
Effect.catchTag("WorkbaseNotFoundError", () =>
|
|
156
|
+
workbases
|
|
157
|
+
.getDefault()
|
|
158
|
+
.pipe(Effect.map((entry) => entry?.path ?? process.cwd())),
|
|
159
|
+
),
|
|
160
|
+
)
|
|
161
|
+
}),
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
// Read version from package.json
|
|
165
|
+
const packageJson = await Bun.file(
|
|
166
|
+
new URL("./package.json", import.meta.url),
|
|
167
|
+
).json()
|
|
168
|
+
const VERSION = packageJson.version
|
|
169
|
+
|
|
170
|
+
// Define commands
|
|
171
|
+
const commands: Record<string, Command> = {
|
|
172
|
+
claim: {
|
|
173
|
+
run: async (args: string[], options: Record<string, any>) => {
|
|
174
|
+
if (options.help) return console.log(claimHelp)
|
|
175
|
+
await runCommand(
|
|
176
|
+
claimCommand({
|
|
177
|
+
operation: "claim",
|
|
178
|
+
taskId: args[0],
|
|
179
|
+
phaseId: args[1],
|
|
180
|
+
claimant: options.claimant,
|
|
181
|
+
runner: options.runner,
|
|
182
|
+
sessionId: options["session-id"],
|
|
183
|
+
revision: options.revision,
|
|
184
|
+
expiresAt: options["expires-at"],
|
|
185
|
+
json: options.json,
|
|
186
|
+
silent: options.silent,
|
|
187
|
+
verbose: options.verbose,
|
|
188
|
+
cwd: options.cwd,
|
|
189
|
+
}),
|
|
190
|
+
)
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
release: {
|
|
194
|
+
run: async (args: string[], options: Record<string, any>) => {
|
|
195
|
+
if (options.help) return console.log(releaseHelp)
|
|
196
|
+
await runCommand(
|
|
197
|
+
claimCommand({
|
|
198
|
+
operation: "release",
|
|
199
|
+
taskId: args[0],
|
|
200
|
+
phaseId: args[1],
|
|
201
|
+
sessionId: options["session-id"],
|
|
202
|
+
revision: options.revision,
|
|
203
|
+
json: options.json,
|
|
204
|
+
silent: options.silent,
|
|
205
|
+
verbose: options.verbose,
|
|
206
|
+
cwd: options.cwd,
|
|
207
|
+
}),
|
|
208
|
+
)
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
finish: {
|
|
212
|
+
run: async (args: string[], options: Record<string, any>) => {
|
|
213
|
+
if (options.help) return console.log(finishHelp)
|
|
214
|
+
await runCommand(
|
|
215
|
+
claimCommand({
|
|
216
|
+
operation: "finish",
|
|
217
|
+
taskId: args[0],
|
|
218
|
+
phaseId: args[1],
|
|
219
|
+
sessionId: options["session-id"],
|
|
220
|
+
revision: options.revision,
|
|
221
|
+
outcome: options.outcome,
|
|
222
|
+
noPullRequest: options["no-pull-request"],
|
|
223
|
+
summary: options.summary,
|
|
224
|
+
evidenceUrl: options["evidence-url"],
|
|
225
|
+
json: options.json,
|
|
226
|
+
silent: options.silent,
|
|
227
|
+
verbose: options.verbose,
|
|
228
|
+
cwd: options.cwd,
|
|
229
|
+
}),
|
|
230
|
+
)
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
init: {
|
|
234
|
+
run: async (args: string[], options: Record<string, any>) => {
|
|
235
|
+
if (options.help) {
|
|
236
|
+
console.log(initHelp)
|
|
237
|
+
return
|
|
238
|
+
}
|
|
239
|
+
await runCommand(
|
|
240
|
+
init({
|
|
241
|
+
path: args[0],
|
|
242
|
+
json: options.json,
|
|
243
|
+
silent: options.silent,
|
|
244
|
+
verbose: options.verbose,
|
|
245
|
+
cwd: options.cwd,
|
|
246
|
+
}),
|
|
247
|
+
)
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
epic: {
|
|
251
|
+
run: async (args: string[], options: Record<string, any>) => {
|
|
252
|
+
if (options.help) {
|
|
253
|
+
console.log(epicHelp)
|
|
254
|
+
return
|
|
255
|
+
}
|
|
256
|
+
await runCommand(
|
|
257
|
+
epic({
|
|
258
|
+
subcommand: args[0],
|
|
259
|
+
args: args.slice(1),
|
|
260
|
+
ticketUrl: options["ticket-url"],
|
|
261
|
+
description: options.description,
|
|
262
|
+
clearDescription: options["clear-description"],
|
|
263
|
+
ifRevision: options["if-revision"],
|
|
264
|
+
repos: options.repo,
|
|
265
|
+
json: options.json,
|
|
266
|
+
statuses: options.status,
|
|
267
|
+
repositories: options.repository,
|
|
268
|
+
ready: options.ready,
|
|
269
|
+
blocked: options.blocked,
|
|
270
|
+
pr: options.pr ? true : options["no-pr"] ? false : undefined,
|
|
271
|
+
work: options.work,
|
|
272
|
+
auto: options.auto,
|
|
273
|
+
silent: options.silent,
|
|
274
|
+
verbose: options.verbose,
|
|
275
|
+
inputAllowed: options.inputAllowed,
|
|
276
|
+
cwd: options.cwd,
|
|
277
|
+
}),
|
|
278
|
+
)
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
pr: {
|
|
282
|
+
run: async (args: string[], options: Record<string, any>) => {
|
|
283
|
+
if (options.help) {
|
|
284
|
+
console.log(prHelp)
|
|
285
|
+
return
|
|
286
|
+
}
|
|
287
|
+
await runCommand(
|
|
288
|
+
pr({
|
|
289
|
+
subcommand: args[0],
|
|
290
|
+
taskId: args[1],
|
|
291
|
+
phaseId: args[2],
|
|
292
|
+
draft: options.draft,
|
|
293
|
+
force: options.force,
|
|
294
|
+
json: options.json,
|
|
295
|
+
silent: options.silent,
|
|
296
|
+
verbose: options.verbose,
|
|
297
|
+
cwd: options.cwd,
|
|
298
|
+
}),
|
|
299
|
+
)
|
|
300
|
+
},
|
|
301
|
+
},
|
|
302
|
+
phase: {
|
|
303
|
+
run: async (args: string[], options: Record<string, any>) => {
|
|
304
|
+
if (options.help) return console.log(phaseHelp)
|
|
305
|
+
await runCommand(
|
|
306
|
+
phase({
|
|
307
|
+
subcommand: args[0],
|
|
308
|
+
args: args.slice(1),
|
|
309
|
+
description: options.description,
|
|
310
|
+
clearDescription: options["clear-description"],
|
|
311
|
+
repo: options.repo?.[0],
|
|
312
|
+
references: options.reference,
|
|
313
|
+
branch: options.branch,
|
|
314
|
+
base: options.base,
|
|
315
|
+
clearReferences: options["clear-references"],
|
|
316
|
+
prUrl: options["pr-url"],
|
|
317
|
+
clearPr: options["clear-pr"],
|
|
318
|
+
noPullRequest: options["no-pull-request"],
|
|
319
|
+
summary: options.summary,
|
|
320
|
+
evidenceUrl: options["evidence-url"],
|
|
321
|
+
ifRevision: options["if-revision"],
|
|
322
|
+
dependsOn: options["depends-on"],
|
|
323
|
+
firstPhase: options["first-phase"],
|
|
324
|
+
json: options.json,
|
|
325
|
+
statuses: options.status,
|
|
326
|
+
repositories: options.repository,
|
|
327
|
+
ready: options.ready,
|
|
328
|
+
blocked: options.blocked,
|
|
329
|
+
pr: options.pr ? true : options["no-pr"] ? false : undefined,
|
|
330
|
+
work: options.work,
|
|
331
|
+
auto: options.auto,
|
|
332
|
+
silent: options.silent,
|
|
333
|
+
verbose: options.verbose,
|
|
334
|
+
inputAllowed: options.inputAllowed,
|
|
335
|
+
cwd: options.cwd,
|
|
336
|
+
}),
|
|
337
|
+
)
|
|
338
|
+
},
|
|
339
|
+
},
|
|
340
|
+
archive: {
|
|
341
|
+
run: async (args: string[], options: Record<string, any>) => {
|
|
342
|
+
if (options.help) {
|
|
343
|
+
console.log(archiveHelp)
|
|
344
|
+
return
|
|
345
|
+
}
|
|
346
|
+
await runCommand(
|
|
347
|
+
archive({
|
|
348
|
+
type: args[0],
|
|
349
|
+
args: args.slice(1),
|
|
350
|
+
json: options.json,
|
|
351
|
+
dryRun: options["dry-run"],
|
|
352
|
+
kinds: options.kind,
|
|
353
|
+
statuses: options.status,
|
|
354
|
+
repositories: options.repository,
|
|
355
|
+
silent: options.silent,
|
|
356
|
+
verbose: options.verbose,
|
|
357
|
+
cwd: options.cwd,
|
|
358
|
+
}),
|
|
359
|
+
)
|
|
360
|
+
},
|
|
361
|
+
},
|
|
362
|
+
restore: {
|
|
363
|
+
run: async (args: string[], options: Record<string, any>) => {
|
|
364
|
+
if (options.help) {
|
|
365
|
+
console.log(restoreHelp)
|
|
366
|
+
return
|
|
367
|
+
}
|
|
368
|
+
await runCommand(
|
|
369
|
+
restore({
|
|
370
|
+
type: args[0],
|
|
371
|
+
args: args.slice(1),
|
|
372
|
+
json: options.json,
|
|
373
|
+
dryRun: options["dry-run"],
|
|
374
|
+
silent: options.silent,
|
|
375
|
+
verbose: options.verbose,
|
|
376
|
+
cwd: options.cwd,
|
|
377
|
+
}),
|
|
378
|
+
)
|
|
379
|
+
},
|
|
380
|
+
},
|
|
381
|
+
workbase: {
|
|
382
|
+
run: async (args: string[], options: Record<string, any>) => {
|
|
383
|
+
if (options.help) {
|
|
384
|
+
console.log(workbaseHelp)
|
|
385
|
+
return
|
|
386
|
+
}
|
|
387
|
+
if (args[0] === "init") {
|
|
388
|
+
await runCommand(
|
|
389
|
+
init({
|
|
390
|
+
path: args[1],
|
|
391
|
+
json: options.json,
|
|
392
|
+
silent: options.silent,
|
|
393
|
+
verbose: options.verbose,
|
|
394
|
+
cwd: options.cwd,
|
|
395
|
+
}),
|
|
396
|
+
)
|
|
397
|
+
return
|
|
398
|
+
}
|
|
399
|
+
await runCommand(
|
|
400
|
+
workbase({
|
|
401
|
+
subcommand: args[0],
|
|
402
|
+
args: args.slice(1),
|
|
403
|
+
json: options.json,
|
|
404
|
+
name: options.name,
|
|
405
|
+
clear: options.clear,
|
|
406
|
+
silent: options.silent,
|
|
407
|
+
verbose: options.verbose,
|
|
408
|
+
cwd: options.cwd,
|
|
409
|
+
}),
|
|
410
|
+
)
|
|
411
|
+
},
|
|
412
|
+
},
|
|
413
|
+
integration: {
|
|
414
|
+
run: async (args: string[], options: Record<string, any>) => {
|
|
415
|
+
if (options.help) {
|
|
416
|
+
console.log(integrationHelp)
|
|
417
|
+
return
|
|
418
|
+
}
|
|
419
|
+
await runCommand(
|
|
420
|
+
integration({
|
|
421
|
+
subcommand: args[0],
|
|
422
|
+
json: options.json,
|
|
423
|
+
silent: options.silent,
|
|
424
|
+
verbose: options.verbose,
|
|
425
|
+
cwd: options.cwd,
|
|
426
|
+
}),
|
|
427
|
+
)
|
|
428
|
+
},
|
|
429
|
+
},
|
|
430
|
+
repo: {
|
|
431
|
+
run: async (args: string[], options: Record<string, any>) => {
|
|
432
|
+
if (options.help) {
|
|
433
|
+
console.log(repoHelp)
|
|
434
|
+
return
|
|
435
|
+
}
|
|
436
|
+
await runCommand(
|
|
437
|
+
repo({
|
|
438
|
+
subcommand: args[0],
|
|
439
|
+
args: args.slice(1),
|
|
440
|
+
silent: options.silent,
|
|
441
|
+
verbose: options.verbose,
|
|
442
|
+
json: options.json,
|
|
443
|
+
dryRun: options["dry-run"],
|
|
444
|
+
apply: options.apply,
|
|
445
|
+
cwd: options.cwd,
|
|
446
|
+
}),
|
|
447
|
+
)
|
|
448
|
+
},
|
|
449
|
+
},
|
|
450
|
+
task: {
|
|
451
|
+
run: async (args: string[], options: Record<string, any>) => {
|
|
452
|
+
if (options.help) {
|
|
453
|
+
console.log(taskHelp)
|
|
454
|
+
return
|
|
455
|
+
}
|
|
456
|
+
await runCommand(
|
|
457
|
+
task({
|
|
458
|
+
subcommand: args[0],
|
|
459
|
+
args: args.slice(1),
|
|
460
|
+
ticketUrl: options["ticket-url"],
|
|
461
|
+
clearTicket: options["clear-ticket"],
|
|
462
|
+
description: options.description,
|
|
463
|
+
clearDescription: options["clear-description"],
|
|
464
|
+
epic: options.epic,
|
|
465
|
+
repo: options.repo?.[0],
|
|
466
|
+
review: options.review,
|
|
467
|
+
pullRequest: options["pull-request"],
|
|
468
|
+
ref: options.ref,
|
|
469
|
+
references: options.reference,
|
|
470
|
+
branch: options.branch,
|
|
471
|
+
base: options.base,
|
|
472
|
+
clearReferences: options["clear-references"],
|
|
473
|
+
prUrl: options["pr-url"],
|
|
474
|
+
clearPr: options["clear-pr"],
|
|
475
|
+
noPullRequest: options["no-pull-request"],
|
|
476
|
+
summary: options.summary,
|
|
477
|
+
evidenceUrl: options["evidence-url"],
|
|
478
|
+
ifRevision: options["if-revision"],
|
|
479
|
+
noEpic: options["no-epic"],
|
|
480
|
+
multiPhase: options["multi-phase"],
|
|
481
|
+
json: options.json,
|
|
482
|
+
statuses: options.status,
|
|
483
|
+
repositories: options.repository,
|
|
484
|
+
ready: options.ready,
|
|
485
|
+
blocked: options.blocked,
|
|
486
|
+
pr: options.pr ? true : options["no-pr"] ? false : undefined,
|
|
487
|
+
work: options.work,
|
|
488
|
+
auto: options.auto,
|
|
489
|
+
silent: options.silent,
|
|
490
|
+
verbose: options.verbose,
|
|
491
|
+
inputAllowed: options.inputAllowed,
|
|
492
|
+
cwd: options.cwd,
|
|
493
|
+
}),
|
|
494
|
+
)
|
|
495
|
+
},
|
|
496
|
+
},
|
|
497
|
+
review: {
|
|
498
|
+
run: async (args: string[], options: Record<string, any>) => {
|
|
499
|
+
if (options.help) return console.log(reviewHelp)
|
|
500
|
+
await runCommand(
|
|
501
|
+
review({
|
|
502
|
+
subcommand: args[0],
|
|
503
|
+
taskId: args[1],
|
|
504
|
+
ifRevision: options["if-revision"],
|
|
505
|
+
json: options.json,
|
|
506
|
+
silent: options.silent,
|
|
507
|
+
verbose: options.verbose,
|
|
508
|
+
cwd: options.cwd,
|
|
509
|
+
}),
|
|
510
|
+
)
|
|
511
|
+
},
|
|
512
|
+
},
|
|
513
|
+
work: {
|
|
514
|
+
run: async (args: string[], options: Record<string, any>) => {
|
|
515
|
+
if (options.help) {
|
|
516
|
+
console.log(workHelp)
|
|
517
|
+
return
|
|
518
|
+
}
|
|
519
|
+
const preparing = args[0] === "prepare"
|
|
520
|
+
await runCommand(
|
|
521
|
+
(preparing ? workPrepare : work)({
|
|
522
|
+
directory: args[preparing ? 1 : 0],
|
|
523
|
+
epicId: options.epic,
|
|
524
|
+
json: options.json,
|
|
525
|
+
dryRun: options["dry-run"],
|
|
526
|
+
silent: options.silent,
|
|
527
|
+
verbose: options.verbose,
|
|
528
|
+
opencode: options.opencode,
|
|
529
|
+
claude: options.claude,
|
|
530
|
+
runner: options.runner,
|
|
531
|
+
auto: options.auto,
|
|
532
|
+
printCommand: options["print-command"],
|
|
533
|
+
force: options.force,
|
|
534
|
+
inputAllowed: options.inputAllowed,
|
|
535
|
+
cwd: options.cwd,
|
|
536
|
+
taskId: options.task,
|
|
537
|
+
phaseId: options.phase,
|
|
538
|
+
}),
|
|
539
|
+
)
|
|
540
|
+
},
|
|
541
|
+
},
|
|
542
|
+
worktree: {
|
|
543
|
+
run: async (args: string[], options: Record<string, any>) => {
|
|
544
|
+
if (options.help) {
|
|
545
|
+
console.log(worktreeHelp)
|
|
546
|
+
return
|
|
547
|
+
}
|
|
548
|
+
await runCommand(
|
|
549
|
+
worktree({
|
|
550
|
+
subcommand: args[0],
|
|
551
|
+
args: args.slice(1),
|
|
552
|
+
dryRun: options["dry-run"],
|
|
553
|
+
json: options.json,
|
|
554
|
+
silent: options.silent,
|
|
555
|
+
verbose: options.verbose,
|
|
556
|
+
cwd: options.cwd,
|
|
557
|
+
}),
|
|
558
|
+
)
|
|
559
|
+
},
|
|
560
|
+
},
|
|
561
|
+
vcs: {
|
|
562
|
+
run: async (args: string[], options: Record<string, any>) => {
|
|
563
|
+
if (options.help) {
|
|
564
|
+
console.log(vcsHelp)
|
|
565
|
+
return
|
|
566
|
+
}
|
|
567
|
+
await runCommand(
|
|
568
|
+
vcs({
|
|
569
|
+
subcommand: args[0],
|
|
570
|
+
target: args[1],
|
|
571
|
+
apply: options.apply,
|
|
572
|
+
dryRun: options["dry-run"],
|
|
573
|
+
json: options.json,
|
|
574
|
+
silent: options.silent,
|
|
575
|
+
verbose: options.verbose,
|
|
576
|
+
cwd: options.cwd,
|
|
577
|
+
}),
|
|
578
|
+
)
|
|
579
|
+
},
|
|
580
|
+
},
|
|
581
|
+
next: {
|
|
582
|
+
run: async (_args: string[], options: Record<string, any>) => {
|
|
583
|
+
if (options.help) return console.log(nextHelp)
|
|
584
|
+
await runCommand(
|
|
585
|
+
next({
|
|
586
|
+
select: options.select,
|
|
587
|
+
json: options.json,
|
|
588
|
+
silent: options.silent,
|
|
589
|
+
verbose: options.verbose,
|
|
590
|
+
cwd: options.cwd,
|
|
591
|
+
}),
|
|
592
|
+
)
|
|
593
|
+
},
|
|
594
|
+
},
|
|
595
|
+
status: {
|
|
596
|
+
run: async (_args: string[], options: Record<string, any>) => {
|
|
597
|
+
if (options.help) {
|
|
598
|
+
console.log(statusHelp)
|
|
599
|
+
return
|
|
600
|
+
}
|
|
601
|
+
await runCommand(
|
|
602
|
+
status({
|
|
603
|
+
silent: options.silent,
|
|
604
|
+
verbose: options.verbose,
|
|
605
|
+
json: options.json,
|
|
606
|
+
statuses: options.status,
|
|
607
|
+
repositories: options.repository,
|
|
608
|
+
ready: options.ready,
|
|
609
|
+
blocked: options.blocked,
|
|
610
|
+
pr: options.pr ? true : options["no-pr"] ? false : undefined,
|
|
611
|
+
cwd: options.cwd,
|
|
612
|
+
}),
|
|
613
|
+
)
|
|
614
|
+
},
|
|
615
|
+
},
|
|
616
|
+
doctor: {
|
|
617
|
+
run: async (_args: string[], options: Record<string, any>) => {
|
|
618
|
+
if (options.help) {
|
|
619
|
+
console.log(doctorHelp)
|
|
620
|
+
return
|
|
621
|
+
}
|
|
622
|
+
await runCommand(
|
|
623
|
+
doctor({
|
|
624
|
+
silent: options.silent,
|
|
625
|
+
verbose: options.verbose,
|
|
626
|
+
json: options.json,
|
|
627
|
+
cwd: options.cwd,
|
|
628
|
+
}),
|
|
629
|
+
)
|
|
630
|
+
},
|
|
631
|
+
},
|
|
632
|
+
validate: {
|
|
633
|
+
run: async (args: string[], options: Record<string, any>) => {
|
|
634
|
+
if (options.help) {
|
|
635
|
+
console.log(validateHelp)
|
|
636
|
+
return
|
|
637
|
+
}
|
|
638
|
+
await runCommand(
|
|
639
|
+
validate({
|
|
640
|
+
path: args[0],
|
|
641
|
+
silent: options.silent,
|
|
642
|
+
verbose: options.verbose,
|
|
643
|
+
json: options.json,
|
|
644
|
+
inputAllowed: options.inputAllowed,
|
|
645
|
+
cwd: options.cwd,
|
|
646
|
+
}),
|
|
647
|
+
)
|
|
648
|
+
},
|
|
649
|
+
},
|
|
650
|
+
context: {
|
|
651
|
+
run: async (args: string[], options: Record<string, any>) => {
|
|
652
|
+
if (options.help) {
|
|
653
|
+
console.log(contextHelp)
|
|
654
|
+
return
|
|
655
|
+
}
|
|
656
|
+
await runCommand(
|
|
657
|
+
context({
|
|
658
|
+
target: options.epic
|
|
659
|
+
? join("epics", options.epic)
|
|
660
|
+
: options.phase
|
|
661
|
+
? join("tasks", options.task, "phases", options.phase)
|
|
662
|
+
: options.task
|
|
663
|
+
? join("tasks", options.task)
|
|
664
|
+
: args[0],
|
|
665
|
+
compact: options.compact,
|
|
666
|
+
full: options.full,
|
|
667
|
+
json: options.json,
|
|
668
|
+
silent: options.silent,
|
|
669
|
+
verbose: options.verbose,
|
|
670
|
+
cwd: options.cwd,
|
|
671
|
+
}),
|
|
672
|
+
)
|
|
673
|
+
},
|
|
674
|
+
},
|
|
675
|
+
graph: {
|
|
676
|
+
run: async (_args: string[], options: Record<string, any>) => {
|
|
677
|
+
if (options.help) {
|
|
678
|
+
console.log(graphHelp)
|
|
679
|
+
return
|
|
680
|
+
}
|
|
681
|
+
await runCommand(
|
|
682
|
+
graph({
|
|
683
|
+
json: options.json,
|
|
684
|
+
jsonl: options.jsonl,
|
|
685
|
+
ready: options.ready,
|
|
686
|
+
blocked: options.blocked,
|
|
687
|
+
statuses: options.status,
|
|
688
|
+
repositories: options.repository,
|
|
689
|
+
kinds: options.kind,
|
|
690
|
+
include: options.include,
|
|
691
|
+
silent: options.silent,
|
|
692
|
+
verbose: options.verbose,
|
|
693
|
+
cwd: options.cwd,
|
|
694
|
+
}),
|
|
695
|
+
)
|
|
696
|
+
},
|
|
697
|
+
},
|
|
698
|
+
sync: {
|
|
699
|
+
run: async (_args: string[], options: Record<string, any>) => {
|
|
700
|
+
if (options.help) {
|
|
701
|
+
console.log(syncHelp)
|
|
702
|
+
return
|
|
703
|
+
}
|
|
704
|
+
await runCommand(
|
|
705
|
+
sync({
|
|
706
|
+
apply: options.apply,
|
|
707
|
+
dryRun: options["dry-run"],
|
|
708
|
+
json: options.json,
|
|
709
|
+
silent: options.silent,
|
|
710
|
+
verbose: options.verbose,
|
|
711
|
+
cwd: options.cwd,
|
|
712
|
+
}),
|
|
713
|
+
)
|
|
714
|
+
},
|
|
715
|
+
},
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
function showMainHelp() {
|
|
719
|
+
console.log(`
|
|
720
|
+
agency v${VERSION}
|
|
721
|
+
|
|
722
|
+
Usage: agency <command> [options]
|
|
723
|
+
|
|
724
|
+
Commands:
|
|
725
|
+
init [path] Initialize an Agency workbase
|
|
726
|
+
workbase <subcommand> Manage registered workbases
|
|
727
|
+
integration <command> Inspect or sync managed integration files
|
|
728
|
+
epic <subcommand> Manage epics
|
|
729
|
+
phase <subcommand> Manage task phases
|
|
730
|
+
claim <task> [phase] Claim an execution unit
|
|
731
|
+
release <task> [phase] Release an execution unit
|
|
732
|
+
finish <task> [phase] Finish an execution unit
|
|
733
|
+
archive <type> Archive a work item
|
|
734
|
+
task <subcommand> Manage tasks
|
|
735
|
+
work [directory|task] Work on an epic, task, or phase
|
|
736
|
+
worktree <subcommand> Inspect and maintain managed workspaces
|
|
737
|
+
vcs <subcommand> Inspect or migrate the version-control backend
|
|
738
|
+
next List or select ready execution units
|
|
739
|
+
pr create Create a pull request for an execution unit
|
|
740
|
+
review refresh Explicitly refresh a pinned review task
|
|
741
|
+
repo <subcommand> Manage workbase repositories
|
|
742
|
+
status Show status for the current workbase
|
|
743
|
+
doctor Diagnose workbase health and integrations
|
|
744
|
+
validate [path] Validate a workbase
|
|
745
|
+
context [target] Return complete target context
|
|
746
|
+
graph Export the complete workbase graph
|
|
747
|
+
sync Reconcile declarations with external state
|
|
748
|
+
|
|
749
|
+
Global Options:
|
|
750
|
+
-h, --help Show help for a command
|
|
751
|
+
-V, --version Show version number
|
|
752
|
+
-s, --silent Suppress output messages
|
|
753
|
+
-v, --verbose Show verbose output including detailed debugging info
|
|
754
|
+
--no-input Never open an interactive prompt or selector
|
|
755
|
+
--workbase <selector> Use a registered workbase ID, name, or path
|
|
756
|
+
--cwd <path> Resolve context from this directory
|
|
757
|
+
|
|
758
|
+
Examples:
|
|
759
|
+
agency init # Initialize the current directory
|
|
760
|
+
agency task list # List tasks
|
|
761
|
+
agency work tasks/refresh-cli-copy # Start working on a task
|
|
762
|
+
|
|
763
|
+
For more information about a command, run:
|
|
764
|
+
agency <command> --help
|
|
765
|
+
`)
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
const machineMode = process.argv
|
|
769
|
+
.slice(2)
|
|
770
|
+
.some((argument) => argument === "--json" || argument === "--jsonl")
|
|
771
|
+
|
|
772
|
+
try {
|
|
773
|
+
const args = process.argv.slice(2)
|
|
774
|
+
const { commandName, args: commandArgs, values } = parseCli(args)
|
|
775
|
+
|
|
776
|
+
// Handle global flags
|
|
777
|
+
if (values.version) {
|
|
778
|
+
if (machineMode) {
|
|
779
|
+
writeEnvelope(successEnvelope({ version: VERSION }))
|
|
780
|
+
} else {
|
|
781
|
+
console.log(`v${VERSION}`)
|
|
782
|
+
}
|
|
783
|
+
process.exit(0)
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// Get command
|
|
787
|
+
// Show help if no command
|
|
788
|
+
if (!commandName) {
|
|
789
|
+
showMainHelp()
|
|
790
|
+
process.exit(values.help ? 0 : 1)
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
const command = commands[commandName]!
|
|
794
|
+
const inputAllowed =
|
|
795
|
+
!values.json &&
|
|
796
|
+
!values["no-input"] &&
|
|
797
|
+
Boolean(process.stdin.isTTY && process.stdout.isTTY)
|
|
798
|
+
const cwd = await resolveInvocationCwd(commandName, values)
|
|
799
|
+
if (values.json || (values.jsonl && values.help)) {
|
|
800
|
+
const result = await collectCommandResult(() =>
|
|
801
|
+
command.run(commandArgs, { ...values, cwd, inputAllowed }),
|
|
802
|
+
)
|
|
803
|
+
writeEnvelope(successEnvelope(result))
|
|
804
|
+
} else if (values.jsonl) {
|
|
805
|
+
await command.run(commandArgs, { ...values, cwd, inputAllowed: false })
|
|
806
|
+
} else {
|
|
807
|
+
await command.run(commandArgs, { ...values, cwd, inputAllowed })
|
|
808
|
+
}
|
|
809
|
+
} catch (error) {
|
|
810
|
+
if (machineMode) {
|
|
811
|
+
writeEnvelope(errorEnvelope(error))
|
|
812
|
+
process.exit(1)
|
|
813
|
+
}
|
|
814
|
+
if (error instanceof Error) {
|
|
815
|
+
let message = error.message
|
|
816
|
+
let details: any = error
|
|
817
|
+
|
|
818
|
+
// Handle Effect FiberFailure errors that wrap tagged errors
|
|
819
|
+
// When the message is generic "An error has occurred", try to extract the actual error
|
|
820
|
+
if (message === "An error has occurred") {
|
|
821
|
+
// Try to extract the actual error from Effect's Cause structure
|
|
822
|
+
const causeSymbol = Object.getOwnPropertySymbols(error).find((s) =>
|
|
823
|
+
s.toString().includes("Cause"),
|
|
824
|
+
)
|
|
825
|
+
if (causeSymbol) {
|
|
826
|
+
const cause = (error as any)[causeSymbol]
|
|
827
|
+
if (cause && cause._tag === "Fail" && cause.failure) {
|
|
828
|
+
const failure = cause.failure
|
|
829
|
+
details = failure
|
|
830
|
+
// Try common error message patterns
|
|
831
|
+
message =
|
|
832
|
+
failure.message ||
|
|
833
|
+
failure.stderr ||
|
|
834
|
+
(failure._tag
|
|
835
|
+
? `${failure._tag}: ${JSON.stringify(failure)}`
|
|
836
|
+
: JSON.stringify(failure))
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
for (const [field, label] of [
|
|
841
|
+
["completed", "Completed"],
|
|
842
|
+
["rolledBack", "Rolled back"],
|
|
843
|
+
["manualRecovery", "Manual recovery"],
|
|
844
|
+
] as const) {
|
|
845
|
+
if (Array.isArray(details[field]) && details[field].length > 0)
|
|
846
|
+
message += `\n${label}: ${details[field].join("; ")}`
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
console.error(`ⓘ ${message}`)
|
|
850
|
+
} else {
|
|
851
|
+
console.error("An unexpected error occurred:", error)
|
|
852
|
+
}
|
|
853
|
+
process.exit(1)
|
|
854
|
+
}
|