@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.
@@ -427,7 +427,8 @@ describe("WorktreeService", () => {
427
427
  repo: "agency",
428
428
  base: "absent-base",
429
429
  config: { version: 2 },
430
- expected: "Failed to create branch 'task/bad-base'",
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(