@markjaquith/agency 1.11.0 → 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 -415
- 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 -518
- package/src/commands/push.ts +0 -434
- 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 -1183
- 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
package/src/commands/push.ts
DELETED
|
@@ -1,434 +0,0 @@
|
|
|
1
|
-
import { Effect, Either } from "effect"
|
|
2
|
-
import type { BaseCommandOptions } from "../utils/command"
|
|
3
|
-
import { GitCommandError, GitService } from "../services/GitService"
|
|
4
|
-
import { ConfigService } from "../services/ConfigService"
|
|
5
|
-
import { resolveBranchPairWithAgencyJson } from "../utils/pr-branch"
|
|
6
|
-
import { emit } from "./emit"
|
|
7
|
-
import highlight, { done } from "../utils/colors"
|
|
8
|
-
import {
|
|
9
|
-
createLoggers,
|
|
10
|
-
ensureGitRepo,
|
|
11
|
-
getRemoteName,
|
|
12
|
-
withBranchProtection,
|
|
13
|
-
} from "../utils/effect"
|
|
14
|
-
import { withSpinner } from "../utils/spinner"
|
|
15
|
-
import { spawnProcess } from "../utils/process"
|
|
16
|
-
|
|
17
|
-
interface PushOptions extends BaseCommandOptions {
|
|
18
|
-
baseBranch?: string
|
|
19
|
-
emit?: string
|
|
20
|
-
branch?: string // Deprecated: use emit instead
|
|
21
|
-
force?: boolean
|
|
22
|
-
noVerify?: boolean
|
|
23
|
-
pr?: boolean
|
|
24
|
-
skipFilter?: boolean
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const getPushFailureStderr = (error: unknown): string => {
|
|
28
|
-
if (error instanceof GitCommandError) {
|
|
29
|
-
return error.stderr.trim()
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (
|
|
33
|
-
typeof error === "object" &&
|
|
34
|
-
error !== null &&
|
|
35
|
-
"cause" in error &&
|
|
36
|
-
(error as { cause?: unknown }).cause instanceof GitCommandError
|
|
37
|
-
) {
|
|
38
|
-
return (error as { cause: GitCommandError }).cause.stderr.trim()
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (error instanceof Error) {
|
|
42
|
-
return error.message.trim()
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return String(error).trim()
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const formatProcessOutput = (result: {
|
|
49
|
-
readonly stdout?: string
|
|
50
|
-
readonly stderr?: string
|
|
51
|
-
}): string => [result.stdout, result.stderr].filter(Boolean).join("\n").trim()
|
|
52
|
-
|
|
53
|
-
const pushFailureNeedsForce = (stderr: string): boolean => {
|
|
54
|
-
const normalized = stderr.toLowerCase()
|
|
55
|
-
|
|
56
|
-
return (
|
|
57
|
-
normalized.includes("non-fast-forward") ||
|
|
58
|
-
normalized.includes(
|
|
59
|
-
"updates were rejected because the tip of your current branch is behind",
|
|
60
|
-
) ||
|
|
61
|
-
(normalized.includes("[rejected]") && normalized.includes("fetch first"))
|
|
62
|
-
)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const formatPushFailure = (error: unknown): Error => {
|
|
66
|
-
const stderr = getPushFailureStderr(error)
|
|
67
|
-
|
|
68
|
-
if (pushFailureNeedsForce(stderr)) {
|
|
69
|
-
return new Error(
|
|
70
|
-
"Failed to push branch to remote: the remote rejected a non-fast-forward update, which usually means the branch was rebased or rewritten.\n" +
|
|
71
|
-
"Run `agency push --force` to replace the remote branch.",
|
|
72
|
-
)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return new Error(`Failed to push branch to remote: ${stderr}`)
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export const push = (options: PushOptions = {}) =>
|
|
79
|
-
Effect.gen(function* () {
|
|
80
|
-
const gitRoot = yield* ensureGitRepo()
|
|
81
|
-
|
|
82
|
-
// Wrap the entire push operation with branch protection
|
|
83
|
-
// This ensures we return to the original branch on Ctrl-C interrupt
|
|
84
|
-
yield* withBranchProtection(gitRoot, pushCore(gitRoot, options))
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
const pushCore = (gitRoot: string, options: PushOptions) =>
|
|
88
|
-
Effect.gen(function* () {
|
|
89
|
-
const { log, verboseLog } = createLoggers(options)
|
|
90
|
-
|
|
91
|
-
const git = yield* GitService
|
|
92
|
-
const configService = yield* ConfigService
|
|
93
|
-
|
|
94
|
-
// Check for uncommitted changes
|
|
95
|
-
const statusOutput = yield* git.getStatus(gitRoot)
|
|
96
|
-
|
|
97
|
-
if (statusOutput && statusOutput.trim().length > 0) {
|
|
98
|
-
return yield* Effect.fail(
|
|
99
|
-
new Error(
|
|
100
|
-
`You have uncommitted changes. Please commit or stash them before pushing.\n` +
|
|
101
|
-
`The push command performs internal checkouts that would destroy uncommitted work.\n` +
|
|
102
|
-
`Run 'git status' to see the changes.`,
|
|
103
|
-
),
|
|
104
|
-
)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
verboseLog(`Working directory is clean`)
|
|
108
|
-
|
|
109
|
-
// Load config to check emit branch pattern
|
|
110
|
-
const config = yield* configService.loadConfig()
|
|
111
|
-
|
|
112
|
-
// Get current branch
|
|
113
|
-
let sourceBranch = yield* git.getCurrentBranch(gitRoot)
|
|
114
|
-
|
|
115
|
-
// Check if we're already on an emit branch using proper branch resolution
|
|
116
|
-
const branchInfo = yield* resolveBranchPairWithAgencyJson(
|
|
117
|
-
gitRoot,
|
|
118
|
-
sourceBranch,
|
|
119
|
-
config.sourceBranchPattern,
|
|
120
|
-
config.emitBranch,
|
|
121
|
-
)
|
|
122
|
-
|
|
123
|
-
// If we're on an emit branch, switch to the source branch first
|
|
124
|
-
if (branchInfo.isOnEmitBranch) {
|
|
125
|
-
const actualSourceBranch = branchInfo.sourceBranch
|
|
126
|
-
// Check if the source branch exists
|
|
127
|
-
const sourceExists = yield* git.branchExists(gitRoot, actualSourceBranch)
|
|
128
|
-
if (sourceExists) {
|
|
129
|
-
verboseLog(
|
|
130
|
-
`Currently on emit branch ${highlight.branch(sourceBranch)}, switching to source branch ${highlight.branch(actualSourceBranch)}`,
|
|
131
|
-
)
|
|
132
|
-
yield* git.checkoutBranch(gitRoot, actualSourceBranch)
|
|
133
|
-
sourceBranch = actualSourceBranch
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
verboseLog(`Starting push workflow from ${highlight.branch(sourceBranch)}`)
|
|
138
|
-
|
|
139
|
-
// Step 1: Create emit branch (agency emit)
|
|
140
|
-
verboseLog("Step 1: Emitting...")
|
|
141
|
-
// Use the emit command for the entire emit step so push and emit stay in sync.
|
|
142
|
-
const prEffectWithOptions = emit({
|
|
143
|
-
baseBranch: options.baseBranch,
|
|
144
|
-
emit: options.emit || options.branch,
|
|
145
|
-
silent: options.silent,
|
|
146
|
-
force: options.force,
|
|
147
|
-
verbose: options.verbose,
|
|
148
|
-
skipFilter: options.skipFilter,
|
|
149
|
-
})
|
|
150
|
-
|
|
151
|
-
const prResult = yield* Effect.either(prEffectWithOptions)
|
|
152
|
-
if (Either.isLeft(prResult)) {
|
|
153
|
-
const error =
|
|
154
|
-
prResult.left instanceof Error
|
|
155
|
-
? prResult.left
|
|
156
|
-
: new Error(String(prResult.left))
|
|
157
|
-
const message = error.message || ""
|
|
158
|
-
const ignorable =
|
|
159
|
-
message === "" ||
|
|
160
|
-
message.includes("already exists") ||
|
|
161
|
-
message.includes("check if branch exists")
|
|
162
|
-
|
|
163
|
-
if (!ignorable) {
|
|
164
|
-
return yield* Effect.fail(
|
|
165
|
-
new Error(`Failed to create emit branch: ${message}`),
|
|
166
|
-
)
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
verboseLog("Emit reported existing branch; continuing")
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// Compute the emit branch name (emit() command now stays on source branch)
|
|
173
|
-
// Use the branchInfo we already computed earlier
|
|
174
|
-
const emitBranchName =
|
|
175
|
-
options.emit || options.branch || branchInfo.emitBranch
|
|
176
|
-
|
|
177
|
-
// Step 2: Push to remote (git push)
|
|
178
|
-
const remote = yield* getRemoteName(gitRoot)
|
|
179
|
-
verboseLog(
|
|
180
|
-
`Step 2: Pushing ${highlight.branch(emitBranchName)} to ${highlight.remote(remote)}...`,
|
|
181
|
-
)
|
|
182
|
-
|
|
183
|
-
// Switch to emit branch before pushing so that pre-push hooks run
|
|
184
|
-
// in the context of the emit branch (without backpack files)
|
|
185
|
-
yield* git.checkoutBranch(gitRoot, emitBranchName)
|
|
186
|
-
|
|
187
|
-
const pushEither = yield* Effect.either(
|
|
188
|
-
withSpinner(
|
|
189
|
-
pushBranchToRemoteEffect(gitRoot, emitBranchName, remote, {
|
|
190
|
-
force: options.force,
|
|
191
|
-
noVerify: options.noVerify,
|
|
192
|
-
verbose: options.verbose,
|
|
193
|
-
}),
|
|
194
|
-
{
|
|
195
|
-
text: options.force
|
|
196
|
-
? `Pushing to ${highlight.remote(remote)} (forced)`
|
|
197
|
-
: `Pushing to ${highlight.remote(remote)}`,
|
|
198
|
-
enabled: !options.silent && !options.verbose,
|
|
199
|
-
},
|
|
200
|
-
),
|
|
201
|
-
)
|
|
202
|
-
if (Either.isLeft(pushEither)) {
|
|
203
|
-
const error = formatPushFailure(pushEither.left)
|
|
204
|
-
// If push failed, switch back to source branch
|
|
205
|
-
yield* git.checkoutBranch(gitRoot, sourceBranch)
|
|
206
|
-
return yield* Effect.fail(error)
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
const usedForce = pushEither.right
|
|
210
|
-
|
|
211
|
-
if (usedForce) {
|
|
212
|
-
log(done(`Pushed to ${highlight.remote(remote)} (forced)`))
|
|
213
|
-
} else {
|
|
214
|
-
log(done(`Pushed to ${highlight.remote(remote)}`))
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// Step 3 (optional): Open GitHub PR if --pr flag is set
|
|
218
|
-
if (options.pr) {
|
|
219
|
-
verboseLog("Step 3: Opening GitHub PR...")
|
|
220
|
-
|
|
221
|
-
const ghEither = yield* Effect.either(
|
|
222
|
-
openGitHubPR(gitRoot, emitBranchName, {
|
|
223
|
-
verbose: options.verbose,
|
|
224
|
-
}),
|
|
225
|
-
)
|
|
226
|
-
|
|
227
|
-
if (Either.isLeft(ghEither)) {
|
|
228
|
-
const error = ghEither.left
|
|
229
|
-
// Don't fail the entire command if gh fails, just warn
|
|
230
|
-
console.error(
|
|
231
|
-
`⚠ Failed to open GitHub PR: ${error instanceof Error ? error.message : String(error)}`,
|
|
232
|
-
)
|
|
233
|
-
} else {
|
|
234
|
-
log(done("Opened GitHub PR in browser"))
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// Switch back to source branch
|
|
239
|
-
yield* git.checkoutBranch(gitRoot, sourceBranch)
|
|
240
|
-
})
|
|
241
|
-
|
|
242
|
-
// Helper: Push branch to remote with optional force and retry logic
|
|
243
|
-
const pushBranchToRemoteEffect = (
|
|
244
|
-
gitRoot: string,
|
|
245
|
-
branchName: string,
|
|
246
|
-
remote: string,
|
|
247
|
-
options: {
|
|
248
|
-
readonly force?: boolean
|
|
249
|
-
readonly noVerify?: boolean
|
|
250
|
-
readonly verbose?: boolean
|
|
251
|
-
},
|
|
252
|
-
) =>
|
|
253
|
-
Effect.gen(function* () {
|
|
254
|
-
const git = yield* GitService
|
|
255
|
-
const { force = false, noVerify = false } = options
|
|
256
|
-
|
|
257
|
-
// Try pushing without force first
|
|
258
|
-
const pushResult = yield* git
|
|
259
|
-
.push(gitRoot, remote, branchName, { setUpstream: true, noVerify })
|
|
260
|
-
.pipe(
|
|
261
|
-
Effect.catchAll((error: any) =>
|
|
262
|
-
Effect.succeed({
|
|
263
|
-
exitCode: error.exitCode ?? -1,
|
|
264
|
-
stdout: "",
|
|
265
|
-
stderr: error.stderr ?? String(error),
|
|
266
|
-
}),
|
|
267
|
-
),
|
|
268
|
-
)
|
|
269
|
-
|
|
270
|
-
let usedForce = false
|
|
271
|
-
|
|
272
|
-
// If push failed, check if we should retry with --force
|
|
273
|
-
if (pushResult.exitCode !== 0) {
|
|
274
|
-
const stderr = formatProcessOutput(pushResult)
|
|
275
|
-
|
|
276
|
-
// Check if this is a force-push-needed error
|
|
277
|
-
const needsForce = pushFailureNeedsForce(stderr)
|
|
278
|
-
|
|
279
|
-
if (needsForce && force) {
|
|
280
|
-
// User provided --force flag, retry with force
|
|
281
|
-
const forceResult = yield* git
|
|
282
|
-
.push(gitRoot, remote, branchName, {
|
|
283
|
-
setUpstream: true,
|
|
284
|
-
force: true,
|
|
285
|
-
noVerify,
|
|
286
|
-
})
|
|
287
|
-
.pipe(
|
|
288
|
-
Effect.catchAll((error: any) =>
|
|
289
|
-
Effect.succeed({
|
|
290
|
-
exitCode: error.exitCode ?? -1,
|
|
291
|
-
stdout: "",
|
|
292
|
-
stderr: error.stderr ?? String(error),
|
|
293
|
-
}),
|
|
294
|
-
),
|
|
295
|
-
)
|
|
296
|
-
|
|
297
|
-
if (forceResult.exitCode !== 0) {
|
|
298
|
-
return yield* Effect.fail(
|
|
299
|
-
new Error(
|
|
300
|
-
`Failed to force push branch to remote: ${formatProcessOutput(forceResult)}`,
|
|
301
|
-
),
|
|
302
|
-
)
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
usedForce = true
|
|
306
|
-
} else if (needsForce && !force) {
|
|
307
|
-
// User didn't provide --force but it's needed
|
|
308
|
-
return yield* Effect.fail(
|
|
309
|
-
formatPushFailure(
|
|
310
|
-
new GitCommandError({
|
|
311
|
-
command: `git push -u ${remote} ${branchName}`,
|
|
312
|
-
exitCode: pushResult.exitCode,
|
|
313
|
-
stderr,
|
|
314
|
-
}),
|
|
315
|
-
),
|
|
316
|
-
)
|
|
317
|
-
} else {
|
|
318
|
-
// Some other error
|
|
319
|
-
return yield* Effect.fail(
|
|
320
|
-
formatPushFailure(
|
|
321
|
-
new GitCommandError({
|
|
322
|
-
command: `git push -u ${remote} ${branchName}`,
|
|
323
|
-
exitCode: pushResult.exitCode,
|
|
324
|
-
stderr,
|
|
325
|
-
}),
|
|
326
|
-
),
|
|
327
|
-
)
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
return usedForce
|
|
332
|
-
})
|
|
333
|
-
|
|
334
|
-
// Helper: Open GitHub PR using gh CLI
|
|
335
|
-
const openGitHubPR = (
|
|
336
|
-
gitRoot: string,
|
|
337
|
-
branchName: string,
|
|
338
|
-
options: {
|
|
339
|
-
readonly verbose?: boolean
|
|
340
|
-
},
|
|
341
|
-
) =>
|
|
342
|
-
Effect.gen(function* () {
|
|
343
|
-
const { verbose = false } = options
|
|
344
|
-
|
|
345
|
-
// Run gh pr create --web with --head to specify the emit branch
|
|
346
|
-
// Set environment variables to ensure non-interactive mode in CI
|
|
347
|
-
// Add a 3 second timeout to prevent hanging in CI environments
|
|
348
|
-
const ghEffect = spawnProcess(
|
|
349
|
-
["gh", "pr", "create", "--web", "--head", branchName],
|
|
350
|
-
{
|
|
351
|
-
cwd: gitRoot,
|
|
352
|
-
stdout: verbose ? "inherit" : "pipe",
|
|
353
|
-
stderr: "pipe",
|
|
354
|
-
env: {
|
|
355
|
-
GH_PROMPT_DISABLED: "1",
|
|
356
|
-
NO_COLOR: "1",
|
|
357
|
-
},
|
|
358
|
-
},
|
|
359
|
-
).pipe(
|
|
360
|
-
Effect.timeout("3 seconds"),
|
|
361
|
-
Effect.catchAll((error) => {
|
|
362
|
-
// Handle timeout or process errors
|
|
363
|
-
if (error._tag === "TimeoutException") {
|
|
364
|
-
return Effect.succeed({
|
|
365
|
-
exitCode: -1,
|
|
366
|
-
stdout: "",
|
|
367
|
-
stderr: "gh command timed out after 3 seconds",
|
|
368
|
-
})
|
|
369
|
-
}
|
|
370
|
-
// Handle ProcessError
|
|
371
|
-
return Effect.succeed({
|
|
372
|
-
exitCode: (error as any).exitCode ?? -1,
|
|
373
|
-
stdout: "",
|
|
374
|
-
stderr: (error as any).stderr ?? String(error),
|
|
375
|
-
})
|
|
376
|
-
}),
|
|
377
|
-
)
|
|
378
|
-
|
|
379
|
-
const ghResult = yield* ghEffect
|
|
380
|
-
|
|
381
|
-
if (ghResult.exitCode !== 0) {
|
|
382
|
-
return yield* Effect.fail(
|
|
383
|
-
new Error(`gh CLI command failed: ${ghResult.stderr.trim()}`),
|
|
384
|
-
)
|
|
385
|
-
}
|
|
386
|
-
})
|
|
387
|
-
|
|
388
|
-
export const help = `
|
|
389
|
-
Usage: agency push [base-branch] [options]
|
|
390
|
-
|
|
391
|
-
Create a emit branch, push it to remote, and return to the source branch.
|
|
392
|
-
|
|
393
|
-
This command is a convenience wrapper that runs operations in sequence:
|
|
394
|
-
1. agency emit [base-branch] - Create emit branch with backpack files reverted
|
|
395
|
-
2. git push -u origin <pr-branch> - Push emit branch to remote
|
|
396
|
-
3. gh pr create --web (optional with --pr) - Open GitHub PR in browser
|
|
397
|
-
4. git checkout <source-branch> - Switch back to source branch
|
|
398
|
-
|
|
399
|
-
The command ensures you end up back on your source branch after pushing
|
|
400
|
-
the emit branch, making it easy to continue working locally while having
|
|
401
|
-
a clean emit branch ready on the remote.
|
|
402
|
-
|
|
403
|
-
Base Branch Selection:
|
|
404
|
-
Same as 'agency emit' - see 'agency emit --help' for details
|
|
405
|
-
|
|
406
|
-
Prerequisites:
|
|
407
|
-
- git-filter-repo must be installed: brew install git-filter-repo
|
|
408
|
-
- Remote 'origin' must be configured
|
|
409
|
-
|
|
410
|
-
Arguments:
|
|
411
|
-
base-branch Base branch to compare against (e.g., origin/main)
|
|
412
|
-
If not provided, will use saved config or auto-detect
|
|
413
|
-
|
|
414
|
-
Options:
|
|
415
|
-
--emit Custom name for emit branch (defaults to pattern from config)
|
|
416
|
-
--branch (Deprecated: use --emit) Custom name for emit branch
|
|
417
|
-
-f, --force Force push to remote if branch has diverged
|
|
418
|
-
--no-verify Bypass git pre-push hooks
|
|
419
|
-
--pr Open GitHub PR in browser after pushing (requires gh CLI)
|
|
420
|
-
|
|
421
|
-
Examples:
|
|
422
|
-
agency push # Create PR, push, return to source
|
|
423
|
-
agency push origin/main # Explicitly use origin/main as base
|
|
424
|
-
agency push --force # Force push if branch has diverged
|
|
425
|
-
agency push --no-verify # Push without running pre-push hooks
|
|
426
|
-
agency push --pr # Push and open GitHub PR in browser
|
|
427
|
-
|
|
428
|
-
Notes:
|
|
429
|
-
- Must be run from a source branch (not a emit branch)
|
|
430
|
-
- Creates or recreates the emit branch
|
|
431
|
-
- Pushes with -u flag to set up tracking
|
|
432
|
-
- Automatically returns to source branch after pushing
|
|
433
|
-
- If any step fails, the command stops and reports the error
|
|
434
|
-
`
|