|
1 | 1 | package main |
2 | 2 |
|
3 | 3 | import ( |
4 | | - "context" |
5 | 4 | "fmt" |
6 | | - "os" |
7 | | - "os/exec" |
8 | 5 | "strconv" |
9 | 6 | "strings" |
10 | | - |
11 | | - iteragent "github.com/GrayCodeAI/iteragent" |
12 | | - "github.com/GrayCodeAI/iterate/internal/ui/selector" |
13 | 7 | ) |
14 | 8 |
|
15 | 9 | // --------------------------------------------------------------------------- |
@@ -85,240 +79,6 @@ func parsePRArgs(args string) parsedPR { |
85 | 79 | } |
86 | 80 | } |
87 | 81 |
|
88 | | -func handlePR(ctx context.Context, line string, a *iteragent.Agent, repoPath string) { |
89 | | - arg := strings.TrimSpace(strings.TrimPrefix(strings.TrimPrefix(line, "/pr"), " ")) |
90 | | - parsed := parsePRArgs(arg) |
91 | | - |
92 | | - switch parsed.sub { |
93 | | - case prSubList: |
94 | | - handlePRList(repoPath) |
95 | | - case prSubView: |
96 | | - handlePRView(repoPath, parsed.number) |
97 | | - case prSubDiff: |
98 | | - handlePRDiff(repoPath, parsed.number) |
99 | | - case prSubReview: |
100 | | - handlePRReview(repoPath, parsed.number, a, ctx) |
101 | | - case prSubComment: |
102 | | - handlePRComment(repoPath, parsed.number, parsed.text) |
103 | | - case prSubCheckout: |
104 | | - handlePRCheckout(repoPath, parsed.number) |
105 | | - case prSubCreate: |
106 | | - handlePRCreate(repoPath, parsed.draft) |
107 | | - case prSubHelp: |
108 | | - fmt.Printf(`%s/pr subcommands:%s |
109 | | - /pr list — list open PRs |
110 | | - /pr view [N] — view PR details |
111 | | - /pr diff [N] — show PR diff |
112 | | - /pr review [N] — AI code review of PR diff |
113 | | - /pr comment N <text> — post a comment |
114 | | - /pr checkout [N] — checkout PR branch |
115 | | - /pr create [--draft] — create a new PR |
116 | | -`, colorDim, colorReset) |
117 | | - } |
118 | | -} |
119 | | - |
120 | | -func handlePRList(repoPath string) { |
121 | | - cmd := exec.Command("gh", "pr", "list", "--limit", "20") |
122 | | - cmd.Dir = repoPath |
123 | | - cmd.Stdout = os.Stdout |
124 | | - cmd.Stderr = os.Stderr |
125 | | - if err := cmd.Run(); err != nil { |
126 | | - fmt.Printf("%serror: %v%s\n", colorRed, err, colorReset) |
127 | | - } |
128 | | - fmt.Println() |
129 | | -} |
130 | | - |
131 | | -func handlePRView(repoPath string, number string) { |
132 | | - if number == "" { |
133 | | - var ok bool |
134 | | - number, ok = selector.PromptLine("PR number:") |
135 | | - if !ok || number == "" { |
136 | | - return |
137 | | - } |
138 | | - } |
139 | | - cmd := exec.Command("gh", "pr", "view", number) |
140 | | - cmd.Dir = repoPath |
141 | | - cmd.Stdout = os.Stdout |
142 | | - cmd.Stderr = os.Stderr |
143 | | - cmd.Run() |
144 | | - fmt.Println() |
145 | | -} |
146 | | - |
147 | | -func handlePRDiff(repoPath string, number string) { |
148 | | - if number == "" { |
149 | | - var ok bool |
150 | | - number, ok = selector.PromptLine("PR number:") |
151 | | - if !ok || number == "" { |
152 | | - return |
153 | | - } |
154 | | - } |
155 | | - out, err := exec.Command("gh", "pr", "diff", number).Output() |
156 | | - if err != nil { |
157 | | - fmt.Printf("%scould not fetch PR diff: %v%s\n", colorRed, err, colorReset) |
158 | | - return |
159 | | - } |
160 | | - diff := string(out) |
161 | | - if len(diff) > 8000 { |
162 | | - diff = diff[:8000] + "\n…[truncated]" |
163 | | - } |
164 | | - fmt.Printf("%s── PR #%s diff ────────────────────%s\n%s%s──────────────────────────────────%s\n\n", |
165 | | - colorDim, number, colorReset, diff, colorDim, colorReset) |
166 | | -} |
167 | | - |
168 | | -func handlePRReview(repoPath string, number string, a *iteragent.Agent, ctx context.Context) { |
169 | | - if number == "" { |
170 | | - var ok bool |
171 | | - number, ok = selector.PromptLine("PR number:") |
172 | | - if !ok || number == "" { |
173 | | - return |
174 | | - } |
175 | | - } |
176 | | - out, err := exec.Command("gh", "pr", "diff", number).Output() |
177 | | - if err != nil { |
178 | | - fmt.Printf("%scould not fetch PR diff: %v%s\n", colorRed, err, colorReset) |
179 | | - return |
180 | | - } |
181 | | - diff := string(out) |
182 | | - if len(diff) > 8000 { |
183 | | - diff = diff[:8000] + "\n…[truncated]" |
184 | | - } |
185 | | - prompt := buildPRReviewPrompt(number, diff) |
186 | | - streamAndPrint(ctx, a, prompt, repoPath) |
187 | | -} |
188 | | - |
189 | | -func handlePRComment(repoPath string, number string, text string) { |
190 | | - if number == "" { |
191 | | - var ok bool |
192 | | - number, ok = selector.PromptLine("PR number:") |
193 | | - if !ok || number == "" { |
194 | | - return |
195 | | - } |
196 | | - } |
197 | | - if text == "" { |
198 | | - var ok bool |
199 | | - text, ok = selector.PromptLine("Comment:") |
200 | | - if !ok || text == "" { |
201 | | - return |
202 | | - } |
203 | | - } |
204 | | - cmd := exec.Command("gh", "pr", "comment", number, "--body", text) |
205 | | - cmd.Dir = repoPath |
206 | | - cmd.Stdout = os.Stdout |
207 | | - cmd.Stderr = os.Stderr |
208 | | - if err := cmd.Run(); err != nil { |
209 | | - fmt.Printf("%serror: %v%s\n", colorRed, err, colorReset) |
210 | | - } else { |
211 | | - fmt.Printf("%s✓ comment posted on PR #%s%s\n\n", colorLime, number, colorReset) |
212 | | - } |
213 | | -} |
214 | | - |
215 | | -func handlePRCheckout(repoPath string, number string) { |
216 | | - if number == "" { |
217 | | - // List PRs and let user pick |
218 | | - out, _ := exec.Command("gh", "pr", "list", "--json", "number,title,headRefName", |
219 | | - "--template", `{{range .}}#{{.number}} {{.title}} ({{.headRefName}}){{"\n"}}{{end}}`).Output() |
220 | | - prs := strings.Split(strings.TrimSpace(string(out)), "\n") |
221 | | - var nonEmpty []string |
222 | | - for _, pr := range prs { |
223 | | - if strings.TrimSpace(pr) != "" { |
224 | | - nonEmpty = append(nonEmpty, pr) |
225 | | - } |
226 | | - } |
227 | | - if len(nonEmpty) == 0 { |
228 | | - fmt.Println("No open PRs found.") |
229 | | - return |
230 | | - } |
231 | | - choice, ok := selector.SelectItem("Select PR to checkout", nonEmpty) |
232 | | - if !ok { |
233 | | - return |
234 | | - } |
235 | | - // Extract number from "#123 ..." |
236 | | - if len(choice) > 1 && choice[0] == '#' { |
237 | | - end := strings.Index(choice[1:], " ") |
238 | | - if end >= 0 { |
239 | | - number = choice[1 : end+1] |
240 | | - } |
241 | | - } |
242 | | - if number == "" { |
243 | | - return |
244 | | - } |
245 | | - } |
246 | | - cmd := exec.Command("gh", "pr", "checkout", number) |
247 | | - cmd.Dir = repoPath |
248 | | - cmd.Stdout = os.Stdout |
249 | | - cmd.Stderr = os.Stderr |
250 | | - if err := cmd.Run(); err != nil { |
251 | | - fmt.Printf("%serror: %v%s\n", colorRed, err, colorReset) |
252 | | - } else { |
253 | | - fmt.Printf("%s✓ checked out PR #%s%s\n\n", colorLime, number, colorReset) |
254 | | - } |
255 | | -} |
256 | | - |
257 | | -func handlePRCreate(repoPath string, draft bool) { |
258 | | - branchOut, _ := exec.Command("git", "-C", repoPath, "branch", "--show-current").Output() |
259 | | - branch := strings.TrimSpace(string(branchOut)) |
260 | | - if branch == "" || branch == "main" || branch == "master" { |
261 | | - fmt.Printf("%screate a feature branch first. current: %s%s\n", colorRed, branch, colorReset) |
262 | | - return |
263 | | - } |
264 | | - title, ok := selector.PromptLine("PR title:") |
265 | | - if !ok || title == "" { |
266 | | - fmt.Println("cancelled.") |
267 | | - return |
268 | | - } |
269 | | - body, ok := selector.PromptLine("PR body (Enter for auto):") |
270 | | - if !ok { |
271 | | - return |
272 | | - } |
273 | | - if body == "" { |
274 | | - body = fmt.Sprintf("Created by iterate from branch `%s`.", branch) |
275 | | - } |
276 | | - // Push branch first |
277 | | - pushCmd := exec.Command("git", "-C", repoPath, "push", "-u", "origin", branch) |
278 | | - pushCmd.Stdout = os.Stdout |
279 | | - pushCmd.Stderr = os.Stderr |
280 | | - pushCmd.Run() |
281 | | - // Create PR |
282 | | - args := []string{"pr", "create", "--title", title, "--body", body} |
283 | | - if draft { |
284 | | - args = append(args, "--draft") |
285 | | - } |
286 | | - prCmd := exec.Command("gh", args...) |
287 | | - prCmd.Dir = repoPath |
288 | | - prCmd.Stdout = os.Stdout |
289 | | - prCmd.Stderr = os.Stderr |
290 | | - prCmd.Run() |
291 | | - fmt.Println() |
292 | | -} |
293 | | - |
294 | | -// --------------------------------------------------------------------------- |
295 | | -// Enhanced /diff with stat summary |
296 | | -// --------------------------------------------------------------------------- |
297 | | - |
298 | | -func showGitDiffEnhanced(repoPath string) { |
299 | | - // Get stat |
300 | | - statOut, _ := exec.Command("git", "-C", repoPath, "diff", "--stat", "HEAD").Output() |
301 | | - if strings.TrimSpace(string(statOut)) == "" { |
302 | | - statOut, _ = exec.Command("git", "-C", repoPath, "diff", "--stat").Output() |
303 | | - } |
304 | | - // Get full colored diff |
305 | | - diffOut, err := exec.Command("git", "-C", repoPath, "diff", "--color=always", "HEAD").Output() |
306 | | - if err != nil || len(strings.TrimSpace(string(diffOut))) == 0 { |
307 | | - diffOut, _ = exec.Command("git", "-C", repoPath, "diff", "--color=always").Output() |
308 | | - } |
309 | | - if len(strings.TrimSpace(string(diffOut))) == 0 { |
310 | | - fmt.Printf("%s(no changes)%s\n\n", colorDim, colorReset) |
311 | | - return |
312 | | - } |
313 | | - fmt.Printf("\n%s── diff ──────────────────────────%s\n", colorDim, colorReset) |
314 | | - if stat := strings.TrimSpace(string(statOut)); stat != "" { |
315 | | - fmt.Println(stat) |
316 | | - fmt.Printf("%s──────────────────────────────────%s\n", colorDim, colorReset) |
317 | | - } |
318 | | - fmt.Print(string(diffOut)) |
319 | | - fmt.Printf("%s──────────────────────────────────%s\n\n", colorDim, colorReset) |
320 | | -} |
321 | | - |
322 | 82 | // buildPRReviewPrompt constructs the AI review prompt for the given PR number and diff. |
323 | 83 | func buildPRReviewPrompt(number, diff string) string { |
324 | 84 | return fmt.Sprintf( |
|
0 commit comments