@markjaquith/agency 2.33.0 → 2.34.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 +9 -1
- package/cli.ts +8 -0
- package/package.json +1 -1
- package/src/cli-parser.test.ts +50 -0
- package/src/cli-parser.ts +56 -3
- package/src/commands/epic.test.ts +33 -0
- package/src/commands/epic.ts +19 -2
- package/src/commands/phase.ts +20 -2
- package/src/commands/task-phase.test.ts +35 -0
- package/src/commands/task.test.ts +35 -0
- package/src/commands/task.ts +20 -1
- package/src/commands/work.ts +3 -1
package/README.md
CHANGED
|
@@ -498,6 +498,8 @@ complete Markdown document.
|
|
|
498
498
|
### Epics
|
|
499
499
|
|
|
500
500
|
```text
|
|
501
|
+
agency epic new <id> --ticket-url <url> [--description <text>]
|
|
502
|
+
--repo <alias>:<ref> [--repo <alias>:<ref>...] [--work [--auto]]
|
|
501
503
|
agency epic create <id> --ticket-url <url> [--description <text>] [--json]
|
|
502
504
|
--repo <alias>:<ref> [--repo <alias>:<ref>...]
|
|
503
505
|
agency epic list [filters] [--json]
|
|
@@ -517,9 +519,13 @@ repository is available, Agency selects it without presenting a redundant
|
|
|
517
519
|
choice. This command requires a TTY and fails with `--no-input`:
|
|
518
520
|
|
|
519
521
|
```text
|
|
520
|
-
agency task new [id]
|
|
522
|
+
agency task new [id] [--work [--auto]]
|
|
521
523
|
```
|
|
522
524
|
|
|
525
|
+
`--work` starts work on the newly created entity. Add `--auto` to pass the
|
|
526
|
+
generated context prompt to the selected runner. These launch options are also
|
|
527
|
+
available on `epic new` and `phase new`; they cannot be combined with `--json`.
|
|
528
|
+
|
|
523
529
|
Create a single-phase task:
|
|
524
530
|
|
|
525
531
|
```text
|
|
@@ -590,6 +596,8 @@ phase. Dependencies remain explicit through `--depends-on`.
|
|
|
590
596
|
### Phases
|
|
591
597
|
|
|
592
598
|
```text
|
|
599
|
+
agency phase new <task-id> <phase-id>
|
|
600
|
+
--repo <alias> --branch <name> --base <name> [--work [--auto]]
|
|
593
601
|
agency phase create <task-id> <phase-id>
|
|
594
602
|
--repo <alias> --branch <name> --base <name>
|
|
595
603
|
[--description <text>] [--reference <alias>:<ref>...]
|
package/cli.ts
CHANGED
|
@@ -251,8 +251,11 @@ const commands: Record<string, Command> = {
|
|
|
251
251
|
ready: options.ready,
|
|
252
252
|
blocked: options.blocked,
|
|
253
253
|
pr: options.pr ? true : options["no-pr"] ? false : undefined,
|
|
254
|
+
work: options.work,
|
|
255
|
+
auto: options.auto,
|
|
254
256
|
silent: options.silent,
|
|
255
257
|
verbose: options.verbose,
|
|
258
|
+
inputAllowed: options.inputAllowed,
|
|
256
259
|
cwd: options.cwd,
|
|
257
260
|
}),
|
|
258
261
|
)
|
|
@@ -304,8 +307,11 @@ const commands: Record<string, Command> = {
|
|
|
304
307
|
ready: options.ready,
|
|
305
308
|
blocked: options.blocked,
|
|
306
309
|
pr: options.pr ? true : options["no-pr"] ? false : undefined,
|
|
310
|
+
work: options.work,
|
|
311
|
+
auto: options.auto,
|
|
307
312
|
silent: options.silent,
|
|
308
313
|
verbose: options.verbose,
|
|
314
|
+
inputAllowed: options.inputAllowed,
|
|
309
315
|
cwd: options.cwd,
|
|
310
316
|
}),
|
|
311
317
|
)
|
|
@@ -452,6 +458,8 @@ const commands: Record<string, Command> = {
|
|
|
452
458
|
ready: options.ready,
|
|
453
459
|
blocked: options.blocked,
|
|
454
460
|
pr: options.pr ? true : options["no-pr"] ? false : undefined,
|
|
461
|
+
work: options.work,
|
|
462
|
+
auto: options.auto,
|
|
455
463
|
silent: options.silent,
|
|
456
464
|
verbose: options.verbose,
|
|
457
465
|
inputAllowed: options.inputAllowed,
|
package/package.json
CHANGED
package/src/cli-parser.test.ts
CHANGED
|
@@ -477,6 +477,56 @@ describe("strict CLI parsing", () => {
|
|
|
477
477
|
)
|
|
478
478
|
})
|
|
479
479
|
|
|
480
|
+
test("accepts work launch options on new entities", () => {
|
|
481
|
+
expect(
|
|
482
|
+
parseCli(["task", "new", "example", "--work", "--auto"]),
|
|
483
|
+
).toMatchObject({
|
|
484
|
+
commandName: "task",
|
|
485
|
+
args: ["new", "example"],
|
|
486
|
+
values: { work: true, auto: true },
|
|
487
|
+
})
|
|
488
|
+
expect(
|
|
489
|
+
parseCli([
|
|
490
|
+
"epic",
|
|
491
|
+
"new",
|
|
492
|
+
"delivery",
|
|
493
|
+
"--ticket-url",
|
|
494
|
+
"https://example.com/delivery",
|
|
495
|
+
"--repo",
|
|
496
|
+
"agency:main",
|
|
497
|
+
"--work",
|
|
498
|
+
]),
|
|
499
|
+
).toMatchObject({ commandName: "epic", args: ["new", "delivery"] })
|
|
500
|
+
expect(
|
|
501
|
+
parseCli([
|
|
502
|
+
"phase",
|
|
503
|
+
"new",
|
|
504
|
+
"delivery",
|
|
505
|
+
"implementation",
|
|
506
|
+
"--repo",
|
|
507
|
+
"agency",
|
|
508
|
+
"--branch",
|
|
509
|
+
"task/implementation",
|
|
510
|
+
"--base",
|
|
511
|
+
"main",
|
|
512
|
+
"--work",
|
|
513
|
+
]),
|
|
514
|
+
).toMatchObject({
|
|
515
|
+
commandName: "phase",
|
|
516
|
+
args: ["new", "delivery", "implementation"],
|
|
517
|
+
})
|
|
518
|
+
expect(() => parseCli(["task", "new", "example", "--auto"])).toThrow(
|
|
519
|
+
"Option '--auto' requires '--work'",
|
|
520
|
+
)
|
|
521
|
+
expect(() =>
|
|
522
|
+
parseCli(["task", "new", "example", "--work", "--json"]),
|
|
523
|
+
).toThrow("cannot be combined")
|
|
524
|
+
expectUsageError(
|
|
525
|
+
["task", "create", "example", "--repo", "agency", "--work"],
|
|
526
|
+
"agency task create",
|
|
527
|
+
)
|
|
528
|
+
})
|
|
529
|
+
|
|
480
530
|
test("accepts repeatable graph filters and rejects output conflicts", () => {
|
|
481
531
|
expect(
|
|
482
532
|
parseCli([
|
package/src/cli-parser.ts
CHANGED
|
@@ -70,6 +70,11 @@ const createOptions = {
|
|
|
70
70
|
repo: { type: "string", multiple: true },
|
|
71
71
|
} satisfies OptionConfig
|
|
72
72
|
|
|
73
|
+
const newWorkOptions = {
|
|
74
|
+
work: { type: "boolean" },
|
|
75
|
+
auto: { type: "boolean" },
|
|
76
|
+
} satisfies OptionConfig
|
|
77
|
+
|
|
73
78
|
const taskCreateOptions = {
|
|
74
79
|
...createOptions,
|
|
75
80
|
reference: { type: "string", multiple: true },
|
|
@@ -282,14 +287,25 @@ const commands = {
|
|
|
282
287
|
},
|
|
283
288
|
},
|
|
284
289
|
epic: {
|
|
285
|
-
usage: "agency epic <create|list|show|update|rename>",
|
|
290
|
+
usage: "agency epic <new|create|list|show|update|rename>",
|
|
286
291
|
options: {
|
|
287
292
|
...createOptions,
|
|
293
|
+
...newWorkOptions,
|
|
288
294
|
...viewOptions,
|
|
289
295
|
...mutationOptions,
|
|
290
296
|
epic: { type: "string" },
|
|
291
297
|
},
|
|
292
298
|
subcommands: {
|
|
299
|
+
new: {
|
|
300
|
+
usage:
|
|
301
|
+
"agency epic new <id> --ticket-url <url> --repo <alias>:<ref> [--repo <alias>:<ref>...] [--work [--auto]]",
|
|
302
|
+
minArgs: 1,
|
|
303
|
+
maxArgs: 1,
|
|
304
|
+
options: ["ticket-url", "description", "repo", "work", "auto", "json"],
|
|
305
|
+
required: ["ticket-url", "repo"],
|
|
306
|
+
repeatable: ["repo"],
|
|
307
|
+
conflicts: [["work", "json"]],
|
|
308
|
+
},
|
|
293
309
|
create: {
|
|
294
310
|
usage:
|
|
295
311
|
"agency epic create <id> --ticket-url <url> --repo <alias>:<ref> [--repo <alias>:<ref>...]",
|
|
@@ -341,6 +357,7 @@ const commands = {
|
|
|
341
357
|
"agency task <new|create|list|show|status|update|rename|move|dependency>",
|
|
342
358
|
options: {
|
|
343
359
|
...taskCreateOptions,
|
|
360
|
+
...newWorkOptions,
|
|
344
361
|
...viewOptions,
|
|
345
362
|
...mutationOptions,
|
|
346
363
|
"pr-url": { type: "string" },
|
|
@@ -348,7 +365,7 @@ const commands = {
|
|
|
348
365
|
},
|
|
349
366
|
subcommands: {
|
|
350
367
|
new: {
|
|
351
|
-
usage: "agency task new [id] [options]",
|
|
368
|
+
usage: "agency task new [id] [options] [--work [--auto]]",
|
|
352
369
|
minArgs: 0,
|
|
353
370
|
maxArgs: 1,
|
|
354
371
|
options: [
|
|
@@ -360,9 +377,12 @@ const commands = {
|
|
|
360
377
|
"branch",
|
|
361
378
|
"base",
|
|
362
379
|
"multi-phase",
|
|
380
|
+
"work",
|
|
381
|
+
"auto",
|
|
363
382
|
"json",
|
|
364
383
|
],
|
|
365
384
|
repeatable: ["reference"],
|
|
385
|
+
conflicts: [["work", "json"]],
|
|
366
386
|
},
|
|
367
387
|
create: {
|
|
368
388
|
usage:
|
|
@@ -452,9 +472,11 @@ const commands = {
|
|
|
452
472
|
},
|
|
453
473
|
},
|
|
454
474
|
phase: {
|
|
455
|
-
usage:
|
|
475
|
+
usage:
|
|
476
|
+
"agency phase <new|create|list|show|status|update|rename|dependency>",
|
|
456
477
|
options: {
|
|
457
478
|
...phaseCreateOptions,
|
|
479
|
+
...newWorkOptions,
|
|
458
480
|
...viewOptions,
|
|
459
481
|
...mutationOptions,
|
|
460
482
|
"pr-url": { type: "string" },
|
|
@@ -462,6 +484,29 @@ const commands = {
|
|
|
462
484
|
phase: { type: "string" },
|
|
463
485
|
},
|
|
464
486
|
subcommands: {
|
|
487
|
+
new: {
|
|
488
|
+
usage:
|
|
489
|
+
"agency phase new <task-id> <phase-id> --repo <alias> --branch <name> --base <name> [options] [--work [--auto]]",
|
|
490
|
+
minArgs: 2,
|
|
491
|
+
maxArgs: 2,
|
|
492
|
+
options: [
|
|
493
|
+
"description",
|
|
494
|
+
"repo",
|
|
495
|
+
"reference",
|
|
496
|
+
"branch",
|
|
497
|
+
"base",
|
|
498
|
+
"depends-on",
|
|
499
|
+
"first-phase",
|
|
500
|
+
"work",
|
|
501
|
+
"auto",
|
|
502
|
+
"json",
|
|
503
|
+
"task",
|
|
504
|
+
"phase",
|
|
505
|
+
],
|
|
506
|
+
required: ["repo", "branch", "base"],
|
|
507
|
+
repeatable: ["reference", "depends-on"],
|
|
508
|
+
conflicts: [["work", "json"]],
|
|
509
|
+
},
|
|
465
510
|
create: {
|
|
466
511
|
usage:
|
|
467
512
|
"agency phase create <task-id> <phase-id> --repo <alias> --branch <name> --base <name> [options]",
|
|
@@ -1295,6 +1340,14 @@ export function parseCli(args: readonly string[]): ParsedCli {
|
|
|
1295
1340
|
) {
|
|
1296
1341
|
validateTaskCreate(parsed.values, spec, subcommand === "create")
|
|
1297
1342
|
}
|
|
1343
|
+
if (
|
|
1344
|
+
["epic", "task", "phase"].includes(commandName) &&
|
|
1345
|
+
subcommand === "new" &&
|
|
1346
|
+
parsed.values.auto &&
|
|
1347
|
+
!parsed.values.work
|
|
1348
|
+
) {
|
|
1349
|
+
throw usageError("Option '--auto' requires '--work'.", spec.usage)
|
|
1350
|
+
}
|
|
1298
1351
|
if (["task", "phase"].includes(commandName) && subcommand === "dependency") {
|
|
1299
1352
|
if (!["add", "remove"].includes(commandPositionals[0] ?? "")) {
|
|
1300
1353
|
throw usageError(
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test"
|
|
2
2
|
import { mkdir } from "node:fs/promises"
|
|
3
3
|
import { join } from "node:path"
|
|
4
|
+
import { Effect } from "effect"
|
|
4
5
|
import {
|
|
5
6
|
captureLogs,
|
|
6
7
|
cleanupTempDir,
|
|
@@ -131,4 +132,36 @@ describe("epic command", () => {
|
|
|
131
132
|
expect(logs[0]).toContain("example open waiting agency")
|
|
132
133
|
expect(logs[0]).not.toMatch(/[\u{e000}-\u{f8ff}]/u)
|
|
133
134
|
})
|
|
135
|
+
|
|
136
|
+
test("starts work on a newly created epic", async () => {
|
|
137
|
+
const launches: unknown[] = []
|
|
138
|
+
|
|
139
|
+
await runTestEffect(
|
|
140
|
+
epic(
|
|
141
|
+
{
|
|
142
|
+
subcommand: "new",
|
|
143
|
+
args: ["immediate"],
|
|
144
|
+
ticketUrl: "https://example.com/immediate",
|
|
145
|
+
repos: ["agency:main"],
|
|
146
|
+
work: true,
|
|
147
|
+
auto: true,
|
|
148
|
+
cwd: root,
|
|
149
|
+
silent: true,
|
|
150
|
+
},
|
|
151
|
+
(options) =>
|
|
152
|
+
Effect.sync(() => {
|
|
153
|
+
launches.push(options)
|
|
154
|
+
return undefined
|
|
155
|
+
}),
|
|
156
|
+
),
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
expect(launches).toEqual([
|
|
160
|
+
expect.objectContaining({
|
|
161
|
+
epicId: "immediate",
|
|
162
|
+
auto: true,
|
|
163
|
+
cwd: root,
|
|
164
|
+
}),
|
|
165
|
+
])
|
|
166
|
+
})
|
|
134
167
|
})
|
package/src/commands/epic.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { formatTable } from "../utils/table"
|
|
|
6
6
|
import { getWorkViews } from "../work-view"
|
|
7
7
|
import { parseRepositoryReferences } from "../workbase/repository-reference"
|
|
8
8
|
import { GraphMutationService } from "../services/GraphMutationService"
|
|
9
|
+
import { work as startWork, type StartWork } from "./work"
|
|
9
10
|
|
|
10
11
|
interface EpicOptions extends BaseCommandOptions {
|
|
11
12
|
readonly subcommand?: string
|
|
@@ -21,9 +22,11 @@ interface EpicOptions extends BaseCommandOptions {
|
|
|
21
22
|
readonly ready?: boolean
|
|
22
23
|
readonly blocked?: boolean
|
|
23
24
|
readonly pr?: boolean
|
|
25
|
+
readonly work?: boolean
|
|
26
|
+
readonly auto?: boolean
|
|
24
27
|
}
|
|
25
28
|
|
|
26
|
-
export const epic = (options: EpicOptions) =>
|
|
29
|
+
export const epic = (options: EpicOptions, work: StartWork = startWork) =>
|
|
27
30
|
Effect.gen(function* () {
|
|
28
31
|
const epics = yield* EpicService
|
|
29
32
|
const mutations = yield* GraphMutationService
|
|
@@ -31,6 +34,7 @@ export const epic = (options: EpicOptions) =>
|
|
|
31
34
|
const cwd = options.cwd ?? process.cwd()
|
|
32
35
|
|
|
33
36
|
switch (options.subcommand) {
|
|
37
|
+
case "new":
|
|
34
38
|
case "create": {
|
|
35
39
|
const id = options.args[0]
|
|
36
40
|
if (!id || !options.ticketUrl || !options.repos?.length) {
|
|
@@ -53,6 +57,16 @@ export const epic = (options: EpicOptions) =>
|
|
|
53
57
|
? JSON.stringify(output, null, 2)
|
|
54
58
|
: `Created epic '${record.id}'`,
|
|
55
59
|
)
|
|
60
|
+
if (options.subcommand === "new" && options.work) {
|
|
61
|
+
yield* work({
|
|
62
|
+
epicId: record.id,
|
|
63
|
+
auto: options.auto,
|
|
64
|
+
cwd,
|
|
65
|
+
inputAllowed: options.inputAllowed,
|
|
66
|
+
silent: options.silent,
|
|
67
|
+
verbose: options.verbose,
|
|
68
|
+
})
|
|
69
|
+
}
|
|
56
70
|
return
|
|
57
71
|
}
|
|
58
72
|
|
|
@@ -157,7 +171,7 @@ export const epic = (options: EpicOptions) =>
|
|
|
157
171
|
default:
|
|
158
172
|
return yield* Effect.fail(
|
|
159
173
|
new Error(
|
|
160
|
-
"Subcommand is required. Available subcommands: create, list, show, update, rename",
|
|
174
|
+
"Subcommand is required. Available subcommands: new, create, list, show, update, rename",
|
|
161
175
|
),
|
|
162
176
|
)
|
|
163
177
|
}
|
|
@@ -167,6 +181,7 @@ export const help = `
|
|
|
167
181
|
Usage: agency epic <subcommand>
|
|
168
182
|
|
|
169
183
|
Subcommands:
|
|
184
|
+
new <id> Create an epic, optionally starting work
|
|
170
185
|
create <id> Create an epic
|
|
171
186
|
list List epics
|
|
172
187
|
show <id> Show an epic
|
|
@@ -177,6 +192,8 @@ Create options:
|
|
|
177
192
|
--ticket-url <url> External ticket URL
|
|
178
193
|
--description <text> Short description of the epic
|
|
179
194
|
--repo <alias>:<ref> Read-only repository reference; repeatable
|
|
195
|
+
--work Start work on the new epic after creating it
|
|
196
|
+
--auto Pass --auto to work; requires --work
|
|
180
197
|
|
|
181
198
|
Update options:
|
|
182
199
|
--ticket-url <url> Replace the external ticket URL
|
package/src/commands/phase.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { formatTable } from "../utils/table"
|
|
|
6
6
|
import { getWorkViews } from "../work-view"
|
|
7
7
|
import { parseRepositoryReferences } from "../workbase/repository-reference"
|
|
8
8
|
import { GraphMutationService } from "../services/GraphMutationService"
|
|
9
|
+
import { work as startWork, type StartWork } from "./work"
|
|
9
10
|
|
|
10
11
|
interface PhaseOptions extends BaseCommandOptions {
|
|
11
12
|
readonly subcommand?: string
|
|
@@ -28,9 +29,11 @@ interface PhaseOptions extends BaseCommandOptions {
|
|
|
28
29
|
readonly ready?: boolean
|
|
29
30
|
readonly blocked?: boolean
|
|
30
31
|
readonly pr?: boolean
|
|
32
|
+
readonly work?: boolean
|
|
33
|
+
readonly auto?: boolean
|
|
31
34
|
}
|
|
32
35
|
|
|
33
|
-
export const phase = (options: PhaseOptions) =>
|
|
36
|
+
export const phase = (options: PhaseOptions, work: StartWork = startWork) =>
|
|
34
37
|
Effect.gen(function* () {
|
|
35
38
|
const phases = yield* PhaseService
|
|
36
39
|
const mutations = yield* GraphMutationService
|
|
@@ -39,6 +42,7 @@ export const phase = (options: PhaseOptions) =>
|
|
|
39
42
|
const [taskId, phaseId] = options.args
|
|
40
43
|
|
|
41
44
|
switch (options.subcommand) {
|
|
45
|
+
case "new":
|
|
42
46
|
case "create": {
|
|
43
47
|
if (
|
|
44
48
|
!taskId ||
|
|
@@ -73,6 +77,17 @@ export const phase = (options: PhaseOptions) =>
|
|
|
73
77
|
? JSON.stringify(output, null, 2)
|
|
74
78
|
: `Created phase '${record.id}' on task '${record.taskId}'`,
|
|
75
79
|
)
|
|
80
|
+
if (options.subcommand === "new" && options.work) {
|
|
81
|
+
yield* work({
|
|
82
|
+
taskId: record.taskId,
|
|
83
|
+
phaseId: record.id,
|
|
84
|
+
auto: options.auto,
|
|
85
|
+
cwd,
|
|
86
|
+
inputAllowed: options.inputAllowed,
|
|
87
|
+
silent: options.silent,
|
|
88
|
+
verbose: options.verbose,
|
|
89
|
+
})
|
|
90
|
+
}
|
|
76
91
|
return
|
|
77
92
|
}
|
|
78
93
|
case "list": {
|
|
@@ -245,7 +260,7 @@ export const phase = (options: PhaseOptions) =>
|
|
|
245
260
|
default:
|
|
246
261
|
return yield* Effect.fail(
|
|
247
262
|
new Error(
|
|
248
|
-
"Subcommand is required. Available: create, list, show, status, update, rename, dependency",
|
|
263
|
+
"Subcommand is required. Available: new, create, list, show, status, update, rename, dependency",
|
|
249
264
|
),
|
|
250
265
|
)
|
|
251
266
|
}
|
|
@@ -255,6 +270,7 @@ export const help = `
|
|
|
255
270
|
Usage: agency phase <subcommand> <task-id> [phase-id]
|
|
256
271
|
|
|
257
272
|
Subcommands:
|
|
273
|
+
new <task> <phase> Create a phase, optionally starting work
|
|
258
274
|
create <task> <phase> Create a phase
|
|
259
275
|
list <task> List task phases
|
|
260
276
|
show <task> <phase> Show a phase
|
|
@@ -278,6 +294,8 @@ Create options:
|
|
|
278
294
|
--base <name> Base branch
|
|
279
295
|
--depends-on <id> Phase dependency; repeatable
|
|
280
296
|
--first-phase <id> Existing execution phase ID when converting a task
|
|
297
|
+
--work Start work on the new phase after creating it
|
|
298
|
+
--auto Pass --auto to work; requires --work
|
|
281
299
|
|
|
282
300
|
Update options:
|
|
283
301
|
--description <text> / --clear-description
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test"
|
|
2
2
|
import { mkdir } from "node:fs/promises"
|
|
3
3
|
import { join } from "node:path"
|
|
4
|
+
import { Effect } from "effect"
|
|
4
5
|
import {
|
|
5
6
|
captureLogs,
|
|
6
7
|
cleanupTempDir,
|
|
@@ -199,6 +200,40 @@ describe("task and phase command JSON output", () => {
|
|
|
199
200
|
})
|
|
200
201
|
})
|
|
201
202
|
|
|
203
|
+
test("starts work on a newly created phase", async () => {
|
|
204
|
+
const launches: unknown[] = []
|
|
205
|
+
|
|
206
|
+
await runTestEffect(
|
|
207
|
+
phase(
|
|
208
|
+
{
|
|
209
|
+
subcommand: "new",
|
|
210
|
+
args: ["multi", "immediate"],
|
|
211
|
+
repo: "agency",
|
|
212
|
+
branch: "task/immediate",
|
|
213
|
+
base: "main",
|
|
214
|
+
work: true,
|
|
215
|
+
auto: true,
|
|
216
|
+
cwd: root,
|
|
217
|
+
silent: true,
|
|
218
|
+
},
|
|
219
|
+
(options) =>
|
|
220
|
+
Effect.sync(() => {
|
|
221
|
+
launches.push(options)
|
|
222
|
+
return undefined
|
|
223
|
+
}),
|
|
224
|
+
),
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
expect(launches).toEqual([
|
|
228
|
+
expect.objectContaining({
|
|
229
|
+
taskId: "multi",
|
|
230
|
+
phaseId: "immediate",
|
|
231
|
+
auto: true,
|
|
232
|
+
cwd: root,
|
|
233
|
+
}),
|
|
234
|
+
])
|
|
235
|
+
})
|
|
236
|
+
|
|
202
237
|
test("sets task and phase status", async () => {
|
|
203
238
|
const phaseLogs = await captureLogs(() =>
|
|
204
239
|
runTestEffect(
|
|
@@ -167,4 +167,39 @@ describe("task creation input", () => {
|
|
|
167
167
|
),
|
|
168
168
|
).rejects.toThrow("task new requires interactive input")
|
|
169
169
|
})
|
|
170
|
+
|
|
171
|
+
test("starts work on a newly created task", async () => {
|
|
172
|
+
const launches: unknown[] = []
|
|
173
|
+
|
|
174
|
+
await runTestEffect(
|
|
175
|
+
task(
|
|
176
|
+
{
|
|
177
|
+
subcommand: "new",
|
|
178
|
+
args: ["immediate"],
|
|
179
|
+
ticketUrl: "",
|
|
180
|
+
description: "",
|
|
181
|
+
multiPhase: false,
|
|
182
|
+
repo: "agency",
|
|
183
|
+
work: true,
|
|
184
|
+
auto: true,
|
|
185
|
+
cwd: root,
|
|
186
|
+
silent: true,
|
|
187
|
+
},
|
|
188
|
+
undefined,
|
|
189
|
+
(options) =>
|
|
190
|
+
Effect.sync(() => {
|
|
191
|
+
launches.push(options)
|
|
192
|
+
return undefined
|
|
193
|
+
}),
|
|
194
|
+
),
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
expect(launches).toEqual([
|
|
198
|
+
expect.objectContaining({
|
|
199
|
+
taskId: "immediate",
|
|
200
|
+
auto: true,
|
|
201
|
+
cwd: root,
|
|
202
|
+
}),
|
|
203
|
+
])
|
|
204
|
+
})
|
|
170
205
|
})
|
package/src/commands/task.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { choose } from "../utils/chooser"
|
|
|
10
10
|
import { formatTable } from "../utils/table"
|
|
11
11
|
import { getWorkViews } from "../work-view"
|
|
12
12
|
import { GraphMutationService } from "../services/GraphMutationService"
|
|
13
|
+
import { work as startWork, type StartWork } from "./work"
|
|
13
14
|
|
|
14
15
|
interface TaskOptions extends BaseCommandOptions {
|
|
15
16
|
readonly subcommand?: string
|
|
@@ -35,6 +36,8 @@ interface TaskOptions extends BaseCommandOptions {
|
|
|
35
36
|
readonly ready?: boolean
|
|
36
37
|
readonly blocked?: boolean
|
|
37
38
|
readonly pr?: boolean
|
|
39
|
+
readonly work?: boolean
|
|
40
|
+
readonly auto?: boolean
|
|
38
41
|
}
|
|
39
42
|
|
|
40
43
|
export interface TaskInteraction {
|
|
@@ -68,7 +71,11 @@ const defaultInteraction = (
|
|
|
68
71
|
),
|
|
69
72
|
})
|
|
70
73
|
|
|
71
|
-
export const task = (
|
|
74
|
+
export const task = (
|
|
75
|
+
options: TaskOptions,
|
|
76
|
+
interaction?: TaskInteraction,
|
|
77
|
+
work: StartWork = startWork,
|
|
78
|
+
) =>
|
|
72
79
|
Effect.gen(function* () {
|
|
73
80
|
const tasks = yield* TaskService
|
|
74
81
|
const epics = yield* EpicService
|
|
@@ -183,6 +190,16 @@ export const task = (options: TaskOptions, interaction?: TaskInteraction) =>
|
|
|
183
190
|
? JSON.stringify(output, null, 2)
|
|
184
191
|
: `Created task '${record.id}'`,
|
|
185
192
|
)
|
|
193
|
+
if (options.subcommand === "new" && options.work) {
|
|
194
|
+
yield* work({
|
|
195
|
+
taskId: record.id,
|
|
196
|
+
auto: options.auto,
|
|
197
|
+
cwd,
|
|
198
|
+
inputAllowed: options.inputAllowed,
|
|
199
|
+
silent: options.silent,
|
|
200
|
+
verbose: options.verbose,
|
|
201
|
+
})
|
|
202
|
+
}
|
|
186
203
|
return
|
|
187
204
|
}
|
|
188
205
|
case "create": {
|
|
@@ -426,6 +443,8 @@ Create options:
|
|
|
426
443
|
--branch <name> Working branch (default: task/<id>)
|
|
427
444
|
--base <name> Base branch (default: main)
|
|
428
445
|
--multi-phase Create a task container for phases
|
|
446
|
+
--work Start work on the new task after creating it
|
|
447
|
+
--auto Pass --auto to work; requires --work
|
|
429
448
|
|
|
430
449
|
Update options:
|
|
431
450
|
--ticket-url <url> / --clear-ticket
|
package/src/commands/work.ts
CHANGED
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
runnerEnvironment,
|
|
30
30
|
} from "../workbase/runner-command"
|
|
31
31
|
|
|
32
|
-
interface WorkOptions extends BaseCommandOptions {
|
|
32
|
+
export interface WorkOptions extends BaseCommandOptions {
|
|
33
33
|
readonly directory?: string
|
|
34
34
|
readonly taskId?: string
|
|
35
35
|
readonly phaseId?: string
|
|
@@ -42,6 +42,8 @@ interface WorkOptions extends BaseCommandOptions {
|
|
|
42
42
|
readonly force?: boolean
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
export type StartWork = (options: WorkOptions) => ReturnType<typeof work>
|
|
46
|
+
|
|
45
47
|
type LaunchAgent = (
|
|
46
48
|
cli: string,
|
|
47
49
|
args: readonly string[],
|