| 40 | argsSchema = GitCreatePrArgs; |
| 41 | |
| 42 | async execute(args: z.infer<typeof GitCreatePrArgs>, ctx: ToolContext): Promise<ToolResult> { |
| 43 | if (!await isGitRepo(ctx.cwd, ctx.signal)) { |
| 44 | return { content: '[NOT_A_GIT_REPO] Current directory is not inside a git working tree.', isError: true }; |
| 45 | } |
| 46 | |
| 47 | // Verify gh is available |
| 48 | const ghAvail = await runOnce('gh', ['--version'], ctx.cwd, ctx.signal); |
| 49 | if (ghAvail.exitCode !== 0) { |
| 50 | return { |
| 51 | content: '[GH_NOT_INSTALLED] The GitHub CLI (`gh`) is not installed or not in PATH. ' + |
| 52 | 'Install it from https://cli.github.com/, then run `gh auth login`. ' + |
| 53 | 'For non-GitHub remotes (GitLab, Gitea, etc.), use `bash` directly with the relevant tool.', |
| 54 | isError: true, |
| 55 | }; |
| 56 | } |
| 57 | |
| 58 | // Verify gh is authenticated |
| 59 | const authR = await runOnce('gh', ['auth', 'status'], ctx.cwd, ctx.signal); |
| 60 | if (authR.exitCode !== 0) { |
| 61 | return { |
| 62 | content: '[GH_NOT_AUTHENTICATED] `gh` is installed but not authenticated. Run `gh auth login` first.\n' + |
| 63 | (authR.stderr.trim() || authR.stdout.trim()), |
| 64 | isError: true, |
| 65 | }; |
| 66 | } |
| 67 | |
| 68 | // Get current branch |
| 69 | const branchR = await git(['rev-parse', '--abbrev-ref', 'HEAD'], { cwd: ctx.cwd, signal: ctx.signal }); |
| 70 | if (branchR.exitCode !== 0 || branchR.stdout.trim() === 'HEAD') { |
| 71 | return { |
| 72 | content: '[DETACHED_HEAD] Cannot create a PR from a detached HEAD. Check out a branch first.', |
| 73 | isError: true, |
| 74 | }; |
| 75 | } |
| 76 | const branch = branchR.stdout.trim(); |
| 77 | |
| 78 | // Push if requested and no upstream is set |
| 79 | const doPush = args.push !== false; |
| 80 | if (doPush) { |
| 81 | const upstreamR = await git(['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{u}'], { cwd: ctx.cwd, signal: ctx.signal }); |
| 82 | const hasUpstream = upstreamR.exitCode === 0 && upstreamR.stdout.trim() !== ''; |
| 83 | const pushArgs = hasUpstream ? ['push'] : ['push', '--set-upstream', 'origin', branch]; |
| 84 | const pushR = await git(pushArgs, { cwd: ctx.cwd, signal: ctx.signal, timeoutMs: 120_000 }); |
| 85 | if (pushR.exitCode !== 0) { |
| 86 | return { |
| 87 | content: `[PUSH_FAILED] Could not push branch '${branch}'.\n${pushR.stderr.trim()}`, |
| 88 | isError: true, |
| 89 | }; |
| 90 | } |
| 91 | } |
| 92 | |
| 93 | // Build gh pr create command |
| 94 | const prArgs = ['pr', 'create', '--title', args.title, '--body', args.body ?? '']; |
| 95 | if (args.base) prArgs.push('--base', args.base); |
| 96 | if (args.draft) prArgs.push('--draft'); |
| 97 | if (args.reviewers) for (const r of args.reviewers) prArgs.push('--reviewer', r); |
| 98 | if (args.labels) for (const l of args.labels) prArgs.push('--label', l); |
| 99 | |