@link-assistant/hive-mind 0.48.3 → 0.49.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/CHANGELOG.md +23 -0
- package/package.json +1 -1
- package/src/config.lib.mjs +1 -1
- package/src/hive.config.lib.mjs +2 -2
- package/src/hive.mjs +1 -1
- package/src/memory-check.mjs +3 -3
- package/src/solve.auto-continue.lib.mjs +15 -12
- package/src/solve.auto-pr.lib.mjs +168 -92
- package/src/solve.config.lib.mjs +51 -2
- package/src/solve.mjs +3 -3
- package/src/solve.results.lib.mjs +81 -68
- package/src/solve.validation.lib.mjs +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 0.49.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Add --claude-file and --gitkeep-file CLI options for choosing between CLAUDE.md and .gitkeep files
|
|
8
|
+
|
|
9
|
+
This feature allows users to choose which file type to use for PR creation:
|
|
10
|
+
- `--claude-file` (default: true): Use CLAUDE.md file for task details
|
|
11
|
+
- `--gitkeep-file` (default: false, experimental): Use .gitkeep file instead
|
|
12
|
+
|
|
13
|
+
The flags are mutually exclusive:
|
|
14
|
+
- Using `--gitkeep-file` automatically disables `--claude-file`
|
|
15
|
+
- Using `--no-claude-file` automatically enables `--gitkeep-file`
|
|
16
|
+
- Both flags cannot be disabled simultaneously
|
|
17
|
+
|
|
18
|
+
This is a step toward making .gitkeep the default behavior in future releases.
|
|
19
|
+
|
|
20
|
+
## 0.48.4
|
|
21
|
+
|
|
22
|
+
### Patch Changes
|
|
23
|
+
|
|
24
|
+
- b010ce6: Increase minimum disk space requirement from 512 MB to 2 GB to provide more room for commands to gracefully finish before running out of disk space and prevent potential OS issues
|
|
25
|
+
|
|
3
26
|
## 0.48.3
|
|
4
27
|
|
|
5
28
|
### Patch Changes
|
package/package.json
CHANGED
package/src/config.lib.mjs
CHANGED
|
@@ -64,7 +64,7 @@ export const githubLimits = {
|
|
|
64
64
|
|
|
65
65
|
// Memory and disk configurations
|
|
66
66
|
export const systemLimits = {
|
|
67
|
-
minDiskSpaceMb: parseIntWithDefault('HIVE_MIND_MIN_DISK_SPACE_MB',
|
|
67
|
+
minDiskSpaceMb: parseIntWithDefault('HIVE_MIND_MIN_DISK_SPACE_MB', 2048),
|
|
68
68
|
defaultPageSizeKb: parseIntWithDefault('HIVE_MIND_DEFAULT_PAGE_SIZE_KB', 16),
|
|
69
69
|
};
|
|
70
70
|
|
package/src/hive.config.lib.mjs
CHANGED
|
@@ -128,8 +128,8 @@ export const createYargsConfig = yargsInstance => {
|
|
|
128
128
|
})
|
|
129
129
|
.option('min-disk-space', {
|
|
130
130
|
type: 'number',
|
|
131
|
-
description: 'Minimum required disk space in MB (default:
|
|
132
|
-
default:
|
|
131
|
+
description: 'Minimum required disk space in MB (default: 2048)',
|
|
132
|
+
default: 2048,
|
|
133
133
|
})
|
|
134
134
|
.option('auto-cleanup', {
|
|
135
135
|
type: 'boolean',
|
package/src/hive.mjs
CHANGED
package/src/memory-check.mjs
CHANGED
|
@@ -27,7 +27,7 @@ const lib = await import('./lib.mjs');
|
|
|
27
27
|
const { log: libLog, setLogFile } = lib;
|
|
28
28
|
|
|
29
29
|
// Function to check available disk space
|
|
30
|
-
export const checkDiskSpace = async (minSpaceMB =
|
|
30
|
+
export const checkDiskSpace = async (minSpaceMB = 2048, options = {}) => {
|
|
31
31
|
const log = options.log || libLog;
|
|
32
32
|
|
|
33
33
|
try {
|
|
@@ -290,7 +290,7 @@ export const getResourceSnapshot = async () => {
|
|
|
290
290
|
|
|
291
291
|
// Combined system check function
|
|
292
292
|
export const checkSystem = async (requirements = {}, options = {}) => {
|
|
293
|
-
const { minMemoryMB = 256, minDiskSpaceMB =
|
|
293
|
+
const { minMemoryMB = 256, minDiskSpaceMB = 2048, exitOnFailure = false } = requirements;
|
|
294
294
|
|
|
295
295
|
// Note: log is passed through options to checkDiskSpace and checkRAM
|
|
296
296
|
const results = {
|
|
@@ -336,7 +336,7 @@ if (import.meta.url === `file://${process.argv[1]}`) {
|
|
|
336
336
|
alias: 'd',
|
|
337
337
|
type: 'number',
|
|
338
338
|
description: 'Minimum required disk space in MB',
|
|
339
|
-
default:
|
|
339
|
+
default: 2048,
|
|
340
340
|
})
|
|
341
341
|
.option('exit-on-failure', {
|
|
342
342
|
alias: 'e',
|
|
@@ -181,18 +181,20 @@ export const checkExistingPRsForAutoContinue = async (argv, isIssueUrl, owner, r
|
|
|
181
181
|
continue;
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
-
// Check if CLAUDE.md exists in this PR branch
|
|
184
|
+
// Check if CLAUDE.md or .gitkeep exists in this PR branch
|
|
185
|
+
// If neither exists, it means work was completed and files were removed
|
|
185
186
|
const claudeMdExists = await checkFileInBranch(owner, repo, 'CLAUDE.md', pr.headRefName);
|
|
187
|
+
const gitkeepExists = await checkFileInBranch(owner, repo, '.gitkeep', pr.headRefName);
|
|
186
188
|
|
|
187
|
-
if (!claudeMdExists) {
|
|
188
|
-
await log(`✅ Auto-continue: Using PR #${pr.number} (CLAUDE.md missing - work completed, branch: ${pr.headRefName})`);
|
|
189
|
+
if (!claudeMdExists && !gitkeepExists) {
|
|
190
|
+
await log(`✅ Auto-continue: Using PR #${pr.number} (CLAUDE.md/.gitkeep missing - work completed, branch: ${pr.headRefName})`);
|
|
189
191
|
|
|
190
|
-
// Switch to continue mode immediately (don't wait 24 hours if
|
|
192
|
+
// Switch to continue mode immediately (don't wait 24 hours if both files are missing)
|
|
191
193
|
isContinueMode = true;
|
|
192
194
|
prNumber = pr.number;
|
|
193
195
|
prBranch = pr.headRefName;
|
|
194
196
|
if (argv.verbose) {
|
|
195
|
-
await log(' Continue mode activated: Auto-continue (
|
|
197
|
+
await log(' Continue mode activated: Auto-continue (initial commit files missing)', { verbose: true });
|
|
196
198
|
await log(` PR Number: ${prNumber}`, { verbose: true });
|
|
197
199
|
await log(` PR Branch: ${prBranch}`, { verbose: true });
|
|
198
200
|
}
|
|
@@ -454,15 +456,16 @@ export const processAutoContinueForIssue = async (argv, isIssueUrl, urlNumber, o
|
|
|
454
456
|
continue;
|
|
455
457
|
}
|
|
456
458
|
|
|
457
|
-
// Check if CLAUDE.md exists in this PR branch
|
|
459
|
+
// Check if CLAUDE.md or .gitkeep exists in this PR branch
|
|
458
460
|
const claudeMdExists = await checkFileInBranch(owner, repo, 'CLAUDE.md', pr.headRefName);
|
|
461
|
+
const gitkeepExists = await checkFileInBranch(owner, repo, '.gitkeep', pr.headRefName);
|
|
459
462
|
|
|
460
|
-
if (!claudeMdExists) {
|
|
461
|
-
await log(`✅ Auto-continue: Using PR #${pr.number} (CLAUDE.md missing - work completed, branch: ${pr.headRefName})`);
|
|
463
|
+
if (!claudeMdExists && !gitkeepExists) {
|
|
464
|
+
await log(`✅ Auto-continue: Using PR #${pr.number} (CLAUDE.md/.gitkeep missing - work completed, branch: ${pr.headRefName})`);
|
|
462
465
|
|
|
463
|
-
// Switch to continue mode immediately (don't wait 24 hours if CLAUDE.md is missing)
|
|
466
|
+
// Switch to continue mode immediately (don't wait 24 hours if CLAUDE.md/.gitkeep is missing)
|
|
464
467
|
if (argv.verbose) {
|
|
465
|
-
await log(' Continue mode activated: Auto-continue (CLAUDE.md missing)', { verbose: true });
|
|
468
|
+
await log(' Continue mode activated: Auto-continue (CLAUDE.md/.gitkeep missing)', { verbose: true });
|
|
466
469
|
await log(` PR Number: ${pr.number}`, { verbose: true });
|
|
467
470
|
await log(` PR Branch: ${pr.headRefName}`, { verbose: true });
|
|
468
471
|
}
|
|
@@ -490,12 +493,12 @@ export const processAutoContinueForIssue = async (argv, isIssueUrl, urlNumber, o
|
|
|
490
493
|
issueNumber,
|
|
491
494
|
};
|
|
492
495
|
} else {
|
|
493
|
-
await log(` PR #${pr.number}: CLAUDE.md exists, age ${ageHours}h < 24h - skipping`);
|
|
496
|
+
await log(` PR #${pr.number}: CLAUDE.md/.gitkeep exists, age ${ageHours}h < 24h - skipping`);
|
|
494
497
|
}
|
|
495
498
|
}
|
|
496
499
|
}
|
|
497
500
|
|
|
498
|
-
await log('⏭️ No suitable PRs found (missing CLAUDE.md or older than 24h) - creating new PR as usual');
|
|
501
|
+
await log('⏭️ No suitable PRs found (missing CLAUDE.md/.gitkeep or older than 24h) - creating new PR as usual');
|
|
499
502
|
} else {
|
|
500
503
|
await log(`📝 No existing PRs found for issue #${issueNumber} - creating new PR`);
|
|
501
504
|
}
|
|
@@ -30,20 +30,54 @@ export async function handleAutoPrCreation({ argv, tempDir, branchName, issueNum
|
|
|
30
30
|
const issueUrl = argv['issue-url'] || argv._[0];
|
|
31
31
|
|
|
32
32
|
try {
|
|
33
|
-
//
|
|
34
|
-
|
|
33
|
+
// Determine which file to create based on CLI flags
|
|
34
|
+
const useClaudeFile = argv.claudeFile !== false; // Default to true
|
|
35
|
+
const useGitkeepFile = argv.gitkeepFile === true; // Default to false
|
|
35
36
|
|
|
36
|
-
//
|
|
37
|
-
|
|
37
|
+
// Log which mode we're using
|
|
38
|
+
if (argv.verbose) {
|
|
39
|
+
await log(` Using ${useClaudeFile ? 'CLAUDE.md' : '.gitkeep'} mode (--claude-file=${useClaudeFile}, --gitkeep-file=${useGitkeepFile})`, { verbose: true });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let filePath;
|
|
43
|
+
let fileName;
|
|
38
44
|
let existingContent = null;
|
|
39
45
|
let fileExisted = false;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
|
|
47
|
+
if (useClaudeFile) {
|
|
48
|
+
// Create CLAUDE.md file with the task details
|
|
49
|
+
await log(formatAligned('📝', 'Creating:', 'CLAUDE.md with task details'));
|
|
50
|
+
|
|
51
|
+
filePath = path.join(tempDir, 'CLAUDE.md');
|
|
52
|
+
fileName = 'CLAUDE.md';
|
|
53
|
+
|
|
54
|
+
// Check if CLAUDE.md already exists and read its content
|
|
55
|
+
try {
|
|
56
|
+
existingContent = await fs.readFile(filePath, 'utf8');
|
|
57
|
+
fileExisted = true;
|
|
58
|
+
} catch (err) {
|
|
59
|
+
// File doesn't exist, which is fine
|
|
60
|
+
if (err.code !== 'ENOENT') {
|
|
61
|
+
throw err;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
// Create .gitkeep file directly (experimental mode)
|
|
66
|
+
await log(formatAligned('📝', 'Creating:', '.gitkeep (experimental mode)'));
|
|
67
|
+
|
|
68
|
+
filePath = path.join(tempDir, '.gitkeep');
|
|
69
|
+
fileName = '.gitkeep';
|
|
70
|
+
|
|
71
|
+
// .gitkeep files are typically small, no need to check for existing content
|
|
72
|
+
// But we'll check if it exists for proper handling
|
|
73
|
+
try {
|
|
74
|
+
existingContent = await fs.readFile(filePath, 'utf8');
|
|
75
|
+
fileExisted = true;
|
|
76
|
+
} catch (err) {
|
|
77
|
+
// File doesn't exist, which is fine
|
|
78
|
+
if (err.code !== 'ENOENT') {
|
|
79
|
+
throw err;
|
|
80
|
+
}
|
|
47
81
|
}
|
|
48
82
|
}
|
|
49
83
|
|
|
@@ -61,44 +95,65 @@ export async function handleAutoPrCreation({ argv, tempDir, branchName, issueNum
|
|
|
61
95
|
// Without this, appending the same task info produces no git changes,
|
|
62
96
|
// leading to "No commits between branches" error during PR creation
|
|
63
97
|
const timestamp = new Date().toISOString();
|
|
64
|
-
|
|
98
|
+
|
|
99
|
+
let finalContent;
|
|
100
|
+
|
|
101
|
+
if (useClaudeFile) {
|
|
102
|
+
// CLAUDE.md: Use detailed task info
|
|
103
|
+
const taskInfo = `Issue to solve: ${issueUrl}
|
|
65
104
|
Your prepared branch: ${branchName}
|
|
66
105
|
Your prepared working directory: ${tempDir}${
|
|
67
|
-
|
|
68
|
-
|
|
106
|
+
argv.fork && forkedRepo
|
|
107
|
+
? `
|
|
69
108
|
Your forked repository: ${forkedRepo}
|
|
70
109
|
Original repository (upstream): ${owner}/${repo}`
|
|
71
|
-
|
|
72
|
-
|
|
110
|
+
: ''
|
|
111
|
+
}
|
|
73
112
|
|
|
74
113
|
Proceed.`;
|
|
75
114
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
115
|
+
// If file already exists, append the task info with separator and timestamp
|
|
116
|
+
// Otherwise, create new file with just the task info (no timestamp needed for new files)
|
|
117
|
+
if (fileExisted && existingContent) {
|
|
118
|
+
await log(` ${fileName} already exists, appending task info...`, { verbose: true });
|
|
119
|
+
// Remove any trailing whitespace and add separator
|
|
120
|
+
const trimmedExisting = existingContent.trimEnd();
|
|
121
|
+
// Add timestamp to ensure uniqueness when appending
|
|
122
|
+
finalContent = `${trimmedExisting}\n\n---\n\n${taskInfo}\n\nRun timestamp: ${timestamp}`;
|
|
123
|
+
} else {
|
|
124
|
+
finalContent = taskInfo;
|
|
125
|
+
}
|
|
85
126
|
} else {
|
|
86
|
-
|
|
127
|
+
// .gitkeep: Use minimal metadata format
|
|
128
|
+
const gitkeepContent = `# Auto-generated file for PR creation
|
|
129
|
+
# Issue: ${issueUrl}
|
|
130
|
+
# Branch: ${branchName}
|
|
131
|
+
# Timestamp: ${timestamp}
|
|
132
|
+
# This file was created with --gitkeep-file flag (experimental)
|
|
133
|
+
# It will be removed when the task is complete`;
|
|
134
|
+
|
|
135
|
+
if (fileExisted && existingContent) {
|
|
136
|
+
await log(` ${fileName} already exists, appending timestamp...`, { verbose: true });
|
|
137
|
+
// For .gitkeep, just append a new timestamp to ensure uniqueness
|
|
138
|
+
finalContent = `${existingContent.trimEnd()}\n# Updated: ${timestamp}`;
|
|
139
|
+
} else {
|
|
140
|
+
finalContent = gitkeepContent;
|
|
141
|
+
}
|
|
87
142
|
}
|
|
88
143
|
|
|
89
|
-
await fs.writeFile(
|
|
90
|
-
await log(formatAligned('✅', 'File created:',
|
|
144
|
+
await fs.writeFile(filePath, finalContent);
|
|
145
|
+
await log(formatAligned('✅', 'File created:', fileName));
|
|
91
146
|
|
|
92
147
|
// Add and commit the file
|
|
93
148
|
await log(formatAligned('📦', 'Adding file:', 'To git staging'));
|
|
94
149
|
|
|
95
150
|
// Use explicit cwd option for better reliability
|
|
96
|
-
const addResult = await $({ cwd: tempDir })`git add
|
|
151
|
+
const addResult = await $({ cwd: tempDir })`git add ${fileName}`;
|
|
97
152
|
|
|
98
153
|
if (addResult.code !== 0) {
|
|
99
|
-
await log(
|
|
154
|
+
await log(`❌ Failed to add ${fileName}`, { level: 'error' });
|
|
100
155
|
await log(` Error: ${addResult.stderr ? addResult.stderr.toString() : 'Unknown error'}`, { level: 'error' });
|
|
101
|
-
throw new Error(
|
|
156
|
+
throw new Error(`Failed to add ${fileName}`);
|
|
102
157
|
}
|
|
103
158
|
|
|
104
159
|
// Verify the file was actually staged
|
|
@@ -110,112 +165,133 @@ Proceed.`;
|
|
|
110
165
|
}
|
|
111
166
|
|
|
112
167
|
// Track which file we're using for the commit
|
|
113
|
-
let commitFileName =
|
|
168
|
+
let commitFileName = fileName;
|
|
114
169
|
|
|
115
170
|
// Check if anything was actually staged
|
|
116
171
|
if (!gitStatus || gitStatus.length === 0) {
|
|
117
172
|
await log('');
|
|
118
|
-
await log(formatAligned('⚠️',
|
|
173
|
+
await log(formatAligned('⚠️', `${fileName} not staged:`, 'Checking if file is ignored'), { level: 'warning' });
|
|
119
174
|
|
|
120
|
-
//
|
|
121
|
-
|
|
122
|
-
|
|
175
|
+
// Only apply fallback logic when using CLAUDE.md mode (not in --gitkeep-file mode)
|
|
176
|
+
if (useClaudeFile) {
|
|
177
|
+
// Check if CLAUDE.md is in .gitignore
|
|
178
|
+
const checkIgnoreResult = await $({ cwd: tempDir })`git check-ignore CLAUDE.md`;
|
|
179
|
+
const isIgnored = checkIgnoreResult.code === 0;
|
|
123
180
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
181
|
+
if (isIgnored) {
|
|
182
|
+
await log(formatAligned('ℹ️', 'CLAUDE.md is ignored:', 'Using .gitkeep fallback'));
|
|
183
|
+
await log('');
|
|
184
|
+
await log(' 📝 Fallback strategy:');
|
|
185
|
+
await log(' CLAUDE.md is in .gitignore, using .gitkeep instead.');
|
|
186
|
+
await log(' This allows auto-PR creation to proceed without modifying .gitignore.');
|
|
187
|
+
await log('');
|
|
131
188
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
189
|
+
// Create a .gitkeep file as fallback
|
|
190
|
+
const gitkeepPath = path.join(tempDir, '.gitkeep');
|
|
191
|
+
const gitkeepContent = `# Auto-generated file for PR creation
|
|
135
192
|
# Issue: ${issueUrl}
|
|
136
193
|
# Branch: ${branchName}
|
|
137
194
|
# This file was created because CLAUDE.md is in .gitignore
|
|
138
195
|
# It will be removed when the task is complete`;
|
|
139
196
|
|
|
140
|
-
|
|
141
|
-
|
|
197
|
+
await fs.writeFile(gitkeepPath, gitkeepContent);
|
|
198
|
+
await log(formatAligned('✅', 'Created:', '.gitkeep file'));
|
|
142
199
|
|
|
143
|
-
|
|
144
|
-
|
|
200
|
+
// Try to add .gitkeep
|
|
201
|
+
const gitkeepAddResult = await $({ cwd: tempDir })`git add .gitkeep`;
|
|
145
202
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
203
|
+
if (gitkeepAddResult.code !== 0) {
|
|
204
|
+
await log('❌ Failed to add .gitkeep', { level: 'error' });
|
|
205
|
+
await log(` Error: ${gitkeepAddResult.stderr ? gitkeepAddResult.stderr.toString() : 'Unknown error'}`, {
|
|
206
|
+
level: 'error',
|
|
207
|
+
});
|
|
208
|
+
throw new Error('Failed to add .gitkeep');
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Verify .gitkeep was staged
|
|
212
|
+
statusResult = await $({ cwd: tempDir })`git status --short`;
|
|
213
|
+
gitStatus = statusResult.stdout ? statusResult.stdout.toString().trim() : '';
|
|
153
214
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
215
|
+
if (!gitStatus || gitStatus.length === 0) {
|
|
216
|
+
await log('');
|
|
217
|
+
await log(formatAligned('❌', 'GIT ADD FAILED:', 'Neither CLAUDE.md nor .gitkeep could be staged'), {
|
|
218
|
+
level: 'error',
|
|
219
|
+
});
|
|
220
|
+
await log('');
|
|
221
|
+
await log(' 🔍 What happened:');
|
|
222
|
+
await log(' Both CLAUDE.md and .gitkeep failed to stage.');
|
|
223
|
+
await log('');
|
|
224
|
+
await log(' 🔧 Troubleshooting steps:');
|
|
225
|
+
await log(` 1. Check git status: cd "${tempDir}" && git status`);
|
|
226
|
+
await log(` 2. Check .gitignore: cat "${tempDir}/.gitignore"`);
|
|
227
|
+
await log(` 3. Try force add: cd "${tempDir}" && git add -f .gitkeep`);
|
|
228
|
+
await log('');
|
|
229
|
+
throw new Error('Git add staged nothing - both files failed');
|
|
230
|
+
}
|
|
157
231
|
|
|
158
|
-
|
|
232
|
+
commitFileName = '.gitkeep';
|
|
233
|
+
await log(formatAligned('✅', 'File staged:', '.gitkeep'));
|
|
234
|
+
} else {
|
|
159
235
|
await log('');
|
|
160
|
-
await log(formatAligned('❌', 'GIT ADD FAILED:', '
|
|
161
|
-
level: 'error',
|
|
162
|
-
});
|
|
236
|
+
await log(formatAligned('❌', 'GIT ADD FAILED:', 'Nothing was staged'), { level: 'error' });
|
|
163
237
|
await log('');
|
|
164
238
|
await log(' 🔍 What happened:');
|
|
165
|
-
await log('
|
|
239
|
+
await log(' CLAUDE.md was created but git did not stage any changes.');
|
|
240
|
+
await log('');
|
|
241
|
+
await log(' 💡 Possible causes:');
|
|
242
|
+
await log(' • CLAUDE.md already exists with identical content');
|
|
243
|
+
await log(' • File system sync issue');
|
|
166
244
|
await log('');
|
|
167
245
|
await log(' 🔧 Troubleshooting steps:');
|
|
168
|
-
await log(` 1. Check
|
|
169
|
-
await log(` 2. Check
|
|
170
|
-
await log(` 3.
|
|
246
|
+
await log(` 1. Check file exists: ls -la "${tempDir}/CLAUDE.md"`);
|
|
247
|
+
await log(` 2. Check git status: cd "${tempDir}" && git status`);
|
|
248
|
+
await log(` 3. Force add: cd "${tempDir}" && git add -f CLAUDE.md`);
|
|
171
249
|
await log('');
|
|
172
|
-
|
|
250
|
+
await log(' 📂 Debug information:');
|
|
251
|
+
await log(` Working directory: ${tempDir}`);
|
|
252
|
+
await log(` Branch: ${branchName}`);
|
|
253
|
+
if (existingContent) {
|
|
254
|
+
await log(' Note: CLAUDE.md already existed (attempted to update with timestamp)');
|
|
255
|
+
}
|
|
256
|
+
await log('');
|
|
257
|
+
throw new Error('Git add staged nothing - CLAUDE.md may be unchanged');
|
|
173
258
|
}
|
|
174
|
-
|
|
175
|
-
commitFileName = '.gitkeep';
|
|
176
|
-
await log(formatAligned('✅', 'File staged:', '.gitkeep'));
|
|
177
259
|
} else {
|
|
260
|
+
// In --gitkeep-file mode, if .gitkeep couldn't be staged, this is an error
|
|
178
261
|
await log('');
|
|
179
262
|
await log(formatAligned('❌', 'GIT ADD FAILED:', 'Nothing was staged'), { level: 'error' });
|
|
180
263
|
await log('');
|
|
181
264
|
await log(' 🔍 What happened:');
|
|
182
|
-
await log(
|
|
265
|
+
await log(` ${fileName} was created but git did not stage any changes.`);
|
|
183
266
|
await log('');
|
|
184
267
|
await log(' 💡 Possible causes:');
|
|
185
|
-
await log(
|
|
268
|
+
await log(` • ${fileName} already exists with identical content`);
|
|
186
269
|
await log(' • File system sync issue');
|
|
270
|
+
await log(` • ${fileName} is in .gitignore`);
|
|
187
271
|
await log('');
|
|
188
272
|
await log(' 🔧 Troubleshooting steps:');
|
|
189
|
-
await log(` 1. Check file exists: ls -la "${tempDir}
|
|
273
|
+
await log(` 1. Check file exists: ls -la "${tempDir}/${fileName}"`);
|
|
190
274
|
await log(` 2. Check git status: cd "${tempDir}" && git status`);
|
|
191
|
-
await log(` 3.
|
|
275
|
+
await log(` 3. Check if ignored: cd "${tempDir}" && git check-ignore ${fileName}`);
|
|
276
|
+
await log(` 4. Force add: cd "${tempDir}" && git add -f ${fileName}`);
|
|
192
277
|
await log('');
|
|
193
278
|
await log(' 📂 Debug information:');
|
|
194
279
|
await log(` Working directory: ${tempDir}`);
|
|
195
280
|
await log(` Branch: ${branchName}`);
|
|
281
|
+
await log(` Mode: ${useClaudeFile ? 'CLAUDE.md' : '.gitkeep'}`);
|
|
196
282
|
if (existingContent) {
|
|
197
|
-
await log(
|
|
283
|
+
await log(` Note: ${fileName} already existed (attempted to update with timestamp)`);
|
|
198
284
|
}
|
|
199
285
|
await log('');
|
|
200
|
-
throw new Error(
|
|
286
|
+
throw new Error(`Git add staged nothing - ${fileName} may be unchanged or ignored`);
|
|
201
287
|
}
|
|
202
288
|
}
|
|
203
289
|
|
|
204
290
|
await log(formatAligned('📝', 'Creating commit:', `With ${commitFileName} file`));
|
|
205
|
-
const commitMessage =
|
|
206
|
-
commitFileName === 'CLAUDE.md'
|
|
207
|
-
? `Initial commit with task details
|
|
208
|
-
|
|
209
|
-
Adding CLAUDE.md with task information for AI processing.
|
|
210
|
-
This file will be removed when the task is complete.
|
|
211
|
-
|
|
212
|
-
Issue: ${issueUrl}`
|
|
213
|
-
: `Initial commit with task details
|
|
214
|
-
|
|
215
|
-
Adding .gitkeep for PR creation (CLAUDE.md is in .gitignore).
|
|
216
|
-
This file will be removed when the task is complete.
|
|
217
291
|
|
|
218
|
-
|
|
292
|
+
// Determine commit message based on which file is being committed
|
|
293
|
+
const fileDesc = commitFileName === 'CLAUDE.md' ? 'CLAUDE.md with task information for AI processing' : `.gitkeep for PR creation (${useGitkeepFile ? 'created with --gitkeep-file flag (experimental)' : 'CLAUDE.md is in .gitignore'})`;
|
|
294
|
+
const commitMessage = `Initial commit with task details\n\nAdding ${fileDesc}.\nThis file will be removed when the task is complete.\n\nIssue: ${issueUrl}`;
|
|
219
295
|
|
|
220
296
|
// Use explicit cwd option for better reliability
|
|
221
297
|
const commitResult = await $({ cwd: tempDir })`git commit -m ${commitMessage}`;
|
|
@@ -228,7 +304,7 @@ Issue: ${issueUrl}`;
|
|
|
228
304
|
await log(formatAligned('❌', 'COMMIT FAILED:', 'Could not create initial commit'), { level: 'error' });
|
|
229
305
|
await log('');
|
|
230
306
|
await log(' 🔍 What happened:');
|
|
231
|
-
await log(
|
|
307
|
+
await log(` Git commit command failed after staging ${commitFileName}.`);
|
|
232
308
|
await log('');
|
|
233
309
|
|
|
234
310
|
// Check for specific error patterns
|
package/src/solve.config.lib.mjs
CHANGED
|
@@ -118,6 +118,16 @@ export const createYargsConfig = yargsInstance => {
|
|
|
118
118
|
description: 'Automatically fork public repositories without write access (fails for private repos)',
|
|
119
119
|
default: true,
|
|
120
120
|
})
|
|
121
|
+
.option('claude-file', {
|
|
122
|
+
type: 'boolean',
|
|
123
|
+
description: 'Create CLAUDE.md file for task details (default, mutually exclusive with --gitkeep-file)',
|
|
124
|
+
default: true,
|
|
125
|
+
})
|
|
126
|
+
.option('gitkeep-file', {
|
|
127
|
+
type: 'boolean',
|
|
128
|
+
description: 'Create .gitkeep file instead of CLAUDE.md (experimental, mutually exclusive with --claude-file)',
|
|
129
|
+
default: false,
|
|
130
|
+
})
|
|
121
131
|
.option('attach-logs', {
|
|
122
132
|
type: 'boolean',
|
|
123
133
|
description: 'Upload the solution draft log file to the Pull Request on completion (⚠️ WARNING: May expose sensitive data)',
|
|
@@ -181,8 +191,8 @@ export const createYargsConfig = yargsInstance => {
|
|
|
181
191
|
})
|
|
182
192
|
.option('min-disk-space', {
|
|
183
193
|
type: 'number',
|
|
184
|
-
description: 'Minimum required disk space in MB (default:
|
|
185
|
-
default:
|
|
194
|
+
description: 'Minimum required disk space in MB (default: 2048)',
|
|
195
|
+
default: 2048,
|
|
186
196
|
})
|
|
187
197
|
.option('log-dir', {
|
|
188
198
|
type: 'string',
|
|
@@ -365,5 +375,44 @@ export const parseArguments = async (yargs, hideBin) => {
|
|
|
365
375
|
argv.model = 'grok-code';
|
|
366
376
|
}
|
|
367
377
|
|
|
378
|
+
// Validate mutual exclusivity of --claude-file and --gitkeep-file
|
|
379
|
+
// Check if both are explicitly enabled (user passed both --claude-file and --gitkeep-file)
|
|
380
|
+
if (argv.claudeFile && argv.gitkeepFile) {
|
|
381
|
+
// Check if they were explicitly set via command line
|
|
382
|
+
const claudeFileExplicit = rawArgs.includes('--claude-file');
|
|
383
|
+
const gitkeepFileExplicit = rawArgs.includes('--gitkeep-file');
|
|
384
|
+
|
|
385
|
+
if (claudeFileExplicit && gitkeepFileExplicit) {
|
|
386
|
+
throw new Error('--claude-file and --gitkeep-file are mutually exclusive. Please use only one.');
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// If only one is explicit, turn off the other
|
|
390
|
+
if (gitkeepFileExplicit && !claudeFileExplicit) {
|
|
391
|
+
argv.claudeFile = false;
|
|
392
|
+
} else if (claudeFileExplicit && !gitkeepFileExplicit) {
|
|
393
|
+
argv.gitkeepFile = false;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Check for both being disabled (both --no-claude-file and --no-gitkeep-file)
|
|
398
|
+
const noClaudeFile = rawArgs.includes('--no-claude-file');
|
|
399
|
+
const noGitkeepFile = rawArgs.includes('--no-gitkeep-file');
|
|
400
|
+
|
|
401
|
+
if (noClaudeFile && noGitkeepFile) {
|
|
402
|
+
throw new Error('Cannot disable both --claude-file and --gitkeep-file. At least one must be enabled for PR creation.');
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// If user explicitly set --no-claude-file, enable gitkeep-file
|
|
406
|
+
if (noClaudeFile && !argv.gitkeepFile) {
|
|
407
|
+
argv.gitkeepFile = true;
|
|
408
|
+
argv.claudeFile = false;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// If user explicitly set --no-gitkeep-file, enable claude-file (this is the default anyway)
|
|
412
|
+
if (noGitkeepFile && !argv.claudeFile) {
|
|
413
|
+
argv.claudeFile = true;
|
|
414
|
+
argv.gitkeepFile = false;
|
|
415
|
+
}
|
|
416
|
+
|
|
368
417
|
return argv;
|
|
369
418
|
};
|
package/src/solve.mjs
CHANGED
|
@@ -211,7 +211,7 @@ await validateAndExitOnInvalidModel(argv.model, tool, safeExit);
|
|
|
211
211
|
// Skip tool CONNECTION validation in dry-run mode or when --skip-tool-connection-check or --no-tool-connection-check is enabled
|
|
212
212
|
// Note: This does NOT skip model validation which is performed above
|
|
213
213
|
const skipToolConnectionCheck = argv.dryRun || argv.skipToolConnectionCheck || argv.toolConnectionCheck === false;
|
|
214
|
-
if (!(await performSystemChecks(argv.minDiskSpace ||
|
|
214
|
+
if (!(await performSystemChecks(argv.minDiskSpace || 2048, skipToolConnectionCheck, argv.model, argv))) {
|
|
215
215
|
await safeExit(1, 'System checks failed');
|
|
216
216
|
}
|
|
217
217
|
// URL validation debug logging
|
|
@@ -1045,8 +1045,8 @@ try {
|
|
|
1045
1045
|
const autoRestartEnabled = argv['autoRestartOnUncommittedChanges'] !== false;
|
|
1046
1046
|
const shouldRestart = await checkForUncommittedChanges(tempDir, owner, repo, branchName, $, log, shouldAutoCommit, autoRestartEnabled);
|
|
1047
1047
|
|
|
1048
|
-
// Remove CLAUDE.md now that Claude command has finished
|
|
1049
|
-
await cleanupClaudeFile(tempDir, branchName, claudeCommitHash);
|
|
1048
|
+
// Remove initial commit file (CLAUDE.md or .gitkeep) now that Claude command has finished
|
|
1049
|
+
await cleanupClaudeFile(tempDir, branchName, claudeCommitHash, argv);
|
|
1050
1050
|
|
|
1051
1051
|
// Show summary of session and log file
|
|
1052
1052
|
await showSessionSummary(sessionId, limitReached, argv, issueUrl, tempDir, shouldAttachLogs);
|
|
@@ -42,13 +42,13 @@ const githubLinking = await import('./github-linking.lib.mjs');
|
|
|
42
42
|
const { hasGitHubLinkingKeyword } = githubLinking;
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
|
-
* Detect the CLAUDE.md commit hash from branch structure when not available in session
|
|
45
|
+
* Detect the CLAUDE.md or .gitkeep commit hash from branch structure when not available in session
|
|
46
46
|
* This handles continue mode where the commit hash was lost between sessions
|
|
47
47
|
*
|
|
48
48
|
* Safety checks to prevent Issue #617 (wrong commit revert):
|
|
49
49
|
* 1. Only look at commits on the PR branch (not default branch commits)
|
|
50
50
|
* 2. Verify the commit message matches our expected pattern
|
|
51
|
-
* 3. Verify the commit ONLY adds CLAUDE.md (no other files changed)
|
|
51
|
+
* 3. Verify the commit ONLY adds CLAUDE.md or .gitkeep (no other files changed)
|
|
52
52
|
* 4. Verify there are additional commits after it (actual work was done)
|
|
53
53
|
*
|
|
54
54
|
* @param {string} tempDir - The temporary directory with the git repo
|
|
@@ -57,12 +57,16 @@ const { hasGitHubLinkingKeyword } = githubLinking;
|
|
|
57
57
|
*/
|
|
58
58
|
const detectClaudeMdCommitFromBranch = async (tempDir, branchName) => {
|
|
59
59
|
try {
|
|
60
|
-
await log(' Attempting to detect CLAUDE.md commit from branch structure...', { verbose: true });
|
|
60
|
+
await log(' Attempting to detect CLAUDE.md or .gitkeep commit from branch structure...', { verbose: true });
|
|
61
61
|
|
|
62
|
-
// First check if CLAUDE.md exists in current branch
|
|
62
|
+
// First check if CLAUDE.md or .gitkeep exists in current branch
|
|
63
63
|
const claudeMdExistsResult = await $({ cwd: tempDir })`git ls-files CLAUDE.md 2>&1`;
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
const gitkeepExistsResult = await $({ cwd: tempDir })`git ls-files .gitkeep 2>&1`;
|
|
65
|
+
const claudeMdExists = claudeMdExistsResult.code === 0 && claudeMdExistsResult.stdout && claudeMdExistsResult.stdout.trim();
|
|
66
|
+
const gitkeepExists = gitkeepExistsResult.code === 0 && gitkeepExistsResult.stdout && gitkeepExistsResult.stdout.trim();
|
|
67
|
+
|
|
68
|
+
if (!claudeMdExists && !gitkeepExists) {
|
|
69
|
+
await log(' Neither CLAUDE.md nor .gitkeep exists in current branch', { verbose: true });
|
|
66
70
|
return null;
|
|
67
71
|
}
|
|
68
72
|
|
|
@@ -80,7 +84,7 @@ const detectClaudeMdCommitFromBranch = async (tempDir, branchName) => {
|
|
|
80
84
|
// Find the merge base (fork point) between current branch and default branch
|
|
81
85
|
const mergeBaseResult = await $({ cwd: tempDir })`git merge-base origin/${defaultBranch} HEAD 2>&1`;
|
|
82
86
|
if (mergeBaseResult.code !== 0 || !mergeBaseResult.stdout) {
|
|
83
|
-
await log(' Could not find merge base, cannot safely detect
|
|
87
|
+
await log(' Could not find merge base, cannot safely detect initial commit', { verbose: true });
|
|
84
88
|
return null;
|
|
85
89
|
}
|
|
86
90
|
const mergeBase = mergeBaseResult.stdout.toString().trim();
|
|
@@ -117,19 +121,19 @@ const detectClaudeMdCommitFromBranch = async (tempDir, branchName) => {
|
|
|
117
121
|
verbose: true,
|
|
118
122
|
});
|
|
119
123
|
|
|
120
|
-
// Safety check: Verify commit message matches expected pattern
|
|
121
|
-
const expectedMessagePatterns = [/^Initial commit with task details/i, /^Add CLAUDE\.md/i, /^CLAUDE\.md/i];
|
|
124
|
+
// Safety check: Verify commit message matches expected pattern (CLAUDE.md or .gitkeep)
|
|
125
|
+
const expectedMessagePatterns = [/^Initial commit with task details/i, /^Add CLAUDE\.md/i, /^CLAUDE\.md/i, /^Add \.gitkeep/i, /\.gitkeep/i];
|
|
122
126
|
|
|
123
127
|
const messageMatches = expectedMessagePatterns.some(pattern => pattern.test(firstCommitMessage));
|
|
124
128
|
if (!messageMatches) {
|
|
125
|
-
await log(' First commit message does not match expected
|
|
126
|
-
await log(' Expected patterns: "Initial commit with task details...", "Add CLAUDE.md", etc.', {
|
|
129
|
+
await log(' First commit message does not match expected pattern', { verbose: true });
|
|
130
|
+
await log(' Expected patterns: "Initial commit with task details...", "Add CLAUDE.md", ".gitkeep", etc.', {
|
|
127
131
|
verbose: true,
|
|
128
132
|
});
|
|
129
133
|
return null;
|
|
130
134
|
}
|
|
131
135
|
|
|
132
|
-
// Safety check: Verify the commit ONLY adds CLAUDE.md file (no other files)
|
|
136
|
+
// Safety check: Verify the commit ONLY adds CLAUDE.md or .gitkeep file (no other files)
|
|
133
137
|
const filesChangedResult = await $({
|
|
134
138
|
cwd: tempDir,
|
|
135
139
|
})`git diff-tree --no-commit-id --name-only -r ${firstCommitHash} 2>&1`;
|
|
@@ -141,16 +145,20 @@ const detectClaudeMdCommitFromBranch = async (tempDir, branchName) => {
|
|
|
141
145
|
const filesChanged = filesChangedResult.stdout.toString().trim().split('\n').filter(Boolean);
|
|
142
146
|
await log(` Files changed in first commit: ${filesChanged.join(', ')}`, { verbose: true });
|
|
143
147
|
|
|
144
|
-
// Check if CLAUDE.md is in the files changed
|
|
145
|
-
|
|
146
|
-
|
|
148
|
+
// Check if CLAUDE.md or .gitkeep is in the files changed
|
|
149
|
+
const hasClaudeMd = filesChanged.includes('CLAUDE.md');
|
|
150
|
+
const hasGitkeep = filesChanged.includes('.gitkeep');
|
|
151
|
+
if (!hasClaudeMd && !hasGitkeep) {
|
|
152
|
+
await log(' First commit does not include CLAUDE.md or .gitkeep', { verbose: true });
|
|
147
153
|
return null;
|
|
148
154
|
}
|
|
149
155
|
|
|
150
|
-
|
|
156
|
+
const targetFile = hasClaudeMd ? 'CLAUDE.md' : '.gitkeep';
|
|
157
|
+
|
|
158
|
+
// CRITICAL SAFETY CHECK: Only allow revert if the target file is the ONLY file changed
|
|
151
159
|
// This prevents Issue #617 where reverting a commit deleted .gitignore, LICENSE, README.md
|
|
152
160
|
if (filesChanged.length > 1) {
|
|
153
|
-
await log(` ⚠️ First commit changes more than just
|
|
161
|
+
await log(` ⚠️ First commit changes more than just ${targetFile} (${filesChanged.length} files)`, {
|
|
154
162
|
verbose: true,
|
|
155
163
|
});
|
|
156
164
|
await log(` Files: ${filesChanged.join(', ')}`, { verbose: true });
|
|
@@ -159,81 +167,86 @@ const detectClaudeMdCommitFromBranch = async (tempDir, branchName) => {
|
|
|
159
167
|
}
|
|
160
168
|
|
|
161
169
|
// All safety checks passed!
|
|
162
|
-
await log(` ✅ Detected
|
|
163
|
-
await log(
|
|
170
|
+
await log(` ✅ Detected ${targetFile} commit: ${firstCommitHash.substring(0, 7)}`, { verbose: true });
|
|
171
|
+
await log(` ✅ Commit only contains ${targetFile} (safe to revert)`, { verbose: true });
|
|
164
172
|
await log(` ✅ Branch has ${branchCommits.length - 1} additional commit(s) (work was done)`, { verbose: true });
|
|
165
173
|
|
|
166
174
|
return firstCommitHash;
|
|
167
175
|
} catch (error) {
|
|
168
176
|
reportError(error, {
|
|
169
|
-
context: '
|
|
177
|
+
context: 'detect_initial_commit',
|
|
170
178
|
tempDir,
|
|
171
179
|
branchName,
|
|
172
180
|
operation: 'detect_commit_from_branch_structure',
|
|
173
181
|
});
|
|
174
|
-
await log(` Error detecting
|
|
182
|
+
await log(` Error detecting initial commit: ${error.message}`, { verbose: true });
|
|
175
183
|
return null;
|
|
176
184
|
}
|
|
177
185
|
};
|
|
178
186
|
|
|
179
|
-
// Revert the CLAUDE.md commit to restore original state
|
|
187
|
+
// Revert the CLAUDE.md or .gitkeep commit to restore original state
|
|
180
188
|
export const cleanupClaudeFile = async (tempDir, branchName, claudeCommitHash = null) => {
|
|
181
189
|
try {
|
|
182
190
|
// If no commit hash provided, try to detect it from branch structure
|
|
183
191
|
// This handles continue mode where the hash was lost between sessions
|
|
184
192
|
if (!claudeCommitHash) {
|
|
185
|
-
await log(' No
|
|
193
|
+
await log(' No initial commit hash from session, attempting to detect from branch...', { verbose: true });
|
|
186
194
|
claudeCommitHash = await detectClaudeMdCommitFromBranch(tempDir, branchName);
|
|
187
195
|
|
|
188
196
|
if (!claudeCommitHash) {
|
|
189
|
-
await log(' Could not safely detect
|
|
197
|
+
await log(' Could not safely detect initial commit to revert', { verbose: true });
|
|
190
198
|
return;
|
|
191
199
|
}
|
|
192
|
-
await log(` Detected
|
|
200
|
+
await log(` Detected initial commit: ${claudeCommitHash.substring(0, 7)}`, { verbose: true });
|
|
193
201
|
}
|
|
194
202
|
|
|
195
|
-
|
|
203
|
+
// Determine which file was used based on the commit message or flags
|
|
204
|
+
// Check the commit message to determine which file was committed
|
|
205
|
+
const commitMsgResult = await $({ cwd: tempDir })`git log -1 --format=%s ${claudeCommitHash} 2>&1`;
|
|
206
|
+
const commitMsg = commitMsgResult.stdout?.trim() || '';
|
|
207
|
+
const isGitkeepFile = commitMsg.includes('.gitkeep');
|
|
208
|
+
const fileName = isGitkeepFile ? '.gitkeep' : 'CLAUDE.md';
|
|
209
|
+
|
|
210
|
+
await log(formatAligned('🔄', 'Cleanup:', `Reverting ${fileName} commit`));
|
|
196
211
|
await log(` Using saved commit hash: ${claudeCommitHash.substring(0, 7)}...`, { verbose: true });
|
|
197
212
|
|
|
198
213
|
const commitToRevert = claudeCommitHash;
|
|
199
214
|
|
|
200
215
|
// APPROACH 3: Check for modifications before reverting (proactive detection)
|
|
201
|
-
// This is the main strategy - detect if
|
|
202
|
-
await log(
|
|
203
|
-
const diffResult = await $({ cwd: tempDir })`git diff ${commitToRevert} HEAD --
|
|
216
|
+
// This is the main strategy - detect if the file was modified after initial commit
|
|
217
|
+
await log(` Checking if ${fileName} was modified since initial commit...`, { verbose: true });
|
|
218
|
+
const diffResult = await $({ cwd: tempDir })`git diff ${commitToRevert} HEAD -- ${fileName} 2>&1`;
|
|
204
219
|
|
|
205
220
|
if (diffResult.stdout && diffResult.stdout.trim()) {
|
|
206
|
-
//
|
|
207
|
-
await log(
|
|
221
|
+
// File was modified after initial commit - use manual approach to avoid conflicts
|
|
222
|
+
await log(` ${fileName} was modified after initial commit, using manual cleanup...`, { verbose: true });
|
|
208
223
|
|
|
209
|
-
// Get the state of
|
|
224
|
+
// Get the state of the file from before the initial commit (parent of the commit we're reverting)
|
|
210
225
|
const parentCommit = `${commitToRevert}~1`;
|
|
211
|
-
const parentFileExists = await $({ cwd: tempDir })`git cat-file -e ${parentCommit}
|
|
226
|
+
const parentFileExists = await $({ cwd: tempDir })`git cat-file -e ${parentCommit}:${fileName} 2>&1`;
|
|
212
227
|
|
|
213
228
|
if (parentFileExists.code === 0) {
|
|
214
|
-
//
|
|
215
|
-
await log(
|
|
216
|
-
await $({ cwd: tempDir })`git checkout ${parentCommit} --
|
|
229
|
+
// File existed before the initial commit - restore it to that state
|
|
230
|
+
await log(` ${fileName} existed before session, restoring to previous state...`, { verbose: true });
|
|
231
|
+
await $({ cwd: tempDir })`git checkout ${parentCommit} -- ${fileName}`;
|
|
217
232
|
} else {
|
|
218
|
-
//
|
|
219
|
-
await log(
|
|
220
|
-
await $({ cwd: tempDir })`git rm -f
|
|
233
|
+
// File didn't exist before the initial commit - delete it
|
|
234
|
+
await log(` ${fileName} was created in session, removing it...`, { verbose: true });
|
|
235
|
+
await $({ cwd: tempDir })`git rm -f ${fileName} 2>&1`;
|
|
221
236
|
}
|
|
222
237
|
|
|
223
238
|
// Create a manual revert commit
|
|
224
|
-
const commitResult = await $({
|
|
225
|
-
cwd: tempDir,
|
|
226
|
-
})`git commit -m "Revert: Remove CLAUDE.md changes from initial commit" 2>&1`;
|
|
239
|
+
const commitResult = await $({ cwd: tempDir })`git commit -m "Revert: Remove ${fileName} changes from initial commit" 2>&1`;
|
|
227
240
|
|
|
228
241
|
if (commitResult.code === 0) {
|
|
229
|
-
await log(formatAligned('📦', 'Committed:',
|
|
242
|
+
await log(formatAligned('📦', 'Committed:', `${fileName} revert (manual)`));
|
|
230
243
|
|
|
231
244
|
// Push the revert
|
|
232
245
|
const pushRevertResult = await $({ cwd: tempDir })`git push origin ${branchName} 2>&1`;
|
|
233
246
|
if (pushRevertResult.code === 0) {
|
|
234
|
-
await log(formatAligned('📤', 'Pushed:',
|
|
247
|
+
await log(formatAligned('📤', 'Pushed:', `${fileName} revert to GitHub`));
|
|
235
248
|
} else {
|
|
236
|
-
await log(
|
|
249
|
+
await log(` Warning: Could not push ${fileName} revert`, { verbose: true });
|
|
237
250
|
}
|
|
238
251
|
} else {
|
|
239
252
|
await log(' Warning: Could not create manual revert commit', { verbose: true });
|
|
@@ -246,14 +259,14 @@ export const cleanupClaudeFile = async (tempDir, branchName, claudeCommitHash =
|
|
|
246
259
|
// FALLBACK 1: Standard git revert
|
|
247
260
|
const revertResult = await $({ cwd: tempDir })`git revert ${commitToRevert} --no-edit 2>&1`;
|
|
248
261
|
if (revertResult.code === 0) {
|
|
249
|
-
await log(formatAligned('📦', 'Committed:',
|
|
262
|
+
await log(formatAligned('📦', 'Committed:', `${fileName} revert`));
|
|
250
263
|
|
|
251
264
|
// Push the revert
|
|
252
265
|
const pushRevertResult = await $({ cwd: tempDir })`git push origin ${branchName} 2>&1`;
|
|
253
266
|
if (pushRevertResult.code === 0) {
|
|
254
|
-
await log(formatAligned('📤', 'Pushed:',
|
|
267
|
+
await log(formatAligned('📤', 'Pushed:', `${fileName} revert to GitHub`));
|
|
255
268
|
} else {
|
|
256
|
-
await log(
|
|
269
|
+
await log(` Warning: Could not push ${fileName} revert`, { verbose: true });
|
|
257
270
|
}
|
|
258
271
|
} else {
|
|
259
272
|
// FALLBACK 2: Handle unexpected conflicts (three-way merge with automatic resolution)
|
|
@@ -267,24 +280,24 @@ export const cleanupClaudeFile = async (tempDir, branchName, claudeCommitHash =
|
|
|
267
280
|
const statusResult = await $({ cwd: tempDir })`git status --short 2>&1`;
|
|
268
281
|
const statusOutput = statusResult.stdout || '';
|
|
269
282
|
|
|
270
|
-
// Check if
|
|
271
|
-
if (statusOutput.includes(
|
|
272
|
-
await log(
|
|
283
|
+
// Check if the file is in the conflict
|
|
284
|
+
if (statusOutput.includes(fileName)) {
|
|
285
|
+
await log(` Resolving ${fileName} conflict by restoring pre-session state...`, { verbose: true });
|
|
273
286
|
|
|
274
|
-
// Get the state of
|
|
287
|
+
// Get the state of the file from before the initial commit (parent of the commit we're reverting)
|
|
275
288
|
const parentCommit = `${commitToRevert}~1`;
|
|
276
|
-
const parentFileExists = await $({ cwd: tempDir })`git cat-file -e ${parentCommit}
|
|
289
|
+
const parentFileExists = await $({ cwd: tempDir })`git cat-file -e ${parentCommit}:${fileName} 2>&1`;
|
|
277
290
|
|
|
278
291
|
if (parentFileExists.code === 0) {
|
|
279
|
-
//
|
|
280
|
-
await log(
|
|
281
|
-
await $({ cwd: tempDir })`git checkout ${parentCommit} --
|
|
282
|
-
// Stage the resolved
|
|
283
|
-
await $({ cwd: tempDir })`git add
|
|
292
|
+
// File existed before the initial commit - restore it to that state
|
|
293
|
+
await log(` ${fileName} existed before session, restoring to previous state...`, { verbose: true });
|
|
294
|
+
await $({ cwd: tempDir })`git checkout ${parentCommit} -- ${fileName}`;
|
|
295
|
+
// Stage the resolved file
|
|
296
|
+
await $({ cwd: tempDir })`git add ${fileName} 2>&1`;
|
|
284
297
|
} else {
|
|
285
|
-
//
|
|
286
|
-
await log(
|
|
287
|
-
await $({ cwd: tempDir })`git rm -f
|
|
298
|
+
// File didn't exist before the initial commit - delete it
|
|
299
|
+
await log(` ${fileName} was created in session, removing it...`, { verbose: true });
|
|
300
|
+
await $({ cwd: tempDir })`git rm -f ${fileName} 2>&1`;
|
|
288
301
|
// No need to git add since git rm stages the deletion
|
|
289
302
|
}
|
|
290
303
|
|
|
@@ -292,27 +305,27 @@ export const cleanupClaudeFile = async (tempDir, branchName, claudeCommitHash =
|
|
|
292
305
|
const continueResult = await $({ cwd: tempDir })`git revert --continue --no-edit 2>&1`;
|
|
293
306
|
|
|
294
307
|
if (continueResult.code === 0) {
|
|
295
|
-
await log(formatAligned('📦', 'Committed:',
|
|
308
|
+
await log(formatAligned('📦', 'Committed:', `${fileName} revert (conflict resolved)`));
|
|
296
309
|
|
|
297
310
|
// Push the revert
|
|
298
311
|
const pushRevertResult = await $({ cwd: tempDir })`git push origin ${branchName} 2>&1`;
|
|
299
312
|
if (pushRevertResult.code === 0) {
|
|
300
|
-
await log(formatAligned('📤', 'Pushed:',
|
|
313
|
+
await log(formatAligned('📤', 'Pushed:', `${fileName} revert to GitHub`));
|
|
301
314
|
} else {
|
|
302
|
-
await log(
|
|
315
|
+
await log(` Warning: Could not push ${fileName} revert`, { verbose: true });
|
|
303
316
|
}
|
|
304
317
|
} else {
|
|
305
318
|
await log(' Warning: Could not complete revert after conflict resolution', { verbose: true });
|
|
306
319
|
await log(` Continue output: ${continueResult.stderr || continueResult.stdout}`, { verbose: true });
|
|
307
320
|
}
|
|
308
321
|
} else {
|
|
309
|
-
// Conflict in some other file, not
|
|
322
|
+
// Conflict in some other file, not expected file - this is unexpected
|
|
310
323
|
await log(' Warning: Revert conflict in unexpected file(s), aborting revert', { verbose: true });
|
|
311
324
|
await $({ cwd: tempDir })`git revert --abort 2>&1`;
|
|
312
325
|
}
|
|
313
326
|
} else {
|
|
314
327
|
// Non-conflict error
|
|
315
|
-
await log(
|
|
328
|
+
await log(` Warning: Could not revert ${fileName} commit`, { verbose: true });
|
|
316
329
|
await log(` Revert output: ${revertOutput}`, { verbose: true });
|
|
317
330
|
}
|
|
318
331
|
}
|
|
@@ -321,10 +334,10 @@ export const cleanupClaudeFile = async (tempDir, branchName, claudeCommitHash =
|
|
|
321
334
|
reportError(e, {
|
|
322
335
|
context: 'cleanup_claude_file',
|
|
323
336
|
tempDir,
|
|
324
|
-
operation: '
|
|
337
|
+
operation: 'revert_initial_commit',
|
|
325
338
|
});
|
|
326
339
|
// If revert fails, that's okay - the task is still complete
|
|
327
|
-
await log('
|
|
340
|
+
await log(' Initial commit revert failed or not needed', { verbose: true });
|
|
328
341
|
}
|
|
329
342
|
};
|
|
330
343
|
|
|
@@ -42,7 +42,7 @@ const { reportError } = sentryLib;
|
|
|
42
42
|
const { validateClaudeConnection } = claudeLib;
|
|
43
43
|
|
|
44
44
|
// Wrapper function for disk space check using imported module
|
|
45
|
-
const checkDiskSpace = async (minSpaceMB =
|
|
45
|
+
const checkDiskSpace = async (minSpaceMB = 2048) => {
|
|
46
46
|
const result = await memoryCheck.checkDiskSpace(minSpaceMB, { log });
|
|
47
47
|
return result.success;
|
|
48
48
|
};
|
|
@@ -204,7 +204,7 @@ export const validateContinueOnlyOnFeedback = async (argv, isPrUrl, isIssueUrl)
|
|
|
204
204
|
// Perform all system checks (disk space, memory, tool connection, GitHub permissions)
|
|
205
205
|
// Note: skipToolConnection only skips the connection check, not model validation
|
|
206
206
|
// Model validation should be done separately before calling this function
|
|
207
|
-
export const performSystemChecks = async (minDiskSpace =
|
|
207
|
+
export const performSystemChecks = async (minDiskSpace = 2048, skipToolConnection = false, model = 'sonnet', argv = {}) => {
|
|
208
208
|
// Check disk space before proceeding
|
|
209
209
|
const hasEnoughSpace = await checkDiskSpace(minDiskSpace);
|
|
210
210
|
if (!hasEnoughSpace) {
|