@markjaquith/agency 1.11.0 → 2.0.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 (130) hide show
  1. package/README.md +302 -113
  2. package/cli.ts +123 -415
  3. package/index.ts +1 -1
  4. package/package.json +12 -17
  5. package/skills/agency/SKILL.md +258 -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 +172 -419
  24. package/src/commands/work.ts +47 -166
  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/worktree-command.test.ts +61 -0
  53. package/src/workbase/worktree-command.ts +52 -0
  54. package/src/commands/base.test.ts +0 -198
  55. package/src/commands/base.ts +0 -198
  56. package/src/commands/clean.test.ts +0 -299
  57. package/src/commands/clean.ts +0 -274
  58. package/src/commands/completions.test.ts +0 -51
  59. package/src/commands/completions.ts +0 -308
  60. package/src/commands/emit.integration.test.ts +0 -549
  61. package/src/commands/emit.test.ts +0 -576
  62. package/src/commands/emit.ts +0 -700
  63. package/src/commands/emitted.test.ts +0 -226
  64. package/src/commands/emitted.ts +0 -57
  65. package/src/commands/loop.test.ts +0 -621
  66. package/src/commands/loop.ts +0 -406
  67. package/src/commands/merge.integration.test.ts +0 -195
  68. package/src/commands/merge.test.ts +0 -246
  69. package/src/commands/merge.ts +0 -244
  70. package/src/commands/pull.test.ts +0 -395
  71. package/src/commands/pull.ts +0 -205
  72. package/src/commands/push.test.ts +0 -518
  73. package/src/commands/push.ts +0 -434
  74. package/src/commands/rebase.test.ts +0 -521
  75. package/src/commands/rebase.ts +0 -232
  76. package/src/commands/save.test.ts +0 -247
  77. package/src/commands/save.ts +0 -162
  78. package/src/commands/source.test.ts +0 -195
  79. package/src/commands/source.ts +0 -72
  80. package/src/commands/switch.test.ts +0 -194
  81. package/src/commands/switch.ts +0 -84
  82. package/src/commands/task-branching.test.ts +0 -633
  83. package/src/commands/task-continue.test.ts +0 -311
  84. package/src/commands/task-edit.test.ts +0 -235
  85. package/src/commands/task-main.test.ts +0 -1066
  86. package/src/commands/task-squash.test.ts +0 -266
  87. package/src/commands/tasks.test.ts +0 -391
  88. package/src/commands/tasks.ts +0 -161
  89. package/src/commands/template-delete.test.ts +0 -178
  90. package/src/commands/template-delete.ts +0 -98
  91. package/src/commands/template-list.test.ts +0 -135
  92. package/src/commands/template-list.ts +0 -87
  93. package/src/commands/template-view.test.ts +0 -158
  94. package/src/commands/template-view.ts +0 -86
  95. package/src/commands/template.test.ts +0 -32
  96. package/src/commands/template.ts +0 -96
  97. package/src/commands/use.test.ts +0 -87
  98. package/src/commands/use.ts +0 -97
  99. package/src/constants.ts +0 -10
  100. package/src/errors.ts +0 -17
  101. package/src/schemas.ts +0 -33
  102. package/src/services/AgencyMetadataService.ts +0 -311
  103. package/src/services/ClaudeService.test.ts +0 -184
  104. package/src/services/ClaudeService.ts +0 -91
  105. package/src/services/ConfigService.ts +0 -115
  106. package/src/services/FilterRepoService.ts +0 -164
  107. package/src/services/FormatterService.test.ts +0 -432
  108. package/src/services/FormatterService.ts +0 -219
  109. package/src/services/GitService.ts +0 -1183
  110. package/src/services/MockFilterRepoService.ts +0 -140
  111. package/src/services/OpencodeService.ts +0 -263
  112. package/src/services/PromptService.ts +0 -183
  113. package/src/services/TemplateService.ts +0 -81
  114. package/src/types/native-exec.d.ts +0 -8
  115. package/src/utils/colors.ts +0 -178
  116. package/src/utils/git-path.ts +0 -6
  117. package/src/utils/glob.test.ts +0 -161
  118. package/src/utils/glob.ts +0 -78
  119. package/src/utils/paths.ts +0 -51
  120. package/src/utils/pr-branch.test.ts +0 -376
  121. package/src/utils/pr-branch.ts +0 -508
  122. package/src/utils/spinner.test.ts +0 -45
  123. package/src/utils/spinner.ts +0 -96
  124. package/src/utils/task-parser.test.ts +0 -172
  125. package/src/utils/task-parser.ts +0 -93
  126. package/templates/AGENCY.md +0 -22
  127. package/templates/AGENTS.md +0 -11
  128. package/templates/CLAUDE.md +0 -3
  129. package/templates/TASK.md +0 -5
  130. package/templates/opencode.json +0 -4
@@ -1,266 +0,0 @@
1
- import { test, expect, describe, beforeEach, afterEach } from "bun:test"
2
- import { join } from "path"
3
- import { task } from "./task"
4
- import {
5
- createTempDir,
6
- cleanupTempDir,
7
- initGitRepo,
8
- initAgency,
9
- fileExists,
10
- readFile,
11
- runTestEffect,
12
- getCurrentBranch,
13
- getGitOutput,
14
- } from "../test-utils"
15
-
16
- describe("task --continue --squash", () => {
17
- let tempDir: string
18
- let originalCwd: string
19
- let originalConfigDir: string | undefined
20
-
21
- beforeEach(async () => {
22
- tempDir = await createTempDir()
23
- originalCwd = process.cwd()
24
- originalConfigDir = process.env.AGENCY_CONFIG_DIR
25
- process.env.AGENCY_CONFIG_DIR = await createTempDir()
26
- })
27
-
28
- afterEach(async () => {
29
- process.chdir(originalCwd)
30
- if (originalConfigDir !== undefined) {
31
- process.env.AGENCY_CONFIG_DIR = originalConfigDir
32
- } else {
33
- delete process.env.AGENCY_CONFIG_DIR
34
- }
35
- if (
36
- process.env.AGENCY_CONFIG_DIR &&
37
- process.env.AGENCY_CONFIG_DIR !== originalConfigDir
38
- ) {
39
- await cleanupTempDir(process.env.AGENCY_CONFIG_DIR)
40
- }
41
- await cleanupTempDir(tempDir)
42
- })
43
-
44
- test("fails when --squash is used without --continue", async () => {
45
- await initGitRepo(tempDir)
46
- process.chdir(tempDir)
47
-
48
- await initAgency(tempDir, "test")
49
-
50
- await expect(
51
- runTestEffect(task({ silent: true, squash: true, emit: "some-branch" })),
52
- ).rejects.toThrow("--squash flag can only be used with --continue")
53
- })
54
-
55
- test("squashes emitted commits into a single commit on new branch", async () => {
56
- await initGitRepo(tempDir)
57
- process.chdir(tempDir)
58
-
59
- await initAgency(tempDir, "test")
60
-
61
- // Create a task branch with agency files
62
- await runTestEffect(
63
- task({ silent: true, emit: "feature-v1", task: "Original task" }),
64
- )
65
-
66
- // Add two code commits to the branch
67
- await Bun.write(join(tempDir, "feature-a.txt"), "feature A content")
68
- await Bun.spawn(["git", "add", "feature-a.txt"], {
69
- cwd: tempDir,
70
- stdout: "pipe",
71
- stderr: "pipe",
72
- }).exited
73
- await Bun.spawn(["git", "commit", "--no-verify", "-m", "Add feature A"], {
74
- cwd: tempDir,
75
- stdout: "pipe",
76
- stderr: "pipe",
77
- }).exited
78
-
79
- await Bun.write(join(tempDir, "feature-b.txt"), "feature B content")
80
- await Bun.spawn(["git", "add", "feature-b.txt"], {
81
- cwd: tempDir,
82
- stdout: "pipe",
83
- stderr: "pipe",
84
- }).exited
85
- await Bun.spawn(["git", "commit", "--no-verify", "-m", "Add feature B"], {
86
- cwd: tempDir,
87
- stdout: "pipe",
88
- stderr: "pipe",
89
- }).exited
90
-
91
- // Now continue with squash
92
- await runTestEffect(
93
- task({
94
- silent: true,
95
- continue: true,
96
- squash: true,
97
- emit: "feature-v2",
98
- task: "New task for v2",
99
- }),
100
- )
101
-
102
- // Verify we're on the new branch
103
- const newBranch = await getCurrentBranch(tempDir)
104
- expect(newBranch).toBe("agency--feature-v2")
105
-
106
- // Verify both feature files exist (squash brought them in)
107
- expect(await fileExists(join(tempDir, "feature-a.txt"))).toBe(true)
108
- expect(await fileExists(join(tempDir, "feature-b.txt"))).toBe(true)
109
-
110
- // Verify agency files exist
111
- expect(await fileExists(join(tempDir, "agency.json"))).toBe(true)
112
- expect(await fileExists(join(tempDir, "TASK.md"))).toBe(true)
113
- expect(await fileExists(join(tempDir, "AGENCY.md"))).toBe(true)
114
-
115
- // Verify TASK.md has FRESH content (not the old "Original task")
116
- const taskContent = await readFile(join(tempDir, "TASK.md"))
117
- expect(taskContent).toContain("New task for v2")
118
- expect(taskContent).not.toContain("Original task")
119
-
120
- // Verify agency.json has updated emitBranch
121
- const agencyJson = JSON.parse(await readFile(join(tempDir, "agency.json")))
122
- expect(agencyJson.emitBranch).toBe("feature-v2")
123
-
124
- // Verify commit history: should be initial commit + squash commit + agency files commit
125
- // (not the individual "Add feature A" and "Add feature B" commits)
126
- const logOutput = await getGitOutput(tempDir, [
127
- "log",
128
- "--oneline",
129
- "--format=%s",
130
- ])
131
- const commits = logOutput.trim().split("\n")
132
-
133
- // Should NOT contain individual feature commits
134
- expect(logOutput).not.toContain("Add feature A")
135
- expect(logOutput).not.toContain("Add feature B")
136
-
137
- // Should contain a squash commit
138
- expect(logOutput).toContain("squash: prior work from feature-v1")
139
- })
140
-
141
- test("creates fresh TASK.md with default placeholder when no task provided in silent mode", async () => {
142
- await initGitRepo(tempDir)
143
- process.chdir(tempDir)
144
-
145
- await initAgency(tempDir, "test")
146
-
147
- // Create a task branch
148
- await runTestEffect(
149
- task({ silent: true, emit: "feature-v1", task: "Old task" }),
150
- )
151
-
152
- // Add a code commit
153
- await Bun.write(join(tempDir, "code.txt"), "code content")
154
- await Bun.spawn(["git", "add", "code.txt"], {
155
- cwd: tempDir,
156
- stdout: "pipe",
157
- stderr: "pipe",
158
- }).exited
159
- await Bun.spawn(["git", "commit", "--no-verify", "-m", "Add code"], {
160
- cwd: tempDir,
161
- stdout: "pipe",
162
- stderr: "pipe",
163
- }).exited
164
-
165
- // Continue with squash but no --task option (silent mode)
166
- await runTestEffect(
167
- task({
168
- silent: true,
169
- continue: true,
170
- squash: true,
171
- emit: "feature-v2",
172
- }),
173
- )
174
-
175
- // TASK.md should have the default placeholder, not the old task
176
- const taskContent = await readFile(join(tempDir, "TASK.md"))
177
- expect(taskContent).toContain("{task}")
178
- expect(taskContent).not.toContain("Old task")
179
- })
180
-
181
- test("handles empty squash when old branch has no code commits", async () => {
182
- await initGitRepo(tempDir)
183
- process.chdir(tempDir)
184
-
185
- await initAgency(tempDir, "test")
186
-
187
- // Create a task branch WITHOUT any code commits
188
- await runTestEffect(
189
- task({ silent: true, emit: "feature-v1", task: "Just agency files" }),
190
- )
191
-
192
- // Continue with squash - should succeed even with no code changes
193
- await runTestEffect(
194
- task({
195
- silent: true,
196
- continue: true,
197
- squash: true,
198
- emit: "feature-v2",
199
- task: "New task",
200
- }),
201
- )
202
-
203
- // Verify we're on the new branch
204
- const newBranch = await getCurrentBranch(tempDir)
205
- expect(newBranch).toBe("agency--feature-v2")
206
-
207
- // Verify agency files exist
208
- expect(await fileExists(join(tempDir, "agency.json"))).toBe(true)
209
- expect(await fileExists(join(tempDir, "TASK.md"))).toBe(true)
210
-
211
- // Verify fresh TASK.md
212
- const taskContent = await readFile(join(tempDir, "TASK.md"))
213
- expect(taskContent).toContain("New task")
214
- })
215
-
216
- test("preserves other agency files while creating fresh TASK.md", async () => {
217
- await initGitRepo(tempDir)
218
- process.chdir(tempDir)
219
-
220
- await initAgency(tempDir, "test")
221
-
222
- // Create a task branch
223
- await runTestEffect(
224
- task({ silent: true, emit: "feature-v1", task: "Old task" }),
225
- )
226
-
227
- // Read the original AGENCY.md content
228
- const originalAgencyMd = await readFile(join(tempDir, "AGENCY.md"))
229
-
230
- // Add a code commit
231
- await Bun.write(join(tempDir, "code.txt"), "code")
232
- await Bun.spawn(["git", "add", "code.txt"], {
233
- cwd: tempDir,
234
- stdout: "pipe",
235
- stderr: "pipe",
236
- }).exited
237
- await Bun.spawn(["git", "commit", "--no-verify", "-m", "Add code"], {
238
- cwd: tempDir,
239
- stdout: "pipe",
240
- stderr: "pipe",
241
- }).exited
242
-
243
- // Continue with squash
244
- await runTestEffect(
245
- task({
246
- silent: true,
247
- continue: true,
248
- squash: true,
249
- emit: "feature-v2",
250
- task: "New task",
251
- }),
252
- )
253
-
254
- // AGENCY.md should be carried forward (not fresh)
255
- const newAgencyMd = await readFile(join(tempDir, "AGENCY.md"))
256
- expect(newAgencyMd).toBe(originalAgencyMd)
257
-
258
- // TASK.md should be fresh
259
- const taskContent = await readFile(join(tempDir, "TASK.md"))
260
- expect(taskContent).toContain("New task")
261
- expect(taskContent).not.toContain("Old task")
262
-
263
- // opencode.json should be carried forward
264
- expect(await fileExists(join(tempDir, "opencode.json"))).toBe(true)
265
- })
266
- })
@@ -1,391 +0,0 @@
1
- import { test, expect, describe, beforeEach, afterEach } from "bun:test"
2
- import { join } from "path"
3
- import { tasks } from "./tasks"
4
- import {
5
- createTempDir,
6
- cleanupTempDir,
7
- initGitRepo,
8
- getCurrentBranch,
9
- createCommit,
10
- initAgency,
11
- runTestEffect,
12
- } from "../test-utils"
13
- import { writeAgencyMetadata } from "../types"
14
-
15
- async function createBranch(cwd: string, branchName: string): Promise<void> {
16
- await Bun.spawn(["git", "checkout", "-b", branchName], {
17
- cwd,
18
- stdout: "pipe",
19
- stderr: "pipe",
20
- }).exited
21
- }
22
-
23
- async function checkoutBranch(cwd: string, branchName: string): Promise<void> {
24
- await Bun.spawn(["git", "checkout", branchName], {
25
- cwd,
26
- stdout: "pipe",
27
- stderr: "pipe",
28
- }).exited
29
- }
30
-
31
- describe("tasks command", () => {
32
- let tempDir: string
33
- let originalCwd: string
34
-
35
- beforeEach(async () => {
36
- tempDir = await createTempDir()
37
- originalCwd = process.cwd()
38
- process.chdir(tempDir)
39
-
40
- // Set config path to non-existent file to use defaults
41
- process.env.AGENCY_CONFIG_PATH = join(tempDir, "non-existent-config.json")
42
-
43
- // Initialize git repo
44
- await initGitRepo(tempDir)
45
- await createCommit(tempDir, "Initial commit")
46
-
47
- // Rename to main if needed
48
- const currentBranch = await getCurrentBranch(tempDir)
49
- if (currentBranch === "master") {
50
- await Bun.spawn(["git", "branch", "-m", "main"], {
51
- cwd: tempDir,
52
- stdout: "pipe",
53
- stderr: "pipe",
54
- }).exited
55
- }
56
- })
57
-
58
- afterEach(async () => {
59
- process.chdir(originalCwd)
60
- delete process.env.AGENCY_CONFIG_PATH
61
- await cleanupTempDir(tempDir)
62
- })
63
-
64
- describe("basic functionality", () => {
65
- test("shows no task branches when none exist", async () => {
66
- let output = ""
67
- const originalLog = console.log
68
- console.log = (msg: string) => {
69
- output += msg + "\n"
70
- }
71
-
72
- await runTestEffect(tasks({}))
73
-
74
- console.log = originalLog
75
- expect(output).toContain("No task branches found")
76
- expect(output).toContain("agency task")
77
- })
78
-
79
- test("lists single task branch", async () => {
80
- // Initialize agency
81
- await initAgency(tempDir, "test-template")
82
-
83
- // Create feature branch with agency-- prefix and agency.json
84
- await createBranch(tempDir, "agency--feature")
85
- await writeAgencyMetadata(tempDir, {
86
- version: 1,
87
- injectedFiles: ["AGENTS.md"],
88
- template: "test-template",
89
- baseBranch: "main",
90
- createdAt: new Date().toISOString(),
91
- } as any)
92
-
93
- // Commit agency.json
94
- await Bun.spawn(["git", "add", "agency.json"], {
95
- cwd: tempDir,
96
- stdout: "pipe",
97
- stderr: "pipe",
98
- }).exited
99
- await createCommit(tempDir, "Add agency.json")
100
-
101
- let output = ""
102
- const originalLog = console.log
103
- console.log = (msg: string) => {
104
- output += msg + "\n"
105
- }
106
-
107
- await runTestEffect(tasks({}))
108
-
109
- console.log = originalLog
110
- expect(output).toContain("agency--feature")
111
- expect(output.trim()).toBe("agency--feature")
112
- })
113
-
114
- test("lists multiple task branches", async () => {
115
- await initAgency(tempDir, "test-template")
116
-
117
- // Create first feature branch
118
- await createBranch(tempDir, "agency--feature-1")
119
- await writeAgencyMetadata(tempDir, {
120
- version: 1,
121
- injectedFiles: ["AGENTS.md"],
122
- template: "test-template",
123
- baseBranch: "main",
124
- createdAt: new Date().toISOString(),
125
- } as any)
126
- await Bun.spawn(["git", "add", "agency.json"], {
127
- cwd: tempDir,
128
- stdout: "pipe",
129
- stderr: "pipe",
130
- }).exited
131
- await createCommit(tempDir, "Add agency.json")
132
-
133
- // Go back to main and create second feature branch
134
- await checkoutBranch(tempDir, "main")
135
- await createBranch(tempDir, "agency--feature-2")
136
- await writeAgencyMetadata(tempDir, {
137
- version: 1,
138
- injectedFiles: ["opencode.json"],
139
- template: "another-template",
140
- baseBranch: "main",
141
- createdAt: new Date().toISOString(),
142
- } as any)
143
- await Bun.spawn(["git", "add", "agency.json"], {
144
- cwd: tempDir,
145
- stdout: "pipe",
146
- stderr: "pipe",
147
- }).exited
148
- await createCommit(tempDir, "Add agency.json")
149
-
150
- let output = ""
151
- const originalLog = console.log
152
- console.log = (msg: string) => {
153
- output += msg + "\n"
154
- }
155
-
156
- await runTestEffect(tasks({}))
157
-
158
- console.log = originalLog
159
- expect(output).toContain("agency--feature-1")
160
- expect(output).toContain("agency--feature-2")
161
- const lines = output.trim().split("\n")
162
- expect(lines.length).toBe(2)
163
- })
164
-
165
- test("ignores branches without agency-- prefix", async () => {
166
- await initAgency(tempDir, "test-template")
167
-
168
- // Create a branch without agency-- prefix (even with agency.json)
169
- await createBranch(tempDir, "no-prefix")
170
- await writeAgencyMetadata(tempDir, {
171
- version: 1,
172
- injectedFiles: [],
173
- template: "test-template",
174
- createdAt: new Date().toISOString(),
175
- } as any)
176
- await Bun.spawn(["git", "add", "agency.json"], {
177
- cwd: tempDir,
178
- stdout: "pipe",
179
- stderr: "pipe",
180
- }).exited
181
- await createCommit(tempDir, "Add agency.json")
182
-
183
- // Go back to main and create a branch with agency-- prefix
184
- await checkoutBranch(tempDir, "main")
185
- await createBranch(tempDir, "agency--with-prefix")
186
- await writeAgencyMetadata(tempDir, {
187
- version: 1,
188
- injectedFiles: [],
189
- template: "test-template",
190
- createdAt: new Date().toISOString(),
191
- } as any)
192
- await Bun.spawn(["git", "add", "agency.json"], {
193
- cwd: tempDir,
194
- stdout: "pipe",
195
- stderr: "pipe",
196
- }).exited
197
- await createCommit(tempDir, "Add agency.json")
198
-
199
- let output = ""
200
- const originalLog = console.log
201
- console.log = (msg: string) => {
202
- output += msg + "\n"
203
- }
204
-
205
- await runTestEffect(tasks({}))
206
-
207
- console.log = originalLog
208
- expect(output).toContain("agency--with-prefix")
209
- expect(output).not.toContain("no-prefix")
210
- expect(output.trim()).toBe("agency--with-prefix")
211
- })
212
-
213
- test("lists agency-- branches even without agency.json", async () => {
214
- // Create a branch with agency-- prefix but no agency.json
215
- await createBranch(tempDir, "agency--no-metadata")
216
- await createCommit(tempDir, "Some work")
217
-
218
- let output = ""
219
- const originalLog = console.log
220
- console.log = (msg: string) => {
221
- output += msg + "\n"
222
- }
223
-
224
- await runTestEffect(tasks({}))
225
-
226
- console.log = originalLog
227
- expect(output).toContain("agency--no-metadata")
228
- expect(output.trim()).toBe("agency--no-metadata")
229
- })
230
- })
231
-
232
- describe("JSON output", () => {
233
- test("outputs JSON format when --json is provided", async () => {
234
- await initAgency(tempDir, "test-template")
235
-
236
- await createBranch(tempDir, "agency--feature")
237
- const createdAt = new Date().toISOString()
238
- await writeAgencyMetadata(tempDir, {
239
- version: 1,
240
- injectedFiles: ["AGENTS.md"],
241
- template: "test-template",
242
- baseBranch: "main",
243
- createdAt,
244
- } as any)
245
- await Bun.spawn(["git", "add", "agency.json"], {
246
- cwd: tempDir,
247
- stdout: "pipe",
248
- stderr: "pipe",
249
- }).exited
250
- await createCommit(tempDir, "Add agency.json")
251
-
252
- let output = ""
253
- const originalLog = console.log
254
- console.log = (msg: string) => {
255
- output += msg + "\n"
256
- }
257
-
258
- await runTestEffect(tasks({ json: true }))
259
-
260
- console.log = originalLog
261
-
262
- // Parse JSON output
263
- const data = JSON.parse(output.trim())
264
- expect(Array.isArray(data)).toBe(true)
265
- expect(data.length).toBe(1)
266
- expect(data[0].branch).toBe("agency--feature")
267
- expect(data[0].template).toBe("test-template")
268
- expect(data[0].baseBranch).toBe("main")
269
- expect(data[0].createdAt).toBeDefined()
270
- })
271
-
272
- test("outputs empty array in JSON format when no task branches exist", async () => {
273
- let output = ""
274
- const originalLog = console.log
275
- console.log = (msg: string) => {
276
- output += msg + "\n"
277
- }
278
-
279
- await runTestEffect(tasks({ json: true }))
280
-
281
- console.log = originalLog
282
-
283
- const data = JSON.parse(output.trim())
284
- expect(Array.isArray(data)).toBe(true)
285
- expect(data.length).toBe(0)
286
- })
287
-
288
- test("JSON output shows null metadata for branches without agency.json", async () => {
289
- // Create a branch with agency-- prefix but no agency.json
290
- await createBranch(tempDir, "agency--no-metadata")
291
- await createCommit(tempDir, "Some work")
292
-
293
- let output = ""
294
- const originalLog = console.log
295
- console.log = (msg: string) => {
296
- output += msg + "\n"
297
- }
298
-
299
- await runTestEffect(tasks({ json: true }))
300
-
301
- console.log = originalLog
302
-
303
- const data = JSON.parse(output.trim())
304
- expect(Array.isArray(data)).toBe(true)
305
- expect(data.length).toBe(1)
306
- expect(data[0].branch).toBe("agency--no-metadata")
307
- expect(data[0].template).toBeNull()
308
- expect(data[0].baseBranch).toBeNull()
309
- expect(data[0].createdAt).toBeNull()
310
- })
311
- })
312
-
313
- describe("edge cases", () => {
314
- test("handles branches with invalid agency.json gracefully", async () => {
315
- await initAgency(tempDir, "test-template")
316
-
317
- // Create branch with invalid agency.json but agency-- prefix
318
- await createBranch(tempDir, "agency--invalid")
319
- await Bun.write(join(tempDir, "agency.json"), "{ invalid json }")
320
- await Bun.spawn(["git", "add", "agency.json"], {
321
- cwd: tempDir,
322
- stdout: "pipe",
323
- stderr: "pipe",
324
- }).exited
325
- await createCommit(tempDir, "Add invalid agency.json")
326
-
327
- // Create branch with valid agency.json and agency-- prefix
328
- await checkoutBranch(tempDir, "main")
329
- await createBranch(tempDir, "agency--valid")
330
- await writeAgencyMetadata(tempDir, {
331
- version: 1,
332
- injectedFiles: [],
333
- template: "test-template",
334
- createdAt: new Date().toISOString(),
335
- } as any)
336
- await Bun.spawn(["git", "add", "agency.json"], {
337
- cwd: tempDir,
338
- stdout: "pipe",
339
- stderr: "pipe",
340
- }).exited
341
- await createCommit(tempDir, "Add valid agency.json")
342
-
343
- let output = ""
344
- const originalLog = console.log
345
- console.log = (msg: string) => {
346
- output += msg + "\n"
347
- }
348
-
349
- await runTestEffect(tasks({}))
350
-
351
- console.log = originalLog
352
- // Both branches should be listed since they match the prefix
353
- expect(output).toContain("agency--valid")
354
- expect(output).toContain("agency--invalid")
355
- const lines = output.trim().split("\n")
356
- expect(lines.length).toBe(2)
357
- })
358
-
359
- test("handles branches with old version agency.json", async () => {
360
- await initAgency(tempDir, "test-template")
361
-
362
- // Create branch with version 0 agency.json (future or old version)
363
- await createBranch(tempDir, "agency--old-version")
364
- await Bun.write(
365
- join(tempDir, "agency.json"),
366
- JSON.stringify({
367
- version: 0,
368
- template: "old-template",
369
- }),
370
- )
371
- await Bun.spawn(["git", "add", "agency.json"], {
372
- cwd: tempDir,
373
- stdout: "pipe",
374
- stderr: "pipe",
375
- }).exited
376
- await createCommit(tempDir, "Add old version agency.json")
377
-
378
- let output = ""
379
- const originalLog = console.log
380
- console.log = (msg: string) => {
381
- output += msg + "\n"
382
- }
383
-
384
- await runTestEffect(tasks({}))
385
-
386
- console.log = originalLog
387
- // Branch should still be listed since it matches the prefix
388
- expect(output).toContain("agency--old-version")
389
- })
390
- })
391
- })