@markjaquith/agency 0.5.1 → 0.6.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.
@@ -42,6 +42,74 @@ describe("task command - branching functionality", () => {
42
42
  })
43
43
 
44
44
  describe("--from flag", () => {
45
+ test("creates new agency/some-branch when using --from some-branch with explicit name", async () => {
46
+ await initGitRepo(tempDir)
47
+ process.chdir(tempDir)
48
+ await initAgency(tempDir, "test")
49
+
50
+ // Create a feature branch called 'some-branch'
51
+ await runGitCommand(tempDir, ["git", "checkout", "-b", "some-branch"])
52
+ await createFile(tempDir, "feature.txt", "content")
53
+ await runGitCommand(tempDir, ["git", "add", "."])
54
+ await runGitCommand(tempDir, ["git", "commit", "-m", "Add feature"])
55
+
56
+ // Run agency task my-feature --from some-branch
57
+ // This should create a NEW branch called agency/my-feature
58
+ await runTestEffect(
59
+ task({
60
+ silent: true,
61
+ emit: "my-feature",
62
+ from: "some-branch",
63
+ }),
64
+ )
65
+
66
+ const currentBranch = await getCurrentBranch(tempDir)
67
+ expect(currentBranch).toBe("agency/my-feature")
68
+
69
+ // Verify feature.txt exists (came from some-branch)
70
+ const featureFile = await Bun.file(join(tempDir, "feature.txt")).text()
71
+ expect(featureFile).toBe("content")
72
+
73
+ // Verify TASK.md was created (agency files added)
74
+ const taskMdExists = await Bun.file(join(tempDir, "TASK.md")).exists()
75
+ expect(taskMdExists).toBe(true)
76
+ })
77
+
78
+ test("throws error when agency/some-branch already exists", async () => {
79
+ await initGitRepo(tempDir)
80
+ process.chdir(tempDir)
81
+ await initAgency(tempDir, "test")
82
+
83
+ // Create a feature branch called 'some-branch'
84
+ await runGitCommand(tempDir, ["git", "checkout", "-b", "some-branch"])
85
+ await createFile(tempDir, "feature.txt", "content")
86
+ await runGitCommand(tempDir, ["git", "add", "."])
87
+ await runGitCommand(tempDir, ["git", "commit", "-m", "Add feature"])
88
+
89
+ // Create agency/my-feature first
90
+ await runTestEffect(
91
+ task({
92
+ silent: true,
93
+ emit: "my-feature",
94
+ from: "some-branch",
95
+ }),
96
+ )
97
+
98
+ // Go back to some-branch
99
+ await runGitCommand(tempDir, ["git", "checkout", "some-branch"])
100
+
101
+ // Try to create it again - should fail
102
+ await expect(
103
+ runTestEffect(
104
+ task({
105
+ silent: true,
106
+ emit: "my-feature",
107
+ from: "some-branch",
108
+ }),
109
+ ),
110
+ ).rejects.toThrow("already exists")
111
+ })
112
+
45
113
  test("branches from specified non-agency branch", async () => {
46
114
  await initGitRepo(tempDir)
47
115
  process.chdir(tempDir)
@@ -60,7 +128,7 @@ describe("task command - branching functionality", () => {
60
128
  await runTestEffect(
61
129
  task({
62
130
  silent: true,
63
- branch: "my-task",
131
+ emit: "my-task",
64
132
  from: "feature-base",
65
133
  }),
66
134
  )
@@ -82,7 +150,7 @@ describe("task command - branching functionality", () => {
82
150
  runTestEffect(
83
151
  task({
84
152
  silent: true,
85
- branch: "my-task",
153
+ emit: "my-task",
86
154
  from: "nonexistent-branch",
87
155
  }),
88
156
  ),
@@ -98,7 +166,7 @@ describe("task command - branching functionality", () => {
98
166
  await runTestEffect(
99
167
  task({
100
168
  silent: true,
101
- branch: "first-task",
169
+ emit: "first-task",
102
170
  }),
103
171
  )
104
172
 
@@ -122,7 +190,7 @@ describe("task command - branching functionality", () => {
122
190
  await runTestEffect(
123
191
  task({
124
192
  silent: true,
125
- branch: "second-task",
193
+ emit: "second-task",
126
194
  from: "agency/first-task",
127
195
  }),
128
196
  )
@@ -153,7 +221,7 @@ describe("task command - branching functionality", () => {
153
221
  await runTestEffect(
154
222
  task({
155
223
  silent: true,
156
- branch: "unemitted-task",
224
+ emit: "unemitted-task",
157
225
  }),
158
226
  )
159
227
 
@@ -165,7 +233,7 @@ describe("task command - branching functionality", () => {
165
233
  runTestEffect(
166
234
  task({
167
235
  silent: true,
168
- branch: "second-task",
236
+ emit: "second-task",
169
237
  from: "agency/unemitted-task",
170
238
  }),
171
239
  ),
@@ -192,7 +260,7 @@ describe("task command - branching functionality", () => {
192
260
  await runTestEffect(
193
261
  task({
194
262
  silent: true,
195
- branch: "my-task",
263
+ emit: "my-task",
196
264
  from: "feature-current",
197
265
  }),
198
266
  )
@@ -214,7 +282,7 @@ describe("task command - branching functionality", () => {
214
282
  await runTestEffect(
215
283
  task({
216
284
  silent: true,
217
- branch: "first-task",
285
+ emit: "first-task",
218
286
  }),
219
287
  )
220
288
 
@@ -232,7 +300,7 @@ describe("task command - branching functionality", () => {
232
300
  await runTestEffect(
233
301
  task({
234
302
  silent: true,
235
- branch: "second-task",
303
+ emit: "second-task",
236
304
  from: "agency/first-task",
237
305
  }),
238
306
  )
@@ -254,7 +322,7 @@ describe("task command - branching functionality", () => {
254
322
  await runTestEffect(
255
323
  task({
256
324
  silent: true,
257
- branch: "unemitted-task",
325
+ emit: "unemitted-task",
258
326
  }),
259
327
  )
260
328
 
@@ -266,7 +334,7 @@ describe("task command - branching functionality", () => {
266
334
  runTestEffect(
267
335
  task({
268
336
  silent: true,
269
- branch: "second-task",
337
+ emit: "second-task",
270
338
  from: "agency/unemitted-task",
271
339
  }),
272
340
  ),
@@ -284,7 +352,7 @@ describe("task command - branching functionality", () => {
284
352
  runTestEffect(
285
353
  task({
286
354
  silent: true,
287
- branch: "my-task",
355
+ emit: "my-task",
288
356
  from: "main",
289
357
  fromCurrent: true,
290
358
  }),
@@ -293,6 +361,51 @@ describe("task command - branching functionality", () => {
293
361
  })
294
362
  })
295
363
 
364
+ describe("--from flag branch naming", () => {
365
+ test("agency task --from foo requires explicit branch name in silent mode", async () => {
366
+ await initGitRepo(tempDir)
367
+ process.chdir(tempDir)
368
+ await initAgency(tempDir, "test")
369
+
370
+ await runGitCommand(tempDir, ["git", "checkout", "-b", "foo"])
371
+ await runGitCommand(tempDir, ["git", "checkout", "main"])
372
+
373
+ await expect(
374
+ runTestEffect(
375
+ task({
376
+ silent: true,
377
+ from: "foo",
378
+ }),
379
+ ),
380
+ ).rejects.toThrow("Branch name")
381
+ })
382
+
383
+ test("agency task out --from foo creates agency/out emitting to out", async () => {
384
+ await initGitRepo(tempDir)
385
+ process.chdir(tempDir)
386
+ await initAgency(tempDir, "test")
387
+
388
+ await runGitCommand(tempDir, ["git", "checkout", "-b", "foo"])
389
+ await runGitCommand(tempDir, ["git", "checkout", "main"])
390
+
391
+ await runTestEffect(
392
+ task({
393
+ silent: true,
394
+ emit: "out",
395
+ from: "foo",
396
+ }),
397
+ )
398
+
399
+ const currentBranch = await getCurrentBranch(tempDir)
400
+ expect(currentBranch).toBe("agency/out")
401
+
402
+ const agencyJson = JSON.parse(
403
+ await Bun.file(join(tempDir, "agency.json")).text(),
404
+ )
405
+ expect(agencyJson.emitBranch).toBe("out")
406
+ })
407
+ })
408
+
296
409
  describe("default branching behavior", () => {
297
410
  test("branches from auto-detected main branch by default", async () => {
298
411
  await initGitRepo(tempDir)
@@ -317,7 +430,7 @@ describe("task command - branching functionality", () => {
317
430
  await runTestEffect(
318
431
  task({
319
432
  silent: true,
320
- branch: "my-task",
433
+ emit: "my-task",
321
434
  }),
322
435
  )
323
436
 
@@ -331,4 +444,190 @@ describe("task command - branching functionality", () => {
331
444
  expect(otherExists).toBe(false)
332
445
  })
333
446
  })
447
+
448
+ describe("remote branch preference", () => {
449
+ let remoteDir: string
450
+
451
+ beforeEach(async () => {
452
+ // Create a bare repository to act as the "remote"
453
+ remoteDir = await createTempDir()
454
+ await runGitCommand(remoteDir, ["git", "init", "--bare", "-b", "main"])
455
+ })
456
+
457
+ afterEach(async () => {
458
+ await cleanupTempDir(remoteDir)
459
+ })
460
+
461
+ test("prefers origin/main over local main when remote is ahead", async () => {
462
+ await initGitRepo(tempDir)
463
+ process.chdir(tempDir)
464
+ await initAgency(tempDir, "test")
465
+
466
+ // Add the remote
467
+ await runGitCommand(tempDir, [
468
+ "git",
469
+ "remote",
470
+ "add",
471
+ "origin",
472
+ remoteDir,
473
+ ])
474
+
475
+ // Push current state to remote
476
+ await runGitCommand(tempDir, ["git", "push", "-u", "origin", "main"])
477
+
478
+ // Make a commit on local main
479
+ await createFile(tempDir, "local-only.txt", "local content")
480
+ await runGitCommand(tempDir, ["git", "add", "."])
481
+ await runGitCommand(tempDir, ["git", "commit", "-m", "Local-only commit"])
482
+
483
+ // Reset local main back to origin/main (simulate local being behind)
484
+ await runGitCommand(tempDir, ["git", "reset", "--hard", "origin/main"])
485
+
486
+ // Make a NEW commit on origin/main by pushing from a separate clone
487
+ const cloneDir = await createTempDir()
488
+ await runGitCommand(cloneDir, ["git", "clone", remoteDir, "."])
489
+ await runGitCommand(cloneDir, [
490
+ "git",
491
+ "config",
492
+ "user.email",
493
+ "test@example.com",
494
+ ])
495
+ await runGitCommand(cloneDir, ["git", "config", "user.name", "Test User"])
496
+ await createFile(cloneDir, "remote-only.txt", "remote content")
497
+ await runGitCommand(cloneDir, ["git", "add", "."])
498
+ await runGitCommand(cloneDir, [
499
+ "git",
500
+ "commit",
501
+ "-m",
502
+ "Remote-only commit",
503
+ ])
504
+ await runGitCommand(cloneDir, ["git", "push", "origin", "main"])
505
+ await cleanupTempDir(cloneDir)
506
+
507
+ // Fetch so origin/main is updated but local main stays behind
508
+ await runGitCommand(tempDir, ["git", "fetch", "origin"])
509
+
510
+ // Configure agency.mainBranch to "main" (local) and agency.remote to "origin"
511
+ await runGitCommand(tempDir, [
512
+ "git",
513
+ "config",
514
+ "--local",
515
+ "agency.mainBranch",
516
+ "main",
517
+ ])
518
+ await runGitCommand(tempDir, [
519
+ "git",
520
+ "config",
521
+ "--local",
522
+ "agency.remote",
523
+ "origin",
524
+ ])
525
+
526
+ // Create task - should branch from origin/main (which has remote-only.txt)
527
+ await runTestEffect(
528
+ task({
529
+ silent: true,
530
+ emit: "my-task",
531
+ }),
532
+ )
533
+
534
+ const currentBranch = await getCurrentBranch(tempDir)
535
+ expect(currentBranch).toBe("agency/my-task")
536
+
537
+ // The new branch should have remote-only.txt (from origin/main)
538
+ const remoteFileExists = await Bun.file(
539
+ join(tempDir, "remote-only.txt"),
540
+ ).exists()
541
+ expect(remoteFileExists).toBe(true)
542
+ })
543
+
544
+ test("falls back to local main when remote branch does not exist", async () => {
545
+ await initGitRepo(tempDir)
546
+ process.chdir(tempDir)
547
+ await initAgency(tempDir, "test")
548
+
549
+ // Create a commit on local main
550
+ await createFile(tempDir, "local.txt", "local content")
551
+ await runGitCommand(tempDir, ["git", "add", "."])
552
+ await runGitCommand(tempDir, ["git", "commit", "-m", "Local commit"])
553
+
554
+ // Add a remote but don't push (so origin/main doesn't exist)
555
+ await runGitCommand(tempDir, [
556
+ "git",
557
+ "remote",
558
+ "add",
559
+ "origin",
560
+ remoteDir,
561
+ ])
562
+
563
+ // Configure agency.mainBranch to "main" and agency.remote to "origin"
564
+ await runGitCommand(tempDir, [
565
+ "git",
566
+ "config",
567
+ "--local",
568
+ "agency.mainBranch",
569
+ "main",
570
+ ])
571
+ await runGitCommand(tempDir, [
572
+ "git",
573
+ "config",
574
+ "--local",
575
+ "agency.remote",
576
+ "origin",
577
+ ])
578
+
579
+ // Create task - should fall back to local main since origin/main doesn't exist
580
+ await runTestEffect(
581
+ task({
582
+ silent: true,
583
+ emit: "my-task",
584
+ }),
585
+ )
586
+
587
+ const currentBranch = await getCurrentBranch(tempDir)
588
+ expect(currentBranch).toBe("agency/my-task")
589
+
590
+ // The new branch should have local.txt
591
+ const localFileExists = await Bun.file(
592
+ join(tempDir, "local.txt"),
593
+ ).exists()
594
+ expect(localFileExists).toBe(true)
595
+ })
596
+
597
+ test("uses configured remote branch as-is when it already has remote prefix", async () => {
598
+ await initGitRepo(tempDir)
599
+ process.chdir(tempDir)
600
+ await initAgency(tempDir, "test")
601
+
602
+ // Add the remote and push
603
+ await runGitCommand(tempDir, [
604
+ "git",
605
+ "remote",
606
+ "add",
607
+ "origin",
608
+ remoteDir,
609
+ ])
610
+ await runGitCommand(tempDir, ["git", "push", "-u", "origin", "main"])
611
+
612
+ // Configure agency.mainBranch to "origin/main" (already has remote prefix)
613
+ await runGitCommand(tempDir, [
614
+ "git",
615
+ "config",
616
+ "--local",
617
+ "agency.mainBranch",
618
+ "origin/main",
619
+ ])
620
+
621
+ // Create task
622
+ await runTestEffect(
623
+ task({
624
+ silent: true,
625
+ emit: "my-task",
626
+ }),
627
+ )
628
+
629
+ const currentBranch = await getCurrentBranch(tempDir)
630
+ expect(currentBranch).toBe("agency/my-task")
631
+ })
632
+ })
334
633
  })