@bobbyg603/mog 1.2.0 → 1.4.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 +8 -1
- package/package.json +1 -1
- package/src/github.ts +65 -1
- package/src/index.ts +162 -32
- package/src/sandbox.ts +9 -1
- package/src/worktree.ts +23 -0
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ That's it. `mog` will:
|
|
|
19
19
|
## Prerequisites
|
|
20
20
|
|
|
21
21
|
- **macOS or Windows** (Docker sandbox microVMs require Docker Desktop)
|
|
22
|
-
- **Docker Desktop
|
|
22
|
+
- **Docker Desktop 4.40+** — running and up to date. Docker sandbox support (required by mog) was introduced in Docker Desktop 4.40. Verify with `docker sandbox ls`.
|
|
23
23
|
- **Bun** — install from [bun.sh](https://bun.sh)
|
|
24
24
|
- **GitHub CLI** (`gh`) — authenticated via `gh auth login`
|
|
25
25
|
- **Git** with push access to your target repos
|
|
@@ -33,6 +33,9 @@ bun install -g @bobbyg603/mog
|
|
|
33
33
|
## Quick start
|
|
34
34
|
|
|
35
35
|
```bash
|
|
36
|
+
# 0. Verify Docker sandbox support is available
|
|
37
|
+
docker sandbox ls
|
|
38
|
+
|
|
36
39
|
# 1. One-time setup: create sandbox & authenticate
|
|
37
40
|
mog init
|
|
38
41
|
# This launches Claude Code — use /login to authenticate with your Max subscription
|
|
@@ -131,6 +134,10 @@ git worktree remove ../repo-worktrees/123-fix-broken-login
|
|
|
131
134
|
|
|
132
135
|
**"No changes detected"** — Claude may have struggled with the issue. Check the worktree manually, or re-run with a more detailed issue description.
|
|
133
136
|
|
|
137
|
+
**"Docker sandbox state is stale"** — Restart Docker Desktop, or remove and recreate the sandbox: `docker sandbox rm mog && mog init`.
|
|
138
|
+
|
|
139
|
+
**"docker: 'sandbox' is not a docker command"** — Your Docker Desktop version doesn't support sandboxes. Update Docker Desktop to **4.40 or later**, then verify with `docker sandbox ls`.
|
|
140
|
+
|
|
134
141
|
**"Failed to push"** — Ensure `gh` is authenticated with push access. Try `gh auth login` and select HTTPS.
|
|
135
142
|
|
|
136
143
|
## Managing the sandbox
|
package/package.json
CHANGED
package/src/github.ts
CHANGED
|
@@ -4,6 +4,7 @@ export interface Issue {
|
|
|
4
4
|
title: string;
|
|
5
5
|
body: string;
|
|
6
6
|
labels: string;
|
|
7
|
+
comments: string;
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
export function fetchIssue(repo: string, issueNum: string): Issue {
|
|
@@ -12,7 +13,7 @@ export function fetchIssue(repo: string, issueNum: string): Issue {
|
|
|
12
13
|
const proc = Bun.spawnSync([
|
|
13
14
|
"gh", "issue", "view", issueNum,
|
|
14
15
|
"--repo", repo,
|
|
15
|
-
"--json", "title,body,labels",
|
|
16
|
+
"--json", "title,body,labels,comments",
|
|
16
17
|
]);
|
|
17
18
|
|
|
18
19
|
if (proc.exitCode !== 0) {
|
|
@@ -21,13 +22,60 @@ export function fetchIssue(repo: string, issueNum: string): Issue {
|
|
|
21
22
|
|
|
22
23
|
const json = JSON.parse(proc.stdout.toString());
|
|
23
24
|
|
|
25
|
+
const comments = (json.comments || [])
|
|
26
|
+
.map((c: { author: { login: string }; body: string }) => `**@${c.author.login}:** ${c.body}`)
|
|
27
|
+
.join("\n\n");
|
|
28
|
+
|
|
24
29
|
return {
|
|
25
30
|
title: json.title,
|
|
26
31
|
body: json.body || "No description provided.",
|
|
27
32
|
labels: json.labels?.map((l: { name: string }) => l.name).join(", ") || "none",
|
|
33
|
+
comments,
|
|
28
34
|
};
|
|
29
35
|
}
|
|
30
36
|
|
|
37
|
+
export function listIssues(repo: string, verbose: boolean): void {
|
|
38
|
+
log.info(`Fetching open issues for ${repo}...`);
|
|
39
|
+
|
|
40
|
+
const fields = verbose
|
|
41
|
+
? "number,title,body,labels,assignees"
|
|
42
|
+
: "number,title";
|
|
43
|
+
|
|
44
|
+
const proc = Bun.spawnSync([
|
|
45
|
+
"gh", "issue", "list",
|
|
46
|
+
"--repo", repo,
|
|
47
|
+
"--state", "open",
|
|
48
|
+
"--json", fields,
|
|
49
|
+
]);
|
|
50
|
+
|
|
51
|
+
if (proc.exitCode !== 0) {
|
|
52
|
+
log.die(`Failed to fetch issues for ${repo}. Check the repo name.`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const issues = JSON.parse(proc.stdout.toString());
|
|
56
|
+
|
|
57
|
+
if (issues.length === 0) {
|
|
58
|
+
log.info("No open issues found.");
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
log.ok(`${issues.length} open issue(s):\n`);
|
|
63
|
+
|
|
64
|
+
for (const issue of issues) {
|
|
65
|
+
if (verbose) {
|
|
66
|
+
const labels = issue.labels?.map((l: { name: string }) => l.name).join(", ") || "none";
|
|
67
|
+
const assignees = issue.assignees?.map((a: { login: string }) => a.login).join(", ") || "unassigned";
|
|
68
|
+
console.log(` #${issue.number} ${issue.title}`);
|
|
69
|
+
console.log(` Labels: ${labels}`);
|
|
70
|
+
console.log(` Assignees: ${assignees}`);
|
|
71
|
+
console.log(` ${(issue.body || "No description.").split("\n")[0]}`);
|
|
72
|
+
console.log();
|
|
73
|
+
} else {
|
|
74
|
+
console.log(` #${issue.number} ${issue.title}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
31
79
|
export function pushAndCreatePR(
|
|
32
80
|
repo: string,
|
|
33
81
|
worktreeDir: string,
|
|
@@ -65,6 +113,22 @@ export function pushAndCreatePR(
|
|
|
65
113
|
}
|
|
66
114
|
}
|
|
67
115
|
|
|
116
|
+
// Squash all commits into one
|
|
117
|
+
const commitCount = Bun.spawnSync(["git", "rev-list", "--count", `${defaultBranch}..HEAD`], { cwd: worktreeDir });
|
|
118
|
+
const count = parseInt(commitCount.stdout.toString().trim(), 10) || 0;
|
|
119
|
+
if (count > 1) {
|
|
120
|
+
log.info(`Squashing ${count} commits into one...`);
|
|
121
|
+
const prefix = issue.labels.includes("enhancement") || issue.labels.includes("feature") ? "feat" : "fix";
|
|
122
|
+
const squash = Bun.spawnSync(["git", "reset", "--soft", defaultBranch], { cwd: worktreeDir });
|
|
123
|
+
if (squash.exitCode === 0) {
|
|
124
|
+
const msg = `${prefix}: ${issue.title.toLowerCase()} (#${issueNum})`;
|
|
125
|
+
Bun.spawnSync(["git", "commit", "-m", msg], { cwd: worktreeDir });
|
|
126
|
+
log.ok("Commits squashed.");
|
|
127
|
+
} else {
|
|
128
|
+
log.warn("Failed to squash — pushing individual commits instead.");
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
68
132
|
// Push
|
|
69
133
|
log.info(`Pushing branch '${branchName}' to origin...`);
|
|
70
134
|
const push = Bun.spawnSync(["git", "push", "-u", "origin", branchName], { cwd: worktreeDir });
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { fetchIssue, listIssues } from "./github";
|
|
6
|
+
import { detectRepo, ensureRepo, createWorktree } from "./worktree";
|
|
5
7
|
import { runClaude } from "./sandbox";
|
|
6
8
|
import { pushAndCreatePR } from "./github";
|
|
7
9
|
import { log } from "./log";
|
|
@@ -54,7 +56,7 @@ async function init() {
|
|
|
54
56
|
log.ok("Snapshot saved.");
|
|
55
57
|
}
|
|
56
58
|
|
|
57
|
-
log.ok("mog is ready. Run: mog <owner/repo> <issue_number>");
|
|
59
|
+
log.ok("mog is ready. Run: mog <issue_number> (from a git repo) or mog <owner/repo> <issue_number>");
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
async function main() {
|
|
@@ -84,22 +86,99 @@ async function main() {
|
|
|
84
86
|
return;
|
|
85
87
|
}
|
|
86
88
|
|
|
87
|
-
|
|
89
|
+
// mog list [--verbose] or mog <owner/repo> list [--verbose]
|
|
90
|
+
if (args[0] === "list" || args[1] === "list") {
|
|
91
|
+
let repo: string;
|
|
92
|
+
const verbose = args.includes("--verbose");
|
|
93
|
+
|
|
94
|
+
if (args[0] === "list") {
|
|
95
|
+
const detected = detectRepo();
|
|
96
|
+
if (!detected) {
|
|
97
|
+
log.die("Could not detect repo from git remote. Run from inside a git repo or use: mog <owner/repo> list");
|
|
98
|
+
}
|
|
99
|
+
repo = detected;
|
|
100
|
+
} else {
|
|
101
|
+
repo = args[0];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
listIssues(repo, verbose);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (args.length < 1) {
|
|
88
109
|
console.log("Usage:");
|
|
89
110
|
console.log(" mog init — one-time setup (create sandbox & login)");
|
|
111
|
+
console.log(" mog <issue_num> — auto-detect repo from git remote");
|
|
90
112
|
console.log(" mog <owner/repo> <issue_num> — fetch issue, run Claude, open PR");
|
|
113
|
+
console.log(" mog list [--verbose] — list open issues (auto-detect repo)");
|
|
114
|
+
console.log(" mog <owner/repo> list [--verbose] — list open issues for a repo");
|
|
115
|
+
console.log();
|
|
116
|
+
console.log("Options:");
|
|
117
|
+
console.log(" --include <file> — copy a file into the worktree (repeatable)");
|
|
91
118
|
console.log();
|
|
92
119
|
console.log("Example:");
|
|
93
120
|
console.log(" mog init");
|
|
121
|
+
console.log(" mog 123");
|
|
122
|
+
console.log(" mog 123 --include .env");
|
|
94
123
|
console.log(" mog workingdevshero/automate-it 123");
|
|
124
|
+
console.log(" mog list");
|
|
125
|
+
console.log(" mog list --verbose");
|
|
95
126
|
return;
|
|
96
127
|
}
|
|
97
128
|
|
|
98
|
-
|
|
99
|
-
const
|
|
129
|
+
// Parse --include flags
|
|
130
|
+
const includeFiles: string[] = [];
|
|
131
|
+
const filteredArgs: string[] = [];
|
|
132
|
+
for (let i = 0; i < args.length; i++) {
|
|
133
|
+
if (args[i] === "--include" && i + 1 < args.length) {
|
|
134
|
+
const filePath = path.resolve(args[i + 1]!);
|
|
135
|
+
if (!fs.existsSync(filePath)) {
|
|
136
|
+
log.die(`Include file not found: ${args[i + 1]}`);
|
|
137
|
+
}
|
|
138
|
+
includeFiles.push(filePath);
|
|
139
|
+
i++; // skip the path argument
|
|
140
|
+
} else {
|
|
141
|
+
filteredArgs.push(args[i]!);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
100
144
|
|
|
101
|
-
|
|
102
|
-
|
|
145
|
+
let repo: string;
|
|
146
|
+
let issueNum: string;
|
|
147
|
+
|
|
148
|
+
if (/^\d+$/.test(filteredArgs[0])) {
|
|
149
|
+
// mog <issue_number> — auto-detect repo
|
|
150
|
+
const detected = detectRepo();
|
|
151
|
+
if (!detected) {
|
|
152
|
+
log.die("Could not detect repo from git remote. Run from inside a git repo or use: mog <owner/repo> <issue_num>");
|
|
153
|
+
}
|
|
154
|
+
repo = detected;
|
|
155
|
+
issueNum = filteredArgs[0];
|
|
156
|
+
} else if (filteredArgs.length >= 2) {
|
|
157
|
+
// mog <owner/repo> <issue_number>
|
|
158
|
+
repo = filteredArgs[0];
|
|
159
|
+
issueNum = filteredArgs[1];
|
|
160
|
+
if (!/^\d+$/.test(issueNum)) {
|
|
161
|
+
log.die(`Invalid issue number: '${issueNum}'. Must be a positive integer.`);
|
|
162
|
+
}
|
|
163
|
+
} else {
|
|
164
|
+
console.log("Usage:");
|
|
165
|
+
console.log(" mog init — one-time setup (create sandbox & login)");
|
|
166
|
+
console.log(" mog <issue_num> — auto-detect repo from git remote");
|
|
167
|
+
console.log(" mog <owner/repo> <issue_num> — fetch issue, run Claude, open PR");
|
|
168
|
+
console.log(" mog list [--verbose] — list open issues (auto-detect repo)");
|
|
169
|
+
console.log(" mog <owner/repo> list [--verbose] — list open issues for a repo");
|
|
170
|
+
console.log();
|
|
171
|
+
console.log("Options:");
|
|
172
|
+
console.log(" --include <file> — copy a file into the worktree (repeatable)");
|
|
173
|
+
console.log();
|
|
174
|
+
console.log("Example:");
|
|
175
|
+
console.log(" mog init");
|
|
176
|
+
console.log(" mog 123");
|
|
177
|
+
console.log(" mog 123 --include .env");
|
|
178
|
+
console.log(" mog workingdevshero/automate-it 123");
|
|
179
|
+
console.log(" mog list");
|
|
180
|
+
console.log(" mog list --verbose");
|
|
181
|
+
return;
|
|
103
182
|
}
|
|
104
183
|
|
|
105
184
|
const parts = repo.split("/");
|
|
@@ -135,10 +214,21 @@ async function main() {
|
|
|
135
214
|
reposDir, owner, repoName, defaultBranch, issueNum, issue.title
|
|
136
215
|
);
|
|
137
216
|
|
|
217
|
+
// Copy included files into worktree
|
|
218
|
+
const copiedFiles: string[] = [];
|
|
219
|
+
for (const filePath of includeFiles) {
|
|
220
|
+
const basename = path.basename(filePath);
|
|
221
|
+
const dest = path.join(worktreeDir, basename);
|
|
222
|
+
fs.copyFileSync(filePath, dest);
|
|
223
|
+
copiedFiles.push(dest);
|
|
224
|
+
log.ok(`Included: ${basename}`);
|
|
225
|
+
}
|
|
226
|
+
|
|
138
227
|
// Build prompts
|
|
139
228
|
const planningPrompt = buildPlanningPrompt(repo, issueNum, issue);
|
|
140
229
|
const buildingPromptFn = (remaining: string[], plan: string) =>
|
|
141
230
|
buildBuildingPrompt(repo, issueNum, issue, remaining, plan);
|
|
231
|
+
const reviewPrompt = buildReviewPrompt(repo, issueNum, issue);
|
|
142
232
|
|
|
143
233
|
// Run Claude in sandbox
|
|
144
234
|
log.info("Launching Claude Code in sandbox...");
|
|
@@ -146,7 +236,18 @@ async function main() {
|
|
|
146
236
|
log.info(`Worktree: ${worktreeDir}`);
|
|
147
237
|
console.log();
|
|
148
238
|
|
|
149
|
-
const summary = await runClaude(SANDBOX_NAME, worktreeDir, planningPrompt, buildingPromptFn);
|
|
239
|
+
const summary = await runClaude(SANDBOX_NAME, worktreeDir, planningPrompt, buildingPromptFn, reviewPrompt);
|
|
240
|
+
|
|
241
|
+
// Remove included files so they don't end up in the PR
|
|
242
|
+
for (const filePath of copiedFiles) {
|
|
243
|
+
try {
|
|
244
|
+
fs.unlinkSync(filePath);
|
|
245
|
+
// Unstage if Claude happened to git add it
|
|
246
|
+
Bun.spawnSync(["git", "rm", "--cached", "--ignore-unmatch", path.basename(filePath)], { cwd: worktreeDir });
|
|
247
|
+
} catch {
|
|
248
|
+
// File may already be gone
|
|
249
|
+
}
|
|
250
|
+
}
|
|
150
251
|
|
|
151
252
|
// Push and create PR
|
|
152
253
|
pushAndCreatePR(repo, worktreeDir, branchName, defaultBranch, issueNum, issue, summary);
|
|
@@ -201,29 +302,45 @@ function tryRecoverSandbox(reposDir: string): boolean {
|
|
|
201
302
|
return true;
|
|
202
303
|
}
|
|
203
304
|
|
|
204
|
-
function
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
## Issue: ${issue.title}
|
|
305
|
+
function formatIssueContext(issueNum: string, issue: { title: string; body: string; labels: string; comments: string }): string {
|
|
306
|
+
let context = `## Issue: ${issue.title}
|
|
208
307
|
|
|
209
308
|
### Description
|
|
210
309
|
${issue.body}
|
|
211
310
|
|
|
212
311
|
### Labels
|
|
213
|
-
${issue.labels}
|
|
312
|
+
${issue.labels}`;
|
|
313
|
+
|
|
314
|
+
if (issue.comments) {
|
|
315
|
+
context += `
|
|
316
|
+
|
|
317
|
+
### Comments
|
|
318
|
+
${issue.comments}`;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return context;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function buildPlanningPrompt(repo: string, issueNum: string, issue: { title: string; body: string; labels: string; comments: string }): string {
|
|
325
|
+
return `You are working on GitHub issue #${issueNum} for the repository ${repo}.
|
|
326
|
+
|
|
327
|
+
${formatIssueContext(issueNum, issue)}
|
|
214
328
|
|
|
215
329
|
## Instructions
|
|
216
330
|
|
|
217
331
|
Your job in this step is to **plan only** — do NOT implement anything and do NOT commit.
|
|
218
332
|
|
|
219
333
|
1. Read and understand the codebase structure thoroughly.
|
|
220
|
-
2.
|
|
221
|
-
3.
|
|
334
|
+
2. **Search the entire codebase** for code related to the issue — look for similar patterns, duplicate logic, and any modules that handle the same concern. Use Grep and Glob liberally to find all relevant locations, not just the most obvious one.
|
|
335
|
+
3. Analyze the issue and break it down into small, atomic implementation tasks.
|
|
336
|
+
4. Create a file called \`IMPLEMENTATION_PLAN.md\` in the root of the repository with a checklist of tasks.
|
|
222
337
|
|
|
223
338
|
The plan should:
|
|
224
339
|
- Have 3-8 tasks (fewer for simple issues, more for complex ones)
|
|
225
340
|
- Order tasks by dependency (implement foundations first)
|
|
226
341
|
- Each task should be a single, atomic unit of work that results in one commit
|
|
342
|
+
- **Include tasks to update ALL locations** where the same pattern or concern exists — not just the most obvious one. If the same logic appears in multiple modules, the plan must cover all of them.
|
|
343
|
+
- If you find duplicate or near-duplicate logic across modules, include a task to consolidate it into a shared utility or function.
|
|
227
344
|
- Use markdown checklist format: \`- [ ] Task description\`
|
|
228
345
|
|
|
229
346
|
Example format:
|
|
@@ -242,7 +359,7 @@ Do NOT implement any code changes. Do NOT make any commits. Only create the plan
|
|
|
242
359
|
function buildBuildingPrompt(
|
|
243
360
|
repo: string,
|
|
244
361
|
issueNum: string,
|
|
245
|
-
issue: { title: string; body: string; labels: string },
|
|
362
|
+
issue: { title: string; body: string; labels: string; comments: string },
|
|
246
363
|
remainingItems: string[],
|
|
247
364
|
planContent: string,
|
|
248
365
|
): string {
|
|
@@ -250,19 +367,13 @@ function buildBuildingPrompt(
|
|
|
250
367
|
if (remainingItems.length === 0 && !planContent) {
|
|
251
368
|
return `You are working on GitHub issue #${issueNum} for the repository ${repo}.
|
|
252
369
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
### Description
|
|
256
|
-
${issue.body}
|
|
257
|
-
|
|
258
|
-
### Labels
|
|
259
|
-
${issue.labels}
|
|
370
|
+
${formatIssueContext(issueNum, issue)}
|
|
260
371
|
|
|
261
372
|
## Instructions
|
|
262
373
|
1. Read and understand the codebase structure first.
|
|
263
374
|
2. Implement the changes described in the issue above.
|
|
264
375
|
3. Write clean, well-documented code that follows the existing project conventions.
|
|
265
|
-
4.
|
|
376
|
+
4. If the project has an existing test suite, add or update tests to cover the changes.
|
|
266
377
|
5. Make sure the code builds/lints without errors if there's a build system.
|
|
267
378
|
6. Commit your changes with a clear commit message referencing issue #${issueNum}.
|
|
268
379
|
|
|
@@ -274,13 +385,7 @@ a message like: "fix: <short description> (#${issueNum})"`;
|
|
|
274
385
|
|
|
275
386
|
return `You are working on GitHub issue #${issueNum} for the repository ${repo}.
|
|
276
387
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
### Description
|
|
280
|
-
${issue.body}
|
|
281
|
-
|
|
282
|
-
### Labels
|
|
283
|
-
${issue.labels}
|
|
388
|
+
${formatIssueContext(issueNum, issue)}
|
|
284
389
|
|
|
285
390
|
## Current Implementation Plan
|
|
286
391
|
|
|
@@ -299,6 +404,31 @@ Rules:
|
|
|
299
404
|
5. Do NOT work on any other tasks after committing.`;
|
|
300
405
|
}
|
|
301
406
|
|
|
407
|
+
function buildReviewPrompt(
|
|
408
|
+
repo: string,
|
|
409
|
+
issueNum: string,
|
|
410
|
+
issue: { title: string; body: string; labels: string; comments: string },
|
|
411
|
+
): string {
|
|
412
|
+
return `You are reviewing changes made for GitHub issue #${issueNum} in the repository ${repo}.
|
|
413
|
+
|
|
414
|
+
${formatIssueContext(issueNum, issue)}
|
|
415
|
+
|
|
416
|
+
## Instructions
|
|
417
|
+
|
|
418
|
+
All implementation tasks are complete. Your job is to **review the entire branch** for quality and completeness.
|
|
419
|
+
|
|
420
|
+
Run \`git diff main...HEAD\` (or the equivalent for the default branch) to see all changes made.
|
|
421
|
+
|
|
422
|
+
Check for:
|
|
423
|
+
1. **Missed locations**: Search the codebase for similar patterns, logic, or code that handles the same concern as the changes. If the fix or feature was applied in one place but a similar pattern exists elsewhere, apply it there too.
|
|
424
|
+
2. **Code duplication**: If the changes introduced logic that duplicates existing code (or if pre-existing duplication was missed), consolidate it into a shared function or utility.
|
|
425
|
+
3. **Quality issues**: Look for missing edge cases, error handling gaps, or inconsistencies with the rest of the codebase.
|
|
426
|
+
4. **Tests**: If the project has an existing test suite, verify that tests were added or updated to cover the changes. If not, add them. Do not create a test framework or test infrastructure from scratch.
|
|
427
|
+
|
|
428
|
+
If you find issues, fix them and commit each fix separately with a clear commit message referencing #${issueNum}.
|
|
429
|
+
If everything looks good, do nothing.`;
|
|
430
|
+
}
|
|
431
|
+
|
|
302
432
|
main().catch((err) => {
|
|
303
433
|
log.die(err.message);
|
|
304
434
|
});
|
package/src/sandbox.ts
CHANGED
|
@@ -76,6 +76,7 @@ export async function runClaude(
|
|
|
76
76
|
worktreeDir: string,
|
|
77
77
|
planningPrompt: string,
|
|
78
78
|
buildingPromptFn: (remainingItems: string[], planContent: string) => string,
|
|
79
|
+
reviewPrompt?: string,
|
|
79
80
|
): Promise<string> {
|
|
80
81
|
let lastResult = "";
|
|
81
82
|
|
|
@@ -152,7 +153,14 @@ export async function runClaude(
|
|
|
152
153
|
}
|
|
153
154
|
}
|
|
154
155
|
|
|
155
|
-
// Phase 3 —
|
|
156
|
+
// Phase 3 — Review
|
|
157
|
+
if (reviewPrompt) {
|
|
158
|
+
log.info("Phase 3: Reviewing changes for quality and completeness...");
|
|
159
|
+
const reviewResult = await execClaude(sandboxName, worktreeDir, ["-p", reviewPrompt]);
|
|
160
|
+
if (reviewResult) lastResult = reviewResult;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Phase 4 — Cleanup
|
|
156
164
|
cleanupPlanFile(sandboxName, worktreeDir);
|
|
157
165
|
|
|
158
166
|
const finalPlan = readPlanFile(worktreeDir);
|
package/src/worktree.ts
CHANGED
|
@@ -1,6 +1,29 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import { log } from "./log";
|
|
3
3
|
|
|
4
|
+
export function detectRepo(): string | null {
|
|
5
|
+
const result = Bun.spawnSync(["git", "remote", "get-url", "origin"]);
|
|
6
|
+
if (result.exitCode !== 0) {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const url = result.stdout.toString().trim();
|
|
11
|
+
|
|
12
|
+
// SSH: git@github.com:owner/repo.git
|
|
13
|
+
const sshMatch = url.match(/^git@[^:]+:([^/]+)\/([^/]+?)(?:\.git)?$/);
|
|
14
|
+
if (sshMatch) {
|
|
15
|
+
return `${sshMatch[1]}/${sshMatch[2]}`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// HTTPS: https://github.com/owner/repo.git
|
|
19
|
+
const httpsMatch = url.match(/^https?:\/\/[^/]+\/([^/]+)\/([^/]+?)(?:\.git)?$/);
|
|
20
|
+
if (httpsMatch) {
|
|
21
|
+
return `${httpsMatch[1]}/${httpsMatch[2]}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
|
|
4
27
|
export function ensureRepo(
|
|
5
28
|
repo: string,
|
|
6
29
|
owner: string,
|