@markjaquith/agency 2.24.0 → 2.26.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 +14 -2
- package/cli.ts +10 -0
- package/package.json +1 -1
- package/skills/agency/SKILL.md +10 -1
- package/src/cli-parser.test.ts +26 -0
- package/src/cli-parser.ts +57 -2
- package/src/commands/archive.test.ts +22 -0
- package/src/commands/archive.ts +3 -1
- package/src/commands/repo.test.ts +44 -0
- package/src/commands/repo.ts +107 -1
- package/src/commands/workbase.test.ts +47 -0
- package/src/commands/workbase.ts +52 -1
- package/src/services/ArchiveService.test.ts +59 -0
- package/src/services/ArchiveService.ts +282 -113
- package/src/services/LifecycleTransaction.test.ts +107 -0
- package/src/services/LifecycleTransaction.ts +302 -0
- package/src/services/PhaseService.ts +156 -48
- package/src/services/RepositoryService.test.ts +129 -1
- package/src/services/RepositoryService.ts +181 -0
- package/src/services/TaskPhaseService.test.ts +47 -1
- package/src/services/TaskService.ts +28 -4
- package/src/services/WorkbaseService.test.ts +46 -0
- package/src/services/WorkbaseService.ts +94 -25
- package/src/services/WorktreeLock.ts +60 -0
- package/src/services/WorktreeService.test.ts +174 -1
- package/src/services/WorktreeService.ts +972 -523
|
@@ -427,7 +427,8 @@ describe("WorktreeService", () => {
|
|
|
427
427
|
repo: "agency",
|
|
428
428
|
base: "absent-base",
|
|
429
429
|
config: { version: 2 },
|
|
430
|
-
expected:
|
|
430
|
+
expected:
|
|
431
|
+
"Base 'absent-base' for repository 'agency' does not resolve to a commit",
|
|
431
432
|
},
|
|
432
433
|
{
|
|
433
434
|
id: "failed-command",
|
|
@@ -486,6 +487,64 @@ pr: null
|
|
|
486
487
|
}
|
|
487
488
|
})
|
|
488
489
|
|
|
490
|
+
test("compensates a custom command that fails after creating Git state", async () => {
|
|
491
|
+
await Bun.write(
|
|
492
|
+
join(root, "agency.json"),
|
|
493
|
+
JSON.stringify({
|
|
494
|
+
version: 2,
|
|
495
|
+
worktreeCreateCommand: [
|
|
496
|
+
"sh",
|
|
497
|
+
"-c",
|
|
498
|
+
'git -C "$1" worktree add -b "$3" "$2" "$4" >/dev/null 2>&1; exit 7',
|
|
499
|
+
"agency-worktree",
|
|
500
|
+
"{repo}",
|
|
501
|
+
"{worktree}",
|
|
502
|
+
"{branch}",
|
|
503
|
+
"{base}",
|
|
504
|
+
],
|
|
505
|
+
}),
|
|
506
|
+
)
|
|
507
|
+
await runTestEffect(
|
|
508
|
+
TaskService.pipe(
|
|
509
|
+
Effect.flatMap((service) =>
|
|
510
|
+
service.create(
|
|
511
|
+
{
|
|
512
|
+
id: "compensated",
|
|
513
|
+
ticketUrl: "https://example.com/task",
|
|
514
|
+
repo: "agency",
|
|
515
|
+
branch: "task/compensated",
|
|
516
|
+
base: "main",
|
|
517
|
+
},
|
|
518
|
+
root,
|
|
519
|
+
),
|
|
520
|
+
),
|
|
521
|
+
),
|
|
522
|
+
)
|
|
523
|
+
|
|
524
|
+
await expect(
|
|
525
|
+
runTestEffect(
|
|
526
|
+
WorktreeService.pipe(
|
|
527
|
+
Effect.flatMap((service) =>
|
|
528
|
+
service.materialize("compensated", undefined, root),
|
|
529
|
+
),
|
|
530
|
+
),
|
|
531
|
+
),
|
|
532
|
+
).rejects.toThrow("Failed to create worktree for 'agency'")
|
|
533
|
+
expect(
|
|
534
|
+
await Bun.file(join(root, "tasks/compensated/code/agency")).exists(),
|
|
535
|
+
).toBe(false)
|
|
536
|
+
expect(
|
|
537
|
+
Bun.spawnSync([
|
|
538
|
+
"git",
|
|
539
|
+
"-C",
|
|
540
|
+
join(root, "repos/agency"),
|
|
541
|
+
"show-ref",
|
|
542
|
+
"--verify",
|
|
543
|
+
"refs/heads/task/compensated",
|
|
544
|
+
]).exitCode,
|
|
545
|
+
).not.toBe(0)
|
|
546
|
+
})
|
|
547
|
+
|
|
489
548
|
test("moves and repairs an existing worktree when converting a task", async () => {
|
|
490
549
|
await runTestEffect(
|
|
491
550
|
TaskService.pipe(
|
|
@@ -554,6 +613,54 @@ pr: null
|
|
|
554
613
|
expect(status.exitCode).toBe(0)
|
|
555
614
|
})
|
|
556
615
|
|
|
616
|
+
test("preflights worktree registration before converting a task", async () => {
|
|
617
|
+
await runTestEffect(
|
|
618
|
+
TaskService.pipe(
|
|
619
|
+
Effect.flatMap((service) =>
|
|
620
|
+
service.create(
|
|
621
|
+
{
|
|
622
|
+
id: "unregistered",
|
|
623
|
+
ticketUrl: null,
|
|
624
|
+
repo: "agency",
|
|
625
|
+
branch: "task/unregistered",
|
|
626
|
+
base: "main",
|
|
627
|
+
},
|
|
628
|
+
root,
|
|
629
|
+
),
|
|
630
|
+
),
|
|
631
|
+
),
|
|
632
|
+
)
|
|
633
|
+
const checkout = join(root, "tasks/unregistered/code/agency")
|
|
634
|
+
await mkdir(checkout, { recursive: true })
|
|
635
|
+
await Bun.write(join(checkout, "keep.txt"), "keep\n")
|
|
636
|
+
|
|
637
|
+
await expect(
|
|
638
|
+
runTestEffect(
|
|
639
|
+
PhaseService.pipe(
|
|
640
|
+
Effect.flatMap((service) =>
|
|
641
|
+
service.create(
|
|
642
|
+
{
|
|
643
|
+
taskId: "unregistered",
|
|
644
|
+
id: "follow-up",
|
|
645
|
+
firstPhase: "implementation",
|
|
646
|
+
repo: "agency",
|
|
647
|
+
branch: "task/follow-up-unregistered",
|
|
648
|
+
base: "main",
|
|
649
|
+
},
|
|
650
|
+
root,
|
|
651
|
+
),
|
|
652
|
+
),
|
|
653
|
+
),
|
|
654
|
+
),
|
|
655
|
+
).rejects.toThrow("is not registered as a Git worktree")
|
|
656
|
+
expect(await Bun.file(join(checkout, "keep.txt")).text()).toBe("keep\n")
|
|
657
|
+
expect(
|
|
658
|
+
await Bun.file(
|
|
659
|
+
join(root, "tasks/unregistered/phases/implementation/PHASE.md"),
|
|
660
|
+
).exists(),
|
|
661
|
+
).toBe(false)
|
|
662
|
+
})
|
|
663
|
+
|
|
557
664
|
test("rejects a writable branch checked out in another worktree", async () => {
|
|
558
665
|
const repository = join(root, "repos/agency")
|
|
559
666
|
await git(["-C", repository, "branch", "task/shared", "main"])
|
|
@@ -733,6 +840,21 @@ pr: null
|
|
|
733
840
|
),
|
|
734
841
|
),
|
|
735
842
|
).rejects.toThrow("is attached to branch 'main'")
|
|
843
|
+
expect(
|
|
844
|
+
await Bun.file(
|
|
845
|
+
join(root, "tasks/attached-reference/code/agency"),
|
|
846
|
+
).exists(),
|
|
847
|
+
).toBe(false)
|
|
848
|
+
expect(
|
|
849
|
+
Bun.spawnSync([
|
|
850
|
+
"git",
|
|
851
|
+
"-C",
|
|
852
|
+
join(root, "repos/agency"),
|
|
853
|
+
"show-ref",
|
|
854
|
+
"--verify",
|
|
855
|
+
"refs/heads/task/attached-reference",
|
|
856
|
+
]).exitCode,
|
|
857
|
+
).not.toBe(0)
|
|
736
858
|
})
|
|
737
859
|
|
|
738
860
|
test("removes worktrees without deleting branches", async () => {
|
|
@@ -860,6 +982,57 @@ pr: null
|
|
|
860
982
|
).not.toBe(0)
|
|
861
983
|
})
|
|
862
984
|
|
|
985
|
+
test("dry-run resolves a reference that exists only on the remote", async () => {
|
|
986
|
+
await git(["checkout", "-b", "remote-only"], source)
|
|
987
|
+
await Bun.write(join(source, "remote.txt"), "remote\n")
|
|
988
|
+
await git(["add", "remote.txt"], source)
|
|
989
|
+
await git(
|
|
990
|
+
["-c", "commit.gpgsign=false", "commit", "-m", "remote branch"],
|
|
991
|
+
source,
|
|
992
|
+
)
|
|
993
|
+
await runTestEffect(
|
|
994
|
+
TaskService.pipe(
|
|
995
|
+
Effect.flatMap((service) =>
|
|
996
|
+
service.create(
|
|
997
|
+
{
|
|
998
|
+
id: "remote-reference",
|
|
999
|
+
ticketUrl: null,
|
|
1000
|
+
repo: "agency",
|
|
1001
|
+
repos: [{ repo: "effect", ref: "remote-only" }],
|
|
1002
|
+
branch: "task/remote-reference",
|
|
1003
|
+
base: "main",
|
|
1004
|
+
},
|
|
1005
|
+
root,
|
|
1006
|
+
),
|
|
1007
|
+
),
|
|
1008
|
+
),
|
|
1009
|
+
)
|
|
1010
|
+
|
|
1011
|
+
const workspace = await runTestEffect(
|
|
1012
|
+
WorktreeService.pipe(
|
|
1013
|
+
Effect.flatMap((service) =>
|
|
1014
|
+
service.materialize("remote-reference", undefined, root, {
|
|
1015
|
+
dryRun: true,
|
|
1016
|
+
}),
|
|
1017
|
+
),
|
|
1018
|
+
),
|
|
1019
|
+
)
|
|
1020
|
+
expect(
|
|
1021
|
+
workspace.checkouts.find((checkout) => checkout.repo === "effect")
|
|
1022
|
+
?.resolvedCommit,
|
|
1023
|
+
).toMatch(/^[0-9a-f]{40}$/)
|
|
1024
|
+
expect(
|
|
1025
|
+
Bun.spawnSync([
|
|
1026
|
+
"git",
|
|
1027
|
+
"-C",
|
|
1028
|
+
join(root, "repos/effect"),
|
|
1029
|
+
"show-ref",
|
|
1030
|
+
"--verify",
|
|
1031
|
+
"refs/heads/remote-only",
|
|
1032
|
+
]).exitCode,
|
|
1033
|
+
).not.toBe(0)
|
|
1034
|
+
})
|
|
1035
|
+
|
|
863
1036
|
test("refuses to remove a worktree with uncommitted changes", async () => {
|
|
864
1037
|
await runTestEffect(
|
|
865
1038
|
TaskService.pipe(
|