@markjaquith/agency 3.2.2 → 3.2.4
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 +30 -62
- package/cli-main.ts +20 -83
- package/fixtures/protocol/orchestration-recipes.json +9 -34
- package/package.json +1 -4
- package/schemas/agency-graph-v1.schema.json +2 -31
- package/src/cli-parser.test.ts +34 -132
- package/src/cli-parser.ts +45 -102
- package/src/cli.test.ts +60 -117
- package/src/commands/act.ts +2 -6
- package/src/commands/sync.ts +3 -3
- package/src/commands/validate.ts +3 -1
- package/src/commands/work.test.ts +70 -8
- package/src/commands/work.ts +15 -8
- package/src/graph-schema.ts +0 -2
- package/src/protocol.test.ts +0 -25
- package/src/protocol.ts +0 -11
- package/src/readiness.test.ts +2 -2
- package/src/services/ArchiveBulkService.test.ts +0 -88
- package/src/services/ArchiveService.ts +0 -32
- package/src/services/GraphMutationService.ts +0 -26
- package/src/services/GraphService.test.ts +1 -1
- package/src/services/IntegrationService.test.ts +7 -4
- package/src/services/LifecycleTransaction.ts +5 -1
- package/src/services/PhaseService.ts +1 -8
- package/src/services/ReadinessService.test.ts +0 -34
- package/src/services/ReadinessService.ts +1 -20
- package/src/services/ReviewService.test.ts +0 -64
- package/src/services/ReviewService.ts +0 -5
- package/src/services/SyncService.test.ts +75 -88
- package/src/services/SyncService.ts +52 -123
- package/src/services/TaskPhaseService.test.ts +13 -1
- package/src/services/TaskService.ts +1 -7
- package/src/services/WorkbaseService.ts +0 -3
- package/src/services/WorktreeService.test.ts +192 -1
- package/src/services/WorktreeService.ts +169 -34
- package/src/test-utils.ts +0 -2
- package/src/usage-log.test.ts +89 -5
- package/src/usage-log.ts +139 -31
- package/src/workbase/AGENTS.md +9 -15
- package/src/workbase/agent-command.test.ts +1 -3
- package/src/workbase/agent-command.ts +1 -6
- package/src/workbase/document-revision.ts +0 -4
- package/src/workbase/opencode-plugin-file.ts +1 -0
- package/src/workbase/schemas.test.ts +12 -25
- package/src/workbase/schemas.ts +0 -16
- package/src/commands/claim.ts +0 -122
- package/src/services/ClaimService.test.ts +0 -415
- package/src/services/ClaimService.ts +0 -608
package/src/cli-parser.test.ts
CHANGED
|
@@ -7,6 +7,25 @@ const expectUsageError = (args: string[], usage: string) => {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
describe("strict CLI parsing", () => {
|
|
10
|
+
test("returns canonical paths without positional values", () => {
|
|
11
|
+
expect(parseCli(["task", "show", "private-task-id"]).commandPath).toBe(
|
|
12
|
+
"task/show",
|
|
13
|
+
)
|
|
14
|
+
expect(parseCli(["validate", "/private/customer/path"]).commandPath).toBe(
|
|
15
|
+
"validate",
|
|
16
|
+
)
|
|
17
|
+
expect(parseCli(["work", "prepare", "private-task-id"]).commandPath).toBe(
|
|
18
|
+
"work/prepare",
|
|
19
|
+
)
|
|
20
|
+
expect(parseCli(["pr", "view", "private-task-id"]).commandPath).toBe("pr")
|
|
21
|
+
try {
|
|
22
|
+
parseCli(["task", "create", "private-task-id"])
|
|
23
|
+
expect.unreachable()
|
|
24
|
+
} catch (error) {
|
|
25
|
+
expect(error).toMatchObject({ commandPath: "task/create" })
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
|
|
10
29
|
test("parses act selectors, dry-run, and JSON options", () => {
|
|
11
30
|
expect(
|
|
12
31
|
parseCli([
|
|
@@ -338,51 +357,6 @@ describe("strict CLI parsing", () => {
|
|
|
338
357
|
[["graph", "extra"], "agency graph"],
|
|
339
358
|
[["next", "extra"], "agency next"],
|
|
340
359
|
[["sync", "one", "two", "three"], "agency sync"],
|
|
341
|
-
[
|
|
342
|
-
[
|
|
343
|
-
"claim",
|
|
344
|
-
"one",
|
|
345
|
-
"two",
|
|
346
|
-
"three",
|
|
347
|
-
"--claimant",
|
|
348
|
-
"a",
|
|
349
|
-
"--agent",
|
|
350
|
-
"r",
|
|
351
|
-
"--session-id",
|
|
352
|
-
"s",
|
|
353
|
-
"--revision",
|
|
354
|
-
"0".repeat(64),
|
|
355
|
-
],
|
|
356
|
-
"agency claim",
|
|
357
|
-
],
|
|
358
|
-
[
|
|
359
|
-
[
|
|
360
|
-
"release",
|
|
361
|
-
"one",
|
|
362
|
-
"two",
|
|
363
|
-
"three",
|
|
364
|
-
"--session-id",
|
|
365
|
-
"s",
|
|
366
|
-
"--revision",
|
|
367
|
-
"0".repeat(64),
|
|
368
|
-
],
|
|
369
|
-
"agency release",
|
|
370
|
-
],
|
|
371
|
-
[
|
|
372
|
-
[
|
|
373
|
-
"finish",
|
|
374
|
-
"one",
|
|
375
|
-
"two",
|
|
376
|
-
"three",
|
|
377
|
-
"--session-id",
|
|
378
|
-
"s",
|
|
379
|
-
"--revision",
|
|
380
|
-
"0".repeat(64),
|
|
381
|
-
"--outcome",
|
|
382
|
-
"done",
|
|
383
|
-
],
|
|
384
|
-
"agency finish",
|
|
385
|
-
],
|
|
386
360
|
] as const) {
|
|
387
361
|
expectUsageError([...args], usage)
|
|
388
362
|
}
|
|
@@ -408,6 +382,7 @@ describe("strict CLI parsing", () => {
|
|
|
408
382
|
const args = ["create", "--title", "two words", "--", "--literal"]
|
|
409
383
|
expect(parseCli(["--cwd", "/workbase", "pr", ...args])).toEqual({
|
|
410
384
|
commandName: "pr",
|
|
385
|
+
commandPath: "pr",
|
|
411
386
|
args,
|
|
412
387
|
passthrough: true,
|
|
413
388
|
values: { cwd: "/workbase" },
|
|
@@ -427,6 +402,7 @@ describe("strict CLI parsing", () => {
|
|
|
427
402
|
]),
|
|
428
403
|
).toEqual({
|
|
429
404
|
commandName: "pr",
|
|
405
|
+
commandPath: "pr/create",
|
|
430
406
|
args: ["create", "ship", "release"],
|
|
431
407
|
values: { draft: true, force: true, json: true },
|
|
432
408
|
})
|
|
@@ -500,76 +476,10 @@ describe("strict CLI parsing", () => {
|
|
|
500
476
|
)
|
|
501
477
|
})
|
|
502
478
|
|
|
503
|
-
test("
|
|
504
|
-
const
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
"claim",
|
|
508
|
-
"task",
|
|
509
|
-
"phase",
|
|
510
|
-
"--claimant",
|
|
511
|
-
"orchestrator",
|
|
512
|
-
"--agent",
|
|
513
|
-
"agent",
|
|
514
|
-
"--session-id",
|
|
515
|
-
"job-1",
|
|
516
|
-
"--revision",
|
|
517
|
-
revision,
|
|
518
|
-
"--expires-at",
|
|
519
|
-
"2026-07-17T13:00:00.000Z",
|
|
520
|
-
]),
|
|
521
|
-
).toMatchObject({ commandName: "claim", args: ["task", "phase"] })
|
|
522
|
-
expect(() =>
|
|
523
|
-
parseCli(["release", "task", "--session-id", "job-1"]),
|
|
524
|
-
).toThrow("--revision' is required")
|
|
525
|
-
expect(() =>
|
|
526
|
-
parseCli([
|
|
527
|
-
"finish",
|
|
528
|
-
"task",
|
|
529
|
-
"--session-id",
|
|
530
|
-
"job-1",
|
|
531
|
-
"--revision",
|
|
532
|
-
revision,
|
|
533
|
-
"--outcome",
|
|
534
|
-
"working",
|
|
535
|
-
]),
|
|
536
|
-
).toThrow("must be 'done' or 'dropped'")
|
|
537
|
-
expect(
|
|
538
|
-
parseCli([
|
|
539
|
-
"finish",
|
|
540
|
-
"task",
|
|
541
|
-
"--session-id",
|
|
542
|
-
"job-1",
|
|
543
|
-
"--revision",
|
|
544
|
-
revision,
|
|
545
|
-
"--outcome",
|
|
546
|
-
"done",
|
|
547
|
-
"--no-pull-request",
|
|
548
|
-
"--summary",
|
|
549
|
-
"Investigation completed",
|
|
550
|
-
"--evidence-url",
|
|
551
|
-
"https://example.com/result",
|
|
552
|
-
]),
|
|
553
|
-
).toMatchObject({
|
|
554
|
-
values: {
|
|
555
|
-
"no-pull-request": true,
|
|
556
|
-
summary: "Investigation completed",
|
|
557
|
-
"evidence-url": "https://example.com/result",
|
|
558
|
-
},
|
|
559
|
-
})
|
|
560
|
-
expect(() =>
|
|
561
|
-
parseCli([
|
|
562
|
-
"finish",
|
|
563
|
-
"task",
|
|
564
|
-
"--session-id",
|
|
565
|
-
"job-1",
|
|
566
|
-
"--revision",
|
|
567
|
-
revision,
|
|
568
|
-
"--outcome",
|
|
569
|
-
"done",
|
|
570
|
-
"--no-pull-request",
|
|
571
|
-
]),
|
|
572
|
-
).toThrow("--summary' is required")
|
|
479
|
+
test("rejects removed claim protocol commands", () => {
|
|
480
|
+
for (const command of ["claim", "release", "finish"]) {
|
|
481
|
+
expect(() => parseCli([command])).toThrow(`Unknown command '${command}'`)
|
|
482
|
+
}
|
|
573
483
|
expect(() =>
|
|
574
484
|
parseCli([
|
|
575
485
|
"task",
|
|
@@ -649,6 +559,15 @@ describe("strict CLI parsing", () => {
|
|
|
649
559
|
expect(() => parseCli(["work", "prepare", "--opencode"])).toThrow(
|
|
650
560
|
"cannot be combined",
|
|
651
561
|
)
|
|
562
|
+
expect(() => parseCli(["work", "prepare", "first", "second"])).toThrow(
|
|
563
|
+
"Expected 0-2 positional arguments",
|
|
564
|
+
)
|
|
565
|
+
expect(() =>
|
|
566
|
+
parseCli(["work", "prepare", "example", "--task", "other"]),
|
|
567
|
+
).toThrow("Entity selector options cannot be combined")
|
|
568
|
+
expect(() => parseCli(["work", "prepare", "--phase", "build"])).toThrow(
|
|
569
|
+
"requires '--task'",
|
|
570
|
+
)
|
|
652
571
|
})
|
|
653
572
|
|
|
654
573
|
test("parses worktree lifecycle commands and selectors", () => {
|
|
@@ -977,23 +896,6 @@ describe("strict CLI parsing", () => {
|
|
|
977
896
|
commandName: "phase",
|
|
978
897
|
args: ["status", "ship", "release", "done"],
|
|
979
898
|
})
|
|
980
|
-
expect(
|
|
981
|
-
parseCli([
|
|
982
|
-
"claim",
|
|
983
|
-
"--task",
|
|
984
|
-
"ship",
|
|
985
|
-
"--phase",
|
|
986
|
-
"release",
|
|
987
|
-
"--claimant",
|
|
988
|
-
"agent",
|
|
989
|
-
"--agent",
|
|
990
|
-
"opencode",
|
|
991
|
-
"--session-id",
|
|
992
|
-
"session",
|
|
993
|
-
"--revision",
|
|
994
|
-
"0".repeat(64),
|
|
995
|
-
]),
|
|
996
|
-
).toMatchObject({ args: ["ship", "release"] })
|
|
997
899
|
expect(parseCli(["context", "--epic", "delivery"]).values.epic).toBe(
|
|
998
900
|
"delivery",
|
|
999
901
|
)
|
package/src/cli-parser.ts
CHANGED
|
@@ -114,21 +114,6 @@ const mutationOptions = {
|
|
|
114
114
|
"no-epic": { type: "boolean" },
|
|
115
115
|
} satisfies OptionConfig
|
|
116
116
|
|
|
117
|
-
const claimOptions = {
|
|
118
|
-
...outputOptions,
|
|
119
|
-
claimant: { type: "string" },
|
|
120
|
-
agent: { type: "string" },
|
|
121
|
-
"session-id": { type: "string" },
|
|
122
|
-
revision: { type: "string" },
|
|
123
|
-
"expires-at": { type: "string" },
|
|
124
|
-
} satisfies OptionConfig
|
|
125
|
-
|
|
126
|
-
const ownedClaimOptions = {
|
|
127
|
-
...outputOptions,
|
|
128
|
-
"session-id": { type: "string" },
|
|
129
|
-
revision: { type: "string" },
|
|
130
|
-
} satisfies OptionConfig
|
|
131
|
-
|
|
132
117
|
const nonPrCompletionOptions = {
|
|
133
118
|
"no-pull-request": { type: "boolean" },
|
|
134
119
|
summary: { type: "string" },
|
|
@@ -679,75 +664,6 @@ const commands = {
|
|
|
679
664
|
},
|
|
680
665
|
},
|
|
681
666
|
},
|
|
682
|
-
claim: {
|
|
683
|
-
usage: "agency claim <task-id> [phase-id] [options]",
|
|
684
|
-
options: {
|
|
685
|
-
...claimOptions,
|
|
686
|
-
task: { type: "string" },
|
|
687
|
-
phase: { type: "string" },
|
|
688
|
-
},
|
|
689
|
-
command: {
|
|
690
|
-
usage:
|
|
691
|
-
"agency claim <task-id> [phase-id] --claimant <id> --agent <id> --session-id <id> --revision <sha256> [--expires-at <timestamp>] [--json]",
|
|
692
|
-
minArgs: 1,
|
|
693
|
-
maxArgs: 2,
|
|
694
|
-
options: [
|
|
695
|
-
"claimant",
|
|
696
|
-
"agent",
|
|
697
|
-
"session-id",
|
|
698
|
-
"revision",
|
|
699
|
-
"expires-at",
|
|
700
|
-
"json",
|
|
701
|
-
"task",
|
|
702
|
-
"phase",
|
|
703
|
-
],
|
|
704
|
-
required: ["claimant", "agent", "session-id", "revision"],
|
|
705
|
-
},
|
|
706
|
-
},
|
|
707
|
-
release: {
|
|
708
|
-
usage: "agency release <task-id> [phase-id] [options]",
|
|
709
|
-
options: {
|
|
710
|
-
...ownedClaimOptions,
|
|
711
|
-
task: { type: "string" },
|
|
712
|
-
phase: { type: "string" },
|
|
713
|
-
},
|
|
714
|
-
command: {
|
|
715
|
-
usage:
|
|
716
|
-
"agency release <task-id> [phase-id] --session-id <id> --revision <sha256> [--json]",
|
|
717
|
-
minArgs: 1,
|
|
718
|
-
maxArgs: 2,
|
|
719
|
-
options: ["session-id", "revision", "json", "task", "phase"],
|
|
720
|
-
required: ["session-id", "revision"],
|
|
721
|
-
},
|
|
722
|
-
},
|
|
723
|
-
finish: {
|
|
724
|
-
usage: "agency finish <task-id> [phase-id] [options]",
|
|
725
|
-
options: {
|
|
726
|
-
...ownedClaimOptions,
|
|
727
|
-
...nonPrCompletionOptions,
|
|
728
|
-
outcome: { type: "string" },
|
|
729
|
-
task: { type: "string" },
|
|
730
|
-
phase: { type: "string" },
|
|
731
|
-
},
|
|
732
|
-
command: {
|
|
733
|
-
usage:
|
|
734
|
-
"agency finish <task-id> [phase-id] --session-id <id> --revision <sha256> --outcome <done|dropped> [options] [--json]",
|
|
735
|
-
minArgs: 1,
|
|
736
|
-
maxArgs: 2,
|
|
737
|
-
options: [
|
|
738
|
-
"session-id",
|
|
739
|
-
"revision",
|
|
740
|
-
"outcome",
|
|
741
|
-
"no-pull-request",
|
|
742
|
-
"summary",
|
|
743
|
-
"evidence-url",
|
|
744
|
-
"json",
|
|
745
|
-
"task",
|
|
746
|
-
"phase",
|
|
747
|
-
],
|
|
748
|
-
required: ["session-id", "revision", "outcome"],
|
|
749
|
-
},
|
|
750
|
-
},
|
|
751
667
|
sync: {
|
|
752
668
|
usage: "agency sync [<task-id> [phase-id]] [--dry-run] [--json]",
|
|
753
669
|
options: {
|
|
@@ -1116,6 +1032,7 @@ const preCommandValueOptions = new Set(["--workbase", "--cwd"])
|
|
|
1116
1032
|
|
|
1117
1033
|
export interface ParsedCli {
|
|
1118
1034
|
readonly commandName?: keyof typeof commands
|
|
1035
|
+
readonly commandPath: string
|
|
1119
1036
|
readonly args: string[]
|
|
1120
1037
|
readonly passthrough?: boolean
|
|
1121
1038
|
readonly values: Record<
|
|
@@ -1124,8 +1041,14 @@ export interface ParsedCli {
|
|
|
1124
1041
|
>
|
|
1125
1042
|
}
|
|
1126
1043
|
|
|
1044
|
+
const canonicalCommandPath = (
|
|
1045
|
+
commandName: string,
|
|
1046
|
+
subcommand: string | undefined,
|
|
1047
|
+
) => (subcommand ? `${commandName}/${subcommand}` : commandName)
|
|
1048
|
+
|
|
1127
1049
|
class CliUsageError extends Error {
|
|
1128
1050
|
readonly _tag = "CliUsageError"
|
|
1051
|
+
readonly commandPath: string
|
|
1129
1052
|
|
|
1130
1053
|
constructor(
|
|
1131
1054
|
readonly detail: string,
|
|
@@ -1133,6 +1056,15 @@ class CliUsageError extends Error {
|
|
|
1133
1056
|
) {
|
|
1134
1057
|
super(`${detail}\n\nUsage: ${usage}`)
|
|
1135
1058
|
this.name = "CliUsageError"
|
|
1059
|
+
const [, commandName, subcommand] = usage.split(/\s+/)
|
|
1060
|
+
const definition: CommandDefinition | undefined =
|
|
1061
|
+
commands[commandName as keyof typeof commands]
|
|
1062
|
+
this.commandPath = !definition
|
|
1063
|
+
? "invalid"
|
|
1064
|
+
: subcommand && definition.subcommands?.[subcommand]
|
|
1065
|
+
? `${commandName}/${subcommand}`
|
|
1066
|
+
: commandName!
|
|
1067
|
+
Object.defineProperty(this, "commandPath", { enumerable: false })
|
|
1136
1068
|
}
|
|
1137
1069
|
}
|
|
1138
1070
|
|
|
@@ -1177,8 +1109,6 @@ const targetSlots = (
|
|
|
1177
1109
|
return ["task"]
|
|
1178
1110
|
if (commandName === "phase")
|
|
1179
1111
|
return subcommand === "list" ? ["task"] : ["task", "phase"]
|
|
1180
|
-
if (["claim", "release", "finish"].includes(commandName))
|
|
1181
|
-
return ["task", "phase"]
|
|
1182
1112
|
if (["archive", "restore"].includes(commandName))
|
|
1183
1113
|
return subcommand === "epic"
|
|
1184
1114
|
? ["epic"]
|
|
@@ -1409,7 +1339,7 @@ export function parseCli(args: readonly string[]): ParsedCli {
|
|
|
1409
1339
|
"agency <command> [options]",
|
|
1410
1340
|
)
|
|
1411
1341
|
}
|
|
1412
|
-
return { args: [], values: parsed.values }
|
|
1342
|
+
return { commandPath: "root", args: [], values: parsed.values }
|
|
1413
1343
|
}
|
|
1414
1344
|
|
|
1415
1345
|
const commandName = args[commandIndex]!
|
|
@@ -1454,6 +1384,7 @@ export function parseCli(args: readonly string[]): ParsedCli {
|
|
|
1454
1384
|
}
|
|
1455
1385
|
return {
|
|
1456
1386
|
commandName,
|
|
1387
|
+
commandPath: "pr",
|
|
1457
1388
|
args: prArgs,
|
|
1458
1389
|
passthrough: true,
|
|
1459
1390
|
values: parsed.values,
|
|
@@ -1477,6 +1408,7 @@ export function parseCli(args: readonly string[]): ParsedCli {
|
|
|
1477
1408
|
assertNoDuplicateOptions(parsed.tokens, new Set(), definition.usage)
|
|
1478
1409
|
return {
|
|
1479
1410
|
commandName: commandName as keyof typeof commands,
|
|
1411
|
+
commandPath: commandName,
|
|
1480
1412
|
args: parsed.positionals,
|
|
1481
1413
|
values: parsed.values,
|
|
1482
1414
|
}
|
|
@@ -1527,6 +1459,10 @@ export function parseCli(args: readonly string[]): ParsedCli {
|
|
|
1527
1459
|
if (parsed.values.version) {
|
|
1528
1460
|
return {
|
|
1529
1461
|
commandName: commandName as keyof typeof commands,
|
|
1462
|
+
commandPath: canonicalCommandPath(
|
|
1463
|
+
commandName,
|
|
1464
|
+
selectedSubcommand ? subcommand : undefined,
|
|
1465
|
+
),
|
|
1530
1466
|
args: parsed.positionals,
|
|
1531
1467
|
values: parsed.values,
|
|
1532
1468
|
}
|
|
@@ -1534,6 +1470,10 @@ export function parseCli(args: readonly string[]): ParsedCli {
|
|
|
1534
1470
|
if (parsed.values.help) {
|
|
1535
1471
|
return {
|
|
1536
1472
|
commandName: commandName as keyof typeof commands,
|
|
1473
|
+
commandPath: canonicalCommandPath(
|
|
1474
|
+
commandName,
|
|
1475
|
+
selectedSubcommand ? subcommand : undefined,
|
|
1476
|
+
),
|
|
1537
1477
|
args: parsed.positionals,
|
|
1538
1478
|
values: parsed.values,
|
|
1539
1479
|
}
|
|
@@ -1637,16 +1577,6 @@ export function parseCli(args: readonly string[]): ParsedCli {
|
|
|
1637
1577
|
) {
|
|
1638
1578
|
validateViewOptions(parsed.values, spec)
|
|
1639
1579
|
}
|
|
1640
|
-
if (
|
|
1641
|
-
commandName === "finish" &&
|
|
1642
|
-
parsed.values.outcome !== "done" &&
|
|
1643
|
-
parsed.values.outcome !== "dropped"
|
|
1644
|
-
) {
|
|
1645
|
-
throw usageError(
|
|
1646
|
-
"Option '--outcome' must be 'done' or 'dropped'.",
|
|
1647
|
-
spec.usage,
|
|
1648
|
-
)
|
|
1649
|
-
}
|
|
1650
1580
|
const nonPrCompletion = parsed.values["no-pull-request"] === true
|
|
1651
1581
|
const completionSummary = parsed.values.summary
|
|
1652
1582
|
const completionEvidenceUrl = parsed.values["evidence-url"]
|
|
@@ -1670,19 +1600,24 @@ export function parseCli(args: readonly string[]): ParsedCli {
|
|
|
1670
1600
|
)
|
|
1671
1601
|
}
|
|
1672
1602
|
const completesDone =
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
commandPositionals.at(-1) === "done")
|
|
1603
|
+
["task", "phase"].includes(commandName) &&
|
|
1604
|
+
subcommand === "status" &&
|
|
1605
|
+
commandPositionals.at(-1) === "done"
|
|
1677
1606
|
if (!completesDone) {
|
|
1678
1607
|
throw usageError(
|
|
1679
|
-
"Option '--no-pull-request' is valid only with a done status
|
|
1608
|
+
"Option '--no-pull-request' is valid only with a done status.",
|
|
1680
1609
|
spec.usage,
|
|
1681
1610
|
)
|
|
1682
1611
|
}
|
|
1683
1612
|
}
|
|
1684
1613
|
if (commandName === "work") {
|
|
1685
1614
|
const preparing = commandPositionals[0] === "prepare"
|
|
1615
|
+
if (preparing && parsed.values.phase && !parsed.values.task) {
|
|
1616
|
+
throw usageError(
|
|
1617
|
+
"Option '--phase' requires '--task' with work preparation.",
|
|
1618
|
+
spec.usage,
|
|
1619
|
+
)
|
|
1620
|
+
}
|
|
1686
1621
|
if (
|
|
1687
1622
|
(!preparing && commandPositionals.length > 1) ||
|
|
1688
1623
|
(preparing &&
|
|
@@ -1713,6 +1648,14 @@ export function parseCli(args: readonly string[]): ParsedCli {
|
|
|
1713
1648
|
|
|
1714
1649
|
return {
|
|
1715
1650
|
commandName: commandName as keyof typeof commands,
|
|
1651
|
+
commandPath: canonicalCommandPath(
|
|
1652
|
+
commandName,
|
|
1653
|
+
selectedSubcommand
|
|
1654
|
+
? subcommand
|
|
1655
|
+
: commandName === "work" && commandPositionals[0] === "prepare"
|
|
1656
|
+
? "prepare"
|
|
1657
|
+
: undefined,
|
|
1658
|
+
),
|
|
1716
1659
|
args: selectedSubcommand
|
|
1717
1660
|
? [subcommand!, ...commandPositionals]
|
|
1718
1661
|
: commandPositionals,
|
package/src/cli.test.ts
CHANGED
|
@@ -179,10 +179,31 @@ describe("CLI", () => {
|
|
|
179
179
|
XDG_STATE_HOME: state,
|
|
180
180
|
AGENCY_SESSION_ID: "cli-session",
|
|
181
181
|
AGENCY_NO_USAGE_LOG: "0",
|
|
182
|
+
AGENCY_INVOCATION_SOURCE: "automation",
|
|
183
|
+
AGENCY_USAGE_TEST: "1",
|
|
182
184
|
}
|
|
183
185
|
expect((await runCli(["--version"], projectRoot, env)).exitCode).toBe(0)
|
|
184
186
|
expect(
|
|
185
|
-
(
|
|
187
|
+
(
|
|
188
|
+
await runCli(
|
|
189
|
+
[
|
|
190
|
+
"task",
|
|
191
|
+
"create",
|
|
192
|
+
"private-customer-id",
|
|
193
|
+
"--repo",
|
|
194
|
+
"private-repository",
|
|
195
|
+
"--description",
|
|
196
|
+
"private free-form input",
|
|
197
|
+
"--cwd",
|
|
198
|
+
"/private/customer/path",
|
|
199
|
+
],
|
|
200
|
+
projectRoot,
|
|
201
|
+
env,
|
|
202
|
+
)
|
|
203
|
+
).exitCode,
|
|
204
|
+
).toBe(1)
|
|
205
|
+
expect(
|
|
206
|
+
(await runCli(["unknown", "--private-flag=value"], projectRoot, env))
|
|
186
207
|
.exitCode,
|
|
187
208
|
).toBe(1)
|
|
188
209
|
|
|
@@ -194,147 +215,60 @@ describe("CLI", () => {
|
|
|
194
215
|
.map((line) => JSON.parse(line))
|
|
195
216
|
expect(events).toEqual([
|
|
196
217
|
expect.objectContaining({
|
|
197
|
-
|
|
198
|
-
|
|
218
|
+
journeyId: expect.stringMatching(/^sha256:[a-f0-9]{64}$/),
|
|
219
|
+
journeySequence: 1,
|
|
220
|
+
invocationSource: "automation",
|
|
221
|
+
isTest: true,
|
|
199
222
|
commandPath: "version",
|
|
200
223
|
flagNames: ["version"],
|
|
201
224
|
outcome: "success",
|
|
225
|
+
outcomeCode: "SUCCESS",
|
|
202
226
|
}),
|
|
203
227
|
expect.objectContaining({
|
|
204
|
-
|
|
228
|
+
journeySequence: 2,
|
|
229
|
+
commandPath: "task/create",
|
|
230
|
+
flagNames: ["cwd", "description", "repo"],
|
|
231
|
+
outcome: "failure",
|
|
232
|
+
outcomeCode: "WORKBASE_NOT_FOUND",
|
|
233
|
+
}),
|
|
234
|
+
expect.objectContaining({
|
|
235
|
+
journeySequence: 3,
|
|
205
236
|
commandPath: "invalid",
|
|
206
|
-
flagNames: [
|
|
237
|
+
flagNames: [],
|
|
207
238
|
outcome: "failure",
|
|
239
|
+
outcomeCode: "CLI_USAGE",
|
|
208
240
|
}),
|
|
209
241
|
])
|
|
210
|
-
|
|
242
|
+
for (const value of [
|
|
243
|
+
"cli-session",
|
|
244
|
+
"private-customer-id",
|
|
245
|
+
"private-repository",
|
|
246
|
+
"private free-form input",
|
|
247
|
+
"/private/customer/path",
|
|
248
|
+
"private-flag",
|
|
249
|
+
"value",
|
|
250
|
+
]) {
|
|
251
|
+
expect(exported.stdout).not.toContain(value)
|
|
252
|
+
}
|
|
211
253
|
})
|
|
212
254
|
|
|
213
|
-
test("
|
|
255
|
+
test("records status-based non-PR completion", async () => {
|
|
214
256
|
const root = await createTempDir()
|
|
215
257
|
tempDirs.push(root)
|
|
216
258
|
await Bun.write(join(root, "agency.json"), '{"version":2}\n')
|
|
217
259
|
await mkdir(join(root, "repos", "agency"), { recursive: true })
|
|
218
|
-
parseJson(
|
|
219
|
-
await runCli(
|
|
220
|
-
["task", "create", "claimed", "--repo", "agency", "--json"],
|
|
221
|
-
root,
|
|
222
|
-
),
|
|
223
|
-
)
|
|
224
|
-
const context = parseJson(
|
|
225
|
-
await runCli(["context", "tasks/claimed", "--json"], root),
|
|
226
|
-
)
|
|
227
|
-
const revision = context.documents.task.sha256
|
|
228
|
-
const claimed = parseJson(
|
|
229
|
-
await runCli(
|
|
230
|
-
[
|
|
231
|
-
"claim",
|
|
232
|
-
"claimed",
|
|
233
|
-
"--claimant",
|
|
234
|
-
"orchestrator",
|
|
235
|
-
"--agent",
|
|
236
|
-
"agent",
|
|
237
|
-
"--session-id",
|
|
238
|
-
"job-1",
|
|
239
|
-
"--revision",
|
|
240
|
-
revision,
|
|
241
|
-
"--json",
|
|
242
|
-
],
|
|
243
|
-
root,
|
|
244
|
-
),
|
|
245
|
-
)
|
|
246
|
-
expect(claimed.claim).toMatchObject({
|
|
247
|
-
claimant: "orchestrator",
|
|
248
|
-
agent: "agent",
|
|
249
|
-
sessionId: "job-1",
|
|
250
|
-
state: "active",
|
|
251
|
-
})
|
|
252
|
-
|
|
253
|
-
const conflict = await runCli(
|
|
254
|
-
[
|
|
255
|
-
"claim",
|
|
256
|
-
"claimed",
|
|
257
|
-
"--claimant",
|
|
258
|
-
"other",
|
|
259
|
-
"--agent",
|
|
260
|
-
"other-agent",
|
|
261
|
-
"--session-id",
|
|
262
|
-
"job-2",
|
|
263
|
-
"--revision",
|
|
264
|
-
claimed.revision,
|
|
265
|
-
"--json",
|
|
266
|
-
],
|
|
267
|
-
root,
|
|
268
|
-
)
|
|
269
|
-
expect(conflict.exitCode).toBe(1)
|
|
270
|
-
expect(JSON.parse(conflict.stdout)).toMatchObject({
|
|
271
|
-
ok: false,
|
|
272
|
-
error: {
|
|
273
|
-
code: "CLAIM_CONFLICT",
|
|
274
|
-
retryable: true,
|
|
275
|
-
fields: { claim: { agent: "agent", sessionId: "job-1" } },
|
|
276
|
-
},
|
|
277
|
-
})
|
|
278
|
-
|
|
279
|
-
const finished = parseJson(
|
|
280
|
-
await runCli(
|
|
281
|
-
[
|
|
282
|
-
"finish",
|
|
283
|
-
"claimed",
|
|
284
|
-
"--session-id",
|
|
285
|
-
"job-1",
|
|
286
|
-
"--revision",
|
|
287
|
-
claimed.revision,
|
|
288
|
-
"--outcome",
|
|
289
|
-
"done",
|
|
290
|
-
"--json",
|
|
291
|
-
],
|
|
292
|
-
root,
|
|
293
|
-
),
|
|
294
|
-
)
|
|
295
|
-
expect(finished.claim).toMatchObject({ state: "finished", outcome: "done" })
|
|
296
|
-
expect(
|
|
297
|
-
parseJson(await runCli(["task", "show", "claimed", "--json"], root)).data
|
|
298
|
-
.status,
|
|
299
|
-
).toBe("working")
|
|
300
|
-
|
|
301
260
|
parseJson(
|
|
302
261
|
await runCli(
|
|
303
262
|
["task", "create", "non-pr", "--repo", "agency", "--json"],
|
|
304
263
|
root,
|
|
305
264
|
),
|
|
306
265
|
)
|
|
307
|
-
const nonPrContext = parseJson(
|
|
308
|
-
await runCli(["context", "tasks/non-pr", "--json"], root),
|
|
309
|
-
)
|
|
310
|
-
const nonPrClaim = parseJson(
|
|
311
|
-
await runCli(
|
|
312
|
-
[
|
|
313
|
-
"claim",
|
|
314
|
-
"non-pr",
|
|
315
|
-
"--claimant",
|
|
316
|
-
"orchestrator",
|
|
317
|
-
"--agent",
|
|
318
|
-
"agent",
|
|
319
|
-
"--session-id",
|
|
320
|
-
"job-2",
|
|
321
|
-
"--revision",
|
|
322
|
-
nonPrContext.documents.task.sha256,
|
|
323
|
-
"--json",
|
|
324
|
-
],
|
|
325
|
-
root,
|
|
326
|
-
),
|
|
327
|
-
)
|
|
328
266
|
parseJson(
|
|
329
267
|
await runCli(
|
|
330
268
|
[
|
|
331
|
-
"
|
|
269
|
+
"task",
|
|
270
|
+
"status",
|
|
332
271
|
"non-pr",
|
|
333
|
-
"--session-id",
|
|
334
|
-
"job-2",
|
|
335
|
-
"--revision",
|
|
336
|
-
nonPrClaim.revision,
|
|
337
|
-
"--outcome",
|
|
338
272
|
"done",
|
|
339
273
|
"--no-pull-request",
|
|
340
274
|
"--summary",
|
|
@@ -1073,6 +1007,15 @@ status: open
|
|
|
1073
1007
|
],
|
|
1074
1008
|
}),
|
|
1075
1009
|
])
|
|
1010
|
+
const preparedAgain = parseJson(
|
|
1011
|
+
await runCli(
|
|
1012
|
+
["work", "prepare", join(root, "tasks/example/TASK.md"), "--json"],
|
|
1013
|
+
parent,
|
|
1014
|
+
),
|
|
1015
|
+
)
|
|
1016
|
+
expect(preparedAgain.checkouts).toEqual([
|
|
1017
|
+
expect.objectContaining({ action: "reused", kind: "writable" }),
|
|
1018
|
+
])
|
|
1076
1019
|
const rebuild = parseJson(
|
|
1077
1020
|
await runCli(
|
|
1078
1021
|
["worktree", "rebuild", "example", "--dry-run", "--json"],
|