claude-issue-solver 1.6.4 ā 1.6.6
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/dist/commands/clean.js +144 -70
- package/package.json +1 -1
package/dist/commands/clean.js
CHANGED
|
@@ -124,30 +124,57 @@ function getIssueWorktrees() {
|
|
|
124
124
|
const projectName = (0, git_1.getProjectName)();
|
|
125
125
|
const parentDir = path.dirname(projectRoot);
|
|
126
126
|
const worktrees = [];
|
|
127
|
+
const foundPaths = new Set();
|
|
127
128
|
// Get all worktrees from git
|
|
128
129
|
const output = (0, git_1.exec)('git worktree list --porcelain', projectRoot);
|
|
129
|
-
if (
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
130
|
+
if (output) {
|
|
131
|
+
const lines = output.split('\n');
|
|
132
|
+
let currentPath = '';
|
|
133
|
+
let currentBranch = '';
|
|
134
|
+
for (const line of lines) {
|
|
135
|
+
if (line.startsWith('worktree ')) {
|
|
136
|
+
currentPath = line.replace('worktree ', '');
|
|
137
|
+
}
|
|
138
|
+
else if (line.startsWith('branch refs/heads/')) {
|
|
139
|
+
currentBranch = line.replace('branch refs/heads/', '');
|
|
140
|
+
// Check if this is an issue branch
|
|
141
|
+
const match = currentBranch.match(/^issue-(\d+)-/);
|
|
142
|
+
if (match && currentPath.includes(`${projectName}-issue-`)) {
|
|
143
|
+
worktrees.push({
|
|
144
|
+
path: currentPath,
|
|
145
|
+
branch: currentBranch,
|
|
146
|
+
issueNumber: match[1],
|
|
147
|
+
});
|
|
148
|
+
foundPaths.add(currentPath);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
137
151
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
152
|
+
}
|
|
153
|
+
// Also check for orphaned folders (folders that exist but aren't in git worktree list)
|
|
154
|
+
// This can happen when git worktree remove fails but the folder remains
|
|
155
|
+
try {
|
|
156
|
+
const folderPattern = `${projectName}-issue-`;
|
|
157
|
+
const entries = fs.readdirSync(parentDir, { withFileTypes: true });
|
|
158
|
+
for (const entry of entries) {
|
|
159
|
+
if (entry.isDirectory() && entry.name.startsWith(folderPattern)) {
|
|
160
|
+
const folderPath = path.join(parentDir, entry.name);
|
|
161
|
+
if (!foundPaths.has(folderPath)) {
|
|
162
|
+
// Extract issue number from folder name (e.g., "project-issue-38-slug")
|
|
163
|
+
const match = entry.name.match(new RegExp(`${projectName}-issue-(\\d+)-`));
|
|
164
|
+
if (match) {
|
|
165
|
+
worktrees.push({
|
|
166
|
+
path: folderPath,
|
|
167
|
+
branch: '', // No branch known for orphaned folders
|
|
168
|
+
issueNumber: match[1],
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
148
172
|
}
|
|
149
173
|
}
|
|
150
174
|
}
|
|
175
|
+
catch {
|
|
176
|
+
// Ignore errors reading parent directory
|
|
177
|
+
}
|
|
151
178
|
return worktrees;
|
|
152
179
|
}
|
|
153
180
|
async function cleanAllCommand() {
|
|
@@ -159,7 +186,7 @@ async function cleanAllCommand() {
|
|
|
159
186
|
}
|
|
160
187
|
console.log(chalk_1.default.bold('\nš§¹ Found issue worktrees:\n'));
|
|
161
188
|
for (const wt of worktrees) {
|
|
162
|
-
console.log(` ${chalk_1.default.cyan(`#${wt.issueNumber}`)}\t${wt.branch}`);
|
|
189
|
+
console.log(` ${chalk_1.default.cyan(`#${wt.issueNumber}`)}\t${wt.branch || chalk_1.default.yellow('(orphaned folder)')}`);
|
|
163
190
|
console.log(chalk_1.default.dim(` \t${wt.path}`));
|
|
164
191
|
console.log();
|
|
165
192
|
}
|
|
@@ -188,35 +215,54 @@ async function cleanAllCommand() {
|
|
|
188
215
|
catch {
|
|
189
216
|
// Ignore errors closing windows
|
|
190
217
|
}
|
|
191
|
-
// Remove worktree
|
|
218
|
+
// Remove worktree/folder
|
|
219
|
+
const isOrphaned = !wt.branch;
|
|
192
220
|
if (fs.existsSync(wt.path)) {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
221
|
+
// Try git worktree remove first (only if not orphaned)
|
|
222
|
+
if (!isOrphaned) {
|
|
223
|
+
try {
|
|
224
|
+
(0, child_process_1.execSync)(`git worktree remove "${wt.path}" --force`, {
|
|
225
|
+
cwd: projectRoot,
|
|
226
|
+
stdio: 'pipe',
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
// Ignore - we'll force delete below if needed
|
|
231
|
+
}
|
|
198
232
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
// This handles locked files better than fs.rmSync
|
|
233
|
+
// If folder still exists, force delete it
|
|
234
|
+
if (fs.existsSync(wt.path)) {
|
|
202
235
|
try {
|
|
203
|
-
(0, child_process_1.execSync)(
|
|
236
|
+
(0, child_process_1.execSync)(`/bin/rm -rf "${wt.path}"`, { stdio: 'pipe' });
|
|
204
237
|
}
|
|
205
238
|
catch {
|
|
206
|
-
|
|
239
|
+
try {
|
|
240
|
+
fs.rmSync(wt.path, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
// Ignore - will check at end
|
|
244
|
+
}
|
|
207
245
|
}
|
|
246
|
+
}
|
|
247
|
+
// Prune git worktrees
|
|
248
|
+
try {
|
|
208
249
|
(0, child_process_1.execSync)('git worktree prune', { cwd: projectRoot, stdio: 'pipe' });
|
|
209
250
|
}
|
|
251
|
+
catch {
|
|
252
|
+
// Ignore
|
|
253
|
+
}
|
|
210
254
|
}
|
|
211
|
-
// Delete branch
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
255
|
+
// Delete branch (if we have one)
|
|
256
|
+
if (wt.branch) {
|
|
257
|
+
try {
|
|
258
|
+
(0, child_process_1.execSync)(`git branch -D "${wt.branch}"`, {
|
|
259
|
+
cwd: projectRoot,
|
|
260
|
+
stdio: 'pipe',
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
// Branch may already be deleted
|
|
265
|
+
}
|
|
220
266
|
}
|
|
221
267
|
spinner.succeed(`Cleaned issue #${wt.issueNumber}`);
|
|
222
268
|
}
|
|
@@ -232,7 +278,9 @@ async function cleanAllCommand() {
|
|
|
232
278
|
async function cleanCommand(issueNumber) {
|
|
233
279
|
const projectRoot = (0, git_1.getProjectRoot)();
|
|
234
280
|
const projectName = (0, git_1.getProjectName)();
|
|
281
|
+
const parentDir = path.dirname(projectRoot);
|
|
235
282
|
// Find the worktree for this issue number (don't need to fetch from GitHub)
|
|
283
|
+
// This now also includes orphaned folders
|
|
236
284
|
const worktrees = getIssueWorktrees();
|
|
237
285
|
const worktree = worktrees.find((wt) => wt.issueNumber === String(issueNumber));
|
|
238
286
|
if (!worktree) {
|
|
@@ -242,7 +290,7 @@ async function cleanCommand(issueNumber) {
|
|
|
242
290
|
const branches = output.split('\n').map((b) => b.trim().replace('* ', ''));
|
|
243
291
|
const matchingBranch = branches.find((b) => b.startsWith(branchPattern));
|
|
244
292
|
if (!matchingBranch) {
|
|
245
|
-
console.log(chalk_1.default.red(`\nā No worktree or branch found for issue #${issueNumber}`));
|
|
293
|
+
console.log(chalk_1.default.red(`\nā No worktree, folder, or branch found for issue #${issueNumber}`));
|
|
246
294
|
return;
|
|
247
295
|
}
|
|
248
296
|
// Found a branch but no worktree - just delete the branch
|
|
@@ -270,16 +318,22 @@ async function cleanCommand(issueNumber) {
|
|
|
270
318
|
}
|
|
271
319
|
const branchName = worktree.branch;
|
|
272
320
|
const worktreePath = worktree.path;
|
|
321
|
+
const isOrphaned = !branchName;
|
|
273
322
|
console.log();
|
|
274
323
|
console.log(chalk_1.default.bold(`š§¹ Cleaning up issue #${issueNumber}`));
|
|
275
|
-
|
|
276
|
-
|
|
324
|
+
if (isOrphaned) {
|
|
325
|
+
console.log(chalk_1.default.yellow(` (Orphaned folder - no git worktree reference)`));
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
console.log(chalk_1.default.dim(` Branch: ${branchName}`));
|
|
329
|
+
}
|
|
330
|
+
console.log(chalk_1.default.dim(` Folder: ${worktreePath}`));
|
|
277
331
|
console.log();
|
|
278
332
|
const { confirm } = await inquirer_1.default.prompt([
|
|
279
333
|
{
|
|
280
334
|
type: 'confirm',
|
|
281
335
|
name: 'confirm',
|
|
282
|
-
message: 'Remove worktree and delete branch?',
|
|
336
|
+
message: isOrphaned ? 'Remove orphaned folder?' : 'Remove worktree and delete branch?',
|
|
283
337
|
default: false,
|
|
284
338
|
},
|
|
285
339
|
]);
|
|
@@ -298,46 +352,66 @@ async function cleanCommand(issueNumber) {
|
|
|
298
352
|
catch {
|
|
299
353
|
windowSpinner.warn('Could not close some windows');
|
|
300
354
|
}
|
|
301
|
-
// Remove worktree
|
|
355
|
+
// Remove worktree/folder
|
|
302
356
|
if (fs.existsSync(worktreePath)) {
|
|
303
357
|
const worktreeSpinner = (0, ora_1.default)('Removing worktree...').start();
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
358
|
+
// Try git worktree remove first (only if not orphaned)
|
|
359
|
+
if (!isOrphaned) {
|
|
360
|
+
try {
|
|
361
|
+
(0, child_process_1.execSync)(`git worktree remove "${worktreePath}" --force`, {
|
|
362
|
+
cwd: projectRoot,
|
|
363
|
+
stdio: 'pipe',
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
catch {
|
|
367
|
+
// Ignore - we'll force delete below if needed
|
|
368
|
+
}
|
|
310
369
|
}
|
|
311
|
-
|
|
312
|
-
|
|
370
|
+
// If folder still exists, force delete it
|
|
371
|
+
if (fs.existsSync(worktreePath)) {
|
|
313
372
|
try {
|
|
314
|
-
|
|
315
|
-
(0, child_process_1.execSync)(
|
|
316
|
-
worktreeSpinner.succeed('Worktree removed (manually)');
|
|
373
|
+
// Use rm -rf with full path
|
|
374
|
+
(0, child_process_1.execSync)(`/bin/rm -rf "${worktreePath}"`, { stdio: 'pipe' });
|
|
317
375
|
}
|
|
318
376
|
catch {
|
|
377
|
+
// Try Node's rmSync as fallback
|
|
319
378
|
try {
|
|
320
|
-
fs.rmSync(worktreePath, { recursive: true, force: true });
|
|
321
|
-
(0, child_process_1.execSync)('git worktree prune', { cwd: projectRoot, stdio: 'pipe' });
|
|
322
|
-
worktreeSpinner.succeed('Worktree removed (manually)');
|
|
379
|
+
fs.rmSync(worktreePath, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
323
380
|
}
|
|
324
381
|
catch {
|
|
325
|
-
|
|
382
|
+
// Last resort - try with sudo hint
|
|
383
|
+
worktreeSpinner.warn(`Could not remove directory. Try manually: rm -rf "${worktreePath}"`);
|
|
326
384
|
}
|
|
327
385
|
}
|
|
328
386
|
}
|
|
387
|
+
// Prune git worktrees
|
|
388
|
+
try {
|
|
389
|
+
(0, child_process_1.execSync)('git worktree prune', { cwd: projectRoot, stdio: 'pipe' });
|
|
390
|
+
}
|
|
391
|
+
catch {
|
|
392
|
+
// Ignore
|
|
393
|
+
}
|
|
394
|
+
// Check final result
|
|
395
|
+
if (fs.existsSync(worktreePath)) {
|
|
396
|
+
worktreeSpinner.warn(`Could not fully remove directory: ${worktreePath}`);
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
worktreeSpinner.succeed(isOrphaned ? 'Folder removed' : 'Worktree removed');
|
|
400
|
+
}
|
|
329
401
|
}
|
|
330
|
-
// Delete branch
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
402
|
+
// Delete branch (if we have one)
|
|
403
|
+
if (branchName) {
|
|
404
|
+
const branchSpinner = (0, ora_1.default)('Deleting branch...').start();
|
|
405
|
+
try {
|
|
406
|
+
(0, child_process_1.execSync)(`git branch -D "${branchName}"`, {
|
|
407
|
+
cwd: projectRoot,
|
|
408
|
+
stdio: 'pipe',
|
|
409
|
+
});
|
|
410
|
+
branchSpinner.succeed('Branch deleted');
|
|
411
|
+
}
|
|
412
|
+
catch {
|
|
413
|
+
branchSpinner.warn('Could not delete branch (may already be deleted)');
|
|
414
|
+
}
|
|
341
415
|
}
|
|
342
416
|
console.log();
|
|
343
417
|
console.log(chalk_1.default.green('ā
Cleanup complete!'));
|