@markjaquith/agency 1.6.3 → 1.8.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 (36) hide show
  1. package/cli.ts +24 -2
  2. package/package.json +1 -1
  3. package/src/commands/clean.test.ts +2 -2
  4. package/src/commands/clean.ts +3 -20
  5. package/src/commands/emit.integration.test.ts +17 -17
  6. package/src/commands/emit.test.ts +22 -22
  7. package/src/commands/emit.ts +9 -8
  8. package/src/commands/emitted.test.ts +8 -8
  9. package/src/commands/emitted.ts +1 -1
  10. package/src/commands/merge.integration.test.ts +5 -5
  11. package/src/commands/merge.test.ts +7 -7
  12. package/src/commands/pull.test.ts +15 -15
  13. package/src/commands/push.test.ts +30 -30
  14. package/src/commands/rebase.test.ts +19 -19
  15. package/src/commands/rebase.ts +5 -8
  16. package/src/commands/source.test.ts +13 -13
  17. package/src/commands/status.test.ts +8 -8
  18. package/src/commands/status.ts +4 -26
  19. package/src/commands/switch.test.ts +18 -18
  20. package/src/commands/switch.ts +3 -3
  21. package/src/commands/task-branching.test.ts +19 -19
  22. package/src/commands/task-continue.test.ts +3 -3
  23. package/src/commands/task-main.test.ts +7 -7
  24. package/src/commands/task-squash.test.ts +266 -0
  25. package/src/commands/task.ts +174 -10
  26. package/src/constants.ts +10 -0
  27. package/src/schemas.ts +1 -1
  28. package/src/services/AgencyMetadataService.ts +217 -242
  29. package/src/services/ConfigService.ts +1 -1
  30. package/src/services/FormatterService.test.ts +432 -0
  31. package/src/services/FormatterService.ts +219 -0
  32. package/src/services/TemplateService.ts +9 -3
  33. package/src/test-utils.ts +3 -0
  34. package/src/types.ts +6 -9
  35. package/src/utils/pr-branch.test.ts +36 -32
  36. package/src/utils/pr-branch.ts +12 -15
package/cli.ts CHANGED
@@ -54,9 +54,30 @@ async function runCommand<E>(
54
54
  E,
55
55
  never
56
56
  >
57
- const program = Effect.catchAllDefect(providedEffect, (defect) =>
57
+
58
+ // Catch typed errors and convert to standard Error objects
59
+ const programWithErrorHandling = Effect.catchAll(providedEffect, (error) => {
60
+ // Convert typed errors to standard Error objects with clear messages
61
+ if (error instanceof Error) {
62
+ return Effect.fail(error)
63
+ }
64
+ // Handle objects with message property (common pattern for tagged errors)
65
+ if (
66
+ typeof error === "object" &&
67
+ error !== null &&
68
+ "message" in error &&
69
+ typeof error.message === "string"
70
+ ) {
71
+ return Effect.fail(new Error(error.message))
72
+ }
73
+ // Fallback: convert to string
74
+ return Effect.fail(new Error(String(error)))
75
+ })
76
+
77
+ // Catch defects (unexpected crashes) and convert to errors
78
+ const program = Effect.catchAllDefect(programWithErrorHandling, (defect) =>
58
79
  Effect.fail(defect instanceof Error ? defect : new Error(String(defect))),
59
- ) as Effect.Effect<void, E | Error, never>
80
+ )
60
81
 
61
82
  await Effect.runPromise(program)
62
83
  }
@@ -313,6 +334,7 @@ const commands: Record<string, Command> = {
313
334
  from: options.from,
314
335
  fromCurrent: options["from-current"],
315
336
  continue: options.continue,
337
+ squash: options.squash,
316
338
  }),
317
339
  )
318
340
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markjaquith/agency",
3
- "version": "1.6.3",
3
+ "version": "1.8.0",
4
4
  "description": "Manages personal agents files",
5
5
  "keywords": [
6
6
  "agents",
@@ -87,7 +87,7 @@ describe("clean command", () => {
87
87
  test("finds source branches for merged emit branches", async () => {
88
88
  // Create source branch with agency pattern
89
89
  await checkoutBranch(tempDir, "main")
90
- await Bun.spawn(["git", "checkout", "-b", "agency/feature-2"], {
90
+ await Bun.spawn(["git", "checkout", "-b", "agency--feature-2"], {
91
91
  cwd: tempDir,
92
92
  stdout: "pipe",
93
93
  stderr: "pipe",
@@ -132,7 +132,7 @@ describe("clean command", () => {
132
132
  // Verify both emit and source branches were deleted
133
133
  const branches = await getGitOutput(tempDir, ["branch", "--list"])
134
134
  expect(branches).not.toContain("feature-2")
135
- expect(branches).not.toContain("agency/feature-2")
135
+ expect(branches).not.toContain("agency--feature-2")
136
136
  expect(branches).toContain("main")
137
137
  })
138
138
 
@@ -1,9 +1,8 @@
1
1
  import { Effect } from "effect"
2
- import { Schema } from "@effect/schema"
3
2
  import type { BaseCommandOptions } from "../utils/command"
4
3
  import { GitService } from "../services/GitService"
5
4
  import { ConfigService } from "../services/ConfigService"
6
- import { AgencyMetadata } from "../schemas"
5
+ import { parseAgencyMetadata } from "../services/AgencyMetadataService"
7
6
  import highlight, { done } from "../utils/colors"
8
7
  import { createLoggers, ensureGitRepo } from "../utils/effect"
9
8
  import { extractCleanBranch, makeSourceBranchName } from "../utils/pr-branch"
@@ -28,23 +27,7 @@ const readAgencyMetadata = (gitRoot: string, branch: string) =>
28
27
  return null
29
28
  }
30
29
 
31
- const data = yield* Effect.try({
32
- try: () => JSON.parse(content),
33
- catch: () => new Error("Failed to parse agency.json"),
34
- })
35
-
36
- // Validate version
37
- if (typeof data.version !== "number" || data.version !== 1) {
38
- return null
39
- }
40
-
41
- // Parse and validate using Effect schema
42
- const metadata: AgencyMetadata | null = yield* Effect.try({
43
- try: () => Schema.decodeUnknownSync(AgencyMetadata)(data),
44
- catch: () => new Error("Invalid agency.json format"),
45
- }).pipe(Effect.catchAll(() => Effect.succeed(null)))
46
-
47
- return metadata
30
+ return yield* parseAgencyMetadata(content)
48
31
  }).pipe(Effect.catchAll(() => Effect.succeed(null)))
49
32
 
50
33
  /**
@@ -179,7 +162,7 @@ export const clean = (options: CleanOptions = {}) =>
179
162
 
180
163
  // Load config to get source and emit patterns
181
164
  const config = yield* configService.loadConfig()
182
- const sourcePattern = config.sourceBranchPattern || "agency/%branch%"
165
+ const sourcePattern = config.sourceBranchPattern || "agency--%branch%"
183
166
  const emitPattern = config.emitBranch || "%branch%"
184
167
 
185
168
  verboseLog(
@@ -52,8 +52,8 @@ describe("emit command - integration tests (requires git-filter-repo)", () => {
52
52
  // Initialize git repo with main branch (already includes initial commit)
53
53
  await initGitRepo(tempDir)
54
54
 
55
- // Create a source branch (with agency/ prefix)
56
- await createBranch(tempDir, "agency/test-feature")
55
+ // Create a source branch (with agency-- prefix)
56
+ await createBranch(tempDir, "agency--test-feature")
57
57
 
58
58
  // Initialize AGENTS.md and commit in one go
59
59
  await initAgency(tempDir, "test")
@@ -83,7 +83,7 @@ describe("emit command - integration tests (requires git-filter-repo)", () => {
83
83
 
84
84
  // Go back to main and create a fresh source branch
85
85
  await checkoutBranch(tempDir, "main")
86
- await createBranch(tempDir, "agency/feature")
86
+ await createBranch(tempDir, "agency--feature")
87
87
  // Create agency.json with AGENTS.md as managed file
88
88
  await Bun.write(
89
89
  join(tempDir, "agency.json"),
@@ -104,7 +104,7 @@ describe("emit command - integration tests (requires git-filter-repo)", () => {
104
104
 
105
105
  // Should still be on feature branch
106
106
  const currentBranch = await getCurrentBranch(tempDir)
107
- expect(currentBranch).toBe("agency/feature")
107
+ expect(currentBranch).toBe("agency--feature")
108
108
 
109
109
  // Switch to emit branch to verify files
110
110
  await checkoutBranch(tempDir, "feature")
@@ -132,7 +132,7 @@ describe("emit command - integration tests (requires git-filter-repo)", () => {
132
132
  // Store merge-base before advancing main
133
133
  const initialMergeBase = await getGitOutput(tempDir, [
134
134
  "merge-base",
135
- "agency/test-feature",
135
+ "agency--test-feature",
136
136
  "main",
137
137
  ])
138
138
 
@@ -141,7 +141,7 @@ describe("emit command - integration tests (requires git-filter-repo)", () => {
141
141
 
142
142
  // Should still be on test-feature branch
143
143
  let currentBranch = await getCurrentBranch(tempDir)
144
- expect(currentBranch).toBe("agency/test-feature")
144
+ expect(currentBranch).toBe("agency--test-feature")
145
145
 
146
146
  // Switch to emit branch to verify AGENTS.md is filtered
147
147
  await checkoutBranch(tempDir, "test-feature")
@@ -151,7 +151,7 @@ describe("emit command - integration tests (requires git-filter-repo)", () => {
151
151
  expect(files).toContain("feature.txt")
152
152
 
153
153
  // Switch back to source branch
154
- await checkoutBranch(tempDir, "agency/test-feature")
154
+ await checkoutBranch(tempDir, "agency--test-feature")
155
155
 
156
156
  // Simulate advancing main branch with a different file
157
157
  await checkoutBranch(tempDir, "main")
@@ -159,7 +159,7 @@ describe("emit command - integration tests (requires git-filter-repo)", () => {
159
159
  await addAndCommit(tempDir, "main-file.txt", "Main branch advancement")
160
160
 
161
161
  // Rebase test-feature onto new main
162
- await checkoutBranch(tempDir, "agency/test-feature")
162
+ await checkoutBranch(tempDir, "agency--test-feature")
163
163
  const rebaseProc = Bun.spawn(["git", "rebase", "main"], {
164
164
  cwd: tempDir,
165
165
  stdout: "pipe",
@@ -174,7 +174,7 @@ describe("emit command - integration tests (requires git-filter-repo)", () => {
174
174
  // Verify merge-base has changed after rebase
175
175
  const newMergeBase = await getGitOutput(tempDir, [
176
176
  "merge-base",
177
- "agency/test-feature",
177
+ "agency--test-feature",
178
178
  "main",
179
179
  ])
180
180
  expect(newMergeBase.trim()).not.toBe(initialMergeBase.trim())
@@ -184,7 +184,7 @@ describe("emit command - integration tests (requires git-filter-repo)", () => {
184
184
 
185
185
  // Should still be on test-feature branch
186
186
  currentBranch = await getCurrentBranch(tempDir)
187
- expect(currentBranch).toBe("agency/test-feature")
187
+ expect(currentBranch).toBe("agency--test-feature")
188
188
 
189
189
  // Switch to emit branch to verify files
190
190
  await checkoutBranch(tempDir, "test-feature")
@@ -222,7 +222,7 @@ describe("emit command - integration tests (requires git-filter-repo)", () => {
222
222
  await addAndCommit(tempDir, "CLAUDE.md", "Add CLAUDE.md")
223
223
 
224
224
  // Create a new feature branch
225
- await createBranch(tempDir, "agency/claude-test")
225
+ await createBranch(tempDir, "agency--claude-test")
226
226
 
227
227
  // Initialize agency on this branch - first commit has agency files (NOT including CLAUDE.md)
228
228
  await Bun.write(
@@ -260,7 +260,7 @@ describe("emit command - integration tests (requires git-filter-repo)", () => {
260
260
 
261
261
  // Should still be on source branch
262
262
  const currentBranch = await getCurrentBranch(tempDir)
263
- expect(currentBranch).toBe("agency/claude-test")
263
+ expect(currentBranch).toBe("agency--claude-test")
264
264
 
265
265
  // Switch to emit branch to verify CLAUDE.md is reverted to main's version
266
266
  await checkoutBranch(tempDir, "claude-test")
@@ -294,7 +294,7 @@ describe("emit command - integration tests (requires git-filter-repo)", () => {
294
294
  await Bun.write(join(tempDir, "FILE2.md"), "Original content 2\n")
295
295
  await addAndCommit(tempDir, "FILE1.md FILE2.md", "Add files")
296
296
 
297
- await createBranch(tempDir, "agency/contiguous-test")
297
+ await createBranch(tempDir, "agency--contiguous-test")
298
298
 
299
299
  // Create agency.json
300
300
  await Bun.write(
@@ -352,7 +352,7 @@ describe("emit command - integration tests (requires git-filter-repo)", () => {
352
352
  await Bun.write(join(tempDir, "FILE2.md"), "Original content 2\n")
353
353
  await addAndCommit(tempDir, "FILE1.md FILE2.md", "Add files")
354
354
 
355
- await createBranch(tempDir, "agency/non-contiguous-test")
355
+ await createBranch(tempDir, "agency--non-contiguous-test")
356
356
 
357
357
  await Bun.write(
358
358
  join(tempDir, "agency.json"),
@@ -415,7 +415,7 @@ describe("emit command - integration tests (requires git-filter-repo)", () => {
415
415
  await Bun.write(join(tempDir, "EXISTING.md"), "Original content\n")
416
416
  await addAndCommit(tempDir, "EXISTING.md", "Add existing file")
417
417
 
418
- await createBranch(tempDir, "agency/first-commit-test")
418
+ await createBranch(tempDir, "agency--first-commit-test")
419
419
 
420
420
  // First commit is AGENCY_REMOVE_COMMIT (modifying existing file)
421
421
  await Bun.write(join(tempDir, "EXISTING.md"), "Modified content\n")
@@ -463,7 +463,7 @@ describe("emit command - integration tests (requires git-filter-repo)", () => {
463
463
  await Bun.write(join(tempDir, "EXISTING.md"), "Original content\n")
464
464
  await addAndCommit(tempDir, "EXISTING.md", "Add existing file")
465
465
 
466
- await createBranch(tempDir, "agency/last-commit-test")
466
+ await createBranch(tempDir, "agency--last-commit-test")
467
467
 
468
468
  // agency.json first
469
469
  await Bun.write(
@@ -512,7 +512,7 @@ describe("emit command - integration tests (requires git-filter-repo)", () => {
512
512
  await Bun.write(join(tempDir, "EXISTING.md"), "Original content\n")
513
513
  await addAndCommit(tempDir, "EXISTING.md", "Add existing file")
514
514
 
515
- await createBranch(tempDir, "agency/only-commit-test")
515
+ await createBranch(tempDir, "agency--only-commit-test")
516
516
 
517
517
  // agency.json
518
518
  await Bun.write(
@@ -53,8 +53,8 @@ describe("emit command", () => {
53
53
  // Initialize git repo with main branch (already includes initial commit)
54
54
  await initGitRepo(tempDir)
55
55
 
56
- // Create a source branch (with agency/ prefix)
57
- await createBranch(tempDir, "agency/test-feature")
56
+ // Create a source branch (with agency-- prefix)
57
+ await createBranch(tempDir, "agency--test-feature")
58
58
 
59
59
  // Initialize AGENTS.md and commit in one go
60
60
  await initAgency(tempDir, "test")
@@ -84,7 +84,7 @@ describe("emit command", () => {
84
84
  return
85
85
  }
86
86
 
87
- await createBranch(tempDir, "agency/feature")
87
+ await createBranch(tempDir, "agency--feature")
88
88
  await createCommit(tempDir, "Feature commit")
89
89
 
90
90
  expect(runTestEffect(emit({ silent: true }))).rejects.toThrow(
@@ -95,7 +95,7 @@ describe("emit command", () => {
95
95
  test("creates emit branch with default name", async () => {
96
96
  // Go back to main and create a fresh source branch (no inherited agency.json)
97
97
  await checkoutBranch(tempDir, "main")
98
- await createBranch(tempDir, "agency/feature")
98
+ await createBranch(tempDir, "agency--feature")
99
99
  // Create agency.json for this branch
100
100
  await Bun.write(
101
101
  join(tempDir, "agency.json"),
@@ -121,11 +121,11 @@ describe("emit command", () => {
121
121
 
122
122
  // Check we're still on the source branch
123
123
  const currentBranch = await getCurrentBranch(tempDir)
124
- expect(currentBranch).toBe("agency/feature")
124
+ expect(currentBranch).toBe("agency--feature")
125
125
  })
126
126
 
127
127
  test("creates emit branch with custom name", async () => {
128
- await createBranch(tempDir, "agency/feature")
128
+ await createBranch(tempDir, "agency--feature")
129
129
  await createCommit(tempDir, "Feature commit")
130
130
 
131
131
  // Skip filter for speed - we're just testing branch creation
@@ -142,11 +142,11 @@ describe("emit command", () => {
142
142
 
143
143
  // Check we're still on the source branch
144
144
  const currentBranch = await getCurrentBranch(tempDir)
145
- expect(currentBranch).toBe("agency/feature")
145
+ expect(currentBranch).toBe("agency--feature")
146
146
  })
147
147
 
148
148
  test("completes emit workflow successfully", async () => {
149
- await createBranch(tempDir, "agency/feature")
149
+ await createBranch(tempDir, "agency--feature")
150
150
  await createCommit(tempDir, "Feature commit")
151
151
 
152
152
  // Should complete without throwing (skip filter for speed)
@@ -154,11 +154,11 @@ describe("emit command", () => {
154
154
 
155
155
  // Should still be on source branch
156
156
  const currentBranch = await getCurrentBranch(tempDir)
157
- expect(currentBranch).toBe("agency/feature")
157
+ expect(currentBranch).toBe("agency--feature")
158
158
  })
159
159
 
160
160
  test("preserves files on source branch after emit", async () => {
161
- await createBranch(tempDir, "agency/feature")
161
+ await createBranch(tempDir, "agency--feature")
162
162
  await createCommit(tempDir, "Feature commit")
163
163
 
164
164
  // Skip filter for speed - we're testing source branch preservation
@@ -166,7 +166,7 @@ describe("emit command", () => {
166
166
 
167
167
  // Should still be on source branch
168
168
  const currentBranch = await getCurrentBranch(tempDir)
169
- expect(currentBranch).toBe("agency/feature")
169
+ expect(currentBranch).toBe("agency--feature")
170
170
 
171
171
  // Check that test file still exists on source branch
172
172
  expect(await fileExists(join(tempDir, "test.txt"))).toBe(true)
@@ -176,7 +176,7 @@ describe("emit command", () => {
176
176
  })
177
177
 
178
178
  test("original branch remains untouched", async () => {
179
- await createBranch(tempDir, "agency/feature")
179
+ await createBranch(tempDir, "agency--feature")
180
180
  await createCommit(tempDir, "Feature commit")
181
181
 
182
182
  // Create emit branch (skip filter for speed)
@@ -184,7 +184,7 @@ describe("emit command", () => {
184
184
 
185
185
  // Should still be on feature branch
186
186
  const currentBranch = await getCurrentBranch(tempDir)
187
- expect(currentBranch).toBe("agency/feature")
187
+ expect(currentBranch).toBe("agency--feature")
188
188
 
189
189
  // Check that AGENTS.md still exist on original branch
190
190
  const files = await getGitOutput(tempDir, ["ls-files"])
@@ -192,7 +192,7 @@ describe("emit command", () => {
192
192
  })
193
193
 
194
194
  test("works correctly when run multiple times (recreates emit branch)", async () => {
195
- await createBranch(tempDir, "agency/feature")
195
+ await createBranch(tempDir, "agency--feature")
196
196
 
197
197
  // Modify AGENTS.md on feature branch
198
198
  await Bun.write(
@@ -205,7 +205,7 @@ describe("emit command", () => {
205
205
  await runTestEffect(emit({ silent: true, skipFilter: true }))
206
206
 
207
207
  // Switch back to feature branch
208
- await checkoutBranch(tempDir, "agency/feature")
208
+ await checkoutBranch(tempDir, "agency--feature")
209
209
 
210
210
  // Make another commit
211
211
  await createCommit(tempDir, "Another feature commit")
@@ -215,11 +215,11 @@ describe("emit command", () => {
215
215
 
216
216
  // Should complete successfully without interactive prompts and stay on source branch
217
217
  const currentBranch = await getCurrentBranch(tempDir)
218
- expect(currentBranch).toBe("agency/feature")
218
+ expect(currentBranch).toBe("agency--feature")
219
219
  })
220
220
 
221
221
  test("accepts explicit base branch argument", async () => {
222
- await createBranch(tempDir, "agency/feature")
222
+ await createBranch(tempDir, "agency--feature")
223
223
  await createCommit(tempDir, "Feature commit")
224
224
 
225
225
  // Create emit branch with explicit base branch (skip filter for speed)
@@ -229,11 +229,11 @@ describe("emit command", () => {
229
229
 
230
230
  // Should stay on source branch
231
231
  const currentBranch = await getCurrentBranch(tempDir)
232
- expect(currentBranch).toBe("agency/feature")
232
+ expect(currentBranch).toBe("agency--feature")
233
233
  })
234
234
 
235
235
  test("throws error if provided base branch does not exist", async () => {
236
- await createBranch(tempDir, "agency/feature")
236
+ await createBranch(tempDir, "agency--feature")
237
237
  await createCommit(tempDir, "Feature commit")
238
238
 
239
239
  // This should fail even with skipFilter since base branch validation happens first
@@ -260,7 +260,7 @@ describe("emit command", () => {
260
260
 
261
261
  describe("silent mode", () => {
262
262
  test("silent flag suppresses output", async () => {
263
- await createBranch(tempDir, "agency/feature")
263
+ await createBranch(tempDir, "agency--feature")
264
264
  await createCommit(tempDir, "Feature commit")
265
265
 
266
266
  const logs: string[] = []
@@ -284,7 +284,7 @@ describe("emit command", () => {
284
284
  test("constructs correct filter-repo arguments", async () => {
285
285
  // Set up fresh branch with agency.json
286
286
  await checkoutBranch(tempDir, "main")
287
- await createBranch(tempDir, "agency/filter-test")
287
+ await createBranch(tempDir, "agency--filter-test")
288
288
 
289
289
  // Create agency.json with injected files
290
290
  await Bun.write(
@@ -329,7 +329,7 @@ describe("emit command", () => {
329
329
  test("includes symlink targets in files to filter", async () => {
330
330
  // Set up fresh branch
331
331
  await checkoutBranch(tempDir, "main")
332
- await createBranch(tempDir, "agency/symlink-test")
332
+ await createBranch(tempDir, "agency--symlink-test")
333
333
 
334
334
  // Create AGENTS.md as the real file
335
335
  await Bun.write(join(tempDir, "AGENTS.md"), "# Real file\n")
@@ -23,6 +23,7 @@ import {
23
23
  withBranchProtection,
24
24
  } from "../utils/effect"
25
25
  import { withSpinner } from "../utils/spinner"
26
+ import { AGENCY_REMOVE_COMMIT } from "../constants"
26
27
 
27
28
  interface EmitOptions extends BaseCommandOptions {
28
29
  emit?: string
@@ -42,7 +43,7 @@ export const emit = (options: EmitOptions = {}) =>
42
43
  yield* withBranchProtection(gitRoot, emitCore(gitRoot, options))
43
44
  })
44
45
 
45
- const emitCore = (gitRoot: string, options: EmitOptions) =>
46
+ export const emitCore = (gitRoot: string, options: EmitOptions) =>
46
47
  Effect.gen(function* () {
47
48
  const { force = false, verbose = false, skipFilter = false } = options
48
49
  const { log, verboseLog } = createLoggers(options)
@@ -207,7 +208,7 @@ const emitCore = (gitRoot: string, options: EmitOptions) =>
207
208
  "--commit-callback",
208
209
  // Clear file changes from commits with AGENCY_REMOVE_COMMIT marker
209
210
  // This makes the commit empty (which gets pruned) while preserving the tree state
210
- `if b"AGENCY_REMOVE_COMMIT" in commit.message: commit.file_changes = []`,
211
+ `if b"${AGENCY_REMOVE_COMMIT}" in commit.message: commit.file_changes = []`,
211
212
  "--force",
212
213
  "--refs",
213
214
  `${forkPoint}..${emitBranchName}`,
@@ -433,7 +434,7 @@ Create an emit branch from the current source branch with backpack files (AGENTS
433
434
  reverted to their state on the base branch.
434
435
 
435
436
  Source and Emit Branches:
436
- - Source branches: Your working branches with agency-specific files (e.g., agency/feature-foo)
437
+ - Source branches: Your working branches with agency-specific files (e.g., agency--feature-foo)
437
438
  - Emit branches: Clean branches suitable for PRs without agency files (e.g., feature-foo)
438
439
 
439
440
  This command creates a clean emit branch from your source branch by filtering out
@@ -472,20 +473,20 @@ Options:
472
473
  Configuration:
473
474
  ~/.config/agency/agency.json can contain:
474
475
  {
475
- "sourceBranchPattern": "agency/%branch%", // Pattern for source branch names
476
+ "sourceBranchPattern": "agency--%branch%", // Pattern for source branch names
476
477
  "emitBranch": "%branch%" // Pattern for emit branch names
477
478
  }
478
479
 
479
480
  Use %branch% as placeholder for the clean branch name.
480
481
 
481
482
  Source Pattern Examples:
482
- "agency/%branch%" -> main becomes agency/main (default)
483
+ "agency--%branch%" -> main becomes agency--main (default)
483
484
  "wip/%branch%" -> feature becomes wip/feature
484
485
 
485
486
  Emit Pattern Examples:
486
- "%branch%" -> agency/main emits to main (default)
487
- "%branch%--PR" -> agency/feature emits to feature--PR
488
- "PR/%branch%" -> agency/feature emits to PR/feature
487
+ "%branch%" -> agency--main emits to main (default)
488
+ "%branch%--PR" -> agency--feature emits to feature--PR
489
+ "PR/%branch%" -> agency--feature emits to PR/feature
489
490
 
490
491
  Examples:
491
492
  agency emit # Prompt for base branch (first time) or use saved
@@ -54,8 +54,8 @@ describe("emitted command", () => {
54
54
 
55
55
  describe("basic functionality", () => {
56
56
  test("returns emit branch name when on source branch", async () => {
57
- // Create source branch (agency/main)
58
- await createBranch(tempDir, "agency/main")
57
+ // Create source branch (agency--main)
58
+ await createBranch(tempDir, "agency--main")
59
59
  await createCommit(tempDir, "Work on source")
60
60
 
61
61
  // Capture output
@@ -73,7 +73,7 @@ describe("emitted command", () => {
73
73
 
74
74
  test("returns emit branch name when on emit branch", async () => {
75
75
  // Create source branch
76
- await createBranch(tempDir, "agency/main")
76
+ await createBranch(tempDir, "agency--main")
77
77
  await createCommit(tempDir, "Work on source")
78
78
 
79
79
  // Switch to emit branch (main)
@@ -98,14 +98,14 @@ describe("emitted command", () => {
98
98
  await Bun.write(
99
99
  configPath,
100
100
  JSON.stringify({
101
- sourceBranchPattern: "agency/%branch%",
101
+ sourceBranchPattern: "agency--%branch%",
102
102
  emitBranch: "%branch%--PR",
103
103
  }),
104
104
  )
105
105
  process.env.AGENCY_CONFIG_PATH = configPath
106
106
 
107
107
  // Create source branch
108
- await createBranch(tempDir, "agency/feature")
108
+ await createBranch(tempDir, "agency--feature")
109
109
  await createCommit(tempDir, "Feature work")
110
110
 
111
111
  // Capture output
@@ -123,7 +123,7 @@ describe("emitted command", () => {
123
123
 
124
124
  test("returns emit branch from agency.json when present", async () => {
125
125
  // Create source branch
126
- await createBranch(tempDir, "agency/feature")
126
+ await createBranch(tempDir, "agency--feature")
127
127
 
128
128
  // Create agency.json with custom emitBranch
129
129
  const agencyJsonPath = join(tempDir, "agency.json")
@@ -181,7 +181,7 @@ describe("emitted command", () => {
181
181
 
182
182
  describe("silent mode", () => {
183
183
  test("still outputs when not in silent mode", async () => {
184
- await createBranch(tempDir, "agency/main")
184
+ await createBranch(tempDir, "agency--main")
185
185
 
186
186
  const originalLog = console.log
187
187
  let logCalled = false
@@ -196,7 +196,7 @@ describe("emitted command", () => {
196
196
  })
197
197
 
198
198
  test("silent flag suppresses output", async () => {
199
- await createBranch(tempDir, "agency/main")
199
+ await createBranch(tempDir, "agency--main")
200
200
 
201
201
  const originalLog = console.log
202
202
  let logCalled = false
@@ -41,7 +41,7 @@ Usage: agency emitted [options]
41
41
  Get the name of the emitted branch (or what it would be).
42
42
 
43
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)
44
+ - If on a source branch (e.g., agency--main), shows the emit branch (e.g., main)
45
45
  - If on an emit branch (e.g., main), shows the current branch name
46
46
 
47
47
  This is useful for scripting and automation where you need to know
@@ -58,8 +58,8 @@ describe("merge command - integration tests (requires git-filter-repo)", () => {
58
58
  // Set up origin for git-filter-repo
59
59
  await setupRemote(tempDir, "origin", tempDir)
60
60
 
61
- // Create a source branch (with agency/ prefix per new default config)
62
- await createBranch(tempDir, "agency/feature")
61
+ // Create a source branch (with agency-- prefix per new default config)
62
+ await createBranch(tempDir, "agency--feature")
63
63
 
64
64
  // Initialize AGENTS.md on feature branch
65
65
  await initAgency(tempDir, "test")
@@ -102,7 +102,7 @@ describe("merge command - integration tests (requires git-filter-repo)", () => {
102
102
 
103
103
  // We're on feature branch (source)
104
104
  const currentBranch = await getCurrentBranch(tempDir)
105
- expect(currentBranch).toBe("agency/feature")
105
+ expect(currentBranch).toBe("agency--feature")
106
106
 
107
107
  // Run merge - should create feature--PR and merge it to main
108
108
  await runTestEffect(merge({ silent: true }))
@@ -130,7 +130,7 @@ describe("merge command - integration tests (requires git-filter-repo)", () => {
130
130
  await runTestEffect(emit({ silent: true }))
131
131
 
132
132
  // Go back to feature branch
133
- await checkoutBranch(tempDir, "agency/feature")
133
+ await checkoutBranch(tempDir, "agency--feature")
134
134
 
135
135
  // Make additional changes
136
136
  await createCommit(tempDir, "More feature work")
@@ -186,7 +186,7 @@ describe("merge command - integration tests (requires git-filter-repo)", () => {
186
186
  await checkoutBranch(tempDir, "feature")
187
187
 
188
188
  // Delete the source branch
189
- await deleteBranch(tempDir, "agency/feature", true)
189
+ await deleteBranch(tempDir, "agency--feature", true)
190
190
 
191
191
  // Try to merge - should fail (error message may vary since source branch is deleted)
192
192
  await expect(runTestEffect(merge({ silent: true }))).rejects.toThrow()
@@ -39,8 +39,8 @@ describe("merge command", () => {
39
39
  // Set up origin for git-filter-repo
40
40
  await setupRemote(tempDir, "origin", tempDir)
41
41
 
42
- // Create a source branch (with agency/ prefix per new default config)
43
- await createBranch(tempDir, "agency/feature")
42
+ // Create a source branch (with agency-- prefix per new default config)
43
+ await createBranch(tempDir, "agency--feature")
44
44
 
45
45
  // Initialize AGENTS.md on feature branch
46
46
  await initAgency(tempDir, "test")
@@ -119,7 +119,7 @@ describe("merge command", () => {
119
119
  test("performs squash merge when --squash flag is set", async () => {
120
120
  // We're on feature branch (source)
121
121
  const currentBranch = await getCurrentBranch(tempDir)
122
- expect(currentBranch).toBe("agency/feature")
122
+ expect(currentBranch).toBe("agency--feature")
123
123
 
124
124
  // Run merge with squash flag (skipFilter for speed, we're testing squash behavior)
125
125
  await runTestEffect(
@@ -146,7 +146,7 @@ describe("merge command", () => {
146
146
  test("performs regular merge when --squash flag is not set", async () => {
147
147
  // We're on feature branch (source)
148
148
  const currentBranch = await getCurrentBranch(tempDir)
149
- expect(currentBranch).toBe("agency/feature")
149
+ expect(currentBranch).toBe("agency--feature")
150
150
 
151
151
  // Run merge without squash flag (skipFilter for speed, we're testing merge behavior)
152
152
  await runTestEffect(
@@ -175,7 +175,7 @@ describe("merge command", () => {
175
175
  test("pushes base branch to origin when --push flag is set", async () => {
176
176
  // We're on feature branch (source)
177
177
  const currentBranch = await getCurrentBranch(tempDir)
178
- expect(currentBranch).toBe("agency/feature")
178
+ expect(currentBranch).toBe("agency--feature")
179
179
 
180
180
  // Get the current commit on main before merge
181
181
  await checkoutBranch(tempDir, "main")
@@ -184,7 +184,7 @@ describe("merge command", () => {
184
184
  ).trim()
185
185
 
186
186
  // Go back to feature branch
187
- await checkoutBranch(tempDir, "agency/feature")
187
+ await checkoutBranch(tempDir, "agency--feature")
188
188
 
189
189
  // Run merge with push flag (skipFilter for speed, we're testing push behavior)
190
190
  await runTestEffect(merge({ silent: true, push: true, skipFilter: true }))
@@ -213,7 +213,7 @@ describe("merge command", () => {
213
213
  test("does not push when --push flag is not set", async () => {
214
214
  // We're on feature branch (source)
215
215
  const currentBranch = await getCurrentBranch(tempDir)
216
- expect(currentBranch).toBe("agency/feature")
216
+ expect(currentBranch).toBe("agency--feature")
217
217
 
218
218
  // Get the current commit on origin/main before merge
219
219
  const beforeOriginCommit = (