@markjaquith/agency 1.8.1 → 1.8.2

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markjaquith/agency",
3
- "version": "1.8.1",
3
+ "version": "1.8.2",
4
4
  "description": "Manages personal agents files",
5
5
  "keywords": [
6
6
  "agents",
@@ -80,8 +80,8 @@ describe("tasks command", () => {
80
80
  // Initialize agency
81
81
  await initAgency(tempDir, "test-template")
82
82
 
83
- // Create feature branch with agency.json
84
- await createBranch(tempDir, "feature")
83
+ // Create feature branch with agency-- prefix and agency.json
84
+ await createBranch(tempDir, "agency--feature")
85
85
  await writeAgencyMetadata(tempDir, {
86
86
  version: 1,
87
87
  injectedFiles: ["AGENTS.md"],
@@ -107,15 +107,15 @@ describe("tasks command", () => {
107
107
  await runTestEffect(tasks({}))
108
108
 
109
109
  console.log = originalLog
110
- expect(output).toContain("feature")
111
- expect(output.trim()).toBe("feature")
110
+ expect(output).toContain("agency--feature")
111
+ expect(output.trim()).toBe("agency--feature")
112
112
  })
113
113
 
114
114
  test("lists multiple task branches", async () => {
115
115
  await initAgency(tempDir, "test-template")
116
116
 
117
117
  // Create first feature branch
118
- await createBranch(tempDir, "feature-1")
118
+ await createBranch(tempDir, "agency--feature-1")
119
119
  await writeAgencyMetadata(tempDir, {
120
120
  version: 1,
121
121
  injectedFiles: ["AGENTS.md"],
@@ -132,7 +132,7 @@ describe("tasks command", () => {
132
132
 
133
133
  // Go back to main and create second feature branch
134
134
  await checkoutBranch(tempDir, "main")
135
- await createBranch(tempDir, "feature-2")
135
+ await createBranch(tempDir, "agency--feature-2")
136
136
  await writeAgencyMetadata(tempDir, {
137
137
  version: 1,
138
138
  injectedFiles: ["opencode.json"],
@@ -156,22 +156,33 @@ describe("tasks command", () => {
156
156
  await runTestEffect(tasks({}))
157
157
 
158
158
  console.log = originalLog
159
- expect(output).toContain("feature-1")
160
- expect(output).toContain("feature-2")
159
+ expect(output).toContain("agency--feature-1")
160
+ expect(output).toContain("agency--feature-2")
161
161
  const lines = output.trim().split("\n")
162
162
  expect(lines.length).toBe(2)
163
163
  })
164
164
 
165
- test("ignores branches without agency.json", async () => {
165
+ test("ignores branches without agency-- prefix", async () => {
166
166
  await initAgency(tempDir, "test-template")
167
167
 
168
- // Create a branch without agency.json
169
- await createBranch(tempDir, "no-agency")
170
- await createCommit(tempDir, "Some work")
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")
171
182
 
172
- // Go back to main and create a branch with agency.json
183
+ // Go back to main and create a branch with agency-- prefix
173
184
  await checkoutBranch(tempDir, "main")
174
- await createBranch(tempDir, "with-agency")
185
+ await createBranch(tempDir, "agency--with-prefix")
175
186
  await writeAgencyMetadata(tempDir, {
176
187
  version: 1,
177
188
  injectedFiles: [],
@@ -194,9 +205,27 @@ describe("tasks command", () => {
194
205
  await runTestEffect(tasks({}))
195
206
 
196
207
  console.log = originalLog
197
- expect(output).toContain("with-agency")
198
- expect(output).not.toContain("no-agency")
199
- expect(output.trim()).toBe("with-agency")
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")
200
229
  })
201
230
  })
202
231
 
@@ -204,7 +233,7 @@ describe("tasks command", () => {
204
233
  test("outputs JSON format when --json is provided", async () => {
205
234
  await initAgency(tempDir, "test-template")
206
235
 
207
- await createBranch(tempDir, "feature")
236
+ await createBranch(tempDir, "agency--feature")
208
237
  const createdAt = new Date().toISOString()
209
238
  await writeAgencyMetadata(tempDir, {
210
239
  version: 1,
@@ -234,7 +263,7 @@ describe("tasks command", () => {
234
263
  const data = JSON.parse(output.trim())
235
264
  expect(Array.isArray(data)).toBe(true)
236
265
  expect(data.length).toBe(1)
237
- expect(data[0].branch).toBe("feature")
266
+ expect(data[0].branch).toBe("agency--feature")
238
267
  expect(data[0].template).toBe("test-template")
239
268
  expect(data[0].baseBranch).toBe("main")
240
269
  expect(data[0].createdAt).toBeDefined()
@@ -255,14 +284,38 @@ describe("tasks command", () => {
255
284
  expect(Array.isArray(data)).toBe(true)
256
285
  expect(data.length).toBe(0)
257
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
+ })
258
311
  })
259
312
 
260
313
  describe("edge cases", () => {
261
314
  test("handles branches with invalid agency.json gracefully", async () => {
262
315
  await initAgency(tempDir, "test-template")
263
316
 
264
- // Create branch with invalid agency.json
265
- await createBranch(tempDir, "invalid")
317
+ // Create branch with invalid agency.json but agency-- prefix
318
+ await createBranch(tempDir, "agency--invalid")
266
319
  await Bun.write(join(tempDir, "agency.json"), "{ invalid json }")
267
320
  await Bun.spawn(["git", "add", "agency.json"], {
268
321
  cwd: tempDir,
@@ -271,9 +324,9 @@ describe("tasks command", () => {
271
324
  }).exited
272
325
  await createCommit(tempDir, "Add invalid agency.json")
273
326
 
274
- // Create branch with valid agency.json
327
+ // Create branch with valid agency.json and agency-- prefix
275
328
  await checkoutBranch(tempDir, "main")
276
- await createBranch(tempDir, "valid")
329
+ await createBranch(tempDir, "agency--valid")
277
330
  await writeAgencyMetadata(tempDir, {
278
331
  version: 1,
279
332
  injectedFiles: [],
@@ -296,16 +349,18 @@ describe("tasks command", () => {
296
349
  await runTestEffect(tasks({}))
297
350
 
298
351
  console.log = originalLog
299
- expect(output).toContain("valid")
300
- expect(output).not.toContain("invalid")
301
- expect(output.trim()).toBe("valid")
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)
302
357
  })
303
358
 
304
359
  test("handles branches with old version agency.json", async () => {
305
360
  await initAgency(tempDir, "test-template")
306
361
 
307
362
  // Create branch with version 0 agency.json (future or old version)
308
- await createBranch(tempDir, "old-version")
363
+ await createBranch(tempDir, "agency--old-version")
309
364
  await Bun.write(
310
365
  join(tempDir, "agency.json"),
311
366
  JSON.stringify({
@@ -329,7 +384,8 @@ describe("tasks command", () => {
329
384
  await runTestEffect(tasks({}))
330
385
 
331
386
  console.log = originalLog
332
- expect(output).toContain("No task branches found")
387
+ // Branch should still be listed since it matches the prefix
388
+ expect(output).toContain("agency--old-version")
333
389
  })
334
390
  })
335
391
  })
@@ -2,6 +2,7 @@ import { Effect, DateTime } from "effect"
2
2
  import { Schema } from "@effect/schema"
3
3
  import type { BaseCommandOptions } from "../utils/command"
4
4
  import { GitService } from "../services/GitService"
5
+ import { ConfigService } from "../services/ConfigService"
5
6
  import { AgencyMetadata } from "../schemas"
6
7
  import highlight from "../utils/colors"
7
8
  import { createLoggers, ensureGitRepo } from "../utils/effect"
@@ -62,16 +63,32 @@ const parseAgencyMetadata = (content: string) =>
62
63
  }).pipe(Effect.catchAll(() => Effect.succeed(null)))
63
64
 
64
65
  /**
65
- * Find all branches that contain an agency.json file
66
+ * Extract the prefix from a source branch pattern (everything before %branch%).
67
+ */
68
+ const getSourceBranchPrefix = (pattern: string): string => {
69
+ if (pattern.includes("%branch%")) {
70
+ return pattern.split("%branch%")[0]!
71
+ }
72
+ // If no %branch% placeholder, the whole pattern is the prefix
73
+ return pattern
74
+ }
75
+
76
+ /**
77
+ * Find all branches that match the source branch pattern prefix
66
78
  */
67
79
  const findAllTaskBranches = (gitRoot: string) =>
68
80
  Effect.gen(function* () {
69
81
  const git = yield* GitService
82
+ const configService = yield* ConfigService
83
+
84
+ // Get source branch pattern from config
85
+ const config = yield* configService.loadConfig()
86
+ const prefix = getSourceBranchPrefix(config.sourceBranchPattern)
70
87
 
71
- // Get all local branches
72
- const branches = yield* git.getAllLocalBranches(gitRoot)
88
+ // Get only branches matching the prefix (fast - single git command)
89
+ const branches = yield* git.getBranchesByPrefix(gitRoot, prefix)
73
90
 
74
- // For each branch, try to read agency.json
91
+ // For each matching branch, try to read agency.json for metadata
75
92
  const taskBranches: TaskBranchInfo[] = []
76
93
  for (const branch of branches) {
77
94
  const metadata = yield* readAgencyMetadataFromBranch(gitRoot, branch)
@@ -85,6 +102,14 @@ const findAllTaskBranches = (gitRoot: string) =>
85
102
  ? DateTime.toDateUtc(metadata.createdAt).toISOString()
86
103
  : null,
87
104
  })
105
+ } else {
106
+ // Branch matches prefix but has no valid agency.json - still list it
107
+ taskBranches.push({
108
+ branch,
109
+ template: null,
110
+ baseBranch: null,
111
+ createdAt: null,
112
+ })
88
113
  }
89
114
  }
90
115
 
@@ -124,10 +149,8 @@ export const tasks = (options: TasksOptions = {}) =>
124
149
  export const help = `
125
150
  Usage: agency tasks [options]
126
151
 
127
- List all source branches that have agency tasks (branches containing agency.json).
128
-
129
- This command searches through all local branches and displays those that have
130
- been initialized with 'agency task'.
152
+ List all source branches that have agency tasks (branches matching the source
153
+ branch pattern, e.g. "agency--*").
131
154
 
132
155
  Options:
133
156
  --json Output as JSON (includes metadata: template, base branch, created date)
@@ -967,6 +967,38 @@ export class GitService extends Effect.Service<GitService>()("GitService", {
967
967
  ),
968
968
  ),
969
969
 
970
+ /**
971
+ * Get local branch names matching a prefix.
972
+ * @param gitRoot - The git repository root
973
+ * @param prefix - The prefix to match (e.g., "agency--")
974
+ * @returns Array of matching local branch names
975
+ */
976
+ getBranchesByPrefix: (gitRoot: string, prefix: string) =>
977
+ pipe(
978
+ runGitCommand(
979
+ [
980
+ "git",
981
+ "branch",
982
+ "--list",
983
+ `${prefix}*`,
984
+ "--format=%(refname:short)",
985
+ ],
986
+ gitRoot,
987
+ ),
988
+ Effect.map((result) => {
989
+ if (result.exitCode === 0 && result.stdout.trim()) {
990
+ return result.stdout.trim().split("\n") as readonly string[]
991
+ }
992
+ return [] as readonly string[]
993
+ }),
994
+ Effect.mapError(
995
+ () =>
996
+ new GitError({
997
+ message: `Failed to get branches with prefix "${prefix}"`,
998
+ }),
999
+ ),
1000
+ ),
1001
+
970
1002
  /**
971
1003
  * Get branches that have been merged into a target branch.
972
1004
  * @param gitRoot - The git repository root