@markjaquith/agency 1.11.1 → 2.1.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.
Files changed (132) hide show
  1. package/README.md +313 -117
  2. package/cli.ts +124 -420
  3. package/index.ts +1 -1
  4. package/package.json +12 -17
  5. package/skills/agency/SKILL.md +266 -0
  6. package/src/cli.test.ts +290 -0
  7. package/src/commands/description.test.ts +103 -0
  8. package/src/commands/epic.test.ts +103 -0
  9. package/src/commands/epic.ts +101 -0
  10. package/src/commands/init.test.ts +42 -290
  11. package/src/commands/init.ts +15 -123
  12. package/src/commands/phase.ts +120 -0
  13. package/src/commands/pr.test.ts +19 -170
  14. package/src/commands/pr.ts +23 -179
  15. package/src/commands/repo.test.ts +80 -0
  16. package/src/commands/repo.ts +83 -0
  17. package/src/commands/status.test.ts +35 -477
  18. package/src/commands/status.ts +24 -213
  19. package/src/commands/task-phase.test.ts +206 -0
  20. package/src/commands/task.ts +83 -1285
  21. package/src/commands/validate.test.ts +91 -0
  22. package/src/commands/validate.ts +50 -0
  23. package/src/commands/work.test.ts +326 -399
  24. package/src/commands/work.ts +153 -153
  25. package/src/services/EpicService.test.ts +71 -0
  26. package/src/services/EpicService.ts +135 -0
  27. package/src/services/FileSystemService.ts +61 -92
  28. package/src/services/PhaseService.ts +323 -0
  29. package/src/services/PullRequestService.test.ts +386 -0
  30. package/src/services/PullRequestService.ts +143 -0
  31. package/src/services/RepositoryService.test.ts +106 -0
  32. package/src/services/RepositoryService.ts +159 -0
  33. package/src/services/TaskPhaseService.test.ts +217 -0
  34. package/src/services/TaskService.ts +192 -0
  35. package/src/services/WorkbaseService.test.ts +446 -0
  36. package/src/services/WorkbaseService.ts +493 -0
  37. package/src/services/WorktreeService.test.ts +599 -0
  38. package/src/services/WorktreeService.ts +357 -0
  39. package/src/test-utils.ts +41 -438
  40. package/src/types.ts +1 -215
  41. package/src/utils/command.ts +1 -5
  42. package/src/utils/effect.test.ts +24 -0
  43. package/src/utils/effect.ts +3 -273
  44. package/src/utils/process.test.ts +0 -15
  45. package/src/utils/process.ts +1 -60
  46. package/src/workbase/frontmatter.test.ts +50 -0
  47. package/src/workbase/frontmatter.ts +77 -0
  48. package/src/workbase/repository-reference.test.ts +25 -0
  49. package/src/workbase/repository-reference.ts +18 -0
  50. package/src/workbase/schemas.test.ts +146 -0
  51. package/src/workbase/schemas.ts +78 -0
  52. package/src/workbase/work-target.test.ts +77 -0
  53. package/src/workbase/work-target.ts +180 -0
  54. package/src/workbase/worktree-command.test.ts +61 -0
  55. package/src/workbase/worktree-command.ts +52 -0
  56. package/src/commands/base.test.ts +0 -198
  57. package/src/commands/base.ts +0 -198
  58. package/src/commands/clean.test.ts +0 -299
  59. package/src/commands/clean.ts +0 -274
  60. package/src/commands/completions.test.ts +0 -51
  61. package/src/commands/completions.ts +0 -308
  62. package/src/commands/emit.integration.test.ts +0 -549
  63. package/src/commands/emit.test.ts +0 -576
  64. package/src/commands/emit.ts +0 -700
  65. package/src/commands/emitted.test.ts +0 -226
  66. package/src/commands/emitted.ts +0 -57
  67. package/src/commands/loop.test.ts +0 -621
  68. package/src/commands/loop.ts +0 -406
  69. package/src/commands/merge.integration.test.ts +0 -195
  70. package/src/commands/merge.test.ts +0 -246
  71. package/src/commands/merge.ts +0 -244
  72. package/src/commands/pull.test.ts +0 -395
  73. package/src/commands/pull.ts +0 -205
  74. package/src/commands/push.test.ts +0 -641
  75. package/src/commands/push.ts +0 -482
  76. package/src/commands/rebase.test.ts +0 -521
  77. package/src/commands/rebase.ts +0 -232
  78. package/src/commands/save.test.ts +0 -247
  79. package/src/commands/save.ts +0 -162
  80. package/src/commands/source.test.ts +0 -195
  81. package/src/commands/source.ts +0 -72
  82. package/src/commands/switch.test.ts +0 -194
  83. package/src/commands/switch.ts +0 -84
  84. package/src/commands/task-branching.test.ts +0 -633
  85. package/src/commands/task-continue.test.ts +0 -311
  86. package/src/commands/task-edit.test.ts +0 -235
  87. package/src/commands/task-main.test.ts +0 -1066
  88. package/src/commands/task-squash.test.ts +0 -266
  89. package/src/commands/tasks.test.ts +0 -391
  90. package/src/commands/tasks.ts +0 -161
  91. package/src/commands/template-delete.test.ts +0 -178
  92. package/src/commands/template-delete.ts +0 -98
  93. package/src/commands/template-list.test.ts +0 -135
  94. package/src/commands/template-list.ts +0 -87
  95. package/src/commands/template-view.test.ts +0 -158
  96. package/src/commands/template-view.ts +0 -86
  97. package/src/commands/template.test.ts +0 -32
  98. package/src/commands/template.ts +0 -96
  99. package/src/commands/use.test.ts +0 -87
  100. package/src/commands/use.ts +0 -97
  101. package/src/constants.ts +0 -10
  102. package/src/errors.ts +0 -17
  103. package/src/schemas.ts +0 -33
  104. package/src/services/AgencyMetadataService.ts +0 -311
  105. package/src/services/ClaudeService.test.ts +0 -184
  106. package/src/services/ClaudeService.ts +0 -91
  107. package/src/services/ConfigService.ts +0 -115
  108. package/src/services/FilterRepoService.ts +0 -164
  109. package/src/services/FormatterService.test.ts +0 -432
  110. package/src/services/FormatterService.ts +0 -219
  111. package/src/services/GitService.ts +0 -1187
  112. package/src/services/MockFilterRepoService.ts +0 -140
  113. package/src/services/OpencodeService.ts +0 -263
  114. package/src/services/PromptService.ts +0 -183
  115. package/src/services/TemplateService.ts +0 -81
  116. package/src/types/native-exec.d.ts +0 -8
  117. package/src/utils/colors.ts +0 -178
  118. package/src/utils/git-path.ts +0 -6
  119. package/src/utils/glob.test.ts +0 -161
  120. package/src/utils/glob.ts +0 -78
  121. package/src/utils/paths.ts +0 -51
  122. package/src/utils/pr-branch.test.ts +0 -376
  123. package/src/utils/pr-branch.ts +0 -508
  124. package/src/utils/spinner.test.ts +0 -45
  125. package/src/utils/spinner.ts +0 -96
  126. package/src/utils/task-parser.test.ts +0 -172
  127. package/src/utils/task-parser.ts +0 -93
  128. package/templates/AGENCY.md +0 -22
  129. package/templates/AGENTS.md +0 -11
  130. package/templates/CLAUDE.md +0 -3
  131. package/templates/TASK.md +0 -5
  132. package/templates/opencode.json +0 -4
@@ -0,0 +1,599 @@
1
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test"
2
+ import { Effect } from "effect"
3
+ import { mkdir } from "node:fs/promises"
4
+ import { join } from "node:path"
5
+ import { cleanupTempDir, createTempDir, runTestEffect } from "../test-utils"
6
+ import { TaskService } from "./TaskService"
7
+ import { PhaseService } from "./PhaseService"
8
+ import { WorktreeService } from "./WorktreeService"
9
+
10
+ const git = async (args: string[], cwd?: string) => {
11
+ const process = Bun.spawn(["git", ...args], {
12
+ cwd,
13
+ stdout: "pipe",
14
+ stderr: "pipe",
15
+ })
16
+ await process.exited
17
+ if (process.exitCode !== 0)
18
+ throw new Error(await new Response(process.stderr).text())
19
+ }
20
+
21
+ describe("WorktreeService", () => {
22
+ let root: string
23
+ let source: string
24
+
25
+ beforeEach(async () => {
26
+ root = await createTempDir()
27
+ await Bun.write(join(root, "agency.json"), '{"version":2}\n')
28
+ source = join(root, "source")
29
+ await mkdir(source, { recursive: true })
30
+ await git(["init", "--initial-branch=main"], source)
31
+ await git(["config", "user.email", "test@example.com"], source)
32
+ await git(["config", "user.name", "Test"], source)
33
+ await Bun.write(join(source, "README.md"), "example\n")
34
+ await git(["add", "README.md"], source)
35
+ await git(["-c", "commit.gpgsign=false", "commit", "-m", "initial"], source)
36
+ await mkdir(join(root, "repos"), { recursive: true })
37
+ await git(["clone", "--bare", source, join(root, "repos/agency")])
38
+ await git(["clone", "--bare", source, join(root, "repos/effect")])
39
+ })
40
+
41
+ afterEach(async () => cleanupTempDir(root))
42
+
43
+ test("materializes writable and reference worktrees", async () => {
44
+ await runTestEffect(
45
+ TaskService.pipe(
46
+ Effect.flatMap((service) =>
47
+ service.create(
48
+ {
49
+ id: "example",
50
+ ticketUrl: "https://example.com/task",
51
+ repo: "agency",
52
+ repos: [{ repo: "effect", ref: "main" }],
53
+ branch: "task/example",
54
+ base: "main",
55
+ },
56
+ root,
57
+ ),
58
+ ),
59
+ ),
60
+ )
61
+
62
+ const workspace = await runTestEffect(
63
+ WorktreeService.pipe(
64
+ Effect.flatMap((service) =>
65
+ service.materialize("example", undefined, root),
66
+ ),
67
+ ),
68
+ )
69
+
70
+ expect(
71
+ await Bun.file(join(workspace.writablePath, "README.md")).text(),
72
+ ).toBe("example\n")
73
+ expect(
74
+ await Bun.file(join(workspace.codePath, "effect/README.md")).text(),
75
+ ).toBe("example\n")
76
+ const branch = Bun.spawnSync(
77
+ ["git", "-C", workspace.writablePath, "branch", "--show-current"],
78
+ { stdout: "pipe" },
79
+ )
80
+ expect(new TextDecoder().decode(branch.stdout).trim()).toBe("task/example")
81
+ })
82
+
83
+ test("uses a configured worktree creation command", async () => {
84
+ await Bun.write(
85
+ join(root, "agency.json"),
86
+ JSON.stringify({
87
+ version: 2,
88
+ worktreeCreateCommand: [
89
+ "git",
90
+ "-C",
91
+ "{repo}",
92
+ "worktree",
93
+ "add",
94
+ "-b",
95
+ "{branch}",
96
+ "{worktree}",
97
+ "{base}",
98
+ ],
99
+ }),
100
+ )
101
+ await runTestEffect(
102
+ TaskService.pipe(
103
+ Effect.flatMap((service) =>
104
+ service.create(
105
+ {
106
+ id: "configured",
107
+ ticketUrl: "https://example.com/task",
108
+ repo: "agency",
109
+ branch: "task/configured",
110
+ base: "main",
111
+ },
112
+ root,
113
+ ),
114
+ ),
115
+ ),
116
+ )
117
+
118
+ const workspace = await runTestEffect(
119
+ WorktreeService.pipe(
120
+ Effect.flatMap((service) =>
121
+ service.materialize("configured", undefined, root),
122
+ ),
123
+ ),
124
+ )
125
+ expect(
126
+ await Bun.file(join(workspace.writablePath, "README.md")).text(),
127
+ ).toBe("example\n")
128
+ })
129
+
130
+ test("selects phases and rejects missing or unexpected phase IDs", async () => {
131
+ await runTestEffect(
132
+ TaskService.pipe(
133
+ Effect.flatMap((service) =>
134
+ service.create(
135
+ {
136
+ id: "multi",
137
+ ticketUrl: "https://example.com/task",
138
+ multiPhase: true,
139
+ },
140
+ root,
141
+ ),
142
+ ),
143
+ ),
144
+ )
145
+ await runTestEffect(
146
+ PhaseService.pipe(
147
+ Effect.flatMap((service) =>
148
+ service.create(
149
+ {
150
+ taskId: "multi",
151
+ id: "selected",
152
+ repo: "agency",
153
+ branch: "task/selected",
154
+ base: "main",
155
+ },
156
+ root,
157
+ ),
158
+ ),
159
+ ),
160
+ )
161
+
162
+ await expect(
163
+ runTestEffect(
164
+ WorktreeService.pipe(
165
+ Effect.flatMap((service) =>
166
+ service.materialize("multi", undefined, root),
167
+ ),
168
+ ),
169
+ ),
170
+ ).rejects.toThrow("phase ID is required")
171
+ await expect(
172
+ runTestEffect(
173
+ WorktreeService.pipe(
174
+ Effect.flatMap((service) =>
175
+ service.materialize("multi", "missing", root),
176
+ ),
177
+ ),
178
+ ),
179
+ ).rejects.toThrow("Phase 'missing' does not exist on task 'multi'")
180
+
181
+ const workspace = await runTestEffect(
182
+ WorktreeService.pipe(
183
+ Effect.flatMap((service) =>
184
+ service.materialize("multi", "selected", root),
185
+ ),
186
+ ),
187
+ )
188
+ expect(workspace.phasePath).toBe(
189
+ join(root, "tasks/multi/phases/selected/PHASE.md"),
190
+ )
191
+ expect(workspace.writablePath).toBe(
192
+ join(root, "tasks/multi/phases/selected/code/agency"),
193
+ )
194
+
195
+ await runTestEffect(
196
+ TaskService.pipe(
197
+ Effect.flatMap((service) =>
198
+ service.create(
199
+ {
200
+ id: "single",
201
+ ticketUrl: "https://example.com/task",
202
+ repo: "effect",
203
+ branch: "task/single",
204
+ base: "main",
205
+ },
206
+ root,
207
+ ),
208
+ ),
209
+ ),
210
+ )
211
+ await expect(
212
+ runTestEffect(
213
+ WorktreeService.pipe(
214
+ Effect.flatMap((service) =>
215
+ service.materialize("single", "unexpected", root),
216
+ ),
217
+ ),
218
+ ),
219
+ ).rejects.toThrow("does not accept a phase ID")
220
+ })
221
+
222
+ test("reports meaningful worktree creation failures", async () => {
223
+ const cases = [
224
+ {
225
+ id: "missing-repo",
226
+ repo: "missing",
227
+ base: "main",
228
+ config: { version: 2 },
229
+ expected: "Repository alias 'missing' does not exist",
230
+ },
231
+ {
232
+ id: "bad-base",
233
+ repo: "agency",
234
+ base: "absent-base",
235
+ config: { version: 2 },
236
+ expected: "Failed to create branch 'task/bad-base'",
237
+ },
238
+ {
239
+ id: "failed-command",
240
+ repo: "agency",
241
+ base: "main",
242
+ config: {
243
+ version: 2,
244
+ worktreeCreateCommand: [
245
+ "sh",
246
+ "-c",
247
+ "echo command-failed >&2; exit 7",
248
+ "{repo}",
249
+ "{worktree}",
250
+ ],
251
+ },
252
+ expected: "Failed to create worktree for 'agency': command-failed",
253
+ },
254
+ {
255
+ id: "missing-destination",
256
+ repo: "agency",
257
+ base: "main",
258
+ config: {
259
+ version: 2,
260
+ worktreeCreateCommand: ["sh", "-c", "exit 0", "{repo}", "{worktree}"],
261
+ },
262
+ expected: "Worktree command did not create",
263
+ },
264
+ ] as const
265
+
266
+ for (const fixture of cases) {
267
+ await Bun.write(join(root, "agency.json"), JSON.stringify(fixture.config))
268
+ const taskDirectory = join(root, "tasks", fixture.id)
269
+ await mkdir(taskDirectory, { recursive: true })
270
+ await Bun.write(
271
+ join(taskDirectory, "TASK.md"),
272
+ `---
273
+ ticketUrl: https://example.com/tasks/${fixture.id}
274
+ repo: ${fixture.repo}
275
+ branch: task/${fixture.id}
276
+ base: ${fixture.base}
277
+ pr: null
278
+ ---
279
+ `,
280
+ )
281
+
282
+ await expect(
283
+ runTestEffect(
284
+ WorktreeService.pipe(
285
+ Effect.flatMap((service) =>
286
+ service.materialize(fixture.id, undefined, root),
287
+ ),
288
+ ),
289
+ ),
290
+ ).rejects.toThrow(fixture.expected)
291
+ }
292
+ })
293
+
294
+ test("moves and repairs an existing worktree when converting a task", async () => {
295
+ await runTestEffect(
296
+ TaskService.pipe(
297
+ Effect.flatMap((service) =>
298
+ service.create(
299
+ {
300
+ id: "promoted",
301
+ ticketUrl: "https://example.com/task",
302
+ repo: "agency",
303
+ branch: "task/promoted",
304
+ base: "main",
305
+ },
306
+ root,
307
+ ),
308
+ ),
309
+ ),
310
+ )
311
+ const originalWorkspace = await runTestEffect(
312
+ WorktreeService.pipe(
313
+ Effect.flatMap((service) =>
314
+ service.materialize("promoted", undefined, root),
315
+ ),
316
+ ),
317
+ )
318
+
319
+ await runTestEffect(
320
+ PhaseService.pipe(
321
+ Effect.flatMap((service) =>
322
+ service.create(
323
+ {
324
+ taskId: "promoted",
325
+ id: "follow-up",
326
+ firstPhase: "implementation",
327
+ repo: "agency",
328
+ branch: "task/follow-up",
329
+ base: "main",
330
+ },
331
+ root,
332
+ ),
333
+ ),
334
+ ),
335
+ )
336
+
337
+ expect(
338
+ await Bun.file(
339
+ join(originalWorkspace.writablePath, "README.md"),
340
+ ).exists(),
341
+ ).toBe(false)
342
+ const movedWorkspace = await runTestEffect(
343
+ WorktreeService.pipe(
344
+ Effect.flatMap((service) =>
345
+ service.materialize("promoted", "implementation", root),
346
+ ),
347
+ ),
348
+ )
349
+ expect(movedWorkspace.writablePath).toBe(
350
+ join(root, "tasks/promoted/phases/implementation/code/agency"),
351
+ )
352
+ expect(
353
+ await Bun.file(join(movedWorkspace.writablePath, "README.md")).text(),
354
+ ).toBe("example\n")
355
+ const status = Bun.spawnSync(
356
+ ["git", "-C", movedWorkspace.writablePath, "status", "--porcelain"],
357
+ { stdout: "pipe", stderr: "pipe" },
358
+ )
359
+ expect(status.exitCode).toBe(0)
360
+ })
361
+
362
+ test("rejects a writable branch checked out in another worktree", async () => {
363
+ const repository = join(root, "repos/agency")
364
+ await git(["-C", repository, "branch", "task/shared", "main"])
365
+ const outside = join(root, "outside")
366
+ await git(["-C", repository, "worktree", "add", outside, "task/shared"])
367
+ await runTestEffect(
368
+ TaskService.pipe(
369
+ Effect.flatMap((service) =>
370
+ service.create(
371
+ {
372
+ id: "conflict",
373
+ ticketUrl: "https://example.com/task",
374
+ repo: "agency",
375
+ branch: "task/shared",
376
+ base: "main",
377
+ },
378
+ root,
379
+ ),
380
+ ),
381
+ ),
382
+ )
383
+
384
+ await expect(
385
+ runTestEffect(
386
+ WorktreeService.pipe(
387
+ Effect.flatMap((service) =>
388
+ service.materialize("conflict", undefined, root),
389
+ ),
390
+ ),
391
+ ),
392
+ ).rejects.toThrow("already checked out at")
393
+ })
394
+
395
+ test("rejects duplicate Agency ownership before materializing", async () => {
396
+ for (const id of ["first-owner", "second-owner"]) {
397
+ await runTestEffect(
398
+ TaskService.pipe(
399
+ Effect.flatMap((service) =>
400
+ service.create(
401
+ {
402
+ id,
403
+ ticketUrl: `https://example.com/${id}`,
404
+ repo: "agency",
405
+ branch: "task/shared-owner",
406
+ base: "main",
407
+ },
408
+ root,
409
+ ),
410
+ ),
411
+ ),
412
+ )
413
+ }
414
+
415
+ await expect(
416
+ runTestEffect(
417
+ WorktreeService.pipe(
418
+ Effect.flatMap((service) =>
419
+ service.materialize("first-owner", undefined, root),
420
+ ),
421
+ ),
422
+ ),
423
+ ).rejects.toThrow("also owned by tasks/first-owner/TASK.md")
424
+ })
425
+
426
+ test("rejects an existing Agency checkout on the wrong branch", async () => {
427
+ const repository = join(root, "repos/agency")
428
+ await runTestEffect(
429
+ TaskService.pipe(
430
+ Effect.flatMap((service) =>
431
+ service.create(
432
+ {
433
+ id: "wrong",
434
+ ticketUrl: "https://example.com/task",
435
+ repo: "agency",
436
+ branch: "task/expected",
437
+ base: "main",
438
+ },
439
+ root,
440
+ ),
441
+ ),
442
+ ),
443
+ )
444
+ await git(["-C", repository, "branch", "task/wrong", "main"])
445
+ const checkout = join(root, "tasks/wrong/code/agency")
446
+ await mkdir(join(root, "tasks/wrong/code"), { recursive: true })
447
+ await git(["-C", repository, "worktree", "add", checkout, "task/wrong"])
448
+
449
+ await expect(
450
+ runTestEffect(
451
+ WorktreeService.pipe(
452
+ Effect.flatMap((service) =>
453
+ service.materialize("wrong", undefined, root),
454
+ ),
455
+ ),
456
+ ),
457
+ ).rejects.toThrow("is not registered to branch 'task/expected'")
458
+ })
459
+
460
+ test("rejects a reused reference checkout after its ref advances", async () => {
461
+ await runTestEffect(
462
+ TaskService.pipe(
463
+ Effect.flatMap((service) =>
464
+ service.create(
465
+ {
466
+ id: "pinned-reference",
467
+ ticketUrl: "https://example.com/task",
468
+ repo: "agency",
469
+ repos: [{ repo: "effect", ref: "main" }],
470
+ branch: "task/pinned-reference",
471
+ base: "main",
472
+ },
473
+ root,
474
+ ),
475
+ ),
476
+ ),
477
+ )
478
+ await runTestEffect(
479
+ WorktreeService.pipe(
480
+ Effect.flatMap((service) =>
481
+ service.materialize("pinned-reference", undefined, root),
482
+ ),
483
+ ),
484
+ )
485
+
486
+ await Bun.write(join(source, "README.md"), "updated\n")
487
+ await git(["add", "README.md"], source)
488
+ await git(["-c", "commit.gpgsign=false", "commit", "-m", "update"], source)
489
+ await git(["push", join(root, "repos/effect"), "main"], source)
490
+
491
+ await expect(
492
+ runTestEffect(
493
+ WorktreeService.pipe(
494
+ Effect.flatMap((service) =>
495
+ service.materialize("pinned-reference", undefined, root),
496
+ ),
497
+ ),
498
+ ),
499
+ ).rejects.toThrow("does not match reference 'main'")
500
+ })
501
+
502
+ test("rejects a reference checkout attached to a branch", async () => {
503
+ await runTestEffect(
504
+ TaskService.pipe(
505
+ Effect.flatMap((service) =>
506
+ service.create(
507
+ {
508
+ id: "attached-reference",
509
+ ticketUrl: "https://example.com/task",
510
+ repo: "agency",
511
+ repos: [{ repo: "effect", ref: "main" }],
512
+ branch: "task/attached-reference",
513
+ base: "main",
514
+ },
515
+ root,
516
+ ),
517
+ ),
518
+ ),
519
+ )
520
+ const checkout = join(root, "tasks/attached-reference/code/effect")
521
+ await mkdir(join(root, "tasks/attached-reference/code"), {
522
+ recursive: true,
523
+ })
524
+ await git([
525
+ "-C",
526
+ join(root, "repos/effect"),
527
+ "worktree",
528
+ "add",
529
+ checkout,
530
+ "main",
531
+ ])
532
+
533
+ await expect(
534
+ runTestEffect(
535
+ WorktreeService.pipe(
536
+ Effect.flatMap((service) =>
537
+ service.materialize("attached-reference", undefined, root),
538
+ ),
539
+ ),
540
+ ),
541
+ ).rejects.toThrow("is attached to branch 'main'")
542
+ })
543
+
544
+ test("supports Worktrunk as the configured command", async () => {
545
+ if (Bun.spawnSync(["which", "wt"], { stdout: "ignore" }).exitCode !== 0) {
546
+ return
547
+ }
548
+
549
+ await Bun.write(
550
+ join(root, "agency.json"),
551
+ JSON.stringify({
552
+ version: 2,
553
+ worktreeCreateCommand: [
554
+ "wt",
555
+ "-C",
556
+ "{repo}",
557
+ "-y",
558
+ "--config-set",
559
+ 'worktree-path="{worktree}"',
560
+ "switch",
561
+ "--create",
562
+ "--base",
563
+ "{base}",
564
+ "{branch}",
565
+ "--no-cd",
566
+ "--format",
567
+ "json",
568
+ ],
569
+ }),
570
+ )
571
+ await runTestEffect(
572
+ TaskService.pipe(
573
+ Effect.flatMap((service) =>
574
+ service.create(
575
+ {
576
+ id: "worktrunk",
577
+ ticketUrl: "https://example.com/task",
578
+ repo: "agency",
579
+ branch: "task/worktrunk",
580
+ base: "main",
581
+ },
582
+ root,
583
+ ),
584
+ ),
585
+ ),
586
+ )
587
+
588
+ const workspace = await runTestEffect(
589
+ WorktreeService.pipe(
590
+ Effect.flatMap((service) =>
591
+ service.materialize("worktrunk", undefined, root),
592
+ ),
593
+ ),
594
+ )
595
+ expect(
596
+ await Bun.file(join(workspace.writablePath, "README.md")).text(),
597
+ ).toBe("example\n")
598
+ })
599
+ })