@markjaquith/agency 2.48.1 → 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 +40 -9
- package/cli.ts +32 -0
- package/package.json +1 -1
- package/schemas/agency-graph-v1.schema.json +141 -38
- package/src/cli-parser.test.ts +120 -0
- package/src/cli-parser.ts +122 -7
- package/src/cli.test.ts +71 -0
- package/src/commands/claim.ts +26 -2
- package/src/commands/phase.ts +23 -2
- package/src/commands/review.ts +38 -0
- package/src/commands/task.ts +49 -7
- 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.test.ts +59 -0
- package/src/services/ClaimService.ts +36 -2
- package/src/services/ContextService.ts +56 -4
- package/src/services/DoctorService.ts +45 -1
- package/src/services/GraphMutationService.ts +21 -0
- package/src/services/GraphService.ts +118 -54
- package/src/services/IntegrationService.test.ts +4 -1
- package/src/services/PhaseService.ts +55 -3
- package/src/services/PullRequestService.test.ts +22 -2
- package/src/services/PullRequestService.ts +44 -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 +68 -2
- package/src/services/SyncService.ts +123 -6
- package/src/services/TaskPhaseService.test.ts +182 -0
- package/src/services/TaskService.ts +128 -11
- package/src/services/WorkbaseService.test.ts +58 -0
- package/src/services/WorkbaseService.ts +22 -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/AGENTS.md +7 -2
- package/src/workbase/completion.ts +28 -0
- package/src/workbase/schemas.test.ts +57 -0
- package/src/workbase/schemas.ts +65 -0
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 = {
|
|
@@ -120,6 +123,12 @@ const ownedClaimOptions = {
|
|
|
120
123
|
revision: { type: "string" },
|
|
121
124
|
} satisfies OptionConfig
|
|
122
125
|
|
|
126
|
+
const nonPrCompletionOptions = {
|
|
127
|
+
"no-pull-request": { type: "boolean" },
|
|
128
|
+
summary: { type: "string" },
|
|
129
|
+
"evidence-url": { type: "string" },
|
|
130
|
+
} satisfies OptionConfig
|
|
131
|
+
|
|
123
132
|
const commands = {
|
|
124
133
|
init: {
|
|
125
134
|
usage: "agency init [path] [--json]",
|
|
@@ -360,6 +369,7 @@ const commands = {
|
|
|
360
369
|
...newWorkOptions,
|
|
361
370
|
...viewOptions,
|
|
362
371
|
...mutationOptions,
|
|
372
|
+
...nonPrCompletionOptions,
|
|
363
373
|
"pr-url": { type: "string" },
|
|
364
374
|
task: { type: "string" },
|
|
365
375
|
},
|
|
@@ -386,7 +396,7 @@ const commands = {
|
|
|
386
396
|
},
|
|
387
397
|
create: {
|
|
388
398
|
usage:
|
|
389
|
-
"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]",
|
|
390
400
|
minArgs: 1,
|
|
391
401
|
maxArgs: 1,
|
|
392
402
|
options: [
|
|
@@ -398,6 +408,9 @@ const commands = {
|
|
|
398
408
|
"branch",
|
|
399
409
|
"base",
|
|
400
410
|
"multi-phase",
|
|
411
|
+
"review",
|
|
412
|
+
"pull-request",
|
|
413
|
+
"ref",
|
|
401
414
|
"json",
|
|
402
415
|
],
|
|
403
416
|
repeatable: ["reference"],
|
|
@@ -417,10 +430,10 @@ const commands = {
|
|
|
417
430
|
options: ["json", "task"],
|
|
418
431
|
},
|
|
419
432
|
status: {
|
|
420
|
-
usage: "agency task status <id> <status> [--json]",
|
|
433
|
+
usage: "agency task status <id> <status> [options] [--json]",
|
|
421
434
|
minArgs: 2,
|
|
422
435
|
maxArgs: 2,
|
|
423
|
-
options: ["json", "task"],
|
|
436
|
+
options: ["json", "task", "no-pull-request", "summary", "evidence-url"],
|
|
424
437
|
},
|
|
425
438
|
update: {
|
|
426
439
|
usage: "agency task update <id> [options] [--json]",
|
|
@@ -479,6 +492,7 @@ const commands = {
|
|
|
479
492
|
...newWorkOptions,
|
|
480
493
|
...viewOptions,
|
|
481
494
|
...mutationOptions,
|
|
495
|
+
...nonPrCompletionOptions,
|
|
482
496
|
"pr-url": { type: "string" },
|
|
483
497
|
task: { type: "string" },
|
|
484
498
|
phase: { type: "string" },
|
|
@@ -542,10 +556,18 @@ const commands = {
|
|
|
542
556
|
options: ["json", "task", "phase"],
|
|
543
557
|
},
|
|
544
558
|
status: {
|
|
545
|
-
usage:
|
|
559
|
+
usage:
|
|
560
|
+
"agency phase status <task-id> <phase-id> <status> [options] [--json]",
|
|
546
561
|
minArgs: 3,
|
|
547
562
|
maxArgs: 3,
|
|
548
|
-
options: [
|
|
563
|
+
options: [
|
|
564
|
+
"json",
|
|
565
|
+
"task",
|
|
566
|
+
"phase",
|
|
567
|
+
"no-pull-request",
|
|
568
|
+
"summary",
|
|
569
|
+
"evidence-url",
|
|
570
|
+
],
|
|
549
571
|
},
|
|
550
572
|
update: {
|
|
551
573
|
usage: "agency phase update <task-id> <phase-id> [options] [--json]",
|
|
@@ -631,16 +653,27 @@ const commands = {
|
|
|
631
653
|
usage: "agency finish <task-id> [phase-id] [options]",
|
|
632
654
|
options: {
|
|
633
655
|
...ownedClaimOptions,
|
|
656
|
+
...nonPrCompletionOptions,
|
|
634
657
|
outcome: { type: "string" },
|
|
635
658
|
task: { type: "string" },
|
|
636
659
|
phase: { type: "string" },
|
|
637
660
|
},
|
|
638
661
|
command: {
|
|
639
662
|
usage:
|
|
640
|
-
"agency finish <task-id> [phase-id] --session-id <id> --revision <sha256> --outcome <done|dropped> [--json]",
|
|
663
|
+
"agency finish <task-id> [phase-id] --session-id <id> --revision <sha256> --outcome <done|dropped> [options] [--json]",
|
|
641
664
|
minArgs: 1,
|
|
642
665
|
maxArgs: 2,
|
|
643
|
-
options: [
|
|
666
|
+
options: [
|
|
667
|
+
"session-id",
|
|
668
|
+
"revision",
|
|
669
|
+
"outcome",
|
|
670
|
+
"no-pull-request",
|
|
671
|
+
"summary",
|
|
672
|
+
"evidence-url",
|
|
673
|
+
"json",
|
|
674
|
+
"task",
|
|
675
|
+
"phase",
|
|
676
|
+
],
|
|
644
677
|
required: ["session-id", "revision", "outcome"],
|
|
645
678
|
},
|
|
646
679
|
},
|
|
@@ -733,6 +766,22 @@ const commands = {
|
|
|
733
766
|
},
|
|
734
767
|
},
|
|
735
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
|
+
},
|
|
736
785
|
worktree: {
|
|
737
786
|
usage: "agency worktree <list|inspect|prepare|remove|rebuild|repair>",
|
|
738
787
|
options: {
|
|
@@ -1134,6 +1183,38 @@ function validateTaskCreate(
|
|
|
1134
1183
|
spec: LeafCommand,
|
|
1135
1184
|
requireRepo: boolean,
|
|
1136
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
|
+
}
|
|
1137
1218
|
if (values["multi-phase"]) {
|
|
1138
1219
|
for (const option of ["repo", "reference", "branch", "base"] as const) {
|
|
1139
1220
|
if (values[option] !== undefined) {
|
|
@@ -1409,6 +1490,40 @@ export function parseCli(args: readonly string[]): ParsedCli {
|
|
|
1409
1490
|
spec.usage,
|
|
1410
1491
|
)
|
|
1411
1492
|
}
|
|
1493
|
+
const nonPrCompletion = parsed.values["no-pull-request"] === true
|
|
1494
|
+
const completionSummary = parsed.values.summary
|
|
1495
|
+
const completionEvidenceUrl = parsed.values["evidence-url"]
|
|
1496
|
+
if (
|
|
1497
|
+
(completionSummary !== undefined || completionEvidenceUrl !== undefined) &&
|
|
1498
|
+
!nonPrCompletion
|
|
1499
|
+
) {
|
|
1500
|
+
throw usageError(
|
|
1501
|
+
"Options '--summary' and '--evidence-url' require '--no-pull-request'.",
|
|
1502
|
+
spec.usage,
|
|
1503
|
+
)
|
|
1504
|
+
}
|
|
1505
|
+
if (nonPrCompletion) {
|
|
1506
|
+
if (
|
|
1507
|
+
typeof completionSummary !== "string" ||
|
|
1508
|
+
completionSummary.trim().length === 0
|
|
1509
|
+
) {
|
|
1510
|
+
throw usageError(
|
|
1511
|
+
"Option '--summary' is required with '--no-pull-request'.",
|
|
1512
|
+
spec.usage,
|
|
1513
|
+
)
|
|
1514
|
+
}
|
|
1515
|
+
const completesDone =
|
|
1516
|
+
(commandName === "finish" && parsed.values.outcome === "done") ||
|
|
1517
|
+
(["task", "phase"].includes(commandName) &&
|
|
1518
|
+
subcommand === "status" &&
|
|
1519
|
+
commandPositionals.at(-1) === "done")
|
|
1520
|
+
if (!completesDone) {
|
|
1521
|
+
throw usageError(
|
|
1522
|
+
"Option '--no-pull-request' is valid only with a done status or outcome.",
|
|
1523
|
+
spec.usage,
|
|
1524
|
+
)
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1412
1527
|
if (commandName === "work") {
|
|
1413
1528
|
const preparing = commandPositionals[0] === "prepare"
|
|
1414
1529
|
if (
|
package/src/cli.test.ts
CHANGED
|
@@ -248,6 +248,77 @@ describe("CLI", () => {
|
|
|
248
248
|
parseJson(await runCli(["task", "show", "claimed", "--json"], root)).data
|
|
249
249
|
.status,
|
|
250
250
|
).toBe("working")
|
|
251
|
+
|
|
252
|
+
parseJson(
|
|
253
|
+
await runCli(
|
|
254
|
+
["task", "create", "non-pr", "--repo", "agency", "--json"],
|
|
255
|
+
root,
|
|
256
|
+
),
|
|
257
|
+
)
|
|
258
|
+
const nonPrContext = parseJson(
|
|
259
|
+
await runCli(["context", "tasks/non-pr", "--json"], root),
|
|
260
|
+
)
|
|
261
|
+
const nonPrClaim = parseJson(
|
|
262
|
+
await runCli(
|
|
263
|
+
[
|
|
264
|
+
"claim",
|
|
265
|
+
"non-pr",
|
|
266
|
+
"--claimant",
|
|
267
|
+
"orchestrator",
|
|
268
|
+
"--runner",
|
|
269
|
+
"agent",
|
|
270
|
+
"--session-id",
|
|
271
|
+
"job-2",
|
|
272
|
+
"--revision",
|
|
273
|
+
nonPrContext.documents.task.sha256,
|
|
274
|
+
"--json",
|
|
275
|
+
],
|
|
276
|
+
root,
|
|
277
|
+
),
|
|
278
|
+
)
|
|
279
|
+
parseJson(
|
|
280
|
+
await runCli(
|
|
281
|
+
[
|
|
282
|
+
"finish",
|
|
283
|
+
"non-pr",
|
|
284
|
+
"--session-id",
|
|
285
|
+
"job-2",
|
|
286
|
+
"--revision",
|
|
287
|
+
nonPrClaim.revision,
|
|
288
|
+
"--outcome",
|
|
289
|
+
"done",
|
|
290
|
+
"--no-pull-request",
|
|
291
|
+
"--summary",
|
|
292
|
+
"Investigation completed without changes.",
|
|
293
|
+
"--evidence-url",
|
|
294
|
+
"https://example.com/result",
|
|
295
|
+
"--json",
|
|
296
|
+
],
|
|
297
|
+
root,
|
|
298
|
+
),
|
|
299
|
+
)
|
|
300
|
+
expect(
|
|
301
|
+
parseJson(await runCli(["task", "show", "non-pr", "--json"], root)).data,
|
|
302
|
+
).toMatchObject({
|
|
303
|
+
status: "done",
|
|
304
|
+
completion: {
|
|
305
|
+
mode: "non-pr",
|
|
306
|
+
summary: "Investigation completed without changes.",
|
|
307
|
+
evidenceUrl: "https://example.com/result",
|
|
308
|
+
},
|
|
309
|
+
})
|
|
310
|
+
const prUpdate = await runCli(
|
|
311
|
+
[
|
|
312
|
+
"task",
|
|
313
|
+
"update",
|
|
314
|
+
"non-pr",
|
|
315
|
+
"--pr-url",
|
|
316
|
+
"https://github.com/example/agency/pull/1",
|
|
317
|
+
],
|
|
318
|
+
root,
|
|
319
|
+
)
|
|
320
|
+
expect(prUpdate.exitCode).toBe(1)
|
|
321
|
+
expect(prUpdate.stderr).toContain("Reopen non-PR completed work")
|
|
251
322
|
})
|
|
252
323
|
|
|
253
324
|
test("routes graph mutations through structured output", async () => {
|
package/src/commands/claim.ts
CHANGED
|
@@ -13,6 +13,9 @@ interface ClaimCommandOptions extends BaseCommandOptions {
|
|
|
13
13
|
readonly revision?: string
|
|
14
14
|
readonly expiresAt?: string
|
|
15
15
|
readonly outcome?: string
|
|
16
|
+
readonly noPullRequest?: boolean
|
|
17
|
+
readonly summary?: string
|
|
18
|
+
readonly evidenceUrl?: string
|
|
16
19
|
readonly json?: boolean
|
|
17
20
|
}
|
|
18
21
|
|
|
@@ -32,6 +35,16 @@ export const claimCommand = (options: ClaimCommandOptions) =>
|
|
|
32
35
|
new Error("Claimant and runner identities are required"),
|
|
33
36
|
)
|
|
34
37
|
}
|
|
38
|
+
if (options.noPullRequest && !options.summary?.trim()) {
|
|
39
|
+
return yield* Effect.fail(
|
|
40
|
+
new Error("Non-PR completion requires a non-empty summary"),
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
if (options.noPullRequest && options.outcome !== "done") {
|
|
44
|
+
return yield* Effect.fail(
|
|
45
|
+
new Error("Non-PR completion is valid only with a done outcome"),
|
|
46
|
+
)
|
|
47
|
+
}
|
|
35
48
|
if (
|
|
36
49
|
options.operation === "finish" &&
|
|
37
50
|
options.outcome !== "done" &&
|
|
@@ -65,6 +78,16 @@ export const claimCommand = (options: ClaimCommandOptions) =>
|
|
|
65
78
|
{
|
|
66
79
|
...common,
|
|
67
80
|
outcome: options.outcome as "done" | "dropped",
|
|
81
|
+
...(options.noPullRequest
|
|
82
|
+
? {
|
|
83
|
+
nonPrCompletion: {
|
|
84
|
+
summary: options.summary!,
|
|
85
|
+
...(options.evidenceUrl
|
|
86
|
+
? { evidenceUrl: options.evidenceUrl }
|
|
87
|
+
: {}),
|
|
88
|
+
},
|
|
89
|
+
}
|
|
90
|
+
: {}),
|
|
68
91
|
},
|
|
69
92
|
cwd,
|
|
70
93
|
)
|
|
@@ -91,8 +114,9 @@ Release an execution unit owned by the session.
|
|
|
91
114
|
`
|
|
92
115
|
|
|
93
116
|
export const finishHelp = `
|
|
94
|
-
Usage: agency finish <task-id> [phase-id] --session-id <id> --revision <sha256> --outcome <done|dropped>
|
|
117
|
+
Usage: agency finish <task-id> [phase-id] --session-id <id> --revision <sha256> --outcome <done|dropped> [--no-pull-request --summary <text> [--evidence-url <url>]]
|
|
95
118
|
|
|
96
119
|
Finish a claim owned by the session. A done claim outcome leaves unmerged work
|
|
97
|
-
working; agency sync --apply marks the execution unit done after merge.
|
|
120
|
+
working; agency sync --apply marks the execution unit done after merge. Use
|
|
121
|
+
--no-pull-request with a summary for an explicit non-PR completion.
|
|
98
122
|
`
|
package/src/commands/phase.ts
CHANGED
|
@@ -31,6 +31,9 @@ interface PhaseOptions extends BaseCommandOptions {
|
|
|
31
31
|
readonly pr?: boolean
|
|
32
32
|
readonly work?: boolean
|
|
33
33
|
readonly auto?: boolean
|
|
34
|
+
readonly noPullRequest?: boolean
|
|
35
|
+
readonly summary?: string
|
|
36
|
+
readonly evidenceUrl?: string
|
|
34
37
|
}
|
|
35
38
|
|
|
36
39
|
export const phase = (options: PhaseOptions, work: StartWork = startWork) =>
|
|
@@ -166,7 +169,20 @@ export const phase = (options: PhaseOptions, work: StartWork = startWork) =>
|
|
|
166
169
|
),
|
|
167
170
|
)
|
|
168
171
|
}
|
|
169
|
-
const record = yield* phases.setStatus(
|
|
172
|
+
const record = yield* phases.setStatus(
|
|
173
|
+
taskId,
|
|
174
|
+
phaseId,
|
|
175
|
+
status,
|
|
176
|
+
cwd,
|
|
177
|
+
options.noPullRequest
|
|
178
|
+
? {
|
|
179
|
+
summary: options.summary ?? "",
|
|
180
|
+
...(options.evidenceUrl
|
|
181
|
+
? { evidenceUrl: options.evidenceUrl }
|
|
182
|
+
: {}),
|
|
183
|
+
}
|
|
184
|
+
: undefined,
|
|
185
|
+
)
|
|
170
186
|
const { content: _, ...output } = record
|
|
171
187
|
log(
|
|
172
188
|
options.json
|
|
@@ -275,7 +291,7 @@ Subcommands:
|
|
|
275
291
|
list <task> List task phases
|
|
276
292
|
show <task> <phase> Show a phase
|
|
277
293
|
status <task> <phase> <status>
|
|
278
|
-
Set open, working, or
|
|
294
|
+
Set open, working, dropped, or explicit non-PR done
|
|
279
295
|
update <task> <phase> Update phase metadata
|
|
280
296
|
rename <task> <phase> <new-id>
|
|
281
297
|
Rename a phase and update dependencies
|
|
@@ -285,6 +301,11 @@ Subcommands:
|
|
|
285
301
|
Mutation option:
|
|
286
302
|
--if-revision <hash> Require the target's current revision
|
|
287
303
|
|
|
304
|
+
Non-PR completion options (status done only):
|
|
305
|
+
--no-pull-request Complete without a pull request
|
|
306
|
+
--summary <text> Required durable outcome summary
|
|
307
|
+
--evidence-url <url> Optional supporting record
|
|
308
|
+
|
|
288
309
|
Create options:
|
|
289
310
|
--description <text> Short description of the phase
|
|
290
311
|
--repo <alias> Writable repository
|
|
@@ -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[]
|
|
@@ -38,6 +42,9 @@ interface TaskOptions extends BaseCommandOptions {
|
|
|
38
42
|
readonly pr?: boolean
|
|
39
43
|
readonly work?: boolean
|
|
40
44
|
readonly auto?: boolean
|
|
45
|
+
readonly noPullRequest?: boolean
|
|
46
|
+
readonly summary?: string
|
|
47
|
+
readonly evidenceUrl?: string
|
|
41
48
|
}
|
|
42
49
|
|
|
43
50
|
export interface TaskInteraction {
|
|
@@ -82,6 +89,7 @@ export const task = (
|
|
|
82
89
|
const repositories = yield* RepositoryService
|
|
83
90
|
const workbase = yield* WorkbaseService
|
|
84
91
|
const mutations = yield* GraphMutationService
|
|
92
|
+
const reviews = yield* ReviewService
|
|
85
93
|
const { log } = createLoggers(options)
|
|
86
94
|
const cwd = options.cwd ?? process.cwd()
|
|
87
95
|
|
|
@@ -208,11 +216,18 @@ export const task = (
|
|
|
208
216
|
return yield* Effect.fail(new Error("Task ID is required"))
|
|
209
217
|
}
|
|
210
218
|
const multiPhase = options.multiPhase ?? false
|
|
211
|
-
if (!multiPhase && !options.repo) {
|
|
219
|
+
if (!multiPhase && !options.repo && !options.review) {
|
|
212
220
|
return yield* Effect.fail(
|
|
213
221
|
new Error("Writable repository is required for task create"),
|
|
214
222
|
)
|
|
215
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
|
|
216
231
|
const record = yield* tasks.create(
|
|
217
232
|
{
|
|
218
233
|
id,
|
|
@@ -220,10 +235,16 @@ export const task = (
|
|
|
220
235
|
description: options.description?.trim() || undefined,
|
|
221
236
|
epic: options.epic,
|
|
222
237
|
multiPhase,
|
|
238
|
+
review,
|
|
223
239
|
repo: options.repo,
|
|
224
|
-
repos:
|
|
225
|
-
|
|
226
|
-
|
|
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"),
|
|
227
248
|
},
|
|
228
249
|
cwd,
|
|
229
250
|
)
|
|
@@ -304,7 +325,19 @@ export const task = (
|
|
|
304
325
|
new Error("Usage: agency task status <id> <status>"),
|
|
305
326
|
)
|
|
306
327
|
}
|
|
307
|
-
const record = yield* tasks.setStatus(
|
|
328
|
+
const record = yield* tasks.setStatus(
|
|
329
|
+
id,
|
|
330
|
+
status,
|
|
331
|
+
cwd,
|
|
332
|
+
options.noPullRequest
|
|
333
|
+
? {
|
|
334
|
+
summary: options.summary ?? "",
|
|
335
|
+
...(options.evidenceUrl
|
|
336
|
+
? { evidenceUrl: options.evidenceUrl }
|
|
337
|
+
: {}),
|
|
338
|
+
}
|
|
339
|
+
: undefined,
|
|
340
|
+
)
|
|
308
341
|
const { content: _, ...output } = record
|
|
309
342
|
log(
|
|
310
343
|
options.json
|
|
@@ -423,7 +456,7 @@ Subcommands:
|
|
|
423
456
|
create <id> Create a task without prompting
|
|
424
457
|
list List tasks
|
|
425
458
|
show <id> Show a task
|
|
426
|
-
status <id> <status> Set open, working, or
|
|
459
|
+
status <id> <status> Set open, working, dropped, or explicit non-PR done
|
|
427
460
|
update <id> Update task metadata
|
|
428
461
|
rename <id> <new-id> Rename a task and update graph references
|
|
429
462
|
move <id> Move a task with --epic or --no-epic
|
|
@@ -433,6 +466,11 @@ Subcommands:
|
|
|
433
466
|
Mutation option:
|
|
434
467
|
--if-revision <hash> Require the target's current revision
|
|
435
468
|
|
|
469
|
+
Non-PR completion options (status done only):
|
|
470
|
+
--no-pull-request Complete without a pull request
|
|
471
|
+
--summary <text> Required durable outcome summary
|
|
472
|
+
--evidence-url <url> Optional supporting record
|
|
473
|
+
|
|
436
474
|
Create options:
|
|
437
475
|
--ticket-url <url> External ticket URL (optional)
|
|
438
476
|
--description <text> Short description of the task
|
|
@@ -442,7 +480,11 @@ Create options:
|
|
|
442
480
|
Read-only repository reference; repeatable
|
|
443
481
|
--branch <name> Working branch (default: task/<id>)
|
|
444
482
|
--base <name> Base branch (default: main)
|
|
445
|
-
|
|
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
|
|
446
488
|
--work Start work on the new task after creating it
|
|
447
489
|
--auto Pass --auto to work; requires --work
|
|
448
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
|
})
|
package/src/graph-schema.ts
CHANGED
|
@@ -3,6 +3,8 @@ import {
|
|
|
3
3
|
EpicFrontmatter,
|
|
4
4
|
PhaseFrontmatter,
|
|
5
5
|
PullRequestRecord,
|
|
6
|
+
ClaimRecord,
|
|
7
|
+
ReviewRecord,
|
|
6
8
|
TaskFrontmatter,
|
|
7
9
|
WorkStatus,
|
|
8
10
|
} from "./workbase/schemas"
|
|
@@ -69,13 +71,24 @@ const DocumentHash = Schema.Struct({ sha256: Schema.String })
|
|
|
69
71
|
export const GraphEpicData = Schema.extend(EpicFrontmatter, DocumentHash)
|
|
70
72
|
export const GraphTaskData = Schema.extend(TaskFrontmatter, DocumentHash)
|
|
71
73
|
export const GraphPhaseData = Schema.extend(PhaseFrontmatter, DocumentHash)
|
|
72
|
-
export const GraphExecutionData = Schema.
|
|
73
|
-
|
|
74
|
+
export const GraphExecutionData = Schema.Union(
|
|
75
|
+
Schema.extend(
|
|
76
|
+
PhaseFrontmatter,
|
|
77
|
+
Schema.Struct({
|
|
78
|
+
taskId: Schema.String,
|
|
79
|
+
phaseId: Schema.optional(Schema.String),
|
|
80
|
+
ticketUrl: Schema.optional(Schema.NullOr(Schema.String)),
|
|
81
|
+
epic: Schema.optional(Schema.String),
|
|
82
|
+
}),
|
|
83
|
+
),
|
|
74
84
|
Schema.Struct({
|
|
75
85
|
taskId: Schema.String,
|
|
76
|
-
|
|
77
|
-
|
|
86
|
+
ticketUrl: Schema.NullOr(Schema.String),
|
|
87
|
+
description: Schema.optional(Schema.String),
|
|
78
88
|
epic: Schema.optional(Schema.String),
|
|
89
|
+
review: ReviewRecord,
|
|
90
|
+
status: WorkStatus,
|
|
91
|
+
claim: Schema.optional(ClaimRecord),
|
|
79
92
|
}),
|
|
80
93
|
)
|
|
81
94
|
|
|
@@ -98,15 +111,24 @@ export const GraphRepositoryGit = Schema.Struct({
|
|
|
98
111
|
head: Schema.NullOr(Schema.String),
|
|
99
112
|
branch: Schema.NullOr(Schema.String),
|
|
100
113
|
})
|
|
101
|
-
export const GraphExecutionGit = Schema.
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
114
|
+
export const GraphExecutionGit = Schema.Union(
|
|
115
|
+
Schema.Struct({
|
|
116
|
+
branch: Schema.String,
|
|
117
|
+
base: Schema.String,
|
|
118
|
+
branchCommit: Schema.NullOr(Schema.String),
|
|
119
|
+
baseCommit: Schema.NullOr(Schema.String),
|
|
120
|
+
checkoutCommit: Schema.NullOr(Schema.String),
|
|
121
|
+
checkoutBranch: Schema.NullOr(Schema.String),
|
|
122
|
+
dirty: Schema.NullOr(Schema.Boolean),
|
|
123
|
+
}),
|
|
124
|
+
Schema.Struct({
|
|
125
|
+
pinnedCommit: Schema.String,
|
|
126
|
+
sourceCommit: Schema.NullOr(Schema.String),
|
|
127
|
+
checkoutCommit: Schema.NullOr(Schema.String),
|
|
128
|
+
checkoutBranch: Schema.NullOr(Schema.String),
|
|
129
|
+
dirty: Schema.NullOr(Schema.Boolean),
|
|
130
|
+
}),
|
|
131
|
+
)
|
|
110
132
|
export const GraphPr = Schema.Union(
|
|
111
133
|
Schema.Struct({ url: Schema.Null, state: Schema.Literal("none") }),
|
|
112
134
|
Schema.Struct({ url: Schema.String, state: Schema.Literal("unavailable") }),
|
package/src/protocol.ts
CHANGED
|
@@ -100,6 +100,7 @@ const errorMetadata: Readonly<Record<string, ErrorMetadata>> = {
|
|
|
100
100
|
ArchiveError: { code: "ARCHIVE_ERROR", retryable: false },
|
|
101
101
|
WorktreeError: { code: "WORKTREE_ERROR", retryable: false },
|
|
102
102
|
PullRequestError: { code: "PULL_REQUEST_ERROR", retryable: false },
|
|
103
|
+
ReviewError: { code: "REVIEW_ERROR", retryable: false },
|
|
103
104
|
ContextError: {
|
|
104
105
|
code: "CONTEXT_ERROR",
|
|
105
106
|
retryable: false,
|