@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.
- package/README.md +15 -4
- package/cli.ts +35 -22
- package/package.json +1 -1
- package/src/commands/emit.test.ts +1 -1
- package/src/commands/emit.ts +16 -5
- package/src/commands/push.test.ts +1 -1
- package/src/commands/push.ts +8 -5
- package/src/commands/rebase.test.ts +521 -0
- package/src/commands/rebase.ts +243 -0
- package/src/commands/save.test.ts +8 -8
- package/src/commands/task-branching.test.ts +312 -13
- package/src/commands/task-continue.test.ts +311 -0
- package/src/commands/task-edit.test.ts +4 -4
- package/src/commands/task-main.test.ts +57 -32
- package/src/commands/task.ts +371 -79
- package/src/services/AgencyMetadataService.ts +9 -1
- package/src/services/GitService.ts +61 -1
- package/src/utils/glob.test.ts +154 -0
- package/src/utils/glob.ts +78 -0
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
})
|