claude-issue-solver 1.3.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.
@@ -1 +1,2 @@
1
+ export declare function cleanAllCommand(): Promise<void>;
1
2
  export declare function cleanCommand(issueNumber: number): Promise<void>;
@@ -36,6 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
36
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.cleanAllCommand = cleanAllCommand;
39
40
  exports.cleanCommand = cleanCommand;
40
41
  const chalk_1 = __importDefault(require("chalk"));
41
42
  const ora_1 = __importDefault(require("ora"));
@@ -46,6 +47,94 @@ const child_process_1 = require("child_process");
46
47
  const github_1 = require("../utils/github");
47
48
  const git_1 = require("../utils/git");
48
49
  const helpers_1 = require("../utils/helpers");
50
+ function getIssueWorktrees() {
51
+ const projectRoot = (0, git_1.getProjectRoot)();
52
+ const projectName = (0, git_1.getProjectName)();
53
+ const parentDir = path.dirname(projectRoot);
54
+ const worktrees = [];
55
+ // Get all worktrees from git
56
+ const output = (0, git_1.exec)('git worktree list --porcelain', projectRoot);
57
+ if (!output)
58
+ return worktrees;
59
+ const lines = output.split('\n');
60
+ let currentPath = '';
61
+ let currentBranch = '';
62
+ for (const line of lines) {
63
+ if (line.startsWith('worktree ')) {
64
+ currentPath = line.replace('worktree ', '');
65
+ }
66
+ else if (line.startsWith('branch refs/heads/')) {
67
+ currentBranch = line.replace('branch refs/heads/', '');
68
+ // Check if this is an issue branch
69
+ const match = currentBranch.match(/^issue-(\d+)-/);
70
+ if (match && currentPath.includes(`${projectName}-issue-`)) {
71
+ worktrees.push({
72
+ path: currentPath,
73
+ branch: currentBranch,
74
+ issueNumber: match[1],
75
+ });
76
+ }
77
+ }
78
+ }
79
+ return worktrees;
80
+ }
81
+ async function cleanAllCommand() {
82
+ const projectRoot = (0, git_1.getProjectRoot)();
83
+ const worktrees = getIssueWorktrees();
84
+ if (worktrees.length === 0) {
85
+ console.log(chalk_1.default.yellow('\nNo issue worktrees found.'));
86
+ return;
87
+ }
88
+ console.log(chalk_1.default.bold('\n๐Ÿงน Found issue worktrees:\n'));
89
+ for (const wt of worktrees) {
90
+ console.log(` ${chalk_1.default.cyan(`#${wt.issueNumber}`)}\t${wt.branch}`);
91
+ console.log(chalk_1.default.dim(` \t${wt.path}`));
92
+ console.log();
93
+ }
94
+ const { confirm } = await inquirer_1.default.prompt([
95
+ {
96
+ type: 'confirm',
97
+ name: 'confirm',
98
+ message: `Remove all ${worktrees.length} worktree(s) and delete branches?`,
99
+ default: false,
100
+ },
101
+ ]);
102
+ if (!confirm) {
103
+ console.log(chalk_1.default.dim('Cancelled.'));
104
+ return;
105
+ }
106
+ console.log();
107
+ for (const wt of worktrees) {
108
+ const spinner = (0, ora_1.default)(`Cleaning issue #${wt.issueNumber}...`).start();
109
+ try {
110
+ // Remove worktree
111
+ if (fs.existsSync(wt.path)) {
112
+ (0, child_process_1.execSync)(`git worktree remove "${wt.path}" --force`, {
113
+ cwd: projectRoot,
114
+ stdio: 'pipe',
115
+ });
116
+ }
117
+ // Delete branch
118
+ try {
119
+ (0, child_process_1.execSync)(`git branch -D "${wt.branch}"`, {
120
+ cwd: projectRoot,
121
+ stdio: 'pipe',
122
+ });
123
+ }
124
+ catch {
125
+ // Branch may already be deleted
126
+ }
127
+ spinner.succeed(`Cleaned issue #${wt.issueNumber}`);
128
+ }
129
+ catch (error) {
130
+ spinner.fail(`Failed to clean issue #${wt.issueNumber}`);
131
+ }
132
+ }
133
+ // Prune stale worktrees
134
+ (0, child_process_1.execSync)('git worktree prune', { cwd: projectRoot, stdio: 'pipe' });
135
+ console.log();
136
+ console.log(chalk_1.default.green(`โœ… Cleaned up ${worktrees.length} issue worktree(s)!`));
137
+ }
49
138
  async function cleanCommand(issueNumber) {
50
139
  const spinner = (0, ora_1.default)(`Fetching issue #${issueNumber}...`).start();
51
140
  const issue = (0, github_1.getIssue)(issueNumber);
@@ -0,0 +1 @@
1
+ export declare function goCommand(issueNumber?: number): Promise<void>;
@@ -0,0 +1,149 @@
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.goCommand = goCommand;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const inquirer_1 = __importDefault(require("inquirer"));
9
+ const child_process_1 = require("child_process");
10
+ const git_1 = require("../utils/git");
11
+ function getIssueWorktrees() {
12
+ const projectRoot = (0, git_1.getProjectRoot)();
13
+ const projectName = (0, git_1.getProjectName)();
14
+ const worktrees = [];
15
+ // Get all worktrees from git
16
+ const output = (0, git_1.exec)('git worktree list --porcelain', projectRoot);
17
+ if (!output)
18
+ return worktrees;
19
+ const lines = output.split('\n');
20
+ let currentPath = '';
21
+ let currentBranch = '';
22
+ for (const line of lines) {
23
+ if (line.startsWith('worktree ')) {
24
+ currentPath = line.replace('worktree ', '');
25
+ }
26
+ else if (line.startsWith('branch refs/heads/')) {
27
+ currentBranch = line.replace('branch refs/heads/', '');
28
+ // Check if this is an issue branch
29
+ const match = currentBranch.match(/^issue-(\d+)-/);
30
+ if (match && currentPath.includes(`${projectName}-issue-`)) {
31
+ worktrees.push({
32
+ path: currentPath,
33
+ branch: currentBranch,
34
+ issueNumber: match[1],
35
+ });
36
+ }
37
+ }
38
+ }
39
+ return worktrees;
40
+ }
41
+ function getPRForBranch(branch) {
42
+ try {
43
+ const output = (0, child_process_1.execSync)(`gh pr list --head "${branch}" --json url --jq '.[0].url'`, {
44
+ encoding: 'utf-8',
45
+ stdio: ['pipe', 'pipe', 'pipe'],
46
+ }).trim();
47
+ return output || null;
48
+ }
49
+ catch {
50
+ return null;
51
+ }
52
+ }
53
+ async function goCommand(issueNumber) {
54
+ const worktrees = getIssueWorktrees();
55
+ if (worktrees.length === 0) {
56
+ console.log(chalk_1.default.yellow('\nNo issue worktrees found.'));
57
+ console.log(chalk_1.default.dim('Run `claude-issue <number>` to start working on an issue.'));
58
+ return;
59
+ }
60
+ let selectedWorktree;
61
+ if (issueNumber) {
62
+ // Find specific worktree
63
+ const found = worktrees.find((wt) => wt.issueNumber === String(issueNumber));
64
+ if (!found) {
65
+ console.log(chalk_1.default.red(`\nโŒ No worktree found for issue #${issueNumber}`));
66
+ console.log(chalk_1.default.dim('\nAvailable worktrees:'));
67
+ for (const wt of worktrees) {
68
+ console.log(chalk_1.default.dim(` #${wt.issueNumber}: ${wt.path}`));
69
+ }
70
+ return;
71
+ }
72
+ selectedWorktree = found;
73
+ }
74
+ else {
75
+ // Show selection
76
+ console.log(chalk_1.default.bold('\n๐Ÿ“‚ Issue worktrees:\n'));
77
+ const choices = worktrees.map((wt) => ({
78
+ name: `#${wt.issueNumber}\t${wt.branch}`,
79
+ value: wt,
80
+ }));
81
+ choices.push({
82
+ name: chalk_1.default.dim('Cancel'),
83
+ value: null,
84
+ });
85
+ const { selected } = await inquirer_1.default.prompt([
86
+ {
87
+ type: 'list',
88
+ name: 'selected',
89
+ message: 'Select a worktree to open:',
90
+ choices,
91
+ pageSize: 15,
92
+ },
93
+ ]);
94
+ if (!selected) {
95
+ console.log(chalk_1.default.dim('Cancelled.'));
96
+ return;
97
+ }
98
+ selectedWorktree = selected;
99
+ }
100
+ // Get PR info
101
+ const prUrl = getPRForBranch(selectedWorktree.branch);
102
+ console.log();
103
+ console.log(chalk_1.default.bold(`๐Ÿ“‚ Issue #${selectedWorktree.issueNumber}`));
104
+ console.log(chalk_1.default.dim(` Path: ${selectedWorktree.path}`));
105
+ console.log(chalk_1.default.dim(` Branch: ${selectedWorktree.branch}`));
106
+ if (prUrl) {
107
+ console.log(chalk_1.default.cyan(` PR: ${prUrl}`));
108
+ }
109
+ console.log();
110
+ // Ask what to do
111
+ const actions = [
112
+ { name: '๐Ÿ“ Open in VS Code', value: 'vscode' },
113
+ { name: '๐Ÿ“‚ Open in Finder', value: 'finder' },
114
+ { name: '๐Ÿ’ป Print cd command', value: 'cd' },
115
+ ];
116
+ if (prUrl) {
117
+ actions.unshift({ name: '๐Ÿ”— Open PR in browser', value: 'pr' });
118
+ }
119
+ actions.push({ name: chalk_1.default.dim('Cancel'), value: 'cancel' });
120
+ const { action } = await inquirer_1.default.prompt([
121
+ {
122
+ type: 'list',
123
+ name: 'action',
124
+ message: 'What would you like to do?',
125
+ choices: actions,
126
+ },
127
+ ]);
128
+ switch (action) {
129
+ case 'pr':
130
+ console.log(chalk_1.default.dim(`\nOpening PR in browser...`));
131
+ (0, child_process_1.execSync)(`open "${prUrl}"`, { stdio: 'pipe' });
132
+ break;
133
+ case 'vscode':
134
+ console.log(chalk_1.default.dim(`\nOpening in VS Code...`));
135
+ (0, child_process_1.execSync)(`code "${selectedWorktree.path}"`, { stdio: 'pipe' });
136
+ break;
137
+ case 'finder':
138
+ console.log(chalk_1.default.dim(`\nOpening in Finder...`));
139
+ (0, child_process_1.execSync)(`open "${selectedWorktree.path}"`, { stdio: 'pipe' });
140
+ break;
141
+ case 'cd':
142
+ console.log(chalk_1.default.dim(`\nRun this command:\n`));
143
+ console.log(chalk_1.default.cyan(`cd "${selectedWorktree.path}"`));
144
+ break;
145
+ case 'cancel':
146
+ console.log(chalk_1.default.dim('Cancelled.'));
147
+ break;
148
+ }
149
+ }
package/dist/index.js CHANGED
@@ -13,6 +13,7 @@ const solve_1 = require("./commands/solve");
13
13
  const pr_1 = require("./commands/pr");
14
14
  const clean_1 = require("./commands/clean");
15
15
  const select_1 = require("./commands/select");
16
+ const go_1 = require("./commands/go");
16
17
  // eslint-disable-next-line @typescript-eslint/no-var-requires
17
18
  const packageJson = require('../package.json');
18
19
  const program = new commander_1.Command();
@@ -72,15 +73,37 @@ program
72
73
  });
73
74
  // Clean command
74
75
  program
75
- .command('clean <issue>')
76
+ .command('clean [issue]')
76
77
  .alias('rm')
77
- .description('Remove worktree and branch for an issue')
78
+ .option('-a, --all', 'Clean all issue worktrees')
79
+ .description('Remove worktree and branch for an issue (or all with --all)')
80
+ .action(async (issue, options) => {
81
+ if (options.all) {
82
+ await (0, clean_1.cleanAllCommand)();
83
+ }
84
+ else if (issue) {
85
+ const issueNumber = parseInt(issue, 10);
86
+ if (isNaN(issueNumber)) {
87
+ console.log(chalk_1.default.red(`โŒ Invalid issue number: ${issue}`));
88
+ process.exit(1);
89
+ }
90
+ await (0, clean_1.cleanCommand)(issueNumber);
91
+ }
92
+ else {
93
+ // No issue and no --all flag, show all and let user choose
94
+ await (0, clean_1.cleanAllCommand)();
95
+ }
96
+ });
97
+ // Go command - navigate to worktree and open PR
98
+ program
99
+ .command('go [issue]')
100
+ .description('Navigate to an issue worktree, open VS Code, or view PR')
78
101
  .action(async (issue) => {
79
- const issueNumber = parseInt(issue, 10);
80
- if (isNaN(issueNumber)) {
102
+ const issueNumber = issue ? parseInt(issue, 10) : undefined;
103
+ if (issue && isNaN(issueNumber)) {
81
104
  console.log(chalk_1.default.red(`โŒ Invalid issue number: ${issue}`));
82
105
  process.exit(1);
83
106
  }
84
- await (0, clean_1.cleanCommand)(issueNumber);
107
+ await (0, go_1.goCommand)(issueNumber);
85
108
  });
86
109
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-issue-solver",
3
- "version": "1.3.0",
3
+ "version": "1.5.0",
4
4
  "description": "Automatically solve GitHub issues using Claude Code",
5
5
  "main": "dist/index.js",
6
6
  "bin": {