@bobbyg603/mog 1.4.0 → 1.5.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/package.json +1 -1
- package/src/github.ts +69 -3
- package/src/index.ts +34 -8
package/package.json
CHANGED
package/src/github.ts
CHANGED
|
@@ -76,6 +76,57 @@ export function listIssues(repo: string, verbose: boolean): void {
|
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
export interface PRFeedback {
|
|
80
|
+
prNumber: number;
|
|
81
|
+
prUrl: string;
|
|
82
|
+
reviews: string;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function fetchPRFeedback(repo: string, branchName: string): PRFeedback | null {
|
|
86
|
+
const proc = Bun.spawnSync([
|
|
87
|
+
"gh", "pr", "list",
|
|
88
|
+
"--repo", repo,
|
|
89
|
+
"--head", branchName,
|
|
90
|
+
"--state", "open",
|
|
91
|
+
"--json", "number,url",
|
|
92
|
+
]);
|
|
93
|
+
|
|
94
|
+
if (proc.exitCode !== 0) return null;
|
|
95
|
+
|
|
96
|
+
const prs = JSON.parse(proc.stdout.toString());
|
|
97
|
+
if (prs.length === 0) return null;
|
|
98
|
+
|
|
99
|
+
const prNumber = prs[0].number;
|
|
100
|
+
const prUrl = prs[0].url;
|
|
101
|
+
|
|
102
|
+
// Fetch review comments
|
|
103
|
+
const reviewProc = Bun.spawnSync([
|
|
104
|
+
"gh", "pr", "view", String(prNumber),
|
|
105
|
+
"--repo", repo,
|
|
106
|
+
"--json", "reviews,comments",
|
|
107
|
+
]);
|
|
108
|
+
|
|
109
|
+
let reviews = "";
|
|
110
|
+
if (reviewProc.exitCode === 0) {
|
|
111
|
+
const data = JSON.parse(reviewProc.stdout.toString());
|
|
112
|
+
|
|
113
|
+
const reviewEntries = (data.reviews || [])
|
|
114
|
+
.filter((r: { body: string }) => r.body?.trim())
|
|
115
|
+
.map((r: { author: { login: string }; state: string; body: string }) =>
|
|
116
|
+
`**@${r.author.login}** (${r.state}):\n${r.body}`
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
const commentEntries = (data.comments || [])
|
|
120
|
+
.map((c: { author: { login: string }; body: string }) =>
|
|
121
|
+
`**@${c.author.login}:**\n${c.body}`
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
reviews = [...reviewEntries, ...commentEntries].join("\n\n");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return { prNumber, prUrl, reviews };
|
|
128
|
+
}
|
|
129
|
+
|
|
79
130
|
export function pushAndCreatePR(
|
|
80
131
|
repo: string,
|
|
81
132
|
worktreeDir: string,
|
|
@@ -83,7 +134,8 @@ export function pushAndCreatePR(
|
|
|
83
134
|
defaultBranch: string,
|
|
84
135
|
issueNum: string,
|
|
85
136
|
issue: Issue,
|
|
86
|
-
summary?: string
|
|
137
|
+
summary?: string,
|
|
138
|
+
existingPR?: PRFeedback,
|
|
87
139
|
): void {
|
|
88
140
|
// Check for unpushed commits or uncommitted changes
|
|
89
141
|
const unpushed = Bun.spawnSync(["git", "log", `origin/${defaultBranch}..HEAD`, "--oneline"], { cwd: worktreeDir });
|
|
@@ -129,14 +181,28 @@ export function pushAndCreatePR(
|
|
|
129
181
|
}
|
|
130
182
|
}
|
|
131
183
|
|
|
132
|
-
// Push
|
|
184
|
+
// Push (force-with-lease when updating an existing PR)
|
|
133
185
|
log.info(`Pushing branch '${branchName}' to origin...`);
|
|
134
|
-
const
|
|
186
|
+
const pushArgs = existingPR
|
|
187
|
+
? ["git", "push", "--force-with-lease", "-u", "origin", branchName]
|
|
188
|
+
: ["git", "push", "-u", "origin", branchName];
|
|
189
|
+
const push = Bun.spawnSync(pushArgs, { cwd: worktreeDir });
|
|
135
190
|
if (push.exitCode !== 0) {
|
|
136
191
|
log.die("Failed to push. Check your git credentials.");
|
|
137
192
|
}
|
|
138
193
|
log.ok("Branch pushed.");
|
|
139
194
|
|
|
195
|
+
if (existingPR) {
|
|
196
|
+
// Update existing PR
|
|
197
|
+
log.ok("Existing PR updated!");
|
|
198
|
+
console.log(`\x1b[0;32m${existingPR.prUrl}\x1b[0m`);
|
|
199
|
+
console.log();
|
|
200
|
+
log.ok(`All done! Issue #${issueNum} → Branch '${branchName}' → PR updated.`);
|
|
201
|
+
log.info(`Worktree: ${worktreeDir}`);
|
|
202
|
+
log.info(`To clean up the worktree later: git worktree remove ${worktreeDir}`);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
|
|
140
206
|
// Create PR
|
|
141
207
|
log.info("Opening pull request...");
|
|
142
208
|
|
package/src/index.ts
CHANGED
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
import fs from "fs";
|
|
4
4
|
import path from "path";
|
|
5
|
-
import { fetchIssue, listIssues } from "./github";
|
|
5
|
+
import { fetchIssue, listIssues, fetchPRFeedback } from "./github";
|
|
6
6
|
import { detectRepo, ensureRepo, createWorktree } from "./worktree";
|
|
7
7
|
import { runClaude } from "./sandbox";
|
|
8
8
|
import { pushAndCreatePR } from "./github";
|
|
9
|
+
import type { PRFeedback } from "./github";
|
|
9
10
|
import { log } from "./log";
|
|
10
11
|
|
|
11
12
|
const SANDBOX_NAME = "mog";
|
|
@@ -115,6 +116,7 @@ async function main() {
|
|
|
115
116
|
console.log();
|
|
116
117
|
console.log("Options:");
|
|
117
118
|
console.log(" --include <file> — copy a file into the worktree (repeatable)");
|
|
119
|
+
console.log(" --fresh — ignore existing PR, start a brand new one");
|
|
118
120
|
console.log();
|
|
119
121
|
console.log("Example:");
|
|
120
122
|
console.log(" mog init");
|
|
@@ -126,9 +128,10 @@ async function main() {
|
|
|
126
128
|
return;
|
|
127
129
|
}
|
|
128
130
|
|
|
129
|
-
// Parse --include flags
|
|
131
|
+
// Parse --include and --fresh flags
|
|
130
132
|
const includeFiles: string[] = [];
|
|
131
133
|
const filteredArgs: string[] = [];
|
|
134
|
+
let fresh = false;
|
|
132
135
|
for (let i = 0; i < args.length; i++) {
|
|
133
136
|
if (args[i] === "--include" && i + 1 < args.length) {
|
|
134
137
|
const filePath = path.resolve(args[i + 1]!);
|
|
@@ -137,6 +140,8 @@ async function main() {
|
|
|
137
140
|
}
|
|
138
141
|
includeFiles.push(filePath);
|
|
139
142
|
i++; // skip the path argument
|
|
143
|
+
} else if (args[i] === "--fresh") {
|
|
144
|
+
fresh = true;
|
|
140
145
|
} else {
|
|
141
146
|
filteredArgs.push(args[i]!);
|
|
142
147
|
}
|
|
@@ -170,6 +175,7 @@ async function main() {
|
|
|
170
175
|
console.log();
|
|
171
176
|
console.log("Options:");
|
|
172
177
|
console.log(" --include <file> — copy a file into the worktree (repeatable)");
|
|
178
|
+
console.log(" --fresh — ignore existing PR, start a brand new one");
|
|
173
179
|
console.log();
|
|
174
180
|
console.log("Example:");
|
|
175
181
|
console.log(" mog init");
|
|
@@ -214,6 +220,16 @@ async function main() {
|
|
|
214
220
|
reposDir, owner, repoName, defaultBranch, issueNum, issue.title
|
|
215
221
|
);
|
|
216
222
|
|
|
223
|
+
// Check for existing PR (unless --fresh)
|
|
224
|
+
let existingPR: PRFeedback | undefined;
|
|
225
|
+
if (!fresh) {
|
|
226
|
+
const pr = fetchPRFeedback(repo, branchName);
|
|
227
|
+
if (pr) {
|
|
228
|
+
existingPR = pr;
|
|
229
|
+
log.ok(`Found existing PR #${pr.prNumber} — will include review feedback and update it.`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
217
233
|
// Copy included files into worktree
|
|
218
234
|
const copiedFiles: string[] = [];
|
|
219
235
|
for (const filePath of includeFiles) {
|
|
@@ -225,7 +241,8 @@ async function main() {
|
|
|
225
241
|
}
|
|
226
242
|
|
|
227
243
|
// Build prompts
|
|
228
|
-
const
|
|
244
|
+
const prFeedback = existingPR?.reviews || "";
|
|
245
|
+
const planningPrompt = buildPlanningPrompt(repo, issueNum, issue, prFeedback);
|
|
229
246
|
const buildingPromptFn = (remaining: string[], plan: string) =>
|
|
230
247
|
buildBuildingPrompt(repo, issueNum, issue, remaining, plan);
|
|
231
248
|
const reviewPrompt = buildReviewPrompt(repo, issueNum, issue);
|
|
@@ -249,8 +266,8 @@ async function main() {
|
|
|
249
266
|
}
|
|
250
267
|
}
|
|
251
268
|
|
|
252
|
-
// Push and create PR
|
|
253
|
-
pushAndCreatePR(repo, worktreeDir, branchName, defaultBranch, issueNum, issue, summary);
|
|
269
|
+
// Push and create/update PR
|
|
270
|
+
pushAndCreatePR(repo, worktreeDir, branchName, defaultBranch, issueNum, issue, summary, existingPR);
|
|
254
271
|
}
|
|
255
272
|
|
|
256
273
|
function getReposDir(): string {
|
|
@@ -302,7 +319,7 @@ function tryRecoverSandbox(reposDir: string): boolean {
|
|
|
302
319
|
return true;
|
|
303
320
|
}
|
|
304
321
|
|
|
305
|
-
function formatIssueContext(issueNum: string, issue: { title: string; body: string; labels: string; comments: string }): string {
|
|
322
|
+
function formatIssueContext(issueNum: string, issue: { title: string; body: string; labels: string; comments: string }, prFeedback?: string): string {
|
|
306
323
|
let context = `## Issue: ${issue.title}
|
|
307
324
|
|
|
308
325
|
### Description
|
|
@@ -318,13 +335,22 @@ ${issue.labels}`;
|
|
|
318
335
|
${issue.comments}`;
|
|
319
336
|
}
|
|
320
337
|
|
|
338
|
+
if (prFeedback) {
|
|
339
|
+
context += `
|
|
340
|
+
|
|
341
|
+
### Previous PR Review Feedback
|
|
342
|
+
A previous attempt at this issue was reviewed. Address the following feedback in your implementation:
|
|
343
|
+
|
|
344
|
+
${prFeedback}`;
|
|
345
|
+
}
|
|
346
|
+
|
|
321
347
|
return context;
|
|
322
348
|
}
|
|
323
349
|
|
|
324
|
-
function buildPlanningPrompt(repo: string, issueNum: string, issue: { title: string; body: string; labels: string; comments: string }): string {
|
|
350
|
+
function buildPlanningPrompt(repo: string, issueNum: string, issue: { title: string; body: string; labels: string; comments: string }, prFeedback?: string): string {
|
|
325
351
|
return `You are working on GitHub issue #${issueNum} for the repository ${repo}.
|
|
326
352
|
|
|
327
|
-
${formatIssueContext(issueNum, issue)}
|
|
353
|
+
${formatIssueContext(issueNum, issue, prFeedback)}
|
|
328
354
|
|
|
329
355
|
## Instructions
|
|
330
356
|
|