@markjaquith/agency 0.5.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 (75) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +109 -0
  3. package/cli.ts +569 -0
  4. package/index.ts +1 -0
  5. package/package.json +65 -0
  6. package/src/commands/base.test.ts +198 -0
  7. package/src/commands/base.ts +198 -0
  8. package/src/commands/clean.test.ts +299 -0
  9. package/src/commands/clean.ts +320 -0
  10. package/src/commands/emit.test.ts +412 -0
  11. package/src/commands/emit.ts +521 -0
  12. package/src/commands/emitted.test.ts +226 -0
  13. package/src/commands/emitted.ts +57 -0
  14. package/src/commands/init.test.ts +311 -0
  15. package/src/commands/init.ts +140 -0
  16. package/src/commands/merge.test.ts +365 -0
  17. package/src/commands/merge.ts +253 -0
  18. package/src/commands/pull.test.ts +385 -0
  19. package/src/commands/pull.ts +205 -0
  20. package/src/commands/push.test.ts +394 -0
  21. package/src/commands/push.ts +346 -0
  22. package/src/commands/save.test.ts +247 -0
  23. package/src/commands/save.ts +162 -0
  24. package/src/commands/source.test.ts +195 -0
  25. package/src/commands/source.ts +72 -0
  26. package/src/commands/status.test.ts +489 -0
  27. package/src/commands/status.ts +258 -0
  28. package/src/commands/switch.test.ts +194 -0
  29. package/src/commands/switch.ts +84 -0
  30. package/src/commands/task-branching.test.ts +334 -0
  31. package/src/commands/task-edit.test.ts +141 -0
  32. package/src/commands/task-main.test.ts +872 -0
  33. package/src/commands/task.ts +712 -0
  34. package/src/commands/tasks.test.ts +335 -0
  35. package/src/commands/tasks.ts +155 -0
  36. package/src/commands/template-delete.test.ts +178 -0
  37. package/src/commands/template-delete.ts +98 -0
  38. package/src/commands/template-list.test.ts +135 -0
  39. package/src/commands/template-list.ts +87 -0
  40. package/src/commands/template-view.test.ts +158 -0
  41. package/src/commands/template-view.ts +86 -0
  42. package/src/commands/template.test.ts +32 -0
  43. package/src/commands/template.ts +96 -0
  44. package/src/commands/use.test.ts +87 -0
  45. package/src/commands/use.ts +97 -0
  46. package/src/commands/work.test.ts +462 -0
  47. package/src/commands/work.ts +193 -0
  48. package/src/errors.ts +17 -0
  49. package/src/schemas.ts +33 -0
  50. package/src/services/AgencyMetadataService.ts +287 -0
  51. package/src/services/ClaudeService.test.ts +184 -0
  52. package/src/services/ClaudeService.ts +91 -0
  53. package/src/services/ConfigService.ts +115 -0
  54. package/src/services/FileSystemService.ts +222 -0
  55. package/src/services/GitService.ts +751 -0
  56. package/src/services/OpencodeService.ts +263 -0
  57. package/src/services/PromptService.ts +183 -0
  58. package/src/services/TemplateService.ts +75 -0
  59. package/src/test-utils.ts +362 -0
  60. package/src/types/native-exec.d.ts +8 -0
  61. package/src/types.ts +216 -0
  62. package/src/utils/colors.ts +178 -0
  63. package/src/utils/command.ts +17 -0
  64. package/src/utils/effect.ts +281 -0
  65. package/src/utils/exec.ts +48 -0
  66. package/src/utils/paths.ts +51 -0
  67. package/src/utils/pr-branch.test.ts +372 -0
  68. package/src/utils/pr-branch.ts +473 -0
  69. package/src/utils/process.ts +110 -0
  70. package/src/utils/spinner.ts +82 -0
  71. package/templates/AGENCY.md +20 -0
  72. package/templates/AGENTS.md +11 -0
  73. package/templates/CLAUDE.md +3 -0
  74. package/templates/TASK.md +5 -0
  75. package/templates/opencode.json +4 -0
@@ -0,0 +1,226 @@
1
+ import { test, expect, describe, beforeEach, afterEach } from "bun:test"
2
+ import { join } from "path"
3
+ import { emitted } from "./emitted"
4
+ import {
5
+ createTempDir,
6
+ cleanupTempDir,
7
+ initGitRepo,
8
+ getCurrentBranch,
9
+ createCommit,
10
+ checkoutBranch,
11
+ runTestEffect,
12
+ } from "../test-utils"
13
+
14
+ async function createBranch(cwd: string, branchName: string): Promise<void> {
15
+ await Bun.spawn(["git", "checkout", "-b", branchName], {
16
+ cwd,
17
+ stdout: "pipe",
18
+ stderr: "pipe",
19
+ }).exited
20
+ }
21
+
22
+ describe("emitted command", () => {
23
+ let tempDir: string
24
+ let originalCwd: string
25
+
26
+ beforeEach(async () => {
27
+ tempDir = await createTempDir()
28
+ originalCwd = process.cwd()
29
+ process.chdir(tempDir)
30
+
31
+ // Set config path to non-existent file to use defaults
32
+ process.env.AGENCY_CONFIG_PATH = join(tempDir, "non-existent-config.json")
33
+
34
+ // Initialize git repo
35
+ await initGitRepo(tempDir)
36
+ await createCommit(tempDir, "Initial commit")
37
+
38
+ // Rename to main if needed
39
+ const currentBranch = await getCurrentBranch(tempDir)
40
+ if (currentBranch === "master") {
41
+ await Bun.spawn(["git", "branch", "-m", "main"], {
42
+ cwd: tempDir,
43
+ stdout: "pipe",
44
+ stderr: "pipe",
45
+ }).exited
46
+ }
47
+ })
48
+
49
+ afterEach(async () => {
50
+ process.chdir(originalCwd)
51
+ delete process.env.AGENCY_CONFIG_PATH
52
+ await cleanupTempDir(tempDir)
53
+ })
54
+
55
+ describe("basic functionality", () => {
56
+ test("returns emit branch name when on source branch", async () => {
57
+ // Create source branch (agency/main)
58
+ await createBranch(tempDir, "agency/main")
59
+ await createCommit(tempDir, "Work on source")
60
+
61
+ // Capture output
62
+ const originalLog = console.log
63
+ let capturedOutput = ""
64
+ console.log = (msg: string) => {
65
+ capturedOutput = msg
66
+ }
67
+
68
+ await runTestEffect(emitted({ silent: false }))
69
+
70
+ console.log = originalLog
71
+ expect(capturedOutput).toBe("main")
72
+ })
73
+
74
+ test("returns emit branch name when on emit branch", async () => {
75
+ // Create source branch
76
+ await createBranch(tempDir, "agency/main")
77
+ await createCommit(tempDir, "Work on source")
78
+
79
+ // Switch to emit branch (main)
80
+ await checkoutBranch(tempDir, "main")
81
+
82
+ // Capture output
83
+ const originalLog = console.log
84
+ let capturedOutput = ""
85
+ console.log = (msg: string) => {
86
+ capturedOutput = msg
87
+ }
88
+
89
+ await runTestEffect(emitted({ silent: false }))
90
+
91
+ console.log = originalLog
92
+ expect(capturedOutput).toBe("main")
93
+ })
94
+
95
+ test("returns emit branch with custom pattern", async () => {
96
+ // Create custom config
97
+ const configPath = join(tempDir, "custom-config.json")
98
+ await Bun.write(
99
+ configPath,
100
+ JSON.stringify({
101
+ sourceBranchPattern: "agency/%branch%",
102
+ emitBranch: "%branch%--PR",
103
+ }),
104
+ )
105
+ process.env.AGENCY_CONFIG_PATH = configPath
106
+
107
+ // Create source branch
108
+ await createBranch(tempDir, "agency/feature")
109
+ await createCommit(tempDir, "Feature work")
110
+
111
+ // Capture output
112
+ const originalLog = console.log
113
+ let capturedOutput = ""
114
+ console.log = (msg: string) => {
115
+ capturedOutput = msg
116
+ }
117
+
118
+ await runTestEffect(emitted({ silent: false }))
119
+
120
+ console.log = originalLog
121
+ expect(capturedOutput).toBe("feature--PR")
122
+ })
123
+
124
+ test("returns emit branch from agency.json when present", async () => {
125
+ // Create source branch
126
+ await createBranch(tempDir, "agency/feature")
127
+
128
+ // Create agency.json with custom emitBranch
129
+ const agencyJsonPath = join(tempDir, "agency.json")
130
+ await Bun.write(
131
+ agencyJsonPath,
132
+ JSON.stringify({
133
+ version: 1,
134
+ injectedFiles: [],
135
+ template: "test-template",
136
+ createdAt: new Date().toISOString(),
137
+ emitBranch: "custom-emit-name",
138
+ }),
139
+ )
140
+
141
+ // Commit the agency.json file so it's available on the branch
142
+ await Bun.spawn(["git", "add", "agency.json"], {
143
+ cwd: tempDir,
144
+ stdout: "pipe",
145
+ stderr: "pipe",
146
+ }).exited
147
+ await createCommit(tempDir, "Add agency.json")
148
+
149
+ // Capture output
150
+ const originalLog = console.log
151
+ let capturedOutput = ""
152
+ console.log = (msg: string) => {
153
+ capturedOutput = msg
154
+ }
155
+
156
+ await runTestEffect(emitted({ silent: false }))
157
+
158
+ console.log = originalLog
159
+ expect(capturedOutput).toBe("custom-emit-name")
160
+ })
161
+
162
+ test("works with legacy branch names", async () => {
163
+ // On a branch that doesn't match the source pattern
164
+ // Should treat it as a legacy branch and apply emit pattern
165
+ await createBranch(tempDir, "feature-foo")
166
+
167
+ // Capture output
168
+ const originalLog = console.log
169
+ let capturedOutput = ""
170
+ console.log = (msg: string) => {
171
+ capturedOutput = msg
172
+ }
173
+
174
+ await runTestEffect(emitted({ silent: false }))
175
+
176
+ console.log = originalLog
177
+ // With default pattern "%branch%", emit should be the same as clean branch
178
+ expect(capturedOutput).toBe("feature-foo")
179
+ })
180
+ })
181
+
182
+ describe("silent mode", () => {
183
+ test("still outputs when not in silent mode", async () => {
184
+ await createBranch(tempDir, "agency/main")
185
+
186
+ const originalLog = console.log
187
+ let logCalled = false
188
+ console.log = () => {
189
+ logCalled = true
190
+ }
191
+
192
+ await runTestEffect(emitted({ silent: false }))
193
+
194
+ console.log = originalLog
195
+ expect(logCalled).toBe(true)
196
+ })
197
+
198
+ test("silent flag suppresses output", async () => {
199
+ await createBranch(tempDir, "agency/main")
200
+
201
+ const originalLog = console.log
202
+ let logCalled = false
203
+ console.log = () => {
204
+ logCalled = true
205
+ }
206
+
207
+ await runTestEffect(emitted({ silent: true }))
208
+
209
+ console.log = originalLog
210
+ expect(logCalled).toBe(false)
211
+ })
212
+ })
213
+
214
+ describe("error handling", () => {
215
+ test("throws error when not in a git repository", async () => {
216
+ const nonGitDir = await createTempDir()
217
+ process.chdir(nonGitDir)
218
+
219
+ await expect(runTestEffect(emitted({ silent: true }))).rejects.toThrow(
220
+ "Not in a git repository",
221
+ )
222
+
223
+ await cleanupTempDir(nonGitDir)
224
+ })
225
+ })
226
+ })
@@ -0,0 +1,57 @@
1
+ import { Effect } from "effect"
2
+ import type { BaseCommandOptions } from "../utils/command"
3
+ import { GitService } from "../services/GitService"
4
+ import { ConfigService } from "../services/ConfigService"
5
+ import {
6
+ resolveBranchPairWithAgencyJson,
7
+ type BranchPair,
8
+ } from "../utils/pr-branch"
9
+ import { createLoggers, ensureGitRepo } from "../utils/effect"
10
+
11
+ interface EmittedOptions extends BaseCommandOptions {}
12
+
13
+ export const emitted = (options: EmittedOptions = {}) =>
14
+ Effect.gen(function* () {
15
+ const { log } = createLoggers(options)
16
+
17
+ const git = yield* GitService
18
+ const configService = yield* ConfigService
19
+
20
+ const gitRoot = yield* ensureGitRepo()
21
+
22
+ // Load config
23
+ const config = yield* configService.loadConfig()
24
+
25
+ // Get current branch and resolve the branch pair
26
+ const currentBranch = yield* git.getCurrentBranch(gitRoot)
27
+ const branches: BranchPair = yield* resolveBranchPairWithAgencyJson(
28
+ gitRoot,
29
+ currentBranch,
30
+ config.sourceBranchPattern,
31
+ config.emitBranch,
32
+ )
33
+
34
+ // Return the emit branch name (whether we're on it or not)
35
+ log(branches.emitBranch)
36
+ })
37
+
38
+ export const help = `
39
+ Usage: agency emitted [options]
40
+
41
+ Get the name of the emitted branch (or what it would be).
42
+
43
+ This command shows the emit branch name corresponding to your current branch:
44
+ - If on a source branch (e.g., agency/main), shows the emit branch (e.g., main)
45
+ - If on an emit branch (e.g., main), shows the current branch name
46
+
47
+ This is useful for scripting and automation where you need to know
48
+ the emit branch name without actually creating or switching to it.
49
+
50
+ Example:
51
+ agency emitted # Show the emit branch name
52
+
53
+ Notes:
54
+ - Does not require the emit branch to exist
55
+ - Uses source and emit patterns from ~/.config/agency/agency.json
56
+ - Respects emitBranch field in agency.json when present
57
+ `
@@ -0,0 +1,311 @@
1
+ import { test, expect, describe } from "bun:test"
2
+ import { mkdtemp, rm } from "fs/promises"
3
+ import { tmpdir } from "os"
4
+ import { join } from "path"
5
+ import { init } from "./init"
6
+ import { initGitRepo, getGitConfig, runTestEffect } from "../test-utils"
7
+
8
+ describe("init command", () => {
9
+ test("initializes with template flag", async () => {
10
+ const tempDir = await mkdtemp(join(tmpdir(), "agency-test-"))
11
+ try {
12
+ await initGitRepo(tempDir)
13
+ await runTestEffect(
14
+ init({ template: "test-template", silent: true, cwd: tempDir }),
15
+ )
16
+ expect(await getGitConfig("agency.template", tempDir)).toBe(
17
+ "test-template",
18
+ )
19
+ } finally {
20
+ await rm(tempDir, { recursive: true, force: true })
21
+ }
22
+ })
23
+
24
+ test("saves template name to git config", async () => {
25
+ const tempDir = await mkdtemp(join(tmpdir(), "agency-test-"))
26
+ try {
27
+ await initGitRepo(tempDir)
28
+ await runTestEffect(
29
+ init({ template: "my-template", silent: true, cwd: tempDir }),
30
+ )
31
+ expect(await getGitConfig("agency.template", tempDir)).toBe("my-template")
32
+ } finally {
33
+ await rm(tempDir, { recursive: true, force: true })
34
+ }
35
+ })
36
+
37
+ test("accepts template names with hyphens", async () => {
38
+ const tempDir = await mkdtemp(join(tmpdir(), "agency-test-"))
39
+ try {
40
+ await initGitRepo(tempDir)
41
+ await runTestEffect(
42
+ init({ template: "my-work-template", silent: true, cwd: tempDir }),
43
+ )
44
+ expect(await getGitConfig("agency.template", tempDir)).toBe(
45
+ "my-work-template",
46
+ )
47
+ } finally {
48
+ await rm(tempDir, { recursive: true, force: true })
49
+ }
50
+ })
51
+
52
+ test("accepts template names with underscores", async () => {
53
+ const tempDir = await mkdtemp(join(tmpdir(), "agency-test-"))
54
+ try {
55
+ await initGitRepo(tempDir)
56
+ await runTestEffect(
57
+ init({ template: "my_work_template", silent: true, cwd: tempDir }),
58
+ )
59
+ expect(await getGitConfig("agency.template", tempDir)).toBe(
60
+ "my_work_template",
61
+ )
62
+ } finally {
63
+ await rm(tempDir, { recursive: true, force: true })
64
+ }
65
+ })
66
+
67
+ test("throws error when not in git repository", async () => {
68
+ const tempDir = await mkdtemp(join(tmpdir(), "agency-test-"))
69
+ try {
70
+ await expect(
71
+ runTestEffect(init({ template: "test", silent: true, cwd: tempDir })),
72
+ ).rejects.toThrow("Not in a git repository")
73
+ } finally {
74
+ await rm(tempDir, { recursive: true, force: true })
75
+ }
76
+ })
77
+
78
+ test("throws error when already initialized without template flag", async () => {
79
+ const tempDir = await mkdtemp(join(tmpdir(), "agency-test-"))
80
+ try {
81
+ await initGitRepo(tempDir)
82
+ await runTestEffect(
83
+ init({ template: "first-template", silent: true, cwd: tempDir }),
84
+ )
85
+ await expect(
86
+ runTestEffect(init({ silent: true, cwd: tempDir })),
87
+ ).rejects.toThrow("Already initialized")
88
+ } finally {
89
+ await rm(tempDir, { recursive: true, force: true })
90
+ }
91
+ })
92
+
93
+ test("allows re-initialization with different template", async () => {
94
+ const tempDir = await mkdtemp(join(tmpdir(), "agency-test-"))
95
+ try {
96
+ await initGitRepo(tempDir)
97
+ await runTestEffect(
98
+ init({ template: "first-template", silent: true, cwd: tempDir }),
99
+ )
100
+ await runTestEffect(
101
+ init({ template: "second-template", silent: true, cwd: tempDir }),
102
+ )
103
+ expect(await getGitConfig("agency.template", tempDir)).toBe(
104
+ "second-template",
105
+ )
106
+ } finally {
107
+ await rm(tempDir, { recursive: true, force: true })
108
+ }
109
+ })
110
+
111
+ test("requires template name in silent mode", async () => {
112
+ const tempDir = await mkdtemp(join(tmpdir(), "agency-test-"))
113
+ try {
114
+ await initGitRepo(tempDir)
115
+ await expect(
116
+ runTestEffect(init({ silent: true, cwd: tempDir })),
117
+ ).rejects.toThrow("Template name required")
118
+ } finally {
119
+ await rm(tempDir, { recursive: true, force: true })
120
+ }
121
+ })
122
+
123
+ test("sets config even if template directory does not exist", async () => {
124
+ const tempDir = await mkdtemp(join(tmpdir(), "agency-test-"))
125
+ try {
126
+ await initGitRepo(tempDir)
127
+ await runTestEffect(
128
+ init({ template: "nonexistent", silent: true, cwd: tempDir }),
129
+ )
130
+ expect(await getGitConfig("agency.template", tempDir)).toBe("nonexistent")
131
+ } finally {
132
+ await rm(tempDir, { recursive: true, force: true })
133
+ }
134
+ })
135
+
136
+ test("works without output in silent mode", async () => {
137
+ const tempDir = await mkdtemp(join(tmpdir(), "agency-test-"))
138
+ try {
139
+ await initGitRepo(tempDir)
140
+ await runTestEffect(
141
+ init({ template: "silent-test", silent: true, cwd: tempDir }),
142
+ )
143
+ expect(await getGitConfig("agency.template", tempDir)).toBe("silent-test")
144
+ } finally {
145
+ await rm(tempDir, { recursive: true, force: true })
146
+ }
147
+ })
148
+
149
+ test("fails in silent mode without template", async () => {
150
+ const tempDir = await mkdtemp(join(tmpdir(), "agency-test-"))
151
+ try {
152
+ await initGitRepo(tempDir)
153
+ await expect(
154
+ runTestEffect(init({ silent: true, cwd: tempDir })),
155
+ ).rejects.toThrow("Template name required")
156
+ } finally {
157
+ await rm(tempDir, { recursive: true, force: true })
158
+ }
159
+ })
160
+
161
+ test("works in verbose mode", async () => {
162
+ const tempDir = await mkdtemp(join(tmpdir(), "agency-test-"))
163
+ try {
164
+ await initGitRepo(tempDir)
165
+ const originalLog = console.log
166
+ console.log = () => {}
167
+ try {
168
+ await runTestEffect(
169
+ init({
170
+ template: "verbose-test",
171
+ verbose: true,
172
+ silent: false,
173
+ cwd: tempDir,
174
+ }),
175
+ )
176
+ expect(await getGitConfig("agency.template", tempDir)).toBe(
177
+ "verbose-test",
178
+ )
179
+ } finally {
180
+ console.log = originalLog
181
+ }
182
+ } finally {
183
+ await rm(tempDir, { recursive: true, force: true })
184
+ }
185
+ })
186
+
187
+ test("handles empty template name", async () => {
188
+ const tempDir = await mkdtemp(join(tmpdir(), "agency-test-"))
189
+ try {
190
+ await initGitRepo(tempDir)
191
+ await expect(
192
+ runTestEffect(init({ template: "", silent: true, cwd: tempDir })),
193
+ ).rejects.toThrow("Template name required")
194
+ } finally {
195
+ await rm(tempDir, { recursive: true, force: true })
196
+ }
197
+ })
198
+
199
+ test("handles template name with special characters", async () => {
200
+ const tempDir = await mkdtemp(join(tmpdir(), "agency-test-"))
201
+ try {
202
+ await initGitRepo(tempDir)
203
+ await runTestEffect(
204
+ init({ template: "test.template", silent: true, cwd: tempDir }),
205
+ )
206
+ expect(await getGitConfig("agency.template", tempDir)).toBe(
207
+ "test.template",
208
+ )
209
+ } finally {
210
+ await rm(tempDir, { recursive: true, force: true })
211
+ }
212
+ })
213
+
214
+ test("handles very long template names", async () => {
215
+ const tempDir = await mkdtemp(join(tmpdir(), "agency-test-"))
216
+ try {
217
+ await initGitRepo(tempDir)
218
+ const longName = "a".repeat(100)
219
+ await runTestEffect(
220
+ init({ template: longName, silent: true, cwd: tempDir }),
221
+ )
222
+ expect(await getGitConfig("agency.template", tempDir)).toBe(longName)
223
+ } finally {
224
+ await rm(tempDir, { recursive: true, force: true })
225
+ }
226
+ })
227
+
228
+ test("changing template updates git config", async () => {
229
+ const tempDir = await mkdtemp(join(tmpdir(), "agency-test-"))
230
+ try {
231
+ await initGitRepo(tempDir)
232
+ await runTestEffect(
233
+ init({ template: "template-a", silent: true, cwd: tempDir }),
234
+ )
235
+ expect(await getGitConfig("agency.template", tempDir)).toBe("template-a")
236
+ await runTestEffect(
237
+ init({ template: "template-b", silent: true, cwd: tempDir }),
238
+ )
239
+ expect(await getGitConfig("agency.template", tempDir)).toBe("template-b")
240
+ } finally {
241
+ await rm(tempDir, { recursive: true, force: true })
242
+ }
243
+ })
244
+
245
+ test("preserves template name across command runs", async () => {
246
+ const tempDir = await mkdtemp(join(tmpdir(), "agency-test-"))
247
+ try {
248
+ await initGitRepo(tempDir)
249
+ await runTestEffect(
250
+ init({ template: "persistent", silent: true, cwd: tempDir }),
251
+ )
252
+ expect(await getGitConfig("agency.template", tempDir)).toBe("persistent")
253
+ expect(await getGitConfig("agency.template", tempDir)).toBe("persistent")
254
+ } finally {
255
+ await rm(tempDir, { recursive: true, force: true })
256
+ }
257
+ })
258
+
259
+ test("does not create template directory during init", async () => {
260
+ const tempDir = await mkdtemp(join(tmpdir(), "agency-test-"))
261
+ const configDir = await mkdtemp(join(tmpdir(), "agency-config-"))
262
+ const originalConfigDir = process.env.AGENCY_CONFIG_DIR
263
+ try {
264
+ await initGitRepo(tempDir)
265
+ process.env.AGENCY_CONFIG_DIR = configDir
266
+ await runTestEffect(
267
+ init({ template: "test-template", silent: true, cwd: tempDir }),
268
+ )
269
+ expect(
270
+ await Bun.file(
271
+ `${configDir}/templates/test-template/AGENTS.md`,
272
+ ).exists(),
273
+ ).toBe(false)
274
+ } finally {
275
+ if (originalConfigDir !== undefined) {
276
+ process.env.AGENCY_CONFIG_DIR = originalConfigDir
277
+ } else {
278
+ delete process.env.AGENCY_CONFIG_DIR
279
+ }
280
+ await rm(tempDir, { recursive: true, force: true })
281
+ await rm(configDir, { recursive: true, force: true })
282
+ }
283
+ })
284
+
285
+ test("works when templates directory does not exist", async () => {
286
+ const tempDir = await mkdtemp(join(tmpdir(), "agency-test-"))
287
+ const originalConfigDir = process.env.AGENCY_CONFIG_DIR
288
+ try {
289
+ await initGitRepo(tempDir)
290
+ // Point to a non-existent config directory
291
+ const configDir = join(tmpdir(), `agency-config-${Date.now()}`)
292
+ process.env.AGENCY_CONFIG_DIR = configDir
293
+ // Verify templates directory doesn't exist
294
+ expect(await Bun.file(join(configDir, "templates")).exists()).toBe(false)
295
+ // Init should work even without templates directory
296
+ await runTestEffect(
297
+ init({ template: "new-template", silent: true, cwd: tempDir }),
298
+ )
299
+ expect(await getGitConfig("agency.template", tempDir)).toBe(
300
+ "new-template",
301
+ )
302
+ } finally {
303
+ if (originalConfigDir !== undefined) {
304
+ process.env.AGENCY_CONFIG_DIR = originalConfigDir
305
+ } else {
306
+ delete process.env.AGENCY_CONFIG_DIR
307
+ }
308
+ await rm(tempDir, { recursive: true, force: true })
309
+ }
310
+ })
311
+ })