@markjaquith/agency 1.11.1 → 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.
- package/README.md +302 -113
- package/cli.ts +123 -420
- package/index.ts +1 -1
- package/package.json +12 -17
- package/skills/agency/SKILL.md +258 -0
- package/src/cli.test.ts +290 -0
- package/src/commands/description.test.ts +103 -0
- package/src/commands/epic.test.ts +103 -0
- package/src/commands/epic.ts +101 -0
- package/src/commands/init.test.ts +42 -290
- package/src/commands/init.ts +15 -123
- package/src/commands/phase.ts +120 -0
- package/src/commands/pr.test.ts +19 -170
- package/src/commands/pr.ts +23 -179
- package/src/commands/repo.test.ts +80 -0
- package/src/commands/repo.ts +83 -0
- package/src/commands/status.test.ts +35 -477
- package/src/commands/status.ts +24 -213
- package/src/commands/task-phase.test.ts +206 -0
- package/src/commands/task.ts +83 -1285
- package/src/commands/validate.test.ts +91 -0
- package/src/commands/validate.ts +50 -0
- package/src/commands/work.test.ts +172 -419
- package/src/commands/work.ts +47 -166
- package/src/services/EpicService.test.ts +71 -0
- package/src/services/EpicService.ts +135 -0
- package/src/services/FileSystemService.ts +61 -92
- package/src/services/PhaseService.ts +323 -0
- package/src/services/PullRequestService.test.ts +386 -0
- package/src/services/PullRequestService.ts +143 -0
- package/src/services/RepositoryService.test.ts +106 -0
- package/src/services/RepositoryService.ts +159 -0
- package/src/services/TaskPhaseService.test.ts +217 -0
- package/src/services/TaskService.ts +192 -0
- package/src/services/WorkbaseService.test.ts +446 -0
- package/src/services/WorkbaseService.ts +493 -0
- package/src/services/WorktreeService.test.ts +599 -0
- package/src/services/WorktreeService.ts +357 -0
- package/src/test-utils.ts +41 -438
- package/src/types.ts +1 -215
- package/src/utils/command.ts +1 -5
- package/src/utils/effect.test.ts +24 -0
- package/src/utils/effect.ts +3 -273
- package/src/utils/process.test.ts +0 -15
- package/src/utils/process.ts +1 -60
- package/src/workbase/frontmatter.test.ts +50 -0
- package/src/workbase/frontmatter.ts +77 -0
- package/src/workbase/repository-reference.test.ts +25 -0
- package/src/workbase/repository-reference.ts +18 -0
- package/src/workbase/schemas.test.ts +146 -0
- package/src/workbase/schemas.ts +78 -0
- package/src/workbase/worktree-command.test.ts +61 -0
- package/src/workbase/worktree-command.ts +52 -0
- package/src/commands/base.test.ts +0 -198
- package/src/commands/base.ts +0 -198
- package/src/commands/clean.test.ts +0 -299
- package/src/commands/clean.ts +0 -274
- package/src/commands/completions.test.ts +0 -51
- package/src/commands/completions.ts +0 -308
- package/src/commands/emit.integration.test.ts +0 -549
- package/src/commands/emit.test.ts +0 -576
- package/src/commands/emit.ts +0 -700
- package/src/commands/emitted.test.ts +0 -226
- package/src/commands/emitted.ts +0 -57
- package/src/commands/loop.test.ts +0 -621
- package/src/commands/loop.ts +0 -406
- package/src/commands/merge.integration.test.ts +0 -195
- package/src/commands/merge.test.ts +0 -246
- package/src/commands/merge.ts +0 -244
- package/src/commands/pull.test.ts +0 -395
- package/src/commands/pull.ts +0 -205
- package/src/commands/push.test.ts +0 -641
- package/src/commands/push.ts +0 -482
- package/src/commands/rebase.test.ts +0 -521
- package/src/commands/rebase.ts +0 -232
- package/src/commands/save.test.ts +0 -247
- package/src/commands/save.ts +0 -162
- package/src/commands/source.test.ts +0 -195
- package/src/commands/source.ts +0 -72
- package/src/commands/switch.test.ts +0 -194
- package/src/commands/switch.ts +0 -84
- package/src/commands/task-branching.test.ts +0 -633
- package/src/commands/task-continue.test.ts +0 -311
- package/src/commands/task-edit.test.ts +0 -235
- package/src/commands/task-main.test.ts +0 -1066
- package/src/commands/task-squash.test.ts +0 -266
- package/src/commands/tasks.test.ts +0 -391
- package/src/commands/tasks.ts +0 -161
- package/src/commands/template-delete.test.ts +0 -178
- package/src/commands/template-delete.ts +0 -98
- package/src/commands/template-list.test.ts +0 -135
- package/src/commands/template-list.ts +0 -87
- package/src/commands/template-view.test.ts +0 -158
- package/src/commands/template-view.ts +0 -86
- package/src/commands/template.test.ts +0 -32
- package/src/commands/template.ts +0 -96
- package/src/commands/use.test.ts +0 -87
- package/src/commands/use.ts +0 -97
- package/src/constants.ts +0 -10
- package/src/errors.ts +0 -17
- package/src/schemas.ts +0 -33
- package/src/services/AgencyMetadataService.ts +0 -311
- package/src/services/ClaudeService.test.ts +0 -184
- package/src/services/ClaudeService.ts +0 -91
- package/src/services/ConfigService.ts +0 -115
- package/src/services/FilterRepoService.ts +0 -164
- package/src/services/FormatterService.test.ts +0 -432
- package/src/services/FormatterService.ts +0 -219
- package/src/services/GitService.ts +0 -1187
- package/src/services/MockFilterRepoService.ts +0 -140
- package/src/services/OpencodeService.ts +0 -263
- package/src/services/PromptService.ts +0 -183
- package/src/services/TemplateService.ts +0 -81
- package/src/types/native-exec.d.ts +0 -8
- package/src/utils/colors.ts +0 -178
- package/src/utils/git-path.ts +0 -6
- package/src/utils/glob.test.ts +0 -161
- package/src/utils/glob.ts +0 -78
- package/src/utils/paths.ts +0 -51
- package/src/utils/pr-branch.test.ts +0 -376
- package/src/utils/pr-branch.ts +0 -508
- package/src/utils/spinner.test.ts +0 -45
- package/src/utils/spinner.ts +0 -96
- package/src/utils/task-parser.test.ts +0 -172
- package/src/utils/task-parser.ts +0 -93
- package/templates/AGENCY.md +0 -22
- package/templates/AGENTS.md +0 -11
- package/templates/CLAUDE.md +0 -3
- package/templates/TASK.md +0 -5
- package/templates/opencode.json +0 -4
|
@@ -1,246 +0,0 @@
|
|
|
1
|
-
import { test, expect, describe, beforeEach, afterEach } from "bun:test"
|
|
2
|
-
import { join } from "path"
|
|
3
|
-
import { merge } from "./merge"
|
|
4
|
-
import { emit } from "./emit"
|
|
5
|
-
import { task } from "./task"
|
|
6
|
-
import {
|
|
7
|
-
createTempDir,
|
|
8
|
-
cleanupTempDir,
|
|
9
|
-
initGitRepo,
|
|
10
|
-
initAgency,
|
|
11
|
-
getGitOutput,
|
|
12
|
-
getCurrentBranch,
|
|
13
|
-
createCommit,
|
|
14
|
-
checkoutBranch,
|
|
15
|
-
createBranch,
|
|
16
|
-
addAndCommit,
|
|
17
|
-
setupRemote,
|
|
18
|
-
deleteBranch,
|
|
19
|
-
runTestEffect,
|
|
20
|
-
} from "../test-utils"
|
|
21
|
-
|
|
22
|
-
describe("merge command", () => {
|
|
23
|
-
let tempDir: string
|
|
24
|
-
let originalCwd: string
|
|
25
|
-
|
|
26
|
-
beforeEach(async () => {
|
|
27
|
-
tempDir = await createTempDir()
|
|
28
|
-
originalCwd = process.cwd()
|
|
29
|
-
process.chdir(tempDir)
|
|
30
|
-
|
|
31
|
-
// Set config path to non-existent file to use defaults
|
|
32
|
-
process.env.AGENCY_CONFIG_PATH = join(tempDir, "non-existent-config.json")
|
|
33
|
-
// Set config dir to temp dir to avoid picking up user's config files
|
|
34
|
-
process.env.AGENCY_CONFIG_DIR = await createTempDir()
|
|
35
|
-
|
|
36
|
-
// Initialize git repo with main branch (already includes initial commit)
|
|
37
|
-
await initGitRepo(tempDir)
|
|
38
|
-
|
|
39
|
-
// Set up origin for git-filter-repo
|
|
40
|
-
await setupRemote(tempDir, "origin", tempDir)
|
|
41
|
-
|
|
42
|
-
// Create a source branch (with agency-- prefix per new default config)
|
|
43
|
-
await createBranch(tempDir, "agency--feature")
|
|
44
|
-
|
|
45
|
-
// Initialize AGENTS.md on feature branch
|
|
46
|
-
await initAgency(tempDir, "test")
|
|
47
|
-
|
|
48
|
-
await runTestEffect(task({ silent: true, fromCurrent: true }))
|
|
49
|
-
|
|
50
|
-
// Ensure agency.json has baseBranch set (task should auto-detect it, but ensure it's there)
|
|
51
|
-
const agencyJsonPath = join(tempDir, "agency.json")
|
|
52
|
-
const agencyJson = await Bun.file(agencyJsonPath).json()
|
|
53
|
-
if (!agencyJson.baseBranch) {
|
|
54
|
-
agencyJson.baseBranch = "origin/main"
|
|
55
|
-
await Bun.write(
|
|
56
|
-
agencyJsonPath,
|
|
57
|
-
JSON.stringify(agencyJson, null, 2) + "\n",
|
|
58
|
-
)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
await addAndCommit(tempDir, "AGENTS.md agency.json", "Add AGENTS.md")
|
|
62
|
-
|
|
63
|
-
// Create another commit on feature branch
|
|
64
|
-
await createCommit(tempDir, "Feature work")
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
afterEach(async () => {
|
|
68
|
-
process.chdir(originalCwd)
|
|
69
|
-
delete process.env.AGENCY_CONFIG_PATH
|
|
70
|
-
if (process.env.AGENCY_CONFIG_DIR) {
|
|
71
|
-
await cleanupTempDir(process.env.AGENCY_CONFIG_DIR)
|
|
72
|
-
delete process.env.AGENCY_CONFIG_DIR
|
|
73
|
-
}
|
|
74
|
-
await cleanupTempDir(tempDir)
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
describe("error handling", () => {
|
|
78
|
-
test("throws error when not in a git repository", async () => {
|
|
79
|
-
const nonGitDir = await createTempDir()
|
|
80
|
-
process.chdir(nonGitDir)
|
|
81
|
-
|
|
82
|
-
await expect(runTestEffect(merge({ silent: true }))).rejects.toThrow(
|
|
83
|
-
"Not in a git repository",
|
|
84
|
-
)
|
|
85
|
-
|
|
86
|
-
await cleanupTempDir(nonGitDir)
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
test("throws error if base branch does not exist locally", async () => {
|
|
90
|
-
// Create emit branch (skipFilter for speed since we're testing error handling)
|
|
91
|
-
await runTestEffect(emit({ silent: true, skipFilter: true }))
|
|
92
|
-
|
|
93
|
-
// Delete main branch (the base)
|
|
94
|
-
await checkoutBranch(tempDir, "feature")
|
|
95
|
-
await deleteBranch(tempDir, "main", true)
|
|
96
|
-
|
|
97
|
-
// Try to merge - should fail
|
|
98
|
-
await expect(
|
|
99
|
-
runTestEffect(merge({ silent: true, skipFilter: true })),
|
|
100
|
-
).rejects.toThrow("does not exist locally")
|
|
101
|
-
})
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
describe("silent mode", () => {
|
|
105
|
-
test("silent flag suppresses output", async () => {
|
|
106
|
-
const logs: string[] = []
|
|
107
|
-
const originalLog = console.log
|
|
108
|
-
console.log = (...args: any[]) => logs.push(args.join(" "))
|
|
109
|
-
|
|
110
|
-
await runTestEffect(merge({ silent: true, skipFilter: true }))
|
|
111
|
-
|
|
112
|
-
console.log = originalLog
|
|
113
|
-
|
|
114
|
-
expect(logs.length).toBe(0)
|
|
115
|
-
})
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
describe("squash merge", () => {
|
|
119
|
-
test("performs squash merge when --squash flag is set", async () => {
|
|
120
|
-
// We're on feature branch (source)
|
|
121
|
-
const currentBranch = await getCurrentBranch(tempDir)
|
|
122
|
-
expect(currentBranch).toBe("agency--feature")
|
|
123
|
-
|
|
124
|
-
// Run merge with squash flag (skipFilter for speed, we're testing squash behavior)
|
|
125
|
-
await runTestEffect(
|
|
126
|
-
merge({ silent: true, squash: true, skipFilter: true }),
|
|
127
|
-
)
|
|
128
|
-
|
|
129
|
-
// Should be on main branch after merge
|
|
130
|
-
const afterMergeBranch = await getCurrentBranch(tempDir)
|
|
131
|
-
expect(afterMergeBranch).toBe("main")
|
|
132
|
-
|
|
133
|
-
// Check that changes are staged but not committed
|
|
134
|
-
const status = await getGitOutput(tempDir, ["status", "--porcelain"])
|
|
135
|
-
|
|
136
|
-
// Staged changes should be present (indicated by status codes in first column)
|
|
137
|
-
expect(status.trim().length).toBeGreaterThan(0)
|
|
138
|
-
|
|
139
|
-
// Get the log to verify no merge commit was created
|
|
140
|
-
const log = await getGitOutput(tempDir, ["log", "--oneline", "-5"])
|
|
141
|
-
|
|
142
|
-
// Should not contain a merge commit message
|
|
143
|
-
expect(log).not.toContain("Merge branch")
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
test("performs regular merge when --squash flag is not set", async () => {
|
|
147
|
-
// We're on feature branch (source)
|
|
148
|
-
const currentBranch = await getCurrentBranch(tempDir)
|
|
149
|
-
expect(currentBranch).toBe("agency--feature")
|
|
150
|
-
|
|
151
|
-
// Run merge without squash flag (skipFilter for speed, we're testing merge behavior)
|
|
152
|
-
await runTestEffect(
|
|
153
|
-
merge({ silent: true, squash: false, skipFilter: true }),
|
|
154
|
-
)
|
|
155
|
-
|
|
156
|
-
// Should be on main branch after merge
|
|
157
|
-
const afterMergeBranch = await getCurrentBranch(tempDir)
|
|
158
|
-
expect(afterMergeBranch).toBe("main")
|
|
159
|
-
|
|
160
|
-
// With regular merge (not squash), there should be no staged changes
|
|
161
|
-
const status = await getGitOutput(tempDir, ["status", "--porcelain"])
|
|
162
|
-
|
|
163
|
-
// No staged changes - everything should be committed
|
|
164
|
-
expect(status.trim().length).toBe(0)
|
|
165
|
-
|
|
166
|
-
// Get the log to verify commits were included
|
|
167
|
-
const log = await getGitOutput(tempDir, ["log", "--oneline", "-5"])
|
|
168
|
-
|
|
169
|
-
// Should contain the feature work commit (regular merge includes all commits)
|
|
170
|
-
expect(log).toContain("Feature work")
|
|
171
|
-
})
|
|
172
|
-
})
|
|
173
|
-
|
|
174
|
-
describe("push flag", () => {
|
|
175
|
-
test("pushes base branch to origin when --push flag is set", async () => {
|
|
176
|
-
// We're on feature branch (source)
|
|
177
|
-
const currentBranch = await getCurrentBranch(tempDir)
|
|
178
|
-
expect(currentBranch).toBe("agency--feature")
|
|
179
|
-
|
|
180
|
-
// Get the current commit on main before merge
|
|
181
|
-
await checkoutBranch(tempDir, "main")
|
|
182
|
-
const beforeCommit = (
|
|
183
|
-
await getGitOutput(tempDir, ["rev-parse", "HEAD"])
|
|
184
|
-
).trim()
|
|
185
|
-
|
|
186
|
-
// Go back to feature branch
|
|
187
|
-
await checkoutBranch(tempDir, "agency--feature")
|
|
188
|
-
|
|
189
|
-
// Run merge with push flag (skipFilter for speed, we're testing push behavior)
|
|
190
|
-
await runTestEffect(merge({ silent: true, push: true, skipFilter: true }))
|
|
191
|
-
|
|
192
|
-
// Should be on main branch after merge
|
|
193
|
-
const afterMergeBranch = await getCurrentBranch(tempDir)
|
|
194
|
-
expect(afterMergeBranch).toBe("main")
|
|
195
|
-
|
|
196
|
-
// Get the current commit on main after merge
|
|
197
|
-
const afterCommit = (
|
|
198
|
-
await getGitOutput(tempDir, ["rev-parse", "HEAD"])
|
|
199
|
-
).trim()
|
|
200
|
-
|
|
201
|
-
// The commit should have changed (merge happened)
|
|
202
|
-
expect(afterCommit).not.toBe(beforeCommit)
|
|
203
|
-
|
|
204
|
-
// Verify that origin/main points to the same commit as local main
|
|
205
|
-
const originMainCommit = (
|
|
206
|
-
await getGitOutput(tempDir, ["rev-parse", "origin/main"])
|
|
207
|
-
).trim()
|
|
208
|
-
|
|
209
|
-
// origin/main should be at the same commit as local main (push succeeded)
|
|
210
|
-
expect(originMainCommit).toBe(afterCommit)
|
|
211
|
-
})
|
|
212
|
-
|
|
213
|
-
test("does not push when --push flag is not set", async () => {
|
|
214
|
-
// We're on feature branch (source)
|
|
215
|
-
const currentBranch = await getCurrentBranch(tempDir)
|
|
216
|
-
expect(currentBranch).toBe("agency--feature")
|
|
217
|
-
|
|
218
|
-
// Get the current commit on origin/main before merge
|
|
219
|
-
const beforeOriginCommit = (
|
|
220
|
-
await getGitOutput(tempDir, ["rev-parse", "origin/main"])
|
|
221
|
-
).trim()
|
|
222
|
-
|
|
223
|
-
// Run merge without push flag (skipFilter for speed, we're testing push behavior)
|
|
224
|
-
await runTestEffect(merge({ silent: true, skipFilter: true }))
|
|
225
|
-
|
|
226
|
-
// Should be on main branch after merge
|
|
227
|
-
const afterMergeBranch = await getCurrentBranch(tempDir)
|
|
228
|
-
expect(afterMergeBranch).toBe("main")
|
|
229
|
-
|
|
230
|
-
// Get the current commit on origin/main after merge
|
|
231
|
-
const afterOriginCommit = (
|
|
232
|
-
await getGitOutput(tempDir, ["rev-parse", "origin/main"])
|
|
233
|
-
).trim()
|
|
234
|
-
|
|
235
|
-
// origin/main should still be at the same commit (no push happened)
|
|
236
|
-
expect(afterOriginCommit).toBe(beforeOriginCommit)
|
|
237
|
-
|
|
238
|
-
// But local main should have moved forward
|
|
239
|
-
const localMainCommit = (
|
|
240
|
-
await getGitOutput(tempDir, ["rev-parse", "HEAD"])
|
|
241
|
-
).trim()
|
|
242
|
-
|
|
243
|
-
expect(localMainCommit).not.toBe(beforeOriginCommit)
|
|
244
|
-
})
|
|
245
|
-
})
|
|
246
|
-
})
|
package/src/commands/merge.ts
DELETED
|
@@ -1,244 +0,0 @@
|
|
|
1
|
-
import { Effect } from "effect"
|
|
2
|
-
import type { BaseCommandOptions } from "../utils/command"
|
|
3
|
-
import { GitService, GitCommandError } from "../services/GitService"
|
|
4
|
-
import { ConfigService } from "../services/ConfigService"
|
|
5
|
-
import { resolveBranchPairWithAgencyJson } from "../utils/pr-branch"
|
|
6
|
-
import { FileSystemService } from "../services/FileSystemService"
|
|
7
|
-
import { emit } from "./emit"
|
|
8
|
-
import highlight, { done } from "../utils/colors"
|
|
9
|
-
import {
|
|
10
|
-
createLoggers,
|
|
11
|
-
ensureGitRepo,
|
|
12
|
-
ensureBranchExists,
|
|
13
|
-
getBaseBranchFromMetadataEffect,
|
|
14
|
-
getBaseBranchFromBranch,
|
|
15
|
-
getRemoteName,
|
|
16
|
-
} from "../utils/effect"
|
|
17
|
-
|
|
18
|
-
interface MergeOptions extends BaseCommandOptions {
|
|
19
|
-
squash?: boolean
|
|
20
|
-
push?: boolean
|
|
21
|
-
skipFilter?: boolean
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// Helper to merge a branch using git
|
|
25
|
-
const mergeBranchEffect = (
|
|
26
|
-
gitRoot: string,
|
|
27
|
-
branch: string,
|
|
28
|
-
squash: boolean = false,
|
|
29
|
-
) =>
|
|
30
|
-
Effect.gen(function* () {
|
|
31
|
-
const git = yield* GitService
|
|
32
|
-
|
|
33
|
-
const result = yield* git.merge(gitRoot, branch, { squash })
|
|
34
|
-
|
|
35
|
-
if (result.exitCode !== 0) {
|
|
36
|
-
return yield* Effect.fail(
|
|
37
|
-
new GitCommandError({
|
|
38
|
-
command: `git merge${squash ? " --squash" : ""} ${branch}`,
|
|
39
|
-
exitCode: result.exitCode,
|
|
40
|
-
stderr: result.stderr,
|
|
41
|
-
}),
|
|
42
|
-
)
|
|
43
|
-
}
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
export const merge = (options: MergeOptions = {}) =>
|
|
47
|
-
Effect.gen(function* () {
|
|
48
|
-
const { squash = false, push = false, verbose = false } = options
|
|
49
|
-
const { log, verboseLog } = createLoggers(options)
|
|
50
|
-
|
|
51
|
-
const git = yield* GitService
|
|
52
|
-
const configService = yield* ConfigService
|
|
53
|
-
|
|
54
|
-
const gitRoot = yield* ensureGitRepo()
|
|
55
|
-
|
|
56
|
-
const config = yield* configService.loadConfig()
|
|
57
|
-
const currentBranch = yield* git.getCurrentBranch(gitRoot)
|
|
58
|
-
|
|
59
|
-
verboseLog(`Current branch: ${highlight.branch(currentBranch)}`)
|
|
60
|
-
|
|
61
|
-
// Use proper branch resolution with agency.json support
|
|
62
|
-
const fs = yield* FileSystemService
|
|
63
|
-
const branchInfo = yield* resolveBranchPairWithAgencyJson(
|
|
64
|
-
gitRoot,
|
|
65
|
-
currentBranch,
|
|
66
|
-
config.sourceBranchPattern,
|
|
67
|
-
config.emitBranch,
|
|
68
|
-
)
|
|
69
|
-
|
|
70
|
-
let emitBranchToMerge: string
|
|
71
|
-
let baseBranchToMergeInto: string
|
|
72
|
-
|
|
73
|
-
if (branchInfo.isOnEmitBranch) {
|
|
74
|
-
const sourceBranch = branchInfo.sourceBranch
|
|
75
|
-
verboseLog(
|
|
76
|
-
`Current branch appears to be an emit branch for source: ${highlight.branch(sourceBranch)}`,
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
yield* ensureBranchExists(
|
|
80
|
-
gitRoot,
|
|
81
|
-
sourceBranch,
|
|
82
|
-
`Current branch ${highlight.branch(currentBranch)} appears to be an emit branch, but source branch ${highlight.branch(sourceBranch)} does not exist.\n` +
|
|
83
|
-
`Cannot merge without a valid source branch.`,
|
|
84
|
-
)
|
|
85
|
-
|
|
86
|
-
// Get the base branch from the source branch's agency.json using git show
|
|
87
|
-
// No need to checkout the branch - we can read the file directly
|
|
88
|
-
const configuredBase = yield* getBaseBranchFromBranch(
|
|
89
|
-
gitRoot,
|
|
90
|
-
sourceBranch,
|
|
91
|
-
)
|
|
92
|
-
|
|
93
|
-
if (!configuredBase) {
|
|
94
|
-
return yield* Effect.fail(
|
|
95
|
-
new Error(
|
|
96
|
-
`No base branch configured for ${highlight.branch(sourceBranch)}.\n` +
|
|
97
|
-
`Please switch to ${highlight.branch(sourceBranch)} and run: agency base set <branch>`,
|
|
98
|
-
),
|
|
99
|
-
)
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
verboseLog(`Configured base branch: ${highlight.branch(configuredBase)}`)
|
|
103
|
-
|
|
104
|
-
// For git operations (checkout/merge), use local branch name
|
|
105
|
-
baseBranchToMergeInto = git.stripRemotePrefix(configuredBase)
|
|
106
|
-
|
|
107
|
-
// Verify local base branch exists
|
|
108
|
-
yield* ensureBranchExists(
|
|
109
|
-
gitRoot,
|
|
110
|
-
baseBranchToMergeInto,
|
|
111
|
-
`Base branch ${highlight.branch(baseBranchToMergeInto)} does not exist locally.\n` +
|
|
112
|
-
`You may need to checkout the branch first or update your base branch configuration.`,
|
|
113
|
-
)
|
|
114
|
-
|
|
115
|
-
emitBranchToMerge = currentBranch
|
|
116
|
-
} else {
|
|
117
|
-
// We're on a source branch - need to create/update emit branch first
|
|
118
|
-
verboseLog(
|
|
119
|
-
`Current branch appears to be a source branch, will create emit branch first`,
|
|
120
|
-
)
|
|
121
|
-
|
|
122
|
-
// Use the emit branch from branchInfo we already computed
|
|
123
|
-
const emitBranch = branchInfo.emitBranch
|
|
124
|
-
const emitExists = yield* git.branchExists(gitRoot, emitBranch)
|
|
125
|
-
|
|
126
|
-
if (emitExists) {
|
|
127
|
-
verboseLog(
|
|
128
|
-
`Emit branch ${highlight.branch(emitBranch)} already exists, will recreate it`,
|
|
129
|
-
)
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Run 'agency emit' to create/update the emit branch
|
|
133
|
-
verboseLog(`Creating emit branch ${highlight.branch(emitBranch)}...`)
|
|
134
|
-
yield* emit({ silent: true, verbose, skipFilter: options.skipFilter })
|
|
135
|
-
|
|
136
|
-
// emit() leaves us on the source branch, so we can read agency.json directly
|
|
137
|
-
const configuredBase = yield* getBaseBranchFromMetadataEffect(gitRoot)
|
|
138
|
-
if (!configuredBase) {
|
|
139
|
-
return yield* Effect.fail(
|
|
140
|
-
new Error(
|
|
141
|
-
`No base branch configured for ${highlight.branch(currentBranch)}.\n` +
|
|
142
|
-
`Please set one with: agency base set <branch>`,
|
|
143
|
-
),
|
|
144
|
-
)
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
verboseLog(`Configured base branch: ${highlight.branch(configuredBase)}`)
|
|
148
|
-
|
|
149
|
-
// For git operations (checkout/merge), use local branch name
|
|
150
|
-
baseBranchToMergeInto = git.stripRemotePrefix(configuredBase)
|
|
151
|
-
|
|
152
|
-
// Verify local base branch exists
|
|
153
|
-
yield* ensureBranchExists(
|
|
154
|
-
gitRoot,
|
|
155
|
-
baseBranchToMergeInto,
|
|
156
|
-
`Base branch ${highlight.branch(baseBranchToMergeInto)} does not exist locally.\n` +
|
|
157
|
-
`You may need to checkout the branch first or update your base branch configuration.`,
|
|
158
|
-
)
|
|
159
|
-
|
|
160
|
-
emitBranchToMerge = emitBranch
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Now switch to the base branch
|
|
164
|
-
verboseLog(`Switching to ${highlight.branch(baseBranchToMergeInto)}...`)
|
|
165
|
-
yield* git.checkoutBranch(gitRoot, baseBranchToMergeInto)
|
|
166
|
-
|
|
167
|
-
// Merge the emit branch
|
|
168
|
-
verboseLog(
|
|
169
|
-
`Merging ${highlight.branch(emitBranchToMerge)} into ${highlight.branch(baseBranchToMergeInto)}${squash ? " (squash)" : ""}...`,
|
|
170
|
-
)
|
|
171
|
-
yield* mergeBranchEffect(gitRoot, emitBranchToMerge, squash)
|
|
172
|
-
|
|
173
|
-
if (squash) {
|
|
174
|
-
log(done(`Squash merged (awaiting commit)`))
|
|
175
|
-
} else {
|
|
176
|
-
log(done("Merged"))
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// Push the base branch if --push flag is set
|
|
180
|
-
if (push) {
|
|
181
|
-
const remote = yield* getRemoteName(gitRoot)
|
|
182
|
-
verboseLog(
|
|
183
|
-
`Pushing ${highlight.branch(baseBranchToMergeInto)} to ${highlight.remote(remote)}...`,
|
|
184
|
-
)
|
|
185
|
-
|
|
186
|
-
const pushResult = yield* git.push(gitRoot, remote, baseBranchToMergeInto)
|
|
187
|
-
|
|
188
|
-
if (pushResult.exitCode !== 0) {
|
|
189
|
-
return yield* Effect.fail(
|
|
190
|
-
new GitCommandError({
|
|
191
|
-
command: `git push ${remote} ${baseBranchToMergeInto}`,
|
|
192
|
-
exitCode: pushResult.exitCode,
|
|
193
|
-
stderr: pushResult.stderr,
|
|
194
|
-
}),
|
|
195
|
-
)
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
log(done(`Pushed to ${highlight.remote(remote)}`))
|
|
199
|
-
}
|
|
200
|
-
})
|
|
201
|
-
|
|
202
|
-
export const help = `
|
|
203
|
-
Usage: agency merge [options]
|
|
204
|
-
|
|
205
|
-
Merge the current emit branch into the configured base branch.
|
|
206
|
-
|
|
207
|
-
This command handles two scenarios:
|
|
208
|
-
1. If on an emit branch (e.g., feature--PR): Switches to the base branch and merges the emit branch
|
|
209
|
-
2. If on a source branch (e.g., feature): Runs 'agency emit' first to create/update the emit branch, then merges it
|
|
210
|
-
|
|
211
|
-
Behavior:
|
|
212
|
-
- Automatically detects whether you're on a source or emit branch
|
|
213
|
-
- Retrieves the configured base branch (e.g., 'main') from git config
|
|
214
|
-
- Switches to the base branch
|
|
215
|
-
- Merges the emit branch into the base branch
|
|
216
|
-
- Leaves you on the base branch after merge
|
|
217
|
-
|
|
218
|
-
This is useful for local development workflows where you want to test merging
|
|
219
|
-
your clean emit branch (without AGENTS.md modifications) into the base branch
|
|
220
|
-
before pushing.
|
|
221
|
-
|
|
222
|
-
Prerequisites:
|
|
223
|
-
- Must be on either a source branch or its corresponding emit branch
|
|
224
|
-
- Base branch must exist locally
|
|
225
|
-
- For source branches: Must have a corresponding emit branch or be able to create one
|
|
226
|
-
|
|
227
|
-
Options:
|
|
228
|
-
--squash # Use squash merge instead of regular merge
|
|
229
|
-
--push # Push the base branch to origin after merging
|
|
230
|
-
|
|
231
|
-
Examples:
|
|
232
|
-
agency merge # From source branch: creates emit branch then merges
|
|
233
|
-
agency merge --squash # Squash merge (stages changes, requires manual commit)
|
|
234
|
-
agency merge --push # Merge and push the base branch to origin
|
|
235
|
-
|
|
236
|
-
Notes:
|
|
237
|
-
- The command determines the base branch from git config (agency.pr.<branch>.baseBranch)
|
|
238
|
-
- If you're on a source branch, 'agency emit' is run automatically
|
|
239
|
-
- The emit branch must have both a source branch and base branch configured
|
|
240
|
-
- After merge, you remain on the base branch
|
|
241
|
-
- Merge conflicts must be resolved manually if they occur
|
|
242
|
-
- With --squash, changes are staged but not committed (you must commit manually)
|
|
243
|
-
- With --push, the base branch is pushed to origin after a successful merge
|
|
244
|
-
`
|