claude-issue-solver 1.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/LICENSE +21 -0
- package/README.md +143 -0
- package/dist/commands/clean.d.ts +1 -0
- package/dist/commands/clean.js +107 -0
- package/dist/commands/list.d.ts +1 -0
- package/dist/commands/list.js +22 -0
- package/dist/commands/pr.d.ts +1 -0
- package/dist/commands/pr.js +121 -0
- package/dist/commands/select.d.ts +1 -0
- package/dist/commands/select.js +42 -0
- package/dist/commands/solve.d.ts +1 -0
- package/dist/commands/solve.js +198 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +84 -0
- package/dist/utils/git.d.ts +8 -0
- package/dist/utils/git.js +85 -0
- package/dist/utils/github.d.ts +13 -0
- package/dist/utils/github.js +34 -0
- package/dist/utils/helpers.d.ts +8 -0
- package/dist/utils/helpers.js +150 -0
- package/package.json +45 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Claude Issue Solver
|
|
2
|
+
|
|
3
|
+
Automatically solve GitHub issues using [Claude Code](https://claude.ai/code).
|
|
4
|
+
|
|
5
|
+
This CLI tool fetches an issue from your repo, creates a worktree, opens Claude Code in a new terminal to solve it, and creates a PR when done.
|
|
6
|
+
|
|
7
|
+
## Demo
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
$ claude-issue
|
|
11
|
+
|
|
12
|
+
Open issues for my-project:
|
|
13
|
+
|
|
14
|
+
? Select an issue to solve:
|
|
15
|
+
❯ #42 Add dark mode support
|
|
16
|
+
#38 Fix login bug on mobile
|
|
17
|
+
#35 Update dependencies
|
|
18
|
+
Cancel
|
|
19
|
+
|
|
20
|
+
📋 Fetching issue #42...
|
|
21
|
+
✔ Found issue #42
|
|
22
|
+
📌 Issue: Add dark mode support
|
|
23
|
+
🌿 Creating worktree with branch: issue-42-add-dark-mode-support
|
|
24
|
+
🤖 Opening new terminal to run Claude Code...
|
|
25
|
+
|
|
26
|
+
✅ Worktree created at: ../my-project-issue-42-add-dark-mode-support
|
|
27
|
+
Claude is running in a new terminal window.
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Features
|
|
31
|
+
|
|
32
|
+
- 🎯 **Interactive issue selection** - Lists open issues with arrow-key navigation
|
|
33
|
+
- 🌿 **Worktree isolation** - Each issue gets its own worktree, work on multiple issues in parallel
|
|
34
|
+
- 🤖 **Automatic PR creation** - Creates a PR that closes the issue when merged
|
|
35
|
+
- 📁 **Works with any repo** - Auto-detects project name from git remote
|
|
36
|
+
- 💻 **Opens in new terminal** - Keeps your current terminal free (supports iTerm2 and Terminal.app on macOS)
|
|
37
|
+
|
|
38
|
+
## Requirements
|
|
39
|
+
|
|
40
|
+
- [Node.js](https://nodejs.org/) >= 18
|
|
41
|
+
- [Claude Code CLI](https://claude.ai/code) - `npm install -g @anthropic-ai/claude-code`
|
|
42
|
+
- [GitHub CLI](https://cli.github.com/) - `brew install gh` (and run `gh auth login`)
|
|
43
|
+
- Git
|
|
44
|
+
|
|
45
|
+
## Installation
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npm install -g claude-issue-solver
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Or install from source:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
git clone https://github.com/MikeOuroumis/claude-issue-solver.git
|
|
55
|
+
cd claude-issue-solver
|
|
56
|
+
npm install
|
|
57
|
+
npm run build
|
|
58
|
+
npm link
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Usage
|
|
62
|
+
|
|
63
|
+
Run from any git repository with GitHub issues:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
# Interactive: show issues and select one
|
|
67
|
+
claude-issue
|
|
68
|
+
|
|
69
|
+
# Solve a specific issue directly
|
|
70
|
+
claude-issue 42
|
|
71
|
+
|
|
72
|
+
# List open issues
|
|
73
|
+
claude-issue list
|
|
74
|
+
|
|
75
|
+
# Create PR for a solved issue (if you skipped it earlier)
|
|
76
|
+
claude-issue pr 42
|
|
77
|
+
|
|
78
|
+
# Clean up worktree and branch after PR is merged
|
|
79
|
+
claude-issue clean 42
|
|
80
|
+
|
|
81
|
+
# Show help
|
|
82
|
+
claude-issue --help
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## How it works
|
|
86
|
+
|
|
87
|
+
1. **Fetches issue** - Gets title and description from GitHub
|
|
88
|
+
2. **Creates worktree** - Makes a new git worktree with branch `issue-{number}-{slug}`
|
|
89
|
+
3. **Sets up environment** - Copies `.env` files, symlinks `node_modules`
|
|
90
|
+
4. **Opens Claude** - Launches Claude Code in a new terminal with the issue as context
|
|
91
|
+
5. **Interactive session** - Claude stays open so you can ask for changes
|
|
92
|
+
6. **Creates PR** - When you exit, prompts to create a PR that closes the issue
|
|
93
|
+
|
|
94
|
+
## Workflow
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
┌─────────────────┐
|
|
98
|
+
│ claude-issue │
|
|
99
|
+
│ (select 42) │
|
|
100
|
+
└────────┬────────┘
|
|
101
|
+
│
|
|
102
|
+
▼
|
|
103
|
+
┌─────────────────┐
|
|
104
|
+
│ Create worktree │
|
|
105
|
+
│ ../project-issue│
|
|
106
|
+
│ -42-fix-bug │
|
|
107
|
+
└────────┬────────┘
|
|
108
|
+
│
|
|
109
|
+
▼
|
|
110
|
+
┌─────────────────┐
|
|
111
|
+
│ Open new term │
|
|
112
|
+
│ with Claude │
|
|
113
|
+
└────────┬────────┘
|
|
114
|
+
│
|
|
115
|
+
▼
|
|
116
|
+
┌─────────────────┐
|
|
117
|
+
│ Claude solves │
|
|
118
|
+
│ issue & commits │
|
|
119
|
+
└────────┬────────┘
|
|
120
|
+
│
|
|
121
|
+
▼
|
|
122
|
+
┌─────────────────┐
|
|
123
|
+
│ Create PR │
|
|
124
|
+
│ "Closes #42" │
|
|
125
|
+
└────────┬────────┘
|
|
126
|
+
│
|
|
127
|
+
▼
|
|
128
|
+
┌─────────────────┐
|
|
129
|
+
│ claude-issue │
|
|
130
|
+
│ clean 42 │
|
|
131
|
+
└─────────────────┘
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Tips
|
|
135
|
+
|
|
136
|
+
- Use `/exit` in Claude to end the session and trigger PR creation
|
|
137
|
+
- Worktrees share the same `.git` so commits are visible in main repo
|
|
138
|
+
- Run `claude-issue clean <number>` after merging to clean up
|
|
139
|
+
- You can work on multiple issues in parallel - each gets its own worktree
|
|
140
|
+
|
|
141
|
+
## License
|
|
142
|
+
|
|
143
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function cleanCommand(issueNumber: number): Promise<void>;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.cleanCommand = cleanCommand;
|
|
40
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
41
|
+
const ora_1 = __importDefault(require("ora"));
|
|
42
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
43
|
+
const fs = __importStar(require("fs"));
|
|
44
|
+
const path = __importStar(require("path"));
|
|
45
|
+
const child_process_1 = require("child_process");
|
|
46
|
+
const github_1 = require("../utils/github");
|
|
47
|
+
const git_1 = require("../utils/git");
|
|
48
|
+
const helpers_1 = require("../utils/helpers");
|
|
49
|
+
async function cleanCommand(issueNumber) {
|
|
50
|
+
const spinner = (0, ora_1.default)(`Fetching issue #${issueNumber}...`).start();
|
|
51
|
+
const issue = (0, github_1.getIssue)(issueNumber);
|
|
52
|
+
if (!issue) {
|
|
53
|
+
spinner.fail(`Could not find issue #${issueNumber}`);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
spinner.succeed(`Found issue #${issueNumber}`);
|
|
57
|
+
const projectRoot = (0, git_1.getProjectRoot)();
|
|
58
|
+
const projectName = (0, git_1.getProjectName)();
|
|
59
|
+
const branchSlug = (0, helpers_1.slugify)(issue.title);
|
|
60
|
+
const branchName = `issue-${issueNumber}-${branchSlug}`;
|
|
61
|
+
const worktreePath = path.join(path.dirname(projectRoot), `${projectName}-${branchName}`);
|
|
62
|
+
console.log();
|
|
63
|
+
console.log(chalk_1.default.bold(`🧹 Cleaning up issue #${issueNumber}`));
|
|
64
|
+
console.log(chalk_1.default.dim(` Branch: ${branchName}`));
|
|
65
|
+
console.log(chalk_1.default.dim(` Worktree: ${worktreePath}`));
|
|
66
|
+
console.log();
|
|
67
|
+
const { confirm } = await inquirer_1.default.prompt([
|
|
68
|
+
{
|
|
69
|
+
type: 'confirm',
|
|
70
|
+
name: 'confirm',
|
|
71
|
+
message: 'Remove worktree and delete branch?',
|
|
72
|
+
default: false,
|
|
73
|
+
},
|
|
74
|
+
]);
|
|
75
|
+
if (!confirm) {
|
|
76
|
+
console.log(chalk_1.default.dim('Cancelled.'));
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
// Remove worktree
|
|
80
|
+
if (fs.existsSync(worktreePath)) {
|
|
81
|
+
const worktreeSpinner = (0, ora_1.default)('Removing worktree...').start();
|
|
82
|
+
try {
|
|
83
|
+
(0, child_process_1.execSync)(`git worktree remove "${worktreePath}" --force`, {
|
|
84
|
+
cwd: projectRoot,
|
|
85
|
+
stdio: 'pipe',
|
|
86
|
+
});
|
|
87
|
+
worktreeSpinner.succeed('Worktree removed');
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
worktreeSpinner.warn('Could not remove worktree (may already be removed)');
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// Delete branch
|
|
94
|
+
const branchSpinner = (0, ora_1.default)('Deleting branch...').start();
|
|
95
|
+
try {
|
|
96
|
+
(0, child_process_1.execSync)(`git branch -D "${branchName}"`, {
|
|
97
|
+
cwd: projectRoot,
|
|
98
|
+
stdio: 'pipe',
|
|
99
|
+
});
|
|
100
|
+
branchSpinner.succeed('Branch deleted');
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
branchSpinner.warn('Could not delete branch (may already be deleted)');
|
|
104
|
+
}
|
|
105
|
+
console.log();
|
|
106
|
+
console.log(chalk_1.default.green('✅ Cleanup complete!'));
|
|
107
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function listCommand(): Promise<void>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.listCommand = listCommand;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const github_1 = require("../utils/github");
|
|
9
|
+
const git_1 = require("../utils/git");
|
|
10
|
+
async function listCommand() {
|
|
11
|
+
const projectName = (0, git_1.getProjectName)();
|
|
12
|
+
console.log(chalk_1.default.bold(`\nOpen issues for ${projectName}:\n`));
|
|
13
|
+
const issues = (0, github_1.listIssues)();
|
|
14
|
+
if (issues.length === 0) {
|
|
15
|
+
console.log(chalk_1.default.yellow('No open issues found.'));
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
for (const issue of issues) {
|
|
19
|
+
console.log(` ${chalk_1.default.cyan(`#${issue.number}`)}\t${issue.title}`);
|
|
20
|
+
}
|
|
21
|
+
console.log();
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function prCommand(issueNumber: number): Promise<void>;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.prCommand = prCommand;
|
|
40
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
41
|
+
const ora_1 = __importDefault(require("ora"));
|
|
42
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
43
|
+
const fs = __importStar(require("fs"));
|
|
44
|
+
const path = __importStar(require("path"));
|
|
45
|
+
const child_process_1 = require("child_process");
|
|
46
|
+
const github_1 = require("../utils/github");
|
|
47
|
+
const git_1 = require("../utils/git");
|
|
48
|
+
const helpers_1 = require("../utils/helpers");
|
|
49
|
+
async function prCommand(issueNumber) {
|
|
50
|
+
const spinner = (0, ora_1.default)(`Fetching issue #${issueNumber}...`).start();
|
|
51
|
+
const issue = (0, github_1.getIssue)(issueNumber);
|
|
52
|
+
if (!issue) {
|
|
53
|
+
spinner.fail(`Could not find issue #${issueNumber}`);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
spinner.succeed(`Found issue #${issueNumber}`);
|
|
57
|
+
const projectRoot = (0, git_1.getProjectRoot)();
|
|
58
|
+
const projectName = (0, git_1.getProjectName)();
|
|
59
|
+
const branchSlug = (0, helpers_1.slugify)(issue.title);
|
|
60
|
+
const branchName = `issue-${issueNumber}-${branchSlug}`;
|
|
61
|
+
const worktreePath = path.join(path.dirname(projectRoot), `${projectName}-${branchName}`);
|
|
62
|
+
if (!fs.existsSync(worktreePath)) {
|
|
63
|
+
console.log(chalk_1.default.red(`\n❌ Worktree not found at ${worktreePath}`));
|
|
64
|
+
console.log(chalk_1.default.dim(` Make sure you've run: claude-issue ${issueNumber}`));
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
const commits = (0, git_1.getCommitCount)(worktreePath);
|
|
68
|
+
if (commits === 0) {
|
|
69
|
+
console.log(chalk_1.default.yellow(`\n⚠️ No commits found on branch ${branchName}`));
|
|
70
|
+
console.log(chalk_1.default.dim(` Make sure Claude has committed changes before creating a PR.`));
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
console.log();
|
|
74
|
+
console.log(chalk_1.default.bold(`📋 Issue #${issueNumber}: ${issue.title}`));
|
|
75
|
+
console.log(chalk_1.default.dim(`🌿 Branch: ${branchName}`));
|
|
76
|
+
console.log(chalk_1.default.dim(`📝 Commits: ${commits}`));
|
|
77
|
+
console.log();
|
|
78
|
+
const { confirm } = await inquirer_1.default.prompt([
|
|
79
|
+
{
|
|
80
|
+
type: 'confirm',
|
|
81
|
+
name: 'confirm',
|
|
82
|
+
message: `Create PR to close issue #${issueNumber}?`,
|
|
83
|
+
default: true,
|
|
84
|
+
},
|
|
85
|
+
]);
|
|
86
|
+
if (!confirm) {
|
|
87
|
+
console.log(chalk_1.default.dim('Cancelled.'));
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const pushSpinner = (0, ora_1.default)('Pushing branch and creating PR...').start();
|
|
91
|
+
try {
|
|
92
|
+
(0, child_process_1.execSync)(`git push -u origin "${branchName}"`, {
|
|
93
|
+
cwd: worktreePath,
|
|
94
|
+
stdio: 'pipe',
|
|
95
|
+
});
|
|
96
|
+
const commitList = (0, git_1.getCommitList)(worktreePath);
|
|
97
|
+
const prBody = `## Summary
|
|
98
|
+
|
|
99
|
+
Closes #${issueNumber}
|
|
100
|
+
|
|
101
|
+
## Changes
|
|
102
|
+
|
|
103
|
+
${commitList}
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
🤖 Generated with [Claude Code](https://claude.com/claude-code)`;
|
|
108
|
+
const prUrl = (0, child_process_1.execSync)(`gh pr create --title "Fix #${issueNumber}: ${issue.title.replace(/"/g, '\\"')}" --body "${prBody.replace(/"/g, '\\"')}" --head "${branchName}" --base main`, { cwd: worktreePath, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
109
|
+
pushSpinner.succeed('PR created!');
|
|
110
|
+
console.log();
|
|
111
|
+
console.log(chalk_1.default.green(`✅ PR created: ${prUrl}`));
|
|
112
|
+
console.log();
|
|
113
|
+
console.log(chalk_1.default.dim(`The PR will automatically close issue #${issueNumber} when merged.`));
|
|
114
|
+
console.log(chalk_1.default.dim(`To clean up after merge: claude-issue clean ${issueNumber}`));
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
pushSpinner.fail('Failed to create PR');
|
|
118
|
+
console.error(error);
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function selectCommand(): Promise<void>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.selectCommand = selectCommand;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
9
|
+
const github_1 = require("../utils/github");
|
|
10
|
+
const git_1 = require("../utils/git");
|
|
11
|
+
const solve_1 = require("./solve");
|
|
12
|
+
async function selectCommand() {
|
|
13
|
+
const projectName = (0, git_1.getProjectName)();
|
|
14
|
+
console.log(chalk_1.default.bold(`\nOpen issues for ${projectName}:\n`));
|
|
15
|
+
const issues = (0, github_1.listIssues)();
|
|
16
|
+
if (issues.length === 0) {
|
|
17
|
+
console.log(chalk_1.default.yellow('No open issues found.'));
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const choices = issues.map((issue) => ({
|
|
21
|
+
name: `#${issue.number}\t${issue.title}`,
|
|
22
|
+
value: issue.number,
|
|
23
|
+
}));
|
|
24
|
+
choices.push({
|
|
25
|
+
name: chalk_1.default.dim('Cancel'),
|
|
26
|
+
value: -1,
|
|
27
|
+
});
|
|
28
|
+
const { issueNumber } = await inquirer_1.default.prompt([
|
|
29
|
+
{
|
|
30
|
+
type: 'list',
|
|
31
|
+
name: 'issueNumber',
|
|
32
|
+
message: 'Select an issue to solve:',
|
|
33
|
+
choices,
|
|
34
|
+
pageSize: 15,
|
|
35
|
+
},
|
|
36
|
+
]);
|
|
37
|
+
if (issueNumber === -1) {
|
|
38
|
+
console.log(chalk_1.default.dim('Cancelled.'));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
await (0, solve_1.solveCommand)(issueNumber);
|
|
42
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function solveCommand(issueNumber: number): Promise<void>;
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.solveCommand = solveCommand;
|
|
40
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
41
|
+
const ora_1 = __importDefault(require("ora"));
|
|
42
|
+
const fs = __importStar(require("fs"));
|
|
43
|
+
const path = __importStar(require("path"));
|
|
44
|
+
const child_process_1 = require("child_process");
|
|
45
|
+
const github_1 = require("../utils/github");
|
|
46
|
+
const git_1 = require("../utils/git");
|
|
47
|
+
const helpers_1 = require("../utils/helpers");
|
|
48
|
+
async function solveCommand(issueNumber) {
|
|
49
|
+
const spinner = (0, ora_1.default)(`Fetching issue #${issueNumber}...`).start();
|
|
50
|
+
const issue = (0, github_1.getIssue)(issueNumber);
|
|
51
|
+
if (!issue) {
|
|
52
|
+
spinner.fail(`Could not find issue #${issueNumber}`);
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
spinner.succeed(`Found issue #${issueNumber}`);
|
|
56
|
+
console.log();
|
|
57
|
+
console.log(chalk_1.default.bold(`📌 Issue: ${issue.title}`));
|
|
58
|
+
console.log(chalk_1.default.dim(`🔗 URL: ${issue.url}`));
|
|
59
|
+
console.log();
|
|
60
|
+
const projectRoot = (0, git_1.getProjectRoot)();
|
|
61
|
+
const projectName = (0, git_1.getProjectName)();
|
|
62
|
+
const branchSlug = (0, helpers_1.slugify)(issue.title);
|
|
63
|
+
const branchName = `issue-${issueNumber}-${branchSlug}`;
|
|
64
|
+
const worktreePath = path.join(path.dirname(projectRoot), `${projectName}-${branchName}`);
|
|
65
|
+
// Fetch latest main
|
|
66
|
+
const fetchSpinner = (0, ora_1.default)('Fetching latest main...').start();
|
|
67
|
+
try {
|
|
68
|
+
(0, child_process_1.execSync)('git fetch origin main --quiet', { cwd: projectRoot, stdio: 'pipe' });
|
|
69
|
+
fetchSpinner.succeed('Fetched latest main');
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
fetchSpinner.warn('Could not fetch origin/main');
|
|
73
|
+
}
|
|
74
|
+
// Check if worktree already exists
|
|
75
|
+
if (fs.existsSync(worktreePath)) {
|
|
76
|
+
console.log(chalk_1.default.yellow(`\n🌿 Worktree already exists at: ${worktreePath}`));
|
|
77
|
+
console.log(chalk_1.default.dim(` Resuming work on issue #${issueNumber}...`));
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
const worktreeSpinner = (0, ora_1.default)(`Creating worktree with branch: ${branchName}`).start();
|
|
81
|
+
try {
|
|
82
|
+
if ((0, git_1.branchExists)(branchName)) {
|
|
83
|
+
(0, child_process_1.execSync)(`git worktree add "${worktreePath}" "${branchName}"`, {
|
|
84
|
+
cwd: projectRoot,
|
|
85
|
+
stdio: 'pipe',
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
(0, child_process_1.execSync)(`git worktree add "${worktreePath}" -b "${branchName}" origin/main`, {
|
|
90
|
+
cwd: projectRoot,
|
|
91
|
+
stdio: 'pipe',
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
worktreeSpinner.succeed(`Created worktree at: ${worktreePath}`);
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
worktreeSpinner.fail('Failed to create worktree');
|
|
98
|
+
console.error(error);
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
// Copy env files and symlink node_modules
|
|
102
|
+
const setupSpinner = (0, ora_1.default)('Setting up worktree...').start();
|
|
103
|
+
(0, helpers_1.copyEnvFiles)(projectRoot, worktreePath);
|
|
104
|
+
(0, helpers_1.symlinkNodeModules)(projectRoot, worktreePath);
|
|
105
|
+
setupSpinner.succeed('Worktree setup complete');
|
|
106
|
+
}
|
|
107
|
+
// Build the prompt for Claude
|
|
108
|
+
const prompt = `Please solve this GitHub issue:
|
|
109
|
+
|
|
110
|
+
## Issue #${issueNumber}: ${issue.title}
|
|
111
|
+
|
|
112
|
+
${issue.body}
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
Instructions:
|
|
117
|
+
1. Analyze the issue and understand what needs to be done
|
|
118
|
+
2. Implement the necessary changes
|
|
119
|
+
3. Make sure to run tests if applicable
|
|
120
|
+
4. When done, commit your changes with a descriptive message that references the issue`;
|
|
121
|
+
// Create runner script
|
|
122
|
+
const runnerScript = path.join(worktreePath, '.claude-runner.sh');
|
|
123
|
+
const runnerContent = `#!/bin/bash
|
|
124
|
+
cd "${worktreePath}"
|
|
125
|
+
echo "🤖 Claude Code - Issue #${issueNumber}: ${issue.title}"
|
|
126
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
127
|
+
echo ""
|
|
128
|
+
echo "Claude will stay open after solving the issue."
|
|
129
|
+
echo "You can ask for more changes or type /exit when done."
|
|
130
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
131
|
+
echo ""
|
|
132
|
+
|
|
133
|
+
claude --dangerously-skip-permissions "${prompt.replace(/"/g, '\\"').replace(/\n/g, '\\n')}"
|
|
134
|
+
|
|
135
|
+
echo ""
|
|
136
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
137
|
+
echo "Claude session ended."
|
|
138
|
+
echo ""
|
|
139
|
+
|
|
140
|
+
COMMITS=$(git log origin/main..HEAD --oneline 2>/dev/null | wc -l | tr -d ' ')
|
|
141
|
+
|
|
142
|
+
if [ "$COMMITS" -eq 0 ]; then
|
|
143
|
+
echo "⚠️ No commits were made."
|
|
144
|
+
echo "To clean up: claude-issue clean ${issueNumber}"
|
|
145
|
+
else
|
|
146
|
+
echo "✅ Found $COMMITS commit(s)"
|
|
147
|
+
echo ""
|
|
148
|
+
read -p "Create PR to close issue #${issueNumber}? (Y/n) " -n 1 -r
|
|
149
|
+
echo ""
|
|
150
|
+
|
|
151
|
+
if [[ ! $REPLY =~ ^[Nn]$ ]]; then
|
|
152
|
+
echo ""
|
|
153
|
+
echo "📤 Pushing branch and creating PR..."
|
|
154
|
+
|
|
155
|
+
git push -u origin "${branchName}"
|
|
156
|
+
|
|
157
|
+
COMMIT_LIST=$(git log origin/main..HEAD --pretty=format:'- %s' | head -10)
|
|
158
|
+
|
|
159
|
+
PR_URL=$(gh pr create \\
|
|
160
|
+
--title "Fix #${issueNumber}: ${issue.title.replace(/"/g, '\\"')}" \\
|
|
161
|
+
--body "## Summary
|
|
162
|
+
|
|
163
|
+
Closes #${issueNumber}
|
|
164
|
+
|
|
165
|
+
## Changes
|
|
166
|
+
|
|
167
|
+
$COMMIT_LIST
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
🤖 Generated with [Claude Code](https://claude.com/claude-code)" \\
|
|
172
|
+
--head "${branchName}" \\
|
|
173
|
+
--base main)
|
|
174
|
+
|
|
175
|
+
echo ""
|
|
176
|
+
echo "✅ PR created: $PR_URL"
|
|
177
|
+
echo ""
|
|
178
|
+
echo "The PR will automatically close issue #${issueNumber} when merged."
|
|
179
|
+
fi
|
|
180
|
+
|
|
181
|
+
echo ""
|
|
182
|
+
echo "To clean up after merge: claude-issue clean ${issueNumber}"
|
|
183
|
+
fi
|
|
184
|
+
|
|
185
|
+
echo ""
|
|
186
|
+
rm -f "${runnerScript}"
|
|
187
|
+
`;
|
|
188
|
+
fs.writeFileSync(runnerScript, runnerContent, { mode: 0o755 });
|
|
189
|
+
console.log();
|
|
190
|
+
console.log(chalk_1.default.cyan('🤖 Opening new terminal to run Claude Code...'));
|
|
191
|
+
console.log();
|
|
192
|
+
(0, helpers_1.openInNewTerminal)(`'${runnerScript}'`);
|
|
193
|
+
console.log(chalk_1.default.green(`✅ Worktree created at: ${worktreePath}`));
|
|
194
|
+
console.log(chalk_1.default.dim(` Claude is running in a new terminal window.`));
|
|
195
|
+
console.log();
|
|
196
|
+
console.log(chalk_1.default.dim(` You can also open VS Code: code ${worktreePath}`));
|
|
197
|
+
console.log(chalk_1.default.dim(` To clean up later: claude-issue clean ${issueNumber}`));
|
|
198
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const commander_1 = require("commander");
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
const helpers_1 = require("./utils/helpers");
|
|
10
|
+
const git_1 = require("./utils/git");
|
|
11
|
+
const list_1 = require("./commands/list");
|
|
12
|
+
const solve_1 = require("./commands/solve");
|
|
13
|
+
const pr_1 = require("./commands/pr");
|
|
14
|
+
const clean_1 = require("./commands/clean");
|
|
15
|
+
const select_1 = require("./commands/select");
|
|
16
|
+
const program = new commander_1.Command();
|
|
17
|
+
program
|
|
18
|
+
.name('claude-issue')
|
|
19
|
+
.description('Automatically solve GitHub issues using Claude Code')
|
|
20
|
+
.version('1.0.0');
|
|
21
|
+
// Check requirements before any command
|
|
22
|
+
program.hook('preAction', () => {
|
|
23
|
+
if (!(0, git_1.isGitRepo)()) {
|
|
24
|
+
console.log(chalk_1.default.red('❌ Not in a git repository'));
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
const { ok, missing } = (0, helpers_1.checkRequirements)();
|
|
28
|
+
if (!ok) {
|
|
29
|
+
console.log(chalk_1.default.red('\n❌ Missing requirements:\n'));
|
|
30
|
+
for (const m of missing) {
|
|
31
|
+
console.log(chalk_1.default.yellow(` • ${m}`));
|
|
32
|
+
}
|
|
33
|
+
console.log();
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
// Default command - interactive selection
|
|
38
|
+
program
|
|
39
|
+
.argument('[issue]', 'Issue number to solve')
|
|
40
|
+
.action(async (issue) => {
|
|
41
|
+
if (issue) {
|
|
42
|
+
const issueNumber = parseInt(issue, 10);
|
|
43
|
+
if (isNaN(issueNumber)) {
|
|
44
|
+
console.log(chalk_1.default.red(`❌ Invalid issue number: ${issue}`));
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
await (0, solve_1.solveCommand)(issueNumber);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
await (0, select_1.selectCommand)();
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
// List command
|
|
54
|
+
program
|
|
55
|
+
.command('list')
|
|
56
|
+
.alias('ls')
|
|
57
|
+
.description('List open issues')
|
|
58
|
+
.action(list_1.listCommand);
|
|
59
|
+
// PR command
|
|
60
|
+
program
|
|
61
|
+
.command('pr <issue>')
|
|
62
|
+
.description('Create PR for a solved issue')
|
|
63
|
+
.action(async (issue) => {
|
|
64
|
+
const issueNumber = parseInt(issue, 10);
|
|
65
|
+
if (isNaN(issueNumber)) {
|
|
66
|
+
console.log(chalk_1.default.red(`❌ Invalid issue number: ${issue}`));
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
await (0, pr_1.prCommand)(issueNumber);
|
|
70
|
+
});
|
|
71
|
+
// Clean command
|
|
72
|
+
program
|
|
73
|
+
.command('clean <issue>')
|
|
74
|
+
.alias('rm')
|
|
75
|
+
.description('Remove worktree and branch for an issue')
|
|
76
|
+
.action(async (issue) => {
|
|
77
|
+
const issueNumber = parseInt(issue, 10);
|
|
78
|
+
if (isNaN(issueNumber)) {
|
|
79
|
+
console.log(chalk_1.default.red(`❌ Invalid issue number: ${issue}`));
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
await (0, clean_1.cleanCommand)(issueNumber);
|
|
83
|
+
});
|
|
84
|
+
program.parse();
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare function exec(command: string, cwd?: string): string;
|
|
2
|
+
export declare function execOrFail(command: string, cwd?: string): string;
|
|
3
|
+
export declare function getProjectRoot(): string;
|
|
4
|
+
export declare function getProjectName(): string;
|
|
5
|
+
export declare function isGitRepo(): boolean;
|
|
6
|
+
export declare function branchExists(branchName: string): boolean;
|
|
7
|
+
export declare function getCommitCount(worktreePath: string): number;
|
|
8
|
+
export declare function getCommitList(worktreePath: string, limit?: number): string;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.exec = exec;
|
|
4
|
+
exports.execOrFail = execOrFail;
|
|
5
|
+
exports.getProjectRoot = getProjectRoot;
|
|
6
|
+
exports.getProjectName = getProjectName;
|
|
7
|
+
exports.isGitRepo = isGitRepo;
|
|
8
|
+
exports.branchExists = branchExists;
|
|
9
|
+
exports.getCommitCount = getCommitCount;
|
|
10
|
+
exports.getCommitList = getCommitList;
|
|
11
|
+
const child_process_1 = require("child_process");
|
|
12
|
+
function exec(command, cwd) {
|
|
13
|
+
try {
|
|
14
|
+
return (0, child_process_1.execSync)(command, {
|
|
15
|
+
cwd,
|
|
16
|
+
encoding: 'utf-8',
|
|
17
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
18
|
+
}).trim();
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
return '';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function execOrFail(command, cwd) {
|
|
25
|
+
return (0, child_process_1.execSync)(command, {
|
|
26
|
+
cwd,
|
|
27
|
+
encoding: 'utf-8',
|
|
28
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
29
|
+
}).trim();
|
|
30
|
+
}
|
|
31
|
+
function getProjectRoot() {
|
|
32
|
+
try {
|
|
33
|
+
return exec('git rev-parse --show-toplevel') || process.cwd();
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return process.cwd();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function getProjectName() {
|
|
40
|
+
// Try to get from git remote
|
|
41
|
+
const remoteUrl = exec('git config --get remote.origin.url');
|
|
42
|
+
if (remoteUrl) {
|
|
43
|
+
// Extract repo name from URL (handles both HTTPS and SSH)
|
|
44
|
+
const match = remoteUrl.match(/\/([^/]+?)(\.git)?$/);
|
|
45
|
+
if (match) {
|
|
46
|
+
return match[1].replace('.git', '');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// Fallback to current directory name
|
|
50
|
+
return require('path').basename(process.cwd());
|
|
51
|
+
}
|
|
52
|
+
function isGitRepo() {
|
|
53
|
+
try {
|
|
54
|
+
(0, child_process_1.execSync)('git rev-parse --git-dir', { stdio: 'pipe' });
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function branchExists(branchName) {
|
|
62
|
+
try {
|
|
63
|
+
(0, child_process_1.execSync)(`git show-ref --verify --quiet refs/heads/${branchName}`, {
|
|
64
|
+
stdio: 'pipe',
|
|
65
|
+
});
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function getCommitCount(worktreePath) {
|
|
73
|
+
try {
|
|
74
|
+
const output = exec('git log origin/main..HEAD --oneline', worktreePath);
|
|
75
|
+
if (!output)
|
|
76
|
+
return 0;
|
|
77
|
+
return output.split('\n').filter(Boolean).length;
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return 0;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function getCommitList(worktreePath, limit = 10) {
|
|
84
|
+
return exec(`git log origin/main..HEAD --pretty=format:'- %s' | head -${limit}`, worktreePath);
|
|
85
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface Issue {
|
|
2
|
+
number: number;
|
|
3
|
+
title: string;
|
|
4
|
+
body: string;
|
|
5
|
+
url: string;
|
|
6
|
+
}
|
|
7
|
+
export interface IssueListItem {
|
|
8
|
+
number: number;
|
|
9
|
+
title: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function getIssue(issueNumber: number): Issue | null;
|
|
12
|
+
export declare function listIssues(limit?: number): IssueListItem[];
|
|
13
|
+
export declare function createPullRequest(title: string, body: string, branch: string, base?: string): string;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getIssue = getIssue;
|
|
4
|
+
exports.listIssues = listIssues;
|
|
5
|
+
exports.createPullRequest = createPullRequest;
|
|
6
|
+
const child_process_1 = require("child_process");
|
|
7
|
+
function getIssue(issueNumber) {
|
|
8
|
+
try {
|
|
9
|
+
const output = (0, child_process_1.execSync)(`gh issue view ${issueNumber} --json title,body,url`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
10
|
+
const data = JSON.parse(output);
|
|
11
|
+
return {
|
|
12
|
+
number: issueNumber,
|
|
13
|
+
title: data.title,
|
|
14
|
+
body: data.body || '',
|
|
15
|
+
url: data.url,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function listIssues(limit = 20) {
|
|
23
|
+
try {
|
|
24
|
+
const output = (0, child_process_1.execSync)(`gh issue list --state open --limit ${limit} --json number,title`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
25
|
+
return JSON.parse(output);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function createPullRequest(title, body, branch, base = 'main') {
|
|
32
|
+
const output = (0, child_process_1.execSync)(`gh pr create --title "${title}" --body "${body.replace(/"/g, '\\"')}" --head "${branch}" --base "${base}"`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
33
|
+
return output.trim();
|
|
34
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare function slugify(text: string): string;
|
|
2
|
+
export declare function checkRequirements(): {
|
|
3
|
+
ok: boolean;
|
|
4
|
+
missing: string[];
|
|
5
|
+
};
|
|
6
|
+
export declare function openInNewTerminal(script: string): void;
|
|
7
|
+
export declare function copyEnvFiles(from: string, to: string): void;
|
|
8
|
+
export declare function symlinkNodeModules(from: string, to: string): void;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.slugify = slugify;
|
|
37
|
+
exports.checkRequirements = checkRequirements;
|
|
38
|
+
exports.openInNewTerminal = openInNewTerminal;
|
|
39
|
+
exports.copyEnvFiles = copyEnvFiles;
|
|
40
|
+
exports.symlinkNodeModules = symlinkNodeModules;
|
|
41
|
+
const child_process_1 = require("child_process");
|
|
42
|
+
const fs = __importStar(require("fs"));
|
|
43
|
+
const path = __importStar(require("path"));
|
|
44
|
+
const os = __importStar(require("os"));
|
|
45
|
+
function slugify(text) {
|
|
46
|
+
return text
|
|
47
|
+
.toLowerCase()
|
|
48
|
+
.replace(/[^a-z0-9]/g, '-')
|
|
49
|
+
.replace(/-+/g, '-')
|
|
50
|
+
.replace(/^-|-$/g, '')
|
|
51
|
+
.slice(0, 50);
|
|
52
|
+
}
|
|
53
|
+
function checkRequirements() {
|
|
54
|
+
const missing = [];
|
|
55
|
+
try {
|
|
56
|
+
(0, child_process_1.execSync)('gh --version', { stdio: 'pipe' });
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
missing.push('gh (GitHub CLI) - Install: brew install gh');
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
(0, child_process_1.execSync)('claude --version', { stdio: 'pipe' });
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
missing.push('claude (Claude Code CLI) - Install: npm install -g @anthropic-ai/claude-code');
|
|
66
|
+
}
|
|
67
|
+
return { ok: missing.length === 0, missing };
|
|
68
|
+
}
|
|
69
|
+
function openInNewTerminal(script) {
|
|
70
|
+
const platform = os.platform();
|
|
71
|
+
if (platform === 'darwin') {
|
|
72
|
+
// macOS - try iTerm2 first, then Terminal
|
|
73
|
+
const iTermScript = `
|
|
74
|
+
tell application "iTerm"
|
|
75
|
+
activate
|
|
76
|
+
set newWindow to (create window with default profile)
|
|
77
|
+
tell current session of newWindow
|
|
78
|
+
write text "${script.replace(/"/g, '\\"')}"
|
|
79
|
+
end tell
|
|
80
|
+
end tell
|
|
81
|
+
`;
|
|
82
|
+
const terminalScript = `
|
|
83
|
+
tell application "Terminal"
|
|
84
|
+
activate
|
|
85
|
+
do script "${script.replace(/"/g, '\\"')}"
|
|
86
|
+
end tell
|
|
87
|
+
`;
|
|
88
|
+
try {
|
|
89
|
+
// Check if iTerm is installed
|
|
90
|
+
if (fs.existsSync('/Applications/iTerm.app')) {
|
|
91
|
+
(0, child_process_1.execSync)(`osascript -e '${iTermScript}'`, { stdio: 'pipe' });
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
(0, child_process_1.execSync)(`osascript -e '${terminalScript}'`, { stdio: 'pipe' });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// Fallback to Terminal
|
|
99
|
+
try {
|
|
100
|
+
(0, child_process_1.execSync)(`osascript -e '${terminalScript}'`, { stdio: 'pipe' });
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
console.log('Could not open new terminal. Run manually:');
|
|
104
|
+
console.log(script);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
else if (platform === 'linux') {
|
|
109
|
+
// Linux - try common terminal emulators
|
|
110
|
+
const terminals = ['gnome-terminal', 'xterm', 'konsole'];
|
|
111
|
+
for (const term of terminals) {
|
|
112
|
+
try {
|
|
113
|
+
(0, child_process_1.execSync)(`which ${term}`, { stdio: 'pipe' });
|
|
114
|
+
if (term === 'gnome-terminal') {
|
|
115
|
+
(0, child_process_1.spawn)(term, ['--', 'bash', '-c', script], { detached: true });
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
(0, child_process_1.spawn)(term, ['-e', script], { detached: true });
|
|
119
|
+
}
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
console.log('Could not detect terminal emulator. Run manually:');
|
|
127
|
+
console.log(script);
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
console.log('Unsupported platform. Run manually:');
|
|
131
|
+
console.log(script);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
function copyEnvFiles(from, to) {
|
|
135
|
+
const envFiles = ['.env', '.env.local'];
|
|
136
|
+
for (const file of envFiles) {
|
|
137
|
+
const src = path.join(from, file);
|
|
138
|
+
const dest = path.join(to, file);
|
|
139
|
+
if (fs.existsSync(src) && !fs.existsSync(dest)) {
|
|
140
|
+
fs.copyFileSync(src, dest);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function symlinkNodeModules(from, to) {
|
|
145
|
+
const src = path.join(from, 'node_modules');
|
|
146
|
+
const dest = path.join(to, 'node_modules');
|
|
147
|
+
if (fs.existsSync(src) && !fs.existsSync(dest)) {
|
|
148
|
+
fs.symlinkSync(src, dest);
|
|
149
|
+
}
|
|
150
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "claude-issue-solver",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Automatically solve GitHub issues using Claude Code",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"claude-issue": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"dev": "ts-node src/index.ts",
|
|
12
|
+
"prepublishOnly": "npm run build"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"claude",
|
|
16
|
+
"github",
|
|
17
|
+
"issues",
|
|
18
|
+
"ai",
|
|
19
|
+
"automation",
|
|
20
|
+
"cli"
|
|
21
|
+
],
|
|
22
|
+
"author": "MikeOuroumis",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "https://github.com/MikeOuroumis/claude-issue-solver.git"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"chalk": "^4.1.2",
|
|
30
|
+
"commander": "^12.1.0",
|
|
31
|
+
"inquirer": "^8.2.6",
|
|
32
|
+
"ora": "^5.4.1"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/inquirer": "^8.2.10",
|
|
36
|
+
"@types/node": "^20.10.0",
|
|
37
|
+
"typescript": "^5.3.0"
|
|
38
|
+
},
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=18.0.0"
|
|
41
|
+
},
|
|
42
|
+
"files": [
|
|
43
|
+
"dist"
|
|
44
|
+
]
|
|
45
|
+
}
|