@markjaquith/agency 3.7.0 → 3.9.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 +32 -23
- package/package.json +1 -1
- package/src/act-schema.ts +3 -0
- package/src/cli-parser.ts +12 -4
- package/src/commands/act-actions.ts +799 -29
- package/src/commands/act.ts +9 -20
- package/src/commands/archive.ts +17 -9
- package/src/commands/sync.ts +2 -1
- package/src/commands/work.ts +11 -14
- package/src/services/ArchiveService.ts +25 -7
- package/src/services/SyncService.ts +6 -9
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
import { Effect } from "effect"
|
|
2
|
+
import { join } from "node:path"
|
|
2
3
|
import type { GraphNode } from "../graph-schema"
|
|
3
4
|
import type { BaseCommandOptions } from "../utils/command"
|
|
4
5
|
import type { ActionPrompts } from "./act-prompts"
|
|
5
6
|
import { archive } from "./archive"
|
|
7
|
+
import { doctor } from "./doctor"
|
|
8
|
+
import { epic } from "./epic"
|
|
9
|
+
import { integration } from "./integration"
|
|
6
10
|
import { repo } from "./repo"
|
|
11
|
+
import { review } from "./review"
|
|
7
12
|
import { task } from "./task"
|
|
8
13
|
import { phase } from "./phase"
|
|
14
|
+
import { push } from "./push"
|
|
9
15
|
import { sync } from "./sync"
|
|
10
16
|
import { status } from "./status"
|
|
11
17
|
import { pr, prCreate } from "./pr"
|
|
18
|
+
import { validate } from "./validate"
|
|
12
19
|
import { work as startWork, type StartWork } from "./work"
|
|
20
|
+
import { macchiato } from "../utils/theme"
|
|
13
21
|
|
|
14
22
|
export type ActEntity = Extract<
|
|
15
23
|
GraphNode,
|
|
@@ -18,9 +26,15 @@ export type ActEntity = Extract<
|
|
|
18
26
|
type NativeOperation =
|
|
19
27
|
| ReturnType<typeof task>
|
|
20
28
|
| ReturnType<typeof phase>
|
|
29
|
+
| ReturnType<typeof epic>
|
|
21
30
|
| ReturnType<typeof repo>
|
|
31
|
+
| ReturnType<typeof review>
|
|
32
|
+
| ReturnType<typeof push>
|
|
22
33
|
| ReturnType<typeof sync>
|
|
23
34
|
| ReturnType<typeof status>
|
|
35
|
+
| ReturnType<typeof doctor>
|
|
36
|
+
| ReturnType<typeof integration>
|
|
37
|
+
| ReturnType<typeof validate>
|
|
24
38
|
| ReturnType<typeof prCreate>
|
|
25
39
|
| ReturnType<typeof archive>
|
|
26
40
|
| ReturnType<StartWork>
|
|
@@ -50,11 +64,241 @@ interface Details {
|
|
|
50
64
|
readonly inputs?: readonly Input[]
|
|
51
65
|
}
|
|
52
66
|
export interface ActAction extends Details {
|
|
67
|
+
readonly description: string
|
|
68
|
+
readonly icon: string
|
|
69
|
+
readonly color: string
|
|
53
70
|
readonly preview: Plan
|
|
54
71
|
/** Replayable input collection. All mutations belong in the returned plan.run. */
|
|
55
72
|
readonly prepare: (prompts: ActionPrompts) => Effect.Effect<Plan, Error>
|
|
56
73
|
}
|
|
57
74
|
|
|
75
|
+
const actionPresentation: Record<
|
|
76
|
+
string,
|
|
77
|
+
{
|
|
78
|
+
readonly description: string
|
|
79
|
+
readonly icon: string
|
|
80
|
+
readonly color: string
|
|
81
|
+
}
|
|
82
|
+
> = {
|
|
83
|
+
"repo-add": {
|
|
84
|
+
description: "Clone a remote repository into this workbase.",
|
|
85
|
+
icon: "",
|
|
86
|
+
color: macchiato.green,
|
|
87
|
+
},
|
|
88
|
+
"repo-link": {
|
|
89
|
+
description: "Use an existing local checkout as a repository alias.",
|
|
90
|
+
icon: "",
|
|
91
|
+
color: macchiato.sapphire,
|
|
92
|
+
},
|
|
93
|
+
"repo-setup": {
|
|
94
|
+
description: "Materialize all portable repository declarations.",
|
|
95
|
+
icon: "",
|
|
96
|
+
color: macchiato.blue,
|
|
97
|
+
},
|
|
98
|
+
"repo-materialize": {
|
|
99
|
+
description: "Replace a linked alias with a managed bare clone.",
|
|
100
|
+
icon: "",
|
|
101
|
+
color: macchiato.blue,
|
|
102
|
+
},
|
|
103
|
+
"repo-fetch": {
|
|
104
|
+
description: "Fetch and prune a repository alias.",
|
|
105
|
+
icon: "",
|
|
106
|
+
color: macchiato.sapphire,
|
|
107
|
+
},
|
|
108
|
+
"repo-verify": {
|
|
109
|
+
description: "Check that a repository alias is operational.",
|
|
110
|
+
icon: "",
|
|
111
|
+
color: macchiato.green,
|
|
112
|
+
},
|
|
113
|
+
"repo-rename": {
|
|
114
|
+
description: "Rename an unused repository alias.",
|
|
115
|
+
icon: "",
|
|
116
|
+
color: macchiato.yellow,
|
|
117
|
+
},
|
|
118
|
+
"repo-remote": {
|
|
119
|
+
description: "Update an alias's portable remote declaration.",
|
|
120
|
+
icon: "",
|
|
121
|
+
color: macchiato.sapphire,
|
|
122
|
+
},
|
|
123
|
+
"repo-unlink": {
|
|
124
|
+
description: "Remove only this machine's linked checkout.",
|
|
125
|
+
icon: "",
|
|
126
|
+
color: macchiato.red,
|
|
127
|
+
},
|
|
128
|
+
"repo-remove": {
|
|
129
|
+
description: "Remove an unused alias and its local materialization.",
|
|
130
|
+
icon: "",
|
|
131
|
+
color: macchiato.red,
|
|
132
|
+
},
|
|
133
|
+
"task-create": {
|
|
134
|
+
description: "Create one deliverable execution task.",
|
|
135
|
+
icon: "",
|
|
136
|
+
color: macchiato.green,
|
|
137
|
+
},
|
|
138
|
+
"multi-phase-create": {
|
|
139
|
+
description: "Create a task that will be delivered through phases.",
|
|
140
|
+
icon: "",
|
|
141
|
+
color: macchiato.yellow,
|
|
142
|
+
},
|
|
143
|
+
"investigation-create": {
|
|
144
|
+
description: "Create evidence-gathering work before implementation.",
|
|
145
|
+
icon: "",
|
|
146
|
+
color: macchiato.mauve,
|
|
147
|
+
},
|
|
148
|
+
"epic-create": {
|
|
149
|
+
description: "Create an epic to coordinate related tasks.",
|
|
150
|
+
icon: "",
|
|
151
|
+
color: macchiato.mauve,
|
|
152
|
+
},
|
|
153
|
+
"task-create-in-epic": {
|
|
154
|
+
description: "Create a new execution task inside this epic.",
|
|
155
|
+
icon: "",
|
|
156
|
+
color: macchiato.green,
|
|
157
|
+
},
|
|
158
|
+
review: {
|
|
159
|
+
description: "Create a task pinned to an existing pull request.",
|
|
160
|
+
icon: "",
|
|
161
|
+
color: macchiato.mauve,
|
|
162
|
+
},
|
|
163
|
+
"review-ref": {
|
|
164
|
+
description: "Create a task pinned to a remote branch or commit.",
|
|
165
|
+
icon: "",
|
|
166
|
+
color: macchiato.mauve,
|
|
167
|
+
},
|
|
168
|
+
"current-work": {
|
|
169
|
+
description: "Show tasks and phases currently being worked.",
|
|
170
|
+
icon: "",
|
|
171
|
+
color: macchiato.blue,
|
|
172
|
+
},
|
|
173
|
+
"ready-work": {
|
|
174
|
+
description: "Show execution units that are ready to start.",
|
|
175
|
+
icon: "",
|
|
176
|
+
color: macchiato.green,
|
|
177
|
+
},
|
|
178
|
+
validate: {
|
|
179
|
+
description: "Validate workbase documents and relationships.",
|
|
180
|
+
icon: "",
|
|
181
|
+
color: macchiato.green,
|
|
182
|
+
},
|
|
183
|
+
doctor: {
|
|
184
|
+
description: "Diagnose workbase, repository, and integration health.",
|
|
185
|
+
icon: "",
|
|
186
|
+
color: macchiato.yellow,
|
|
187
|
+
},
|
|
188
|
+
"sync-all": {
|
|
189
|
+
description: "Reconcile all work with repository and provider state.",
|
|
190
|
+
icon: "",
|
|
191
|
+
color: macchiato.sapphire,
|
|
192
|
+
},
|
|
193
|
+
"integration-status": {
|
|
194
|
+
description: "Inspect managed agent integration files.",
|
|
195
|
+
icon: "",
|
|
196
|
+
color: macchiato.blue,
|
|
197
|
+
},
|
|
198
|
+
"integration-sync": {
|
|
199
|
+
description: "Update safe managed agent integration files.",
|
|
200
|
+
icon: "",
|
|
201
|
+
color: macchiato.sapphire,
|
|
202
|
+
},
|
|
203
|
+
work: {
|
|
204
|
+
description: "Prepare this item and start its configured worker.",
|
|
205
|
+
icon: "",
|
|
206
|
+
color: macchiato.green,
|
|
207
|
+
},
|
|
208
|
+
push: {
|
|
209
|
+
description: "Validate and publish this execution branch without a PR.",
|
|
210
|
+
icon: "",
|
|
211
|
+
color: macchiato.sapphire,
|
|
212
|
+
},
|
|
213
|
+
pr: {
|
|
214
|
+
description: "Publish this execution branch and record its pull request.",
|
|
215
|
+
icon: "",
|
|
216
|
+
color: macchiato.mauve,
|
|
217
|
+
},
|
|
218
|
+
"review-refresh": {
|
|
219
|
+
description: "Fetch and repin this review task to its current source.",
|
|
220
|
+
icon: "",
|
|
221
|
+
color: macchiato.sapphire,
|
|
222
|
+
},
|
|
223
|
+
reopen: {
|
|
224
|
+
description: "Return terminal work to open status.",
|
|
225
|
+
icon: "",
|
|
226
|
+
color: macchiato.sapphire,
|
|
227
|
+
},
|
|
228
|
+
drop: {
|
|
229
|
+
description: "Abandon this item without satisfying dependents.",
|
|
230
|
+
icon: "",
|
|
231
|
+
color: macchiato.red,
|
|
232
|
+
},
|
|
233
|
+
archive: {
|
|
234
|
+
description: "Move terminal work into the archive.",
|
|
235
|
+
icon: "",
|
|
236
|
+
color: macchiato.yellow,
|
|
237
|
+
},
|
|
238
|
+
split: {
|
|
239
|
+
description: "Convert or extend this task with another delivery phase.",
|
|
240
|
+
icon: "",
|
|
241
|
+
color: macchiato.yellow,
|
|
242
|
+
},
|
|
243
|
+
handoff: {
|
|
244
|
+
description: "Create distinct implementation work from this investigation.",
|
|
245
|
+
icon: "",
|
|
246
|
+
color: macchiato.mauve,
|
|
247
|
+
},
|
|
248
|
+
complete: {
|
|
249
|
+
description: "Record a genuine non-PR outcome as complete.",
|
|
250
|
+
icon: "",
|
|
251
|
+
color: macchiato.green,
|
|
252
|
+
},
|
|
253
|
+
sync: {
|
|
254
|
+
description: "Reconcile this item with repository and provider state.",
|
|
255
|
+
icon: "",
|
|
256
|
+
color: macchiato.sapphire,
|
|
257
|
+
},
|
|
258
|
+
"pr-ready": {
|
|
259
|
+
description: "Mark this recorded GitHub pull request ready for review.",
|
|
260
|
+
icon: "",
|
|
261
|
+
color: macchiato.green,
|
|
262
|
+
},
|
|
263
|
+
"pr-close": {
|
|
264
|
+
description: "Close this recorded GitHub pull request.",
|
|
265
|
+
icon: "",
|
|
266
|
+
color: macchiato.red,
|
|
267
|
+
},
|
|
268
|
+
rename: {
|
|
269
|
+
description: "Rename this item and update durable references.",
|
|
270
|
+
icon: "",
|
|
271
|
+
color: macchiato.yellow,
|
|
272
|
+
},
|
|
273
|
+
"dependency-add": {
|
|
274
|
+
description: "Require another sibling item to finish first.",
|
|
275
|
+
icon: "",
|
|
276
|
+
color: macchiato.yellow,
|
|
277
|
+
},
|
|
278
|
+
"dependency-remove": {
|
|
279
|
+
description: "Remove a completion dependency from this item.",
|
|
280
|
+
icon: "",
|
|
281
|
+
color: macchiato.red,
|
|
282
|
+
},
|
|
283
|
+
"move-to-epic": {
|
|
284
|
+
description: "Move this task into an existing epic.",
|
|
285
|
+
icon: "",
|
|
286
|
+
color: macchiato.mauve,
|
|
287
|
+
},
|
|
288
|
+
"remove-from-epic": {
|
|
289
|
+
description: "Move this task out of its current epic.",
|
|
290
|
+
icon: "",
|
|
291
|
+
color: macchiato.yellow,
|
|
292
|
+
},
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const present = (details: Details) => {
|
|
296
|
+
const presentation = actionPresentation[details.id]
|
|
297
|
+
if (!presentation)
|
|
298
|
+
throw new Error(`Missing presentation for action '${details.id}'`)
|
|
299
|
+
return { ...details, ...presentation }
|
|
300
|
+
}
|
|
301
|
+
|
|
58
302
|
// A typed argument builder is used for both discovery and execution. Prompts are
|
|
59
303
|
// ordinary TypeScript; discovery does not expose or interpret form instructions.
|
|
60
304
|
const action = <A>(
|
|
@@ -63,12 +307,12 @@ const action = <A>(
|
|
|
63
307
|
read: (p: ActionPrompts) => Effect.Effect<A, Error>,
|
|
64
308
|
build: (args: A) => Plan,
|
|
65
309
|
): ActAction => ({
|
|
66
|
-
...details,
|
|
310
|
+
...present(details),
|
|
67
311
|
preview: build(example),
|
|
68
312
|
prepare: (p) => Effect.map(read(p), build),
|
|
69
313
|
})
|
|
70
314
|
const immediate = (details: Details, plan: Plan): ActAction => ({
|
|
71
|
-
...details,
|
|
315
|
+
...present(details),
|
|
72
316
|
preview: plan,
|
|
73
317
|
prepare: () => Effect.succeed(plan),
|
|
74
318
|
})
|
|
@@ -90,9 +334,15 @@ const taskTarget = (node: ActEntity) =>
|
|
|
90
334
|
export const actionGroups = [
|
|
91
335
|
{
|
|
92
336
|
id: "create",
|
|
93
|
-
label: "Create
|
|
337
|
+
label: "Create work",
|
|
94
338
|
icon: "",
|
|
95
|
-
actions: [
|
|
339
|
+
actions: [
|
|
340
|
+
"task-create",
|
|
341
|
+
"multi-phase-create",
|
|
342
|
+
"investigation-create",
|
|
343
|
+
"epic-create",
|
|
344
|
+
"task-create-in-epic",
|
|
345
|
+
],
|
|
96
346
|
},
|
|
97
347
|
{
|
|
98
348
|
id: "work",
|
|
@@ -120,9 +370,9 @@ export const actionGroups = [
|
|
|
120
370
|
},
|
|
121
371
|
{
|
|
122
372
|
id: "current-work",
|
|
123
|
-
label: "See
|
|
373
|
+
label: "See work status",
|
|
124
374
|
icon: "",
|
|
125
|
-
actions: ["current-work"],
|
|
375
|
+
actions: ["current-work", "ready-work"],
|
|
126
376
|
},
|
|
127
377
|
{
|
|
128
378
|
id: "browse",
|
|
@@ -132,9 +382,9 @@ export const actionGroups = [
|
|
|
132
382
|
},
|
|
133
383
|
{
|
|
134
384
|
id: "pull-request",
|
|
135
|
-
label: "
|
|
385
|
+
label: "Publish or update a pull request",
|
|
136
386
|
icon: "",
|
|
137
|
-
actions: ["sync", "pr", "pr-ready", "pr-close"],
|
|
387
|
+
actions: ["push", "sync", "pr", "pr-ready", "pr-close"],
|
|
138
388
|
},
|
|
139
389
|
{
|
|
140
390
|
id: "close",
|
|
@@ -150,9 +400,44 @@ export const actionGroups = [
|
|
|
150
400
|
},
|
|
151
401
|
{
|
|
152
402
|
id: "repository",
|
|
153
|
-
label: "
|
|
403
|
+
label: "Manage repositories",
|
|
154
404
|
icon: "",
|
|
155
|
-
actions: [
|
|
405
|
+
actions: [
|
|
406
|
+
"repo-add",
|
|
407
|
+
"repo-link",
|
|
408
|
+
"repo-setup",
|
|
409
|
+
"repo-materialize",
|
|
410
|
+
"repo-fetch",
|
|
411
|
+
"repo-verify",
|
|
412
|
+
"repo-rename",
|
|
413
|
+
"repo-remote",
|
|
414
|
+
"repo-unlink",
|
|
415
|
+
"repo-remove",
|
|
416
|
+
],
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
id: "health",
|
|
420
|
+
label: "Check or refresh the workbase",
|
|
421
|
+
icon: "",
|
|
422
|
+
actions: [
|
|
423
|
+
"validate",
|
|
424
|
+
"doctor",
|
|
425
|
+
"sync-all",
|
|
426
|
+
"integration-status",
|
|
427
|
+
"integration-sync",
|
|
428
|
+
],
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
id: "organize",
|
|
432
|
+
label: "Rename or change dependencies",
|
|
433
|
+
icon: "",
|
|
434
|
+
actions: [
|
|
435
|
+
"rename",
|
|
436
|
+
"move-to-epic",
|
|
437
|
+
"remove-from-epic",
|
|
438
|
+
"dependency-add",
|
|
439
|
+
"dependency-remove",
|
|
440
|
+
],
|
|
156
441
|
},
|
|
157
442
|
] as const
|
|
158
443
|
|
|
@@ -209,26 +494,38 @@ export const actActions = (
|
|
|
209
494
|
}),
|
|
210
495
|
),
|
|
211
496
|
),
|
|
212
|
-
...([
|
|
497
|
+
...(["standard", "multi-phase", "investigation"] as const).map((kind) =>
|
|
213
498
|
action(
|
|
214
499
|
{
|
|
215
|
-
id:
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
500
|
+
id:
|
|
501
|
+
kind === "standard"
|
|
502
|
+
? "task-create"
|
|
503
|
+
: kind === "multi-phase"
|
|
504
|
+
? "multi-phase-create"
|
|
505
|
+
: "investigation-create",
|
|
506
|
+
label:
|
|
507
|
+
kind === "standard"
|
|
508
|
+
? "Create a standard task"
|
|
509
|
+
: kind === "multi-phase"
|
|
510
|
+
? "Create a multi-phase task"
|
|
511
|
+
: "Create an investigation",
|
|
512
|
+
blockedReason: kind === "multi-phase" ? null : needsRepository,
|
|
220
513
|
inputs: [
|
|
221
514
|
input("description", "Outcome", { multiline: true }),
|
|
222
515
|
input("id", "Task ID"),
|
|
223
|
-
|
|
224
|
-
|
|
516
|
+
...(kind === "multi-phase"
|
|
517
|
+
? []
|
|
518
|
+
: [
|
|
519
|
+
input("repo", "Repository alias"),
|
|
520
|
+
input("base", "Base branch"),
|
|
521
|
+
]),
|
|
225
522
|
],
|
|
226
523
|
},
|
|
227
524
|
{
|
|
228
525
|
id: "<id>",
|
|
229
526
|
description: "<description>",
|
|
230
|
-
repo: "<repo>",
|
|
231
|
-
base: "<base>",
|
|
527
|
+
repo: kind === "multi-phase" ? undefined : "<repo>",
|
|
528
|
+
base: kind === "multi-phase" ? undefined : "<base>",
|
|
232
529
|
},
|
|
233
530
|
(p) =>
|
|
234
531
|
Effect.gen(function* () {
|
|
@@ -236,8 +533,12 @@ export const actActions = (
|
|
|
236
533
|
return {
|
|
237
534
|
description,
|
|
238
535
|
id: yield* p.id("New task ID", description),
|
|
239
|
-
repo:
|
|
240
|
-
|
|
536
|
+
repo:
|
|
537
|
+
kind === "multi-phase" ? undefined : yield* p.repository(),
|
|
538
|
+
base:
|
|
539
|
+
kind === "multi-phase"
|
|
540
|
+
? undefined
|
|
541
|
+
: yield* p.text("Base branch", "main"),
|
|
241
542
|
}
|
|
242
543
|
}),
|
|
243
544
|
(args) => ({
|
|
@@ -246,25 +547,81 @@ export const actActions = (
|
|
|
246
547
|
"task",
|
|
247
548
|
"create",
|
|
248
549
|
args.id,
|
|
249
|
-
"--repo",
|
|
250
|
-
args.
|
|
251
|
-
"--base",
|
|
252
|
-
args.base,
|
|
550
|
+
...(args.repo ? ["--repo", args.repo] : []),
|
|
551
|
+
...(args.base ? ["--base", args.base] : []),
|
|
253
552
|
"--description",
|
|
254
553
|
args.description,
|
|
255
|
-
...(
|
|
554
|
+
...(kind === "multi-phase" ? ["--multi-phase"] : []),
|
|
555
|
+
...(kind === "investigation"
|
|
556
|
+
? ["--purpose", "investigation"]
|
|
557
|
+
: []),
|
|
256
558
|
],
|
|
257
559
|
run: task({
|
|
258
560
|
...options,
|
|
259
561
|
subcommand: "create",
|
|
260
562
|
args: [args.id],
|
|
261
563
|
...args,
|
|
262
|
-
|
|
564
|
+
multiPhase: kind === "multi-phase",
|
|
565
|
+
purpose: kind === "investigation" ? "investigation" : undefined,
|
|
263
566
|
}),
|
|
264
567
|
next: { taskId: args.id },
|
|
265
568
|
}),
|
|
266
569
|
),
|
|
267
570
|
),
|
|
571
|
+
action(
|
|
572
|
+
{
|
|
573
|
+
id: "epic-create",
|
|
574
|
+
label: "Create an epic",
|
|
575
|
+
blockedReason: needsRepository,
|
|
576
|
+
inputs: [
|
|
577
|
+
input("description", "Outcome", { multiline: true }),
|
|
578
|
+
input("id", "Epic ID"),
|
|
579
|
+
input("ticketUrl", "Ticket URL"),
|
|
580
|
+
input("repo", "Repository alias"),
|
|
581
|
+
input("ref", "Repository ref"),
|
|
582
|
+
],
|
|
583
|
+
},
|
|
584
|
+
{
|
|
585
|
+
id: "<id>",
|
|
586
|
+
description: "<description>",
|
|
587
|
+
ticketUrl: "<ticketUrl>",
|
|
588
|
+
repo: "<repo>",
|
|
589
|
+
ref: "<ref>",
|
|
590
|
+
},
|
|
591
|
+
(p) =>
|
|
592
|
+
Effect.gen(function* () {
|
|
593
|
+
const description = yield* p.text("Outcome")
|
|
594
|
+
return {
|
|
595
|
+
description,
|
|
596
|
+
id: yield* p.id("New epic ID", description),
|
|
597
|
+
ticketUrl: yield* p.text("Ticket URL"),
|
|
598
|
+
repo: yield* p.repository(),
|
|
599
|
+
ref: yield* p.text("Repository ref", "main"),
|
|
600
|
+
}
|
|
601
|
+
}),
|
|
602
|
+
(values) => ({
|
|
603
|
+
command: [
|
|
604
|
+
"agency",
|
|
605
|
+
"epic",
|
|
606
|
+
"create",
|
|
607
|
+
values.id,
|
|
608
|
+
"--ticket-url",
|
|
609
|
+
values.ticketUrl,
|
|
610
|
+
"--repo",
|
|
611
|
+
`${values.repo}:${values.ref}`,
|
|
612
|
+
"--description",
|
|
613
|
+
values.description,
|
|
614
|
+
],
|
|
615
|
+
run: epic({
|
|
616
|
+
...options,
|
|
617
|
+
subcommand: "create",
|
|
618
|
+
args: [values.id],
|
|
619
|
+
ticketUrl: values.ticketUrl,
|
|
620
|
+
repos: [`${values.repo}:${values.ref}`],
|
|
621
|
+
description: values.description,
|
|
622
|
+
}),
|
|
623
|
+
}),
|
|
624
|
+
),
|
|
268
625
|
...(["pull-request", "ref"] as const).map((sourceType) =>
|
|
269
626
|
action(
|
|
270
627
|
{
|
|
@@ -337,6 +694,157 @@ export const actActions = (
|
|
|
337
694
|
run: status({ ...options, statuses: ["working"] }),
|
|
338
695
|
},
|
|
339
696
|
),
|
|
697
|
+
immediate(
|
|
698
|
+
{
|
|
699
|
+
id: "ready-work",
|
|
700
|
+
label: "See ready work",
|
|
701
|
+
blockedReason: null,
|
|
702
|
+
},
|
|
703
|
+
{
|
|
704
|
+
command: ["agency", "status", "--ready"],
|
|
705
|
+
run: status({ ...options, ready: true }),
|
|
706
|
+
},
|
|
707
|
+
),
|
|
708
|
+
immediate(
|
|
709
|
+
{
|
|
710
|
+
id: "repo-setup",
|
|
711
|
+
label: "Set up declared repositories",
|
|
712
|
+
blockedReason: null,
|
|
713
|
+
},
|
|
714
|
+
{
|
|
715
|
+
command: ["agency", "repo", "setup", "--apply"],
|
|
716
|
+
run: repo({ ...options, subcommand: "setup", args: [], apply: true }),
|
|
717
|
+
},
|
|
718
|
+
),
|
|
719
|
+
...(["materialize", "fetch", "verify"] as const).map((operation) =>
|
|
720
|
+
action(
|
|
721
|
+
{
|
|
722
|
+
id: `repo-${operation}`,
|
|
723
|
+
label:
|
|
724
|
+
operation === "materialize"
|
|
725
|
+
? "Materialize a repository"
|
|
726
|
+
: operation === "fetch"
|
|
727
|
+
? "Fetch a repository"
|
|
728
|
+
: "Verify a repository",
|
|
729
|
+
blockedReason: needsRepository,
|
|
730
|
+
inputs: [input("repo", "Repository alias")],
|
|
731
|
+
},
|
|
732
|
+
"<repo>",
|
|
733
|
+
(p) => p.repository(),
|
|
734
|
+
(alias) => ({
|
|
735
|
+
command: ["agency", "repo", operation, alias],
|
|
736
|
+
run: repo({ ...options, subcommand: operation, args: [alias] }),
|
|
737
|
+
}),
|
|
738
|
+
),
|
|
739
|
+
),
|
|
740
|
+
action(
|
|
741
|
+
{
|
|
742
|
+
id: "repo-rename",
|
|
743
|
+
label: "Rename a repository alias",
|
|
744
|
+
blockedReason: needsRepository,
|
|
745
|
+
inputs: [
|
|
746
|
+
input("repo", "Repository alias"),
|
|
747
|
+
input("id", "New repository alias"),
|
|
748
|
+
],
|
|
749
|
+
},
|
|
750
|
+
{ repo: "<repo>", id: "<id>" },
|
|
751
|
+
(p) =>
|
|
752
|
+
Effect.gen(function* () {
|
|
753
|
+
return {
|
|
754
|
+
repo: yield* p.repository(),
|
|
755
|
+
id: yield* p.id("New repository alias", "repository"),
|
|
756
|
+
}
|
|
757
|
+
}),
|
|
758
|
+
({ repo: alias, id }) => ({
|
|
759
|
+
command: ["agency", "repo", "rename", alias, id],
|
|
760
|
+
run: repo({
|
|
761
|
+
...options,
|
|
762
|
+
subcommand: "rename",
|
|
763
|
+
args: [alias, id],
|
|
764
|
+
}),
|
|
765
|
+
}),
|
|
766
|
+
),
|
|
767
|
+
action(
|
|
768
|
+
{
|
|
769
|
+
id: "repo-remote",
|
|
770
|
+
label: "Update a repository remote",
|
|
771
|
+
blockedReason: needsRepository,
|
|
772
|
+
inputs: [
|
|
773
|
+
input("repo", "Repository alias"),
|
|
774
|
+
input("remote", "Remote URL"),
|
|
775
|
+
],
|
|
776
|
+
},
|
|
777
|
+
{ repo: "<repo>", remote: "<remote>" },
|
|
778
|
+
(p) =>
|
|
779
|
+
Effect.gen(function* () {
|
|
780
|
+
return {
|
|
781
|
+
repo: yield* p.repository(),
|
|
782
|
+
remote: yield* p.text("Portable remote URL"),
|
|
783
|
+
}
|
|
784
|
+
}),
|
|
785
|
+
({ repo: alias, remote }) => ({
|
|
786
|
+
command: ["agency", "repo", "remote", alias, remote],
|
|
787
|
+
run: repo({
|
|
788
|
+
...options,
|
|
789
|
+
subcommand: "remote",
|
|
790
|
+
args: [alias, remote],
|
|
791
|
+
}),
|
|
792
|
+
}),
|
|
793
|
+
),
|
|
794
|
+
...(["unlink", "remove"] as const).map((operation) =>
|
|
795
|
+
action(
|
|
796
|
+
{
|
|
797
|
+
id: `repo-${operation}`,
|
|
798
|
+
label:
|
|
799
|
+
operation === "unlink"
|
|
800
|
+
? "Unlink a local repository"
|
|
801
|
+
: "Remove a repository alias",
|
|
802
|
+
blockedReason: needsRepository,
|
|
803
|
+
inputs: [input("repo", "Repository alias")],
|
|
804
|
+
},
|
|
805
|
+
"<repo>",
|
|
806
|
+
(p) => p.repository(),
|
|
807
|
+
(alias) => ({
|
|
808
|
+
command: ["agency", "repo", operation, alias],
|
|
809
|
+
run: repo({ ...options, subcommand: operation, args: [alias] }),
|
|
810
|
+
}),
|
|
811
|
+
),
|
|
812
|
+
),
|
|
813
|
+
immediate(
|
|
814
|
+
{ id: "validate", label: "Validate workbase", blockedReason: null },
|
|
815
|
+
{
|
|
816
|
+
command: ["agency", "validate"],
|
|
817
|
+
run: validate(options),
|
|
818
|
+
},
|
|
819
|
+
),
|
|
820
|
+
immediate(
|
|
821
|
+
{ id: "doctor", label: "Diagnose workbase", blockedReason: null },
|
|
822
|
+
{ command: ["agency", "doctor"], run: doctor(options) },
|
|
823
|
+
),
|
|
824
|
+
immediate(
|
|
825
|
+
{
|
|
826
|
+
id: "sync-all",
|
|
827
|
+
label: "Refresh all Agency state",
|
|
828
|
+
blockedReason: null,
|
|
829
|
+
},
|
|
830
|
+
{ command: ["agency", "sync"], run: sync(options) },
|
|
831
|
+
),
|
|
832
|
+
...(["status", "sync"] as const).map((operation) =>
|
|
833
|
+
immediate(
|
|
834
|
+
{
|
|
835
|
+
id: `integration-${operation}`,
|
|
836
|
+
label:
|
|
837
|
+
operation === "status"
|
|
838
|
+
? "Check agent integration"
|
|
839
|
+
: "Update agent integration",
|
|
840
|
+
blockedReason: null,
|
|
841
|
+
},
|
|
842
|
+
{
|
|
843
|
+
command: ["agency", "integration", operation],
|
|
844
|
+
run: integration({ ...options, subcommand: operation }),
|
|
845
|
+
},
|
|
846
|
+
),
|
|
847
|
+
),
|
|
340
848
|
]
|
|
341
849
|
|
|
342
850
|
const target = taskTarget(node)
|
|
@@ -393,6 +901,14 @@ export const actActions = (
|
|
|
393
901
|
return target.phaseId ? phase(update) : task(update)
|
|
394
902
|
}
|
|
395
903
|
const workTarget = node.kind === "epic" ? { epicId: node.key } : target
|
|
904
|
+
const itemDirectory = join(
|
|
905
|
+
options.cwd ?? process.cwd(),
|
|
906
|
+
node.kind === "epic"
|
|
907
|
+
? `epics/${node.key}`
|
|
908
|
+
: target.phaseId
|
|
909
|
+
? `tasks/${target.taskId}/phases/${target.phaseId}`
|
|
910
|
+
: `tasks/${target.taskId}`,
|
|
911
|
+
)
|
|
396
912
|
const canWork =
|
|
397
913
|
node.readiness.ready ||
|
|
398
914
|
Boolean(
|
|
@@ -424,6 +940,22 @@ export const actActions = (
|
|
|
424
940
|
run: Effect.suspend(() => work({ ...options, ...workTarget })),
|
|
425
941
|
},
|
|
426
942
|
),
|
|
943
|
+
immediate(
|
|
944
|
+
details(
|
|
945
|
+
"push",
|
|
946
|
+
"Publish branch without a pull request",
|
|
947
|
+
noExecution ??
|
|
948
|
+
(parent && "review" in parent.data
|
|
949
|
+
? "Review tasks do not publish delivery branches"
|
|
950
|
+
: node.status !== "working"
|
|
951
|
+
? "Item must be working before publication"
|
|
952
|
+
: null),
|
|
953
|
+
),
|
|
954
|
+
{
|
|
955
|
+
command: ["agency", "--cwd", itemDirectory, "push"],
|
|
956
|
+
run: push({ ...options, cwd: itemDirectory }),
|
|
957
|
+
},
|
|
958
|
+
),
|
|
427
959
|
immediate(
|
|
428
960
|
details(
|
|
429
961
|
"pr",
|
|
@@ -450,6 +982,31 @@ export const actActions = (
|
|
|
450
982
|
run: prCreate({ ...options, ...target }),
|
|
451
983
|
},
|
|
452
984
|
),
|
|
985
|
+
immediate(
|
|
986
|
+
details(
|
|
987
|
+
"review-refresh",
|
|
988
|
+
"Refresh review source",
|
|
989
|
+
node.kind === "task" && "review" in node.data
|
|
990
|
+
? null
|
|
991
|
+
: "Select a review task",
|
|
992
|
+
),
|
|
993
|
+
{
|
|
994
|
+
command: [
|
|
995
|
+
"agency",
|
|
996
|
+
"review",
|
|
997
|
+
"refresh",
|
|
998
|
+
target.taskId,
|
|
999
|
+
"--if-revision",
|
|
1000
|
+
node.data.sha256,
|
|
1001
|
+
],
|
|
1002
|
+
run: review({
|
|
1003
|
+
...options,
|
|
1004
|
+
subcommand: "refresh",
|
|
1005
|
+
taskId: target.taskId,
|
|
1006
|
+
ifRevision: node.data.sha256,
|
|
1007
|
+
}),
|
|
1008
|
+
},
|
|
1009
|
+
),
|
|
453
1010
|
...(["reopen", "drop"] as const).map((id) => {
|
|
454
1011
|
const status = id === "reopen" ? "open" : "dropped"
|
|
455
1012
|
return immediate(
|
|
@@ -481,6 +1038,61 @@ export const actActions = (
|
|
|
481
1038
|
run: archive({ ...options, type: node.kind, args }),
|
|
482
1039
|
},
|
|
483
1040
|
),
|
|
1041
|
+
action(
|
|
1042
|
+
{
|
|
1043
|
+
...details(
|
|
1044
|
+
"task-create-in-epic",
|
|
1045
|
+
"Create a task in this epic",
|
|
1046
|
+
node.kind === "epic" ? needsRepository : "Select an epic",
|
|
1047
|
+
),
|
|
1048
|
+
inputs: [
|
|
1049
|
+
input("description", "Outcome", { multiline: true }),
|
|
1050
|
+
input("id", "Task ID"),
|
|
1051
|
+
input("repo", "Repository alias"),
|
|
1052
|
+
input("base", "Base branch"),
|
|
1053
|
+
],
|
|
1054
|
+
},
|
|
1055
|
+
{
|
|
1056
|
+
id: "<id>",
|
|
1057
|
+
description: "<description>",
|
|
1058
|
+
repo: "<repo>",
|
|
1059
|
+
base: "<base>",
|
|
1060
|
+
},
|
|
1061
|
+
(p) =>
|
|
1062
|
+
Effect.gen(function* () {
|
|
1063
|
+
const description = yield* p.text("Outcome")
|
|
1064
|
+
return {
|
|
1065
|
+
description,
|
|
1066
|
+
id: yield* p.id("New task ID", description),
|
|
1067
|
+
repo: yield* p.repository(node.repositories[0]),
|
|
1068
|
+
base: yield* p.text("Base branch", "main"),
|
|
1069
|
+
}
|
|
1070
|
+
}),
|
|
1071
|
+
(values) => ({
|
|
1072
|
+
command: [
|
|
1073
|
+
"agency",
|
|
1074
|
+
"task",
|
|
1075
|
+
"create",
|
|
1076
|
+
values.id,
|
|
1077
|
+
"--epic",
|
|
1078
|
+
node.key,
|
|
1079
|
+
"--repo",
|
|
1080
|
+
values.repo,
|
|
1081
|
+
"--base",
|
|
1082
|
+
values.base,
|
|
1083
|
+
"--description",
|
|
1084
|
+
values.description,
|
|
1085
|
+
],
|
|
1086
|
+
run: task({
|
|
1087
|
+
...options,
|
|
1088
|
+
subcommand: "create",
|
|
1089
|
+
args: [values.id],
|
|
1090
|
+
epic: node.key,
|
|
1091
|
+
...values,
|
|
1092
|
+
}),
|
|
1093
|
+
next: { taskId: values.id },
|
|
1094
|
+
}),
|
|
1095
|
+
),
|
|
484
1096
|
action(
|
|
485
1097
|
{
|
|
486
1098
|
...details(
|
|
@@ -688,14 +1300,172 @@ export const actActions = (
|
|
|
688
1300
|
},
|
|
689
1301
|
),
|
|
690
1302
|
),
|
|
1303
|
+
action(
|
|
1304
|
+
{
|
|
1305
|
+
...details("rename", "Rename item"),
|
|
1306
|
+
inputs: [input("id", "New item ID")],
|
|
1307
|
+
},
|
|
1308
|
+
"<id>",
|
|
1309
|
+
(p) =>
|
|
1310
|
+
p.id(
|
|
1311
|
+
`New ${node.kind} ID`,
|
|
1312
|
+
node.kind === "phase" ? target.phaseId! : node.key,
|
|
1313
|
+
node.kind === "phase" ? `${target.taskId}/` : "",
|
|
1314
|
+
),
|
|
1315
|
+
(id) => {
|
|
1316
|
+
const command = [
|
|
1317
|
+
"agency",
|
|
1318
|
+
node.kind,
|
|
1319
|
+
"rename",
|
|
1320
|
+
...(node.kind === "phase"
|
|
1321
|
+
? [target.taskId, target.phaseId!, id]
|
|
1322
|
+
: [node.key, id]),
|
|
1323
|
+
"--if-revision",
|
|
1324
|
+
node.data.sha256,
|
|
1325
|
+
]
|
|
1326
|
+
const update = {
|
|
1327
|
+
...options,
|
|
1328
|
+
subcommand: "rename",
|
|
1329
|
+
args:
|
|
1330
|
+
node.kind === "phase"
|
|
1331
|
+
? [target.taskId, target.phaseId!, id]
|
|
1332
|
+
: [node.key, id],
|
|
1333
|
+
ifRevision: node.data.sha256,
|
|
1334
|
+
}
|
|
1335
|
+
return {
|
|
1336
|
+
command,
|
|
1337
|
+
run:
|
|
1338
|
+
node.kind === "epic"
|
|
1339
|
+
? epic(update)
|
|
1340
|
+
: node.kind === "phase"
|
|
1341
|
+
? phase(update)
|
|
1342
|
+
: task(update),
|
|
1343
|
+
}
|
|
1344
|
+
},
|
|
1345
|
+
),
|
|
1346
|
+
action(
|
|
1347
|
+
{
|
|
1348
|
+
...details(
|
|
1349
|
+
"move-to-epic",
|
|
1350
|
+
"Move task to an epic",
|
|
1351
|
+
node.kind === "task" ? null : "Select a task",
|
|
1352
|
+
),
|
|
1353
|
+
inputs: [input("epic", "Epic ID")],
|
|
1354
|
+
},
|
|
1355
|
+
"<epic>",
|
|
1356
|
+
(p) => p.text("Destination epic ID"),
|
|
1357
|
+
(epicId) => ({
|
|
1358
|
+
command: [
|
|
1359
|
+
"agency",
|
|
1360
|
+
"task",
|
|
1361
|
+
"move",
|
|
1362
|
+
target.taskId,
|
|
1363
|
+
"--epic",
|
|
1364
|
+
epicId,
|
|
1365
|
+
"--if-revision",
|
|
1366
|
+
node.data.sha256,
|
|
1367
|
+
],
|
|
1368
|
+
run: task({
|
|
1369
|
+
...options,
|
|
1370
|
+
subcommand: "move",
|
|
1371
|
+
args: [target.taskId],
|
|
1372
|
+
epic: epicId,
|
|
1373
|
+
ifRevision: node.data.sha256,
|
|
1374
|
+
}),
|
|
1375
|
+
}),
|
|
1376
|
+
),
|
|
1377
|
+
immediate(
|
|
1378
|
+
details(
|
|
1379
|
+
"remove-from-epic",
|
|
1380
|
+
"Remove task from its epic",
|
|
1381
|
+
node.kind === "task" && node.data.epic
|
|
1382
|
+
? null
|
|
1383
|
+
: "Select a task that belongs to an epic",
|
|
1384
|
+
),
|
|
1385
|
+
{
|
|
1386
|
+
command: [
|
|
1387
|
+
"agency",
|
|
1388
|
+
"task",
|
|
1389
|
+
"move",
|
|
1390
|
+
target.taskId,
|
|
1391
|
+
"--no-epic",
|
|
1392
|
+
"--if-revision",
|
|
1393
|
+
node.data.sha256,
|
|
1394
|
+
],
|
|
1395
|
+
run: task({
|
|
1396
|
+
...options,
|
|
1397
|
+
subcommand: "move",
|
|
1398
|
+
args: [target.taskId],
|
|
1399
|
+
noEpic: true,
|
|
1400
|
+
ifRevision: node.data.sha256,
|
|
1401
|
+
}),
|
|
1402
|
+
},
|
|
1403
|
+
),
|
|
1404
|
+
...(["add", "remove"] as const).map((operation) =>
|
|
1405
|
+
action(
|
|
1406
|
+
{
|
|
1407
|
+
...details(
|
|
1408
|
+
`dependency-${operation}`,
|
|
1409
|
+
operation === "add" ? "Add dependency" : "Remove dependency",
|
|
1410
|
+
node.kind === "phase"
|
|
1411
|
+
? terminal
|
|
1412
|
+
? "Phase is terminal"
|
|
1413
|
+
: null
|
|
1414
|
+
: node.kind === "task" && node.data.epic
|
|
1415
|
+
? terminal
|
|
1416
|
+
? "Task is terminal"
|
|
1417
|
+
: null
|
|
1418
|
+
: "Select a phase or a task in an epic",
|
|
1419
|
+
),
|
|
1420
|
+
inputs: [input("dependency", "Dependency ID")],
|
|
1421
|
+
},
|
|
1422
|
+
"<dependency>",
|
|
1423
|
+
(p) =>
|
|
1424
|
+
p.text(`${operation === "add" ? "Required" : "Current"} sibling ID`),
|
|
1425
|
+
(dependency) => {
|
|
1426
|
+
const command = [
|
|
1427
|
+
"agency",
|
|
1428
|
+
node.kind === "phase" ? "phase" : "task",
|
|
1429
|
+
"dependency",
|
|
1430
|
+
operation,
|
|
1431
|
+
...args,
|
|
1432
|
+
dependency,
|
|
1433
|
+
"--if-revision",
|
|
1434
|
+
node.data.sha256,
|
|
1435
|
+
]
|
|
1436
|
+
const update = {
|
|
1437
|
+
...options,
|
|
1438
|
+
subcommand: "dependency",
|
|
1439
|
+
args: [operation, ...args, dependency],
|
|
1440
|
+
ifRevision: node.data.sha256,
|
|
1441
|
+
}
|
|
1442
|
+
return {
|
|
1443
|
+
command,
|
|
1444
|
+
run: node.kind === "phase" ? phase(update) : task(update),
|
|
1445
|
+
}
|
|
1446
|
+
},
|
|
1447
|
+
),
|
|
1448
|
+
),
|
|
691
1449
|
]
|
|
692
1450
|
}
|
|
693
1451
|
|
|
694
1452
|
export const actionOutput = (action: ActAction, auto?: boolean) => {
|
|
695
|
-
const {
|
|
1453
|
+
const {
|
|
1454
|
+
id,
|
|
1455
|
+
label,
|
|
1456
|
+
description,
|
|
1457
|
+
icon,
|
|
1458
|
+
color,
|
|
1459
|
+
blockedReason,
|
|
1460
|
+
preview,
|
|
1461
|
+
inputs = [],
|
|
1462
|
+
} = action
|
|
696
1463
|
return {
|
|
697
1464
|
id,
|
|
698
1465
|
label,
|
|
1466
|
+
description,
|
|
1467
|
+
icon,
|
|
1468
|
+
color,
|
|
699
1469
|
available: blockedReason === null,
|
|
700
1470
|
blockedReason,
|
|
701
1471
|
...(blockedReason
|