@markjaquith/agency 2.49.0 → 2.50.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 +22 -1
- package/cli.ts +23 -0
- package/package.json +1 -1
- package/schemas/agency-graph-v1.schema.json +141 -38
- package/src/cli-parser.test.ts +62 -0
- package/src/cli-parser.ts +55 -1
- package/src/commands/review.ts +38 -0
- package/src/commands/task.ts +27 -5
- package/src/commands/work.test.ts +2 -0
- package/src/commands/work.ts +2 -2
- package/src/graph-schema.ts +35 -13
- package/src/protocol.ts +1 -0
- package/src/services/ArchiveService.test.ts +2 -2
- package/src/services/ArchiveService.ts +1 -0
- package/src/services/ClaimService.ts +3 -2
- package/src/services/ContextService.ts +56 -4
- package/src/services/DoctorService.ts +45 -1
- package/src/services/GraphMutationService.ts +5 -0
- package/src/services/GraphService.ts +118 -54
- package/src/services/PhaseService.ts +5 -0
- package/src/services/PullRequestService.test.ts +2 -2
- package/src/services/PullRequestService.ts +23 -3
- package/src/services/ReadinessService.ts +7 -3
- package/src/services/ReviewService.test.ts +472 -0
- package/src/services/ReviewService.ts +404 -0
- package/src/services/SyncService.test.ts +2 -2
- package/src/services/SyncService.ts +110 -6
- package/src/services/TaskService.ts +82 -9
- package/src/services/WorkbaseService.ts +2 -1
- package/src/services/WorktreeService.test.ts +16 -16
- package/src/services/WorktreeService.ts +76 -40
- package/src/test-utils.ts +2 -0
- package/src/work-view.ts +14 -6
- package/src/workbase/schemas.test.ts +57 -0
- package/src/workbase/schemas.ts +56 -0
package/README.md
CHANGED
|
@@ -589,6 +589,26 @@ agency task create <id> --multi-phase
|
|
|
589
589
|
[--ticket-url <url>] [--description <text>] [--epic <id>] [--json]
|
|
590
590
|
```
|
|
591
591
|
|
|
592
|
+
Create a pinned, read-only review task from the selected alias's origin:
|
|
593
|
+
|
|
594
|
+
```text
|
|
595
|
+
agency task create <id> --review <alias> --pull-request <url-or-number>
|
|
596
|
+
agency task create <id> --review <alias> --ref <remote-ref>
|
|
597
|
+
agency review refresh <id> [--if-revision <hash>] [--json]
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
Review creation fetches the source and records its exact 40-character commit.
|
|
601
|
+
GitHub pull requests use the base alias's `refs/pull/<number>/head`, including
|
|
602
|
+
fork pull requests exposed through that ref. Review workspaces contain one
|
|
603
|
+
detached checkout and no writable branch. Source movement is observed separately
|
|
604
|
+
from the pin and applied only by `review refresh`; sync, doctor, work, cleanup,
|
|
605
|
+
and archive never move the pin implicitly. Dirty or structurally unexpected
|
|
606
|
+
review checkouts block refresh and cleanup. Review tasks support normal status
|
|
607
|
+
and claim lifecycle, but reject phase conversion and delivery PR operations.
|
|
608
|
+
Each active or archived review task owns one internal task-scoped pin ref. A
|
|
609
|
+
refresh advances that ref transactionally; failed creation removes it. Archiving
|
|
610
|
+
retains the pin so a deleted source can still be restored and inspected.
|
|
611
|
+
|
|
592
612
|
### Noninteractive Use
|
|
593
613
|
|
|
594
614
|
Agency never prompts when `--no-input` is set or stdin/stderr are not TTYs.
|
|
@@ -771,7 +791,8 @@ their built-in presets. Launches are interactive and promptless by default; use
|
|
|
771
791
|
`--auto` to send Agency's generated context prompt.
|
|
772
792
|
|
|
773
793
|
`agency work prepare` resolves an execution unit and creates or reuses its
|
|
774
|
-
writable and reference worktrees
|
|
794
|
+
writable and reference worktrees, or its single pinned review checkout, without
|
|
795
|
+
launching an agent or changing status.
|
|
775
796
|
Its JSON result includes document and checkout paths, resolved commits, actions,
|
|
776
797
|
and Git operations. Use `--dry-run` to report planned fetch, branch, and worktree
|
|
777
798
|
changes without applying them.
|
package/cli.ts
CHANGED
|
@@ -43,6 +43,8 @@ import { SyncService } from "./src/services/SyncService"
|
|
|
43
43
|
import { ReadinessService } from "./src/services/ReadinessService"
|
|
44
44
|
import { GraphMutationService } from "./src/services/GraphMutationService"
|
|
45
45
|
import { DoctorService } from "./src/services/DoctorService"
|
|
46
|
+
import { ReviewService } from "./src/services/ReviewService"
|
|
47
|
+
import { review, help as reviewHelp } from "./src/commands/review"
|
|
46
48
|
import {
|
|
47
49
|
claimCommand,
|
|
48
50
|
claimHelp,
|
|
@@ -75,6 +77,7 @@ const CliLayer = Layer.mergeAll(
|
|
|
75
77
|
ReadinessService.Default,
|
|
76
78
|
GraphMutationService.Default,
|
|
77
79
|
DoctorService.Default,
|
|
80
|
+
ReviewService.Default,
|
|
78
81
|
)
|
|
79
82
|
|
|
80
83
|
/**
|
|
@@ -449,6 +452,9 @@ const commands: Record<string, Command> = {
|
|
|
449
452
|
clearDescription: options["clear-description"],
|
|
450
453
|
epic: options.epic,
|
|
451
454
|
repo: options.repo?.[0],
|
|
455
|
+
review: options.review,
|
|
456
|
+
pullRequest: options["pull-request"],
|
|
457
|
+
ref: options.ref,
|
|
452
458
|
references: options.reference,
|
|
453
459
|
branch: options.branch,
|
|
454
460
|
base: options.base,
|
|
@@ -477,6 +483,22 @@ const commands: Record<string, Command> = {
|
|
|
477
483
|
)
|
|
478
484
|
},
|
|
479
485
|
},
|
|
486
|
+
review: {
|
|
487
|
+
run: async (args: string[], options: Record<string, any>) => {
|
|
488
|
+
if (options.help) return console.log(reviewHelp)
|
|
489
|
+
await runCommand(
|
|
490
|
+
review({
|
|
491
|
+
subcommand: args[0],
|
|
492
|
+
taskId: args[1],
|
|
493
|
+
ifRevision: options["if-revision"],
|
|
494
|
+
json: options.json,
|
|
495
|
+
silent: options.silent,
|
|
496
|
+
verbose: options.verbose,
|
|
497
|
+
cwd: options.cwd,
|
|
498
|
+
}),
|
|
499
|
+
)
|
|
500
|
+
},
|
|
501
|
+
},
|
|
480
502
|
work: {
|
|
481
503
|
run: async (args: string[], options: Record<string, any>) => {
|
|
482
504
|
if (options.help) {
|
|
@@ -683,6 +705,7 @@ Commands:
|
|
|
683
705
|
worktree <subcommand> Inspect and maintain managed worktrees
|
|
684
706
|
next List or select ready execution units
|
|
685
707
|
pr create Create a pull request for an execution unit
|
|
708
|
+
review refresh Explicitly refresh a pinned review task
|
|
686
709
|
repo <subcommand> Manage workbase repositories
|
|
687
710
|
status Show status for the current workbase
|
|
688
711
|
doctor Diagnose workbase health and integrations
|
package/package.json
CHANGED
|
@@ -176,6 +176,7 @@
|
|
|
176
176
|
{ "$ref": "#/$defs/epicData" },
|
|
177
177
|
{ "$ref": "#/$defs/singleTaskData" },
|
|
178
178
|
{ "$ref": "#/$defs/multiTaskData" },
|
|
179
|
+
{ "$ref": "#/$defs/reviewTaskData" },
|
|
179
180
|
{ "$ref": "#/$defs/phaseData" },
|
|
180
181
|
{ "$ref": "#/$defs/executionData" },
|
|
181
182
|
{ "$ref": "#/$defs/repositoryData" }
|
|
@@ -220,7 +221,8 @@
|
|
|
220
221
|
"data": {
|
|
221
222
|
"oneOf": [
|
|
222
223
|
{ "$ref": "#/$defs/singleTaskData" },
|
|
223
|
-
{ "$ref": "#/$defs/multiTaskData" }
|
|
224
|
+
{ "$ref": "#/$defs/multiTaskData" },
|
|
225
|
+
{ "$ref": "#/$defs/reviewTaskData" }
|
|
224
226
|
]
|
|
225
227
|
},
|
|
226
228
|
"workspace": { "$ref": "#/$defs/documentWorkspace" },
|
|
@@ -358,34 +360,72 @@
|
|
|
358
360
|
"sha256": { "type": "string" }
|
|
359
361
|
}
|
|
360
362
|
},
|
|
361
|
-
"
|
|
363
|
+
"reviewSource": {
|
|
364
|
+
"oneOf": [
|
|
365
|
+
{
|
|
366
|
+
"type": "object",
|
|
367
|
+
"additionalProperties": false,
|
|
368
|
+
"required": [
|
|
369
|
+
"kind",
|
|
370
|
+
"provider",
|
|
371
|
+
"repository",
|
|
372
|
+
"identifier",
|
|
373
|
+
"url",
|
|
374
|
+
"fetchRef"
|
|
375
|
+
],
|
|
376
|
+
"properties": {
|
|
377
|
+
"kind": { "const": "pull-request" },
|
|
378
|
+
"provider": { "const": "github" },
|
|
379
|
+
"repository": { "type": "string" },
|
|
380
|
+
"identifier": { "type": "string", "pattern": "^\\d+$" },
|
|
381
|
+
"url": {
|
|
382
|
+
"type": "string",
|
|
383
|
+
"pattern": "^https://github\\.com/[^/]+/[^/]+/pull/\\d+$"
|
|
384
|
+
},
|
|
385
|
+
"fetchRef": { "type": "string", "pattern": "^refs/pull/\\d+/head$" }
|
|
386
|
+
}
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
"type": "object",
|
|
390
|
+
"additionalProperties": false,
|
|
391
|
+
"required": ["kind", "ref"],
|
|
392
|
+
"properties": {
|
|
393
|
+
"kind": { "const": "branch" },
|
|
394
|
+
"ref": { "type": "string", "pattern": "^refs/heads/" }
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
]
|
|
398
|
+
},
|
|
399
|
+
"reviewRecord": {
|
|
362
400
|
"type": "object",
|
|
363
401
|
"additionalProperties": false,
|
|
364
|
-
"required": ["repo", "
|
|
402
|
+
"required": ["repo", "source", "commit", "refreshedAt"],
|
|
365
403
|
"properties": {
|
|
366
|
-
"description": { "type": "string" },
|
|
367
404
|
"repo": { "type": "string" },
|
|
368
|
-
"
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
405
|
+
"source": { "$ref": "#/$defs/reviewSource" },
|
|
406
|
+
"commit": { "type": "string", "pattern": "^[a-f0-9]{40}$" },
|
|
407
|
+
"refreshedAt": { "type": "string", "format": "date-time" }
|
|
408
|
+
}
|
|
409
|
+
},
|
|
410
|
+
"reviewTaskData": {
|
|
411
|
+
"type": "object",
|
|
412
|
+
"additionalProperties": false,
|
|
413
|
+
"required": ["ticketUrl", "review", "status", "sha256"],
|
|
414
|
+
"properties": {
|
|
415
|
+
"ticketUrl": { "type": ["string", "null"] },
|
|
416
|
+
"description": { "type": "string" },
|
|
417
|
+
"epic": { "type": "string" },
|
|
418
|
+
"review": { "$ref": "#/$defs/reviewRecord" },
|
|
375
419
|
"status": { "$ref": "#/$defs/status" },
|
|
376
420
|
"claim": { "$ref": "#/$defs/claim" },
|
|
377
421
|
"sha256": { "type": "string" }
|
|
378
422
|
}
|
|
379
423
|
},
|
|
380
|
-
"
|
|
424
|
+
"phaseData": {
|
|
381
425
|
"type": "object",
|
|
382
426
|
"additionalProperties": false,
|
|
383
|
-
"required": ["
|
|
427
|
+
"required": ["repo", "branch", "base", "pr", "status", "sha256"],
|
|
384
428
|
"properties": {
|
|
385
|
-
"taskId": { "type": "string" },
|
|
386
|
-
"phaseId": { "type": "string" },
|
|
387
|
-
"ticketUrl": { "type": ["string", "null"] },
|
|
388
|
-
"epic": { "type": "string" },
|
|
389
429
|
"description": { "type": "string" },
|
|
390
430
|
"repo": { "type": "string" },
|
|
391
431
|
"repos": {
|
|
@@ -396,9 +436,50 @@
|
|
|
396
436
|
"base": { "type": "string" },
|
|
397
437
|
"pr": { "type": ["string", "null"] },
|
|
398
438
|
"status": { "$ref": "#/$defs/status" },
|
|
399
|
-
"claim": { "$ref": "#/$defs/claim" }
|
|
439
|
+
"claim": { "$ref": "#/$defs/claim" },
|
|
440
|
+
"sha256": { "type": "string" }
|
|
400
441
|
}
|
|
401
442
|
},
|
|
443
|
+
"executionData": {
|
|
444
|
+
"oneOf": [
|
|
445
|
+
{
|
|
446
|
+
"type": "object",
|
|
447
|
+
"additionalProperties": false,
|
|
448
|
+
"required": ["taskId", "repo", "branch", "base", "pr", "status"],
|
|
449
|
+
"properties": {
|
|
450
|
+
"taskId": { "type": "string" },
|
|
451
|
+
"phaseId": { "type": "string" },
|
|
452
|
+
"ticketUrl": { "type": ["string", "null"] },
|
|
453
|
+
"epic": { "type": "string" },
|
|
454
|
+
"description": { "type": "string" },
|
|
455
|
+
"repo": { "type": "string" },
|
|
456
|
+
"repos": {
|
|
457
|
+
"type": "array",
|
|
458
|
+
"items": { "$ref": "#/$defs/repositoryReference" }
|
|
459
|
+
},
|
|
460
|
+
"branch": { "type": "string" },
|
|
461
|
+
"base": { "type": "string" },
|
|
462
|
+
"pr": { "type": ["string", "null"] },
|
|
463
|
+
"status": { "$ref": "#/$defs/status" },
|
|
464
|
+
"claim": { "$ref": "#/$defs/claim" }
|
|
465
|
+
}
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
"type": "object",
|
|
469
|
+
"additionalProperties": false,
|
|
470
|
+
"required": ["taskId", "ticketUrl", "review", "status"],
|
|
471
|
+
"properties": {
|
|
472
|
+
"taskId": { "type": "string" },
|
|
473
|
+
"ticketUrl": { "type": ["string", "null"] },
|
|
474
|
+
"description": { "type": "string" },
|
|
475
|
+
"epic": { "type": "string" },
|
|
476
|
+
"review": { "$ref": "#/$defs/reviewRecord" },
|
|
477
|
+
"status": { "$ref": "#/$defs/status" },
|
|
478
|
+
"claim": { "$ref": "#/$defs/claim" }
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
]
|
|
482
|
+
},
|
|
402
483
|
"claim": {
|
|
403
484
|
"type": "object",
|
|
404
485
|
"additionalProperties": false,
|
|
@@ -469,26 +550,48 @@
|
|
|
469
550
|
}
|
|
470
551
|
},
|
|
471
552
|
"executionGit": {
|
|
472
|
-
"
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
553
|
+
"oneOf": [
|
|
554
|
+
{
|
|
555
|
+
"type": "object",
|
|
556
|
+
"additionalProperties": false,
|
|
557
|
+
"required": [
|
|
558
|
+
"branch",
|
|
559
|
+
"base",
|
|
560
|
+
"branchCommit",
|
|
561
|
+
"baseCommit",
|
|
562
|
+
"checkoutCommit",
|
|
563
|
+
"checkoutBranch",
|
|
564
|
+
"dirty"
|
|
565
|
+
],
|
|
566
|
+
"properties": {
|
|
567
|
+
"branch": { "type": "string" },
|
|
568
|
+
"base": { "type": "string" },
|
|
569
|
+
"branchCommit": { "type": ["string", "null"] },
|
|
570
|
+
"baseCommit": { "type": ["string", "null"] },
|
|
571
|
+
"checkoutCommit": { "type": ["string", "null"] },
|
|
572
|
+
"checkoutBranch": { "type": ["string", "null"] },
|
|
573
|
+
"dirty": { "type": ["boolean", "null"] }
|
|
574
|
+
}
|
|
575
|
+
},
|
|
576
|
+
{
|
|
577
|
+
"type": "object",
|
|
578
|
+
"additionalProperties": false,
|
|
579
|
+
"required": [
|
|
580
|
+
"pinnedCommit",
|
|
581
|
+
"sourceCommit",
|
|
582
|
+
"checkoutCommit",
|
|
583
|
+
"checkoutBranch",
|
|
584
|
+
"dirty"
|
|
585
|
+
],
|
|
586
|
+
"properties": {
|
|
587
|
+
"pinnedCommit": { "type": "string" },
|
|
588
|
+
"sourceCommit": { "type": ["string", "null"] },
|
|
589
|
+
"checkoutCommit": { "type": ["string", "null"] },
|
|
590
|
+
"checkoutBranch": { "type": ["string", "null"] },
|
|
591
|
+
"dirty": { "type": ["boolean", "null"] }
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
]
|
|
492
595
|
},
|
|
493
596
|
"pr": {
|
|
494
597
|
"oneOf": [
|
package/src/cli-parser.test.ts
CHANGED
|
@@ -7,6 +7,68 @@ const expectUsageError = (args: string[], usage: string) => {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
describe("strict CLI parsing", () => {
|
|
10
|
+
test("accepts review creation and refresh while enforcing one source", () => {
|
|
11
|
+
expect(
|
|
12
|
+
parseCli([
|
|
13
|
+
"task",
|
|
14
|
+
"create",
|
|
15
|
+
"review-it",
|
|
16
|
+
"--review",
|
|
17
|
+
"agency",
|
|
18
|
+
"--pull-request",
|
|
19
|
+
"42",
|
|
20
|
+
]).values,
|
|
21
|
+
).toMatchObject({ review: "agency", "pull-request": "42" })
|
|
22
|
+
expect(() =>
|
|
23
|
+
parseCli([
|
|
24
|
+
"task",
|
|
25
|
+
"create",
|
|
26
|
+
"review-it",
|
|
27
|
+
"--review",
|
|
28
|
+
"agency",
|
|
29
|
+
"--ref",
|
|
30
|
+
"feature/review",
|
|
31
|
+
]),
|
|
32
|
+
).not.toThrow()
|
|
33
|
+
expect(() =>
|
|
34
|
+
parseCli([
|
|
35
|
+
"review",
|
|
36
|
+
"refresh",
|
|
37
|
+
"review-it",
|
|
38
|
+
"--if-revision",
|
|
39
|
+
"a".repeat(64),
|
|
40
|
+
]),
|
|
41
|
+
).not.toThrow()
|
|
42
|
+
expect(() =>
|
|
43
|
+
parseCli([
|
|
44
|
+
"task",
|
|
45
|
+
"create",
|
|
46
|
+
"review-it",
|
|
47
|
+
"--review",
|
|
48
|
+
"agency",
|
|
49
|
+
"--pull-request",
|
|
50
|
+
"42",
|
|
51
|
+
"--ref",
|
|
52
|
+
"feature/review",
|
|
53
|
+
]),
|
|
54
|
+
).toThrow("exactly one")
|
|
55
|
+
for (const extra of [
|
|
56
|
+
["--ref", "feature/review", "--repo", "agency"],
|
|
57
|
+
["--ref", "feature/review", "--multi-phase"],
|
|
58
|
+
]) {
|
|
59
|
+
expect(() =>
|
|
60
|
+
parseCli([
|
|
61
|
+
"task",
|
|
62
|
+
"create",
|
|
63
|
+
"review-it",
|
|
64
|
+
"--review",
|
|
65
|
+
"agency",
|
|
66
|
+
...extra,
|
|
67
|
+
]),
|
|
68
|
+
).toThrow("cannot be combined")
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
|
|
10
72
|
test("accepts every documented orchestration recipe command", () => {
|
|
11
73
|
for (const args of orchestrationRecipes) {
|
|
12
74
|
expect(() => parseCli(args)).not.toThrow()
|
package/src/cli-parser.ts
CHANGED
|
@@ -82,6 +82,9 @@ const taskCreateOptions = {
|
|
|
82
82
|
branch: { type: "string" },
|
|
83
83
|
base: { type: "string" },
|
|
84
84
|
"multi-phase": { type: "boolean" },
|
|
85
|
+
review: { type: "string" },
|
|
86
|
+
"pull-request": { type: "string" },
|
|
87
|
+
ref: { type: "string" },
|
|
85
88
|
} satisfies OptionConfig
|
|
86
89
|
|
|
87
90
|
const phaseCreateOptions = {
|
|
@@ -393,7 +396,7 @@ const commands = {
|
|
|
393
396
|
},
|
|
394
397
|
create: {
|
|
395
398
|
usage:
|
|
396
|
-
"agency task create <id> (--repo <alias> | --multi-phase) [options]",
|
|
399
|
+
"agency task create <id> (--repo <alias> | --multi-phase | --review <alias> (--pull-request <value> | --ref <remote-ref>)) [options]",
|
|
397
400
|
minArgs: 1,
|
|
398
401
|
maxArgs: 1,
|
|
399
402
|
options: [
|
|
@@ -405,6 +408,9 @@ const commands = {
|
|
|
405
408
|
"branch",
|
|
406
409
|
"base",
|
|
407
410
|
"multi-phase",
|
|
411
|
+
"review",
|
|
412
|
+
"pull-request",
|
|
413
|
+
"ref",
|
|
408
414
|
"json",
|
|
409
415
|
],
|
|
410
416
|
repeatable: ["reference"],
|
|
@@ -760,6 +766,22 @@ const commands = {
|
|
|
760
766
|
},
|
|
761
767
|
},
|
|
762
768
|
},
|
|
769
|
+
review: {
|
|
770
|
+
usage: "agency review refresh <task-id> [--if-revision <hash>] [--json]",
|
|
771
|
+
options: {
|
|
772
|
+
...outputOptions,
|
|
773
|
+
"if-revision": { type: "string" },
|
|
774
|
+
},
|
|
775
|
+
subcommands: {
|
|
776
|
+
refresh: {
|
|
777
|
+
usage:
|
|
778
|
+
"agency review refresh <task-id> [--if-revision <hash>] [--json]",
|
|
779
|
+
minArgs: 1,
|
|
780
|
+
maxArgs: 1,
|
|
781
|
+
options: ["if-revision", "json"],
|
|
782
|
+
},
|
|
783
|
+
},
|
|
784
|
+
},
|
|
763
785
|
worktree: {
|
|
764
786
|
usage: "agency worktree <list|inspect|prepare|remove|rebuild|repair>",
|
|
765
787
|
options: {
|
|
@@ -1161,6 +1183,38 @@ function validateTaskCreate(
|
|
|
1161
1183
|
spec: LeafCommand,
|
|
1162
1184
|
requireRepo: boolean,
|
|
1163
1185
|
) {
|
|
1186
|
+
const review = values.review !== undefined
|
|
1187
|
+
const pullRequest = values["pull-request"] !== undefined
|
|
1188
|
+
const ref = values.ref !== undefined
|
|
1189
|
+
if (review) {
|
|
1190
|
+
if (pullRequest === ref) {
|
|
1191
|
+
throw usageError(
|
|
1192
|
+
"Option '--review' requires exactly one of '--pull-request' or '--ref'.",
|
|
1193
|
+
spec.usage,
|
|
1194
|
+
)
|
|
1195
|
+
}
|
|
1196
|
+
for (const option of [
|
|
1197
|
+
"repo",
|
|
1198
|
+
"reference",
|
|
1199
|
+
"branch",
|
|
1200
|
+
"base",
|
|
1201
|
+
"multi-phase",
|
|
1202
|
+
] as const) {
|
|
1203
|
+
if (values[option] !== undefined) {
|
|
1204
|
+
throw usageError(
|
|
1205
|
+
`Option '--review' cannot be combined with '${optionLabel(option)}'.`,
|
|
1206
|
+
spec.usage,
|
|
1207
|
+
)
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
return
|
|
1211
|
+
}
|
|
1212
|
+
if (pullRequest || ref) {
|
|
1213
|
+
throw usageError(
|
|
1214
|
+
`Option '${pullRequest ? "--pull-request" : "--ref"}' requires '--review'.`,
|
|
1215
|
+
spec.usage,
|
|
1216
|
+
)
|
|
1217
|
+
}
|
|
1164
1218
|
if (values["multi-phase"]) {
|
|
1165
1219
|
for (const option of ["repo", "reference", "branch", "base"] as const) {
|
|
1166
1220
|
if (values[option] !== undefined) {
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Effect } from "effect"
|
|
2
|
+
import { ReviewService } from "../services/ReviewService"
|
|
3
|
+
import type { BaseCommandOptions } from "../utils/command"
|
|
4
|
+
import { createLoggers } from "../utils/effect"
|
|
5
|
+
|
|
6
|
+
interface ReviewOptions extends BaseCommandOptions {
|
|
7
|
+
readonly subcommand?: string
|
|
8
|
+
readonly taskId?: string
|
|
9
|
+
readonly ifRevision?: string
|
|
10
|
+
readonly json?: boolean
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const review = (options: ReviewOptions) =>
|
|
14
|
+
Effect.gen(function* () {
|
|
15
|
+
if (options.subcommand !== "refresh" || !options.taskId) {
|
|
16
|
+
return yield* Effect.fail(
|
|
17
|
+
new Error("Usage: agency review refresh <task>"),
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
const result = yield* (yield* ReviewService).refresh(
|
|
21
|
+
options.taskId,
|
|
22
|
+
options.cwd,
|
|
23
|
+
options.ifRevision,
|
|
24
|
+
)
|
|
25
|
+
const { log } = createLoggers(options)
|
|
26
|
+
log(
|
|
27
|
+
options.json
|
|
28
|
+
? JSON.stringify(result, null, 2)
|
|
29
|
+
: `Refreshed review '${options.taskId}' at ${result.commit}`,
|
|
30
|
+
)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
export const help = `
|
|
34
|
+
Usage: agency review refresh <task-id> [--if-revision <hash>] [--json]
|
|
35
|
+
|
|
36
|
+
Fetch the review source explicitly and replace the pinned commit and any clean,
|
|
37
|
+
detached review checkout. Review sources never move implicitly.
|
|
38
|
+
`
|
package/src/commands/task.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { formatTable } from "../utils/table"
|
|
|
11
11
|
import { getWorkViews } from "../work-view"
|
|
12
12
|
import { GraphMutationService } from "../services/GraphMutationService"
|
|
13
13
|
import { work as startWork, type StartWork } from "./work"
|
|
14
|
+
import { ReviewService } from "../services/ReviewService"
|
|
14
15
|
|
|
15
16
|
interface TaskOptions extends BaseCommandOptions {
|
|
16
17
|
readonly subcommand?: string
|
|
@@ -30,6 +31,9 @@ interface TaskOptions extends BaseCommandOptions {
|
|
|
30
31
|
readonly ifRevision?: string
|
|
31
32
|
readonly noEpic?: boolean
|
|
32
33
|
readonly multiPhase?: boolean
|
|
34
|
+
readonly review?: string
|
|
35
|
+
readonly pullRequest?: string
|
|
36
|
+
readonly ref?: string
|
|
33
37
|
readonly json?: boolean
|
|
34
38
|
readonly statuses?: readonly string[]
|
|
35
39
|
readonly repositories?: readonly string[]
|
|
@@ -85,6 +89,7 @@ export const task = (
|
|
|
85
89
|
const repositories = yield* RepositoryService
|
|
86
90
|
const workbase = yield* WorkbaseService
|
|
87
91
|
const mutations = yield* GraphMutationService
|
|
92
|
+
const reviews = yield* ReviewService
|
|
88
93
|
const { log } = createLoggers(options)
|
|
89
94
|
const cwd = options.cwd ?? process.cwd()
|
|
90
95
|
|
|
@@ -211,11 +216,18 @@ export const task = (
|
|
|
211
216
|
return yield* Effect.fail(new Error("Task ID is required"))
|
|
212
217
|
}
|
|
213
218
|
const multiPhase = options.multiPhase ?? false
|
|
214
|
-
if (!multiPhase && !options.repo) {
|
|
219
|
+
if (!multiPhase && !options.repo && !options.review) {
|
|
215
220
|
return yield* Effect.fail(
|
|
216
221
|
new Error("Writable repository is required for task create"),
|
|
217
222
|
)
|
|
218
223
|
}
|
|
224
|
+
const review = options.review
|
|
225
|
+
? yield* reviews.resolve(
|
|
226
|
+
options.review,
|
|
227
|
+
{ pullRequest: options.pullRequest, ref: options.ref },
|
|
228
|
+
cwd,
|
|
229
|
+
)
|
|
230
|
+
: undefined
|
|
219
231
|
const record = yield* tasks.create(
|
|
220
232
|
{
|
|
221
233
|
id,
|
|
@@ -223,10 +235,16 @@ export const task = (
|
|
|
223
235
|
description: options.description?.trim() || undefined,
|
|
224
236
|
epic: options.epic,
|
|
225
237
|
multiPhase,
|
|
238
|
+
review,
|
|
226
239
|
repo: options.repo,
|
|
227
|
-
repos:
|
|
228
|
-
|
|
229
|
-
|
|
240
|
+
repos: review
|
|
241
|
+
? undefined
|
|
242
|
+
: parseRepositoryReferences(options.references),
|
|
243
|
+
branch:
|
|
244
|
+
multiPhase || review
|
|
245
|
+
? undefined
|
|
246
|
+
: (options.branch ?? `task/${id}`),
|
|
247
|
+
base: multiPhase || review ? undefined : (options.base ?? "main"),
|
|
230
248
|
},
|
|
231
249
|
cwd,
|
|
232
250
|
)
|
|
@@ -462,7 +480,11 @@ Create options:
|
|
|
462
480
|
Read-only repository reference; repeatable
|
|
463
481
|
--branch <name> Working branch (default: task/<id>)
|
|
464
482
|
--base <name> Base branch (default: main)
|
|
465
|
-
|
|
483
|
+
--multi-phase Create a task container for phases
|
|
484
|
+
--review <alias> Create a pinned read-only review task
|
|
485
|
+
--pull-request <url-or-number>
|
|
486
|
+
Review a GitHub pull request from the alias origin
|
|
487
|
+
--ref <remote-ref> Review a branch or other fetchable origin ref
|
|
466
488
|
--work Start work on the new task after creating it
|
|
467
489
|
--auto Pass --auto to work; requires --work
|
|
468
490
|
|
|
@@ -24,6 +24,7 @@ const singlePhaseWorkspace: ExecutionWorkspace = {
|
|
|
24
24
|
phasePath: null,
|
|
25
25
|
codePath: "/workbase/tasks/example/code",
|
|
26
26
|
writablePath: "/workbase/tasks/example/code/agency",
|
|
27
|
+
reviewPath: null,
|
|
27
28
|
repo: "agency",
|
|
28
29
|
repos: [],
|
|
29
30
|
dryRun: false,
|
|
@@ -37,6 +38,7 @@ const multiPhaseWorkspace: ExecutionWorkspace = {
|
|
|
37
38
|
phasePath: "/workbase/tasks/example/phases/implementation/PHASE.md",
|
|
38
39
|
codePath: "/workbase/tasks/example/phases/implementation/code",
|
|
39
40
|
writablePath: "/workbase/tasks/example/phases/implementation/code/agency",
|
|
41
|
+
reviewPath: null,
|
|
40
42
|
repo: "agency",
|
|
41
43
|
repos: [],
|
|
42
44
|
dryRun: false,
|
package/src/commands/work.ts
CHANGED
|
@@ -262,7 +262,7 @@ export const work = (
|
|
|
262
262
|
? `${action} the task. Read ${workspace.taskPath} and ${workspace.phasePath}.`
|
|
263
263
|
: `${action} the task. Read ${workspace.taskPath}.`
|
|
264
264
|
launchPath = dirname(workspace.phasePath ?? workspace.taskPath)
|
|
265
|
-
writablePath = workspace.writablePath
|
|
265
|
+
writablePath = workspace.writablePath ?? undefined
|
|
266
266
|
}
|
|
267
267
|
|
|
268
268
|
const explicitlyRequested = Boolean(
|
|
@@ -468,7 +468,7 @@ export const workPrepare = (options: WorkOptions = {}) =>
|
|
|
468
468
|
log(JSON.stringify(workspace, null, 2))
|
|
469
469
|
} else {
|
|
470
470
|
log(
|
|
471
|
-
`${workspace.dryRun ? "Workspace plan" : "Workspace ready"}: ${workspace.writablePath}`,
|
|
471
|
+
`${workspace.dryRun ? "Workspace plan" : "Workspace ready"}: ${workspace.writablePath ?? workspace.reviewPath}`,
|
|
472
472
|
)
|
|
473
473
|
}
|
|
474
474
|
})
|