@ariso-ai/ivan 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/README.md +412 -0
- package/dist/agent.d.ts +11 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +48 -0
- package/dist/agent.js.map +1 -0
- package/dist/config/config.d.ts +20 -0
- package/dist/config/config.d.ts.map +1 -0
- package/dist/config/config.js +187 -0
- package/dist/config/config.js.map +1 -0
- package/dist/config.d.ts +46 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +414 -0
- package/dist/config.js.map +1 -0
- package/dist/database/database.d.ts +12 -0
- package/dist/database/database.d.ts.map +1 -0
- package/dist/database/database.js +45 -0
- package/dist/database/database.js.map +1 -0
- package/dist/database/migration.d.ts +11 -0
- package/dist/database/migration.d.ts.map +1 -0
- package/dist/database/migration.js +64 -0
- package/dist/database/migration.js.map +1 -0
- package/dist/database/migrations/001_create_jobs_table.d.ts +3 -0
- package/dist/database/migrations/001_create_jobs_table.d.ts.map +1 -0
- package/dist/database/migrations/001_create_jobs_table.js +14 -0
- package/dist/database/migrations/001_create_jobs_table.js.map +1 -0
- package/dist/database/migrations/001_initial_schema.d.ts +3 -0
- package/dist/database/migrations/001_initial_schema.d.ts.map +1 -0
- package/dist/database/migrations/001_initial_schema.js +66 -0
- package/dist/database/migrations/001_initial_schema.js.map +1 -0
- package/dist/database/migrations/002_create_tasks_table.d.ts +3 -0
- package/dist/database/migrations/002_create_tasks_table.d.ts.map +1 -0
- package/dist/database/migrations/002_create_tasks_table.js +16 -0
- package/dist/database/migrations/002_create_tasks_table.js.map +1 -0
- package/dist/database/migrations/003_add_log_to_tasks.d.ts +3 -0
- package/dist/database/migrations/003_add_log_to_tasks.d.ts.map +1 -0
- package/dist/database/migrations/003_add_log_to_tasks.js +11 -0
- package/dist/database/migrations/003_add_log_to_tasks.js.map +1 -0
- package/dist/database/migrations/004_add_branch_to_tasks.d.ts +3 -0
- package/dist/database/migrations/004_add_branch_to_tasks.d.ts.map +1 -0
- package/dist/database/migrations/004_add_branch_to_tasks.js +11 -0
- package/dist/database/migrations/004_add_branch_to_tasks.js.map +1 -0
- package/dist/database/migrations/005_add_type_to_tasks.d.ts +3 -0
- package/dist/database/migrations/005_add_type_to_tasks.d.ts.map +1 -0
- package/dist/database/migrations/005_add_type_to_tasks.js +11 -0
- package/dist/database/migrations/005_add_type_to_tasks.js.map +1 -0
- package/dist/database/migrations/006_add_comment_url_and_commit_to_tasks.d.ts +3 -0
- package/dist/database/migrations/006_add_comment_url_and_commit_to_tasks.d.ts.map +1 -0
- package/dist/database/migrations/006_add_comment_url_and_commit_to_tasks.js +15 -0
- package/dist/database/migrations/006_add_comment_url_and_commit_to_tasks.js.map +1 -0
- package/dist/database/migrations/006_add_comment_url_to_tasks.d.ts +3 -0
- package/dist/database/migrations/006_add_comment_url_to_tasks.d.ts.map +1 -0
- package/dist/database/migrations/006_add_comment_url_to_tasks.js +14 -0
- package/dist/database/migrations/006_add_comment_url_to_tasks.js.map +1 -0
- package/dist/database/migrations/007_add_commit_to_tasks.d.ts +3 -0
- package/dist/database/migrations/007_add_commit_to_tasks.d.ts.map +1 -0
- package/dist/database/migrations/007_add_commit_to_tasks.js +13 -0
- package/dist/database/migrations/007_add_commit_to_tasks.js.map +1 -0
- package/dist/database/migrations/008_add_lint_and_test_task_type.d.ts +3 -0
- package/dist/database/migrations/008_add_lint_and_test_task_type.d.ts.map +1 -0
- package/dist/database/migrations/008_add_lint_and_test_task_type.js +42 -0
- package/dist/database/migrations/008_add_lint_and_test_task_type.js.map +1 -0
- package/dist/database/migrations/index.d.ts +3 -0
- package/dist/database/migrations/index.d.ts.map +1 -0
- package/dist/database/migrations/index.js +19 -0
- package/dist/database/migrations/index.js.map +1 -0
- package/dist/database/types.d.ts +34 -0
- package/dist/database/types.d.ts.map +1 -0
- package/dist/database/types.js +2 -0
- package/dist/database/types.js.map +1 -0
- package/dist/database.d.ts +13 -0
- package/dist/database.d.ts.map +1 -0
- package/dist/database.js +32 -0
- package/dist/database.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +285 -0
- package/dist/index.js.map +1 -0
- package/dist/scripts/task-executor.d.ts +3 -0
- package/dist/scripts/task-executor.d.ts.map +1 -0
- package/dist/scripts/task-executor.js +139 -0
- package/dist/scripts/task-executor.js.map +1 -0
- package/dist/scripts/task-planner.d.ts +3 -0
- package/dist/scripts/task-planner.d.ts.map +1 -0
- package/dist/scripts/task-planner.js +81 -0
- package/dist/scripts/task-planner.js.map +1 -0
- package/dist/services/address-executor.d.ts +13 -0
- package/dist/services/address-executor.d.ts.map +1 -0
- package/dist/services/address-executor.js +202 -0
- package/dist/services/address-executor.js.map +1 -0
- package/dist/services/address-task-executor.d.ts +19 -0
- package/dist/services/address-task-executor.d.ts.map +1 -0
- package/dist/services/address-task-executor.js +736 -0
- package/dist/services/address-task-executor.js.map +1 -0
- package/dist/services/claude-cli-executor.d.ts +14 -0
- package/dist/services/claude-cli-executor.d.ts.map +1 -0
- package/dist/services/claude-cli-executor.js +241 -0
- package/dist/services/claude-cli-executor.js.map +1 -0
- package/dist/services/claude-executor.d.ts +14 -0
- package/dist/services/claude-executor.d.ts.map +1 -0
- package/dist/services/claude-executor.js +274 -0
- package/dist/services/claude-executor.js.map +1 -0
- package/dist/services/claude-planner.d.ts +15 -0
- package/dist/services/claude-planner.d.ts.map +1 -0
- package/dist/services/claude-planner.js +107 -0
- package/dist/services/claude-planner.js.map +1 -0
- package/dist/services/docker-orchestrator.d.ts +11 -0
- package/dist/services/docker-orchestrator.d.ts.map +1 -0
- package/dist/services/docker-orchestrator.js +85 -0
- package/dist/services/docker-orchestrator.js.map +1 -0
- package/dist/services/executor-factory.d.ts +14 -0
- package/dist/services/executor-factory.d.ts.map +1 -0
- package/dist/services/executor-factory.js +14 -0
- package/dist/services/executor-factory.js.map +1 -0
- package/dist/services/git-manager.d.ts +36 -0
- package/dist/services/git-manager.d.ts.map +1 -0
- package/dist/services/git-manager.js +728 -0
- package/dist/services/git-manager.js.map +1 -0
- package/dist/services/index.d.ts +9 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +9 -0
- package/dist/services/index.js.map +1 -0
- package/dist/services/job-manager.d.ts +30 -0
- package/dist/services/job-manager.d.ts.map +1 -0
- package/dist/services/job-manager.js +337 -0
- package/dist/services/job-manager.js.map +1 -0
- package/dist/services/openai-service.d.ts +14 -0
- package/dist/services/openai-service.d.ts.map +1 -0
- package/dist/services/openai-service.js +186 -0
- package/dist/services/openai-service.js.map +1 -0
- package/dist/services/pr-service.d.ts +31 -0
- package/dist/services/pr-service.d.ts.map +1 -0
- package/dist/services/pr-service.js +291 -0
- package/dist/services/pr-service.js.map +1 -0
- package/dist/services/repository-manager.d.ts +12 -0
- package/dist/services/repository-manager.d.ts.map +1 -0
- package/dist/services/repository-manager.js +101 -0
- package/dist/services/repository-manager.js.map +1 -0
- package/dist/services/task-executor.d.ts +20 -0
- package/dist/services/task-executor.d.ts.map +1 -0
- package/dist/services/task-executor.js +717 -0
- package/dist/services/task-executor.js.map +1 -0
- package/dist/types/non-interactive-config.d.ts +28 -0
- package/dist/types/non-interactive-config.d.ts.map +1 -0
- package/dist/types/non-interactive-config.js +2 -0
- package/dist/types/non-interactive-config.js.map +1 -0
- package/dist/web-server.d.ts +16 -0
- package/dist/web-server.d.ts.map +1 -0
- package/dist/web-server.js +488 -0
- package/dist/web-server.js.map +1 -0
- package/package.json +71 -0
|
@@ -0,0 +1,736 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { execSync } from 'child_process';
|
|
4
|
+
import { JobManager } from './job-manager.js';
|
|
5
|
+
import { GitManager } from './git-manager.js';
|
|
6
|
+
import { ExecutorFactory } from './executor-factory.js';
|
|
7
|
+
import { OpenAIService } from './openai-service.js';
|
|
8
|
+
import { ConfigManager } from '../config.js';
|
|
9
|
+
import { PRService } from './pr-service.js';
|
|
10
|
+
export class AddressTaskExecutor {
|
|
11
|
+
jobManager;
|
|
12
|
+
gitManager = null;
|
|
13
|
+
claudeExecutor = null;
|
|
14
|
+
openaiService = null;
|
|
15
|
+
configManager;
|
|
16
|
+
prService = null;
|
|
17
|
+
workingDir;
|
|
18
|
+
repoInstructions;
|
|
19
|
+
constructor() {
|
|
20
|
+
this.jobManager = new JobManager();
|
|
21
|
+
this.configManager = new ConfigManager();
|
|
22
|
+
this.workingDir = '';
|
|
23
|
+
}
|
|
24
|
+
getClaudeExecutor() {
|
|
25
|
+
if (!this.claudeExecutor) {
|
|
26
|
+
this.claudeExecutor = ExecutorFactory.getExecutor();
|
|
27
|
+
}
|
|
28
|
+
return this.claudeExecutor;
|
|
29
|
+
}
|
|
30
|
+
getOpenAIService() {
|
|
31
|
+
if (!this.openaiService) {
|
|
32
|
+
this.openaiService = new OpenAIService();
|
|
33
|
+
}
|
|
34
|
+
return this.openaiService;
|
|
35
|
+
}
|
|
36
|
+
async executeAddressTasks(tasks) {
|
|
37
|
+
try {
|
|
38
|
+
console.log(chalk.blue.bold('🚀 Starting address task workflow'));
|
|
39
|
+
console.log('');
|
|
40
|
+
// Validate dependencies
|
|
41
|
+
await this.getClaudeExecutor().validateClaudeCodeInstallation();
|
|
42
|
+
console.log(chalk.green('✅ Claude Code SDK configured'));
|
|
43
|
+
// Get working directory from first task's job
|
|
44
|
+
const db = this.jobManager['dbManager'].getKysely();
|
|
45
|
+
const job = await db
|
|
46
|
+
.selectFrom('jobs')
|
|
47
|
+
.selectAll()
|
|
48
|
+
.where('uuid', '=', tasks[0].job_uuid)
|
|
49
|
+
.executeTakeFirst();
|
|
50
|
+
if (!job) {
|
|
51
|
+
throw new Error('Job not found');
|
|
52
|
+
}
|
|
53
|
+
this.workingDir = job.directory;
|
|
54
|
+
this.gitManager = new GitManager(this.workingDir);
|
|
55
|
+
this.prService = new PRService(this.workingDir);
|
|
56
|
+
this.gitManager.validateGitHubCliInstallation();
|
|
57
|
+
console.log(chalk.green('✅ GitHub CLI is installed'));
|
|
58
|
+
this.gitManager.validateGitHubCliAuthentication();
|
|
59
|
+
console.log(chalk.green('✅ GitHub CLI is authenticated'));
|
|
60
|
+
// Load repository instructions
|
|
61
|
+
this.repoInstructions = await this.configManager.getRepoInstructions(this.workingDir);
|
|
62
|
+
// Group tasks by branch
|
|
63
|
+
const tasksByBranch = new Map();
|
|
64
|
+
for (const task of tasks) {
|
|
65
|
+
const branch = task.branch || 'unknown';
|
|
66
|
+
if (!tasksByBranch.has(branch)) {
|
|
67
|
+
tasksByBranch.set(branch, []);
|
|
68
|
+
}
|
|
69
|
+
const tasks = tasksByBranch.get(branch);
|
|
70
|
+
if (tasks) {
|
|
71
|
+
tasks.push(task);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Execute tasks grouped by branch
|
|
75
|
+
for (const [branch, branchTasks] of tasksByBranch) {
|
|
76
|
+
if (branch === 'unknown') {
|
|
77
|
+
console.log(chalk.yellow('⚠️ Skipping tasks without branch information'));
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
console.log('');
|
|
81
|
+
console.log(chalk.cyan.bold(`🔄 Creating worktree for branch: ${branch}`));
|
|
82
|
+
let spinner = ora('Creating worktree...').start();
|
|
83
|
+
let worktreePath = null;
|
|
84
|
+
try {
|
|
85
|
+
// Create worktree for the branch
|
|
86
|
+
if (!this.gitManager) {
|
|
87
|
+
throw new Error('GitManager not initialized');
|
|
88
|
+
}
|
|
89
|
+
worktreePath = await this.gitManager.createWorktree(branch);
|
|
90
|
+
this.gitManager.switchToWorktree(worktreePath);
|
|
91
|
+
spinner.succeed(`Worktree created: ${worktreePath}`);
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
spinner.fail(`Failed to create worktree for branch: ${branch}`);
|
|
95
|
+
console.error(error);
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
// Handle lint_and_test tasks separately
|
|
99
|
+
const lintAndTestTasks = branchTasks.filter(t => t.type === 'lint_and_test');
|
|
100
|
+
const addressTasks = branchTasks.filter(t => t.type === 'address');
|
|
101
|
+
// Process lint_and_test tasks first
|
|
102
|
+
for (const task of lintAndTestTasks) {
|
|
103
|
+
console.log('');
|
|
104
|
+
console.log(chalk.blue('🔧 Fixing test and lint failures'));
|
|
105
|
+
await this.jobManager.updateTaskStatus(task.uuid, 'active');
|
|
106
|
+
// Extract PR number from task description
|
|
107
|
+
const prNumberMatch = task.description.match(/PR #(\d+)/);
|
|
108
|
+
const taskPrNumber = prNumberMatch ? parseInt(prNumberMatch[1]) : null;
|
|
109
|
+
spinner = ora('Fetching GitHub Actions logs...').start();
|
|
110
|
+
let actionLogs = '';
|
|
111
|
+
if (taskPrNumber && this.prService) {
|
|
112
|
+
try {
|
|
113
|
+
actionLogs = await this.prService.getFailingActionLogs(taskPrNumber);
|
|
114
|
+
if (actionLogs) {
|
|
115
|
+
spinner.succeed('GitHub Actions logs fetched');
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
spinner.info('No failing action logs found');
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
spinner.warn('Could not fetch GitHub Actions logs');
|
|
123
|
+
console.error(error);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
spinner = ora('Running Claude Code to fix test and lint failures...').start();
|
|
127
|
+
// Prepare the prompt for Claude
|
|
128
|
+
let prompt = 'Fix the failing tests and linting issues in this PR.\n\n';
|
|
129
|
+
prompt += 'The following GitHub Actions checks are failing:\n';
|
|
130
|
+
prompt += task.description.replace(/^Fix test and lint failures in PR #\d+: /, '') + '\n\n';
|
|
131
|
+
// Include the actual failing logs if available
|
|
132
|
+
if (actionLogs) {
|
|
133
|
+
prompt += '=== GitHub Actions Failure Logs ===\n';
|
|
134
|
+
prompt += actionLogs;
|
|
135
|
+
prompt += '\n=== End of Logs ===\n\n';
|
|
136
|
+
}
|
|
137
|
+
prompt += 'Please run the tests and linting locally, identify what is failing based on the logs above, and fix all issues.';
|
|
138
|
+
prompt += ' Make sure to run the tests again after fixing to verify they pass.';
|
|
139
|
+
if (this.repoInstructions) {
|
|
140
|
+
prompt += `\n\nRepository-specific instructions:\n${this.repoInstructions}`;
|
|
141
|
+
}
|
|
142
|
+
try {
|
|
143
|
+
const result = await this.getClaudeExecutor().executeTask(prompt, worktreePath || this.workingDir);
|
|
144
|
+
spinner.succeed('Claude Code execution completed');
|
|
145
|
+
await this.jobManager.updateTaskExecutionLog(task.uuid, result.log);
|
|
146
|
+
if (!this.gitManager) {
|
|
147
|
+
throw new Error('GitManager not initialized');
|
|
148
|
+
}
|
|
149
|
+
const changedFiles = this.gitManager.getChangedFiles();
|
|
150
|
+
if (changedFiles.length === 0) {
|
|
151
|
+
console.log(chalk.yellow('⚠️ No changes made'));
|
|
152
|
+
await this.jobManager.updateTaskStatus(task.uuid, 'completed');
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
// Create commit with co-author
|
|
156
|
+
spinner = ora('Creating commit...').start();
|
|
157
|
+
const commitMessage = 'Fix test and lint failures\n\nCo-authored-by: ivan-agent <ivan-agent@users.noreply.github.com>';
|
|
158
|
+
if (!this.gitManager) {
|
|
159
|
+
throw new Error('GitManager not initialized');
|
|
160
|
+
}
|
|
161
|
+
// Try to commit, handling pre-commit hook failures
|
|
162
|
+
const commitResult = await this.tryCommitWithFixes(commitMessage, task, worktreePath || this.workingDir, spinner);
|
|
163
|
+
if (commitResult.succeeded) {
|
|
164
|
+
spinner.succeed('Changes committed');
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
spinner.fail('Failed to commit after multiple attempts');
|
|
168
|
+
throw new Error('Pre-commit hook failures could not be fixed');
|
|
169
|
+
}
|
|
170
|
+
// Get the commit hash
|
|
171
|
+
const commitHash = execSync('git rev-parse HEAD', {
|
|
172
|
+
cwd: worktreePath || this.workingDir,
|
|
173
|
+
encoding: 'utf-8'
|
|
174
|
+
}).trim();
|
|
175
|
+
// Save commit hash to task
|
|
176
|
+
await this.jobManager.updateTaskCommit(task.uuid, commitHash);
|
|
177
|
+
// Push the commit immediately
|
|
178
|
+
spinner = ora('Pushing commit...').start();
|
|
179
|
+
try {
|
|
180
|
+
if (!this.gitManager) {
|
|
181
|
+
throw new Error('GitManager not initialized');
|
|
182
|
+
}
|
|
183
|
+
await this.gitManager.pushBranch(branch);
|
|
184
|
+
spinner.succeed('Commit pushed successfully');
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
spinner.fail('Failed to push commit');
|
|
188
|
+
console.error(error);
|
|
189
|
+
}
|
|
190
|
+
// Add review comment for lint_and_test task
|
|
191
|
+
if (taskPrNumber) {
|
|
192
|
+
spinner = ora('Adding review request comment...').start();
|
|
193
|
+
try {
|
|
194
|
+
const reviewComment = '@codex please review the test and lint fixes that were applied to address the failing CI checks';
|
|
195
|
+
execSync(`gh pr comment ${taskPrNumber} --body "${reviewComment}"`, {
|
|
196
|
+
cwd: worktreePath || this.workingDir,
|
|
197
|
+
stdio: 'pipe'
|
|
198
|
+
});
|
|
199
|
+
spinner.succeed('Review request comment added');
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
spinner.fail('Failed to add review comment');
|
|
203
|
+
console.error(error);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
await this.jobManager.updateTaskStatus(task.uuid, 'completed');
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
spinner.fail('Failed to fix test and lint failures');
|
|
210
|
+
console.error(error);
|
|
211
|
+
const errorLog = error instanceof Error ? error.message : String(error);
|
|
212
|
+
await this.jobManager.updateTaskExecutionLog(task.uuid, `ERROR: ${errorLog}`);
|
|
213
|
+
await this.jobManager.updateTaskStatus(task.uuid, 'not_started');
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
// Get PR number from branch name or task description
|
|
217
|
+
const prMatch = branchTasks[0].description.match(/PR #(\d+)/);
|
|
218
|
+
if (!prMatch && addressTasks.length > 0) {
|
|
219
|
+
console.log(chalk.yellow('⚠️ Could not extract PR number from task'));
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
const prNumber = prMatch ? prMatch[1] : null;
|
|
223
|
+
// Skip comment processing if there are no address tasks
|
|
224
|
+
if (addressTasks.length === 0) {
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
// Get all unaddressed comments for this PR
|
|
228
|
+
spinner = ora('Fetching PR comments...').start();
|
|
229
|
+
if (!prNumber) {
|
|
230
|
+
throw new Error('PR number not found');
|
|
231
|
+
}
|
|
232
|
+
const comments = await this.getUnaddressedComments(parseInt(prNumber));
|
|
233
|
+
spinner.succeed(`Found ${comments.length} unaddressed comments`);
|
|
234
|
+
if (comments.length === 0 && addressTasks.some(t => t.description.includes('comment'))) {
|
|
235
|
+
console.log(chalk.yellow('⚠️ No unaddressed comments found'));
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
// Process each comment
|
|
239
|
+
for (const comment of comments) {
|
|
240
|
+
console.log('');
|
|
241
|
+
console.log(chalk.blue(`📝 Addressing comment from @${comment.author}:`));
|
|
242
|
+
console.log(chalk.gray(` "${comment.body.substring(0, 100)}${comment.body.length > 100 ? '...' : ''}"`));
|
|
243
|
+
// Find the corresponding task
|
|
244
|
+
const task = addressTasks.find(t => t.description.includes(comment.author) &&
|
|
245
|
+
t.description.includes(comment.body.substring(0, 50)));
|
|
246
|
+
if (!task) {
|
|
247
|
+
console.log(chalk.yellow('⚠️ No task found for this comment'));
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
await this.jobManager.updateTaskStatus(task.uuid, 'active');
|
|
251
|
+
// Save comment URL
|
|
252
|
+
if (comment.id) {
|
|
253
|
+
const repoInfo = execSync('gh repo view --json owner,name', {
|
|
254
|
+
cwd: worktreePath || this.workingDir,
|
|
255
|
+
encoding: 'utf-8'
|
|
256
|
+
});
|
|
257
|
+
const { owner, name: repoName } = JSON.parse(repoInfo);
|
|
258
|
+
if (!prNumber) {
|
|
259
|
+
throw new Error('PR number not found');
|
|
260
|
+
}
|
|
261
|
+
const commentUrl = `https://github.com/${owner.login}/${repoName}/pull/${prNumber}#discussion_r${comment.id}`;
|
|
262
|
+
await this.jobManager.updateTaskCommentUrl(task.uuid, commentUrl);
|
|
263
|
+
}
|
|
264
|
+
spinner = ora('Running Claude Code to address comment...').start();
|
|
265
|
+
// Prepare the prompt for Claude
|
|
266
|
+
let prompt = 'Address the following PR review comment:\n\n';
|
|
267
|
+
prompt += `Comment from @${comment.author}:\n"${comment.body}"\n\n`;
|
|
268
|
+
if (comment.path) {
|
|
269
|
+
prompt += `File: ${comment.path}`;
|
|
270
|
+
if (comment.line) {
|
|
271
|
+
prompt += ` (line ${comment.line})`;
|
|
272
|
+
}
|
|
273
|
+
prompt += '\n\n';
|
|
274
|
+
}
|
|
275
|
+
prompt += 'Please make the necessary changes to address this comment.';
|
|
276
|
+
if (this.repoInstructions) {
|
|
277
|
+
prompt += `\n\nRepository-specific instructions:\n${this.repoInstructions}`;
|
|
278
|
+
}
|
|
279
|
+
try {
|
|
280
|
+
const result = await this.getClaudeExecutor().executeTask(prompt, worktreePath || this.workingDir);
|
|
281
|
+
spinner.succeed('Claude Code execution completed');
|
|
282
|
+
await this.jobManager.updateTaskExecutionLog(task.uuid, result.log);
|
|
283
|
+
// Use the last message from Claude's response
|
|
284
|
+
const lastMessage = result.lastMessage;
|
|
285
|
+
if (!this.gitManager) {
|
|
286
|
+
throw new Error('GitManager not initialized');
|
|
287
|
+
}
|
|
288
|
+
const changedFiles = this.gitManager.getChangedFiles();
|
|
289
|
+
if (changedFiles.length === 0) {
|
|
290
|
+
console.log(chalk.yellow('⚠️ No changes made - Claude determined no changes were needed'));
|
|
291
|
+
// Reply to the comment explaining why no changes were made
|
|
292
|
+
spinner = ora('Replying to comment...').start();
|
|
293
|
+
try {
|
|
294
|
+
// Truncate the message if it's too long (GitHub has a 65536 character limit)
|
|
295
|
+
const maxLength = 60000;
|
|
296
|
+
let replyBody = `Ivan: ${lastMessage || 'After reviewing, no code changes were necessary to address this comment.'}`;
|
|
297
|
+
if (replyBody.length > maxLength) {
|
|
298
|
+
replyBody = replyBody.substring(0, maxLength) + '\n\n... (message truncated)';
|
|
299
|
+
}
|
|
300
|
+
// Use a temporary file to avoid shell escaping issues
|
|
301
|
+
const { writeFileSync, unlinkSync } = await import('fs');
|
|
302
|
+
const { join } = await import('path');
|
|
303
|
+
const { tmpdir } = await import('os');
|
|
304
|
+
const tempFile = join(tmpdir(), `ivan-comment-${Date.now()}.txt`);
|
|
305
|
+
writeFileSync(tempFile, replyBody);
|
|
306
|
+
try {
|
|
307
|
+
// Use GraphQL mutation to add a reply to the review thread
|
|
308
|
+
const repoInfo = execSync('gh repo view --json owner,name', {
|
|
309
|
+
cwd: worktreePath || this.workingDir,
|
|
310
|
+
encoding: 'utf-8'
|
|
311
|
+
});
|
|
312
|
+
const { owner, name: repoName } = JSON.parse(repoInfo);
|
|
313
|
+
// Get the review thread ID from the comment
|
|
314
|
+
const threadQuery = `
|
|
315
|
+
query {
|
|
316
|
+
repository(owner: "${owner.login}", name: "${repoName}") {
|
|
317
|
+
pullRequest(number: ${prNumber}) {
|
|
318
|
+
reviewThreads(first: 100) {
|
|
319
|
+
nodes {
|
|
320
|
+
id
|
|
321
|
+
comments(first: 100) {
|
|
322
|
+
nodes {
|
|
323
|
+
databaseId
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
`;
|
|
332
|
+
const threadResult = execSync(`gh api graphql -f query='${threadQuery.replace(/'/g, "'\\''")}'`, {
|
|
333
|
+
cwd: worktreePath || this.workingDir,
|
|
334
|
+
encoding: 'utf-8'
|
|
335
|
+
});
|
|
336
|
+
const threadData = JSON.parse(threadResult);
|
|
337
|
+
const threads = threadData.data?.repository?.pullRequest?.reviewThreads?.nodes || [];
|
|
338
|
+
// Find the thread containing this comment
|
|
339
|
+
let threadId = null;
|
|
340
|
+
for (const thread of threads) {
|
|
341
|
+
const comments = thread.comments?.nodes || [];
|
|
342
|
+
if (comments.some((c) => c.databaseId?.toString() === comment.id)) {
|
|
343
|
+
threadId = thread.id;
|
|
344
|
+
break;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
if (!threadId) {
|
|
348
|
+
throw new Error('Could not find review thread for comment');
|
|
349
|
+
}
|
|
350
|
+
// Add reply using GraphQL mutation
|
|
351
|
+
const mutation = `
|
|
352
|
+
mutation {
|
|
353
|
+
addPullRequestReviewThreadReply(input: {
|
|
354
|
+
pullRequestReviewThreadId: "${threadId}"
|
|
355
|
+
body: ${JSON.stringify(replyBody)}
|
|
356
|
+
}) {
|
|
357
|
+
comment {
|
|
358
|
+
id
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
`;
|
|
363
|
+
execSync(`gh api graphql -f query='${mutation.replace(/'/g, "'\\''")}'`, {
|
|
364
|
+
cwd: worktreePath || this.workingDir,
|
|
365
|
+
stdio: 'pipe'
|
|
366
|
+
});
|
|
367
|
+
spinner.succeed('Reply added to comment');
|
|
368
|
+
}
|
|
369
|
+
finally {
|
|
370
|
+
unlinkSync(tempFile);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
catch (error) {
|
|
374
|
+
spinner.fail('Failed to reply to comment');
|
|
375
|
+
console.error(error);
|
|
376
|
+
}
|
|
377
|
+
await this.jobManager.updateTaskStatus(task.uuid, 'completed');
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
// Create commit with co-author
|
|
381
|
+
spinner = ora('Creating commit...').start();
|
|
382
|
+
const commitMessage = `Address review comment from @${comment.author}
|
|
383
|
+
|
|
384
|
+
${comment.body.substring(0, 200)}${comment.body.length > 200 ? '...' : ''}
|
|
385
|
+
|
|
386
|
+
Co-authored-by: ivan-agent <ivan-agent@users.noreply.github.com}`;
|
|
387
|
+
if (!this.gitManager) {
|
|
388
|
+
throw new Error('GitManager not initialized');
|
|
389
|
+
}
|
|
390
|
+
// Try to commit, handling pre-commit hook failures
|
|
391
|
+
const commitResult = await this.tryCommitWithFixes(commitMessage, task, worktreePath || this.workingDir, spinner);
|
|
392
|
+
if (commitResult.succeeded) {
|
|
393
|
+
spinner.succeed('Changes committed');
|
|
394
|
+
}
|
|
395
|
+
else {
|
|
396
|
+
spinner.fail('Failed to commit after multiple attempts');
|
|
397
|
+
throw new Error('Pre-commit hook failures could not be fixed');
|
|
398
|
+
}
|
|
399
|
+
// Get the commit hash
|
|
400
|
+
const commitHash = execSync('git rev-parse HEAD', {
|
|
401
|
+
cwd: worktreePath || this.workingDir,
|
|
402
|
+
encoding: 'utf-8'
|
|
403
|
+
}).trim();
|
|
404
|
+
// Save commit hash to task
|
|
405
|
+
await this.jobManager.updateTaskCommit(task.uuid, commitHash);
|
|
406
|
+
// Push the commit immediately
|
|
407
|
+
spinner = ora('Pushing commit...').start();
|
|
408
|
+
try {
|
|
409
|
+
if (!this.gitManager) {
|
|
410
|
+
throw new Error('GitManager not initialized');
|
|
411
|
+
}
|
|
412
|
+
await this.gitManager.pushBranch(branch);
|
|
413
|
+
spinner.succeed('Commit pushed successfully');
|
|
414
|
+
}
|
|
415
|
+
catch (error) {
|
|
416
|
+
spinner.fail('Failed to push commit');
|
|
417
|
+
console.error(error);
|
|
418
|
+
}
|
|
419
|
+
// Reply to the comment with the fix
|
|
420
|
+
spinner = ora('Replying to comment...').start();
|
|
421
|
+
try {
|
|
422
|
+
// Truncate the message if it's too long (GitHub has a 65536 character limit)
|
|
423
|
+
const maxLength = 60000;
|
|
424
|
+
let replyBody = lastMessage
|
|
425
|
+
? `Ivan: ${lastMessage}\n\nThis has been addressed in commit ${commitHash.substring(0, 7)}`
|
|
426
|
+
: `Ivan: This has been addressed in commit ${commitHash.substring(0, 7)}`;
|
|
427
|
+
if (replyBody.length > maxLength) {
|
|
428
|
+
replyBody = replyBody.substring(0, maxLength) + '\n\n... (message truncated)\n\n' +
|
|
429
|
+
`This has been addressed in commit ${commitHash.substring(0, 7)}`;
|
|
430
|
+
}
|
|
431
|
+
// Use GraphQL mutation to add a reply to the review thread
|
|
432
|
+
const repoInfo = execSync('gh repo view --json owner,name', {
|
|
433
|
+
cwd: worktreePath || this.workingDir,
|
|
434
|
+
encoding: 'utf-8'
|
|
435
|
+
});
|
|
436
|
+
const { owner, name: repoName } = JSON.parse(repoInfo);
|
|
437
|
+
// Get the review thread ID from the comment
|
|
438
|
+
const threadQuery = `
|
|
439
|
+
query {
|
|
440
|
+
repository(owner: "${owner.login}", name: "${repoName}") {
|
|
441
|
+
pullRequest(number: ${prNumber}) {
|
|
442
|
+
reviewThreads(first: 100) {
|
|
443
|
+
nodes {
|
|
444
|
+
id
|
|
445
|
+
comments(first: 100) {
|
|
446
|
+
nodes {
|
|
447
|
+
databaseId
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
`;
|
|
456
|
+
const threadResult = execSync(`gh api graphql -f query='${threadQuery.replace(/'/g, "'\\''")}'`, {
|
|
457
|
+
cwd: worktreePath || this.workingDir,
|
|
458
|
+
encoding: 'utf-8'
|
|
459
|
+
});
|
|
460
|
+
const threadData = JSON.parse(threadResult);
|
|
461
|
+
const threads = threadData.data?.repository?.pullRequest?.reviewThreads?.nodes || [];
|
|
462
|
+
// Find the thread containing this comment
|
|
463
|
+
let threadId = null;
|
|
464
|
+
for (const thread of threads) {
|
|
465
|
+
const comments = thread.comments?.nodes || [];
|
|
466
|
+
if (comments.some((c) => c.databaseId?.toString() === comment.id)) {
|
|
467
|
+
threadId = thread.id;
|
|
468
|
+
break;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
if (!threadId) {
|
|
472
|
+
throw new Error('Could not find review thread for comment');
|
|
473
|
+
}
|
|
474
|
+
// Add reply using GraphQL mutation
|
|
475
|
+
const mutation = `
|
|
476
|
+
mutation {
|
|
477
|
+
addPullRequestReviewThreadReply(input: {
|
|
478
|
+
pullRequestReviewThreadId: "${threadId}"
|
|
479
|
+
body: ${JSON.stringify(replyBody)}
|
|
480
|
+
}) {
|
|
481
|
+
comment {
|
|
482
|
+
id
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
`;
|
|
487
|
+
execSync(`gh api graphql -f query='${mutation.replace(/'/g, "'\\''")}'`, {
|
|
488
|
+
cwd: worktreePath || this.workingDir,
|
|
489
|
+
stdio: 'pipe'
|
|
490
|
+
});
|
|
491
|
+
spinner.succeed('Reply added to comment');
|
|
492
|
+
}
|
|
493
|
+
catch (error) {
|
|
494
|
+
spinner.fail('Failed to reply to comment');
|
|
495
|
+
console.error(error);
|
|
496
|
+
}
|
|
497
|
+
await this.jobManager.updateTaskStatus(task.uuid, 'completed');
|
|
498
|
+
}
|
|
499
|
+
catch (error) {
|
|
500
|
+
spinner.fail('Failed to address comment');
|
|
501
|
+
console.error(error);
|
|
502
|
+
const errorLog = error instanceof Error ? error.message : String(error);
|
|
503
|
+
await this.jobManager.updateTaskExecutionLog(task.uuid, `ERROR: ${errorLog}`);
|
|
504
|
+
await this.jobManager.updateTaskStatus(task.uuid, 'not_started');
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
// Generate and add specific review comment (only if we have a PR number and made changes)
|
|
508
|
+
if (prNumber && (addressTasks.length > 0 || lintAndTestTasks.length > 0)) {
|
|
509
|
+
spinner = ora('Generating review request...').start();
|
|
510
|
+
try {
|
|
511
|
+
// Get the latest commit changes
|
|
512
|
+
const latestCommit = execSync('git rev-parse HEAD', {
|
|
513
|
+
cwd: worktreePath || this.workingDir,
|
|
514
|
+
encoding: 'utf-8'
|
|
515
|
+
}).trim();
|
|
516
|
+
const commitDiff = execSync(`git show ${latestCommit} --format="" --unified=3`, {
|
|
517
|
+
cwd: worktreePath || this.workingDir,
|
|
518
|
+
encoding: 'utf-8'
|
|
519
|
+
});
|
|
520
|
+
const changedFiles = execSync(`git show --name-only --format="" ${latestCommit}`, {
|
|
521
|
+
cwd: worktreePath || this.workingDir,
|
|
522
|
+
encoding: 'utf-8'
|
|
523
|
+
}).trim().split('\n').filter(Boolean);
|
|
524
|
+
// Generate specific review instructions using OpenAI
|
|
525
|
+
const reviewInstructions = await this.generateReviewInstructions(commitDiff, changedFiles, parseInt(prNumber));
|
|
526
|
+
spinner.succeed('Review request generated');
|
|
527
|
+
// Add the review comment
|
|
528
|
+
spinner = ora('Adding review request comment...').start();
|
|
529
|
+
const reviewComment = `@codex ${reviewInstructions}`;
|
|
530
|
+
execSync(`gh pr comment ${prNumber} --body "${reviewComment.replace(/"/g, '\\"')}"`, {
|
|
531
|
+
cwd: worktreePath || this.workingDir,
|
|
532
|
+
stdio: 'pipe'
|
|
533
|
+
});
|
|
534
|
+
spinner.succeed('Review request comment added');
|
|
535
|
+
}
|
|
536
|
+
catch (error) {
|
|
537
|
+
spinner.fail('Failed to add review comment');
|
|
538
|
+
console.error(error);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
// Clean up worktree after processing branch
|
|
542
|
+
if (this.gitManager && worktreePath) {
|
|
543
|
+
try {
|
|
544
|
+
this.gitManager.switchToOriginalDir();
|
|
545
|
+
await this.gitManager.removeWorktree(branch);
|
|
546
|
+
}
|
|
547
|
+
catch (error) {
|
|
548
|
+
console.log(chalk.yellow(`⚠️ Could not clean up worktree: ${error}`));
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
console.log('');
|
|
553
|
+
console.log(chalk.green.bold('🎉 All address tasks completed!'));
|
|
554
|
+
}
|
|
555
|
+
catch (error) {
|
|
556
|
+
console.error(chalk.red.bold('❌ Address workflow failed:'), error);
|
|
557
|
+
throw error;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
async getUnaddressedComments(prNumber) {
|
|
561
|
+
try {
|
|
562
|
+
// Get PR owner and repo name
|
|
563
|
+
const repoInfo = execSync('gh repo view --json owner,name', {
|
|
564
|
+
cwd: this.gitManager?.['originalWorkingDir'] || this.workingDir,
|
|
565
|
+
encoding: 'utf-8'
|
|
566
|
+
});
|
|
567
|
+
const { owner, name: repoName } = JSON.parse(repoInfo);
|
|
568
|
+
// Use GraphQL to get review threads with resolved status
|
|
569
|
+
const graphqlQuery = `
|
|
570
|
+
query {
|
|
571
|
+
repository(owner: "${owner.login}", name: "${repoName}") {
|
|
572
|
+
pullRequest(number: ${prNumber}) {
|
|
573
|
+
reviewThreads(first: 100) {
|
|
574
|
+
nodes {
|
|
575
|
+
isResolved
|
|
576
|
+
comments(first: 100) {
|
|
577
|
+
nodes {
|
|
578
|
+
id
|
|
579
|
+
databaseId
|
|
580
|
+
body
|
|
581
|
+
author {
|
|
582
|
+
login
|
|
583
|
+
}
|
|
584
|
+
createdAt
|
|
585
|
+
path
|
|
586
|
+
line
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
`;
|
|
595
|
+
const graphqlResult = execSync(`gh api graphql -f query='${graphqlQuery}'`, {
|
|
596
|
+
cwd: this.gitManager?.['originalWorkingDir'] || this.workingDir,
|
|
597
|
+
encoding: 'utf-8'
|
|
598
|
+
});
|
|
599
|
+
const result = JSON.parse(graphqlResult);
|
|
600
|
+
const threads = result.data?.repository?.pullRequest?.reviewThreads?.nodes || [];
|
|
601
|
+
const unaddressedComments = [];
|
|
602
|
+
// Process each thread
|
|
603
|
+
for (const thread of threads) {
|
|
604
|
+
// Skip resolved threads
|
|
605
|
+
if (thread.isResolved) {
|
|
606
|
+
continue;
|
|
607
|
+
}
|
|
608
|
+
const comments = thread.comments?.nodes || [];
|
|
609
|
+
if (comments.length === 0) {
|
|
610
|
+
continue;
|
|
611
|
+
}
|
|
612
|
+
// Get the first comment (the main review comment)
|
|
613
|
+
const firstComment = comments[0];
|
|
614
|
+
// Check if there are replies (more than one comment in thread)
|
|
615
|
+
const hasReplies = comments.length > 1;
|
|
616
|
+
if (!hasReplies && firstComment.path) {
|
|
617
|
+
// Only include if it's an inline code comment (has a path) and has no replies
|
|
618
|
+
unaddressedComments.push({
|
|
619
|
+
id: firstComment.databaseId ? firstComment.databaseId.toString() : firstComment.id,
|
|
620
|
+
author: firstComment.author.login,
|
|
621
|
+
body: firstComment.body,
|
|
622
|
+
createdAt: firstComment.createdAt,
|
|
623
|
+
path: firstComment.path,
|
|
624
|
+
line: firstComment.line
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
return unaddressedComments;
|
|
629
|
+
}
|
|
630
|
+
catch (error) {
|
|
631
|
+
console.error('Error fetching comments:', error);
|
|
632
|
+
return [];
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
async generateReviewInstructions(diff, changedFiles, _prNumber) {
|
|
636
|
+
try {
|
|
637
|
+
const openaiService = this.getOpenAIService();
|
|
638
|
+
const client = await openaiService.getClient();
|
|
639
|
+
const prompt = `You are reviewing code changes that were made to address PR review comments.
|
|
640
|
+
Based on the following diff and changed files, generate a concise, specific review request that tells the reviewer what to focus on.
|
|
641
|
+
|
|
642
|
+
Changed files:
|
|
643
|
+
${changedFiles.join('\n')}
|
|
644
|
+
|
|
645
|
+
Diff (last commit):
|
|
646
|
+
${diff.substring(0, 8000)}${diff.length > 8000 ? '\n... (diff truncated)' : ''}
|
|
647
|
+
|
|
648
|
+
Generate a brief (1-2 sentences) review request that:
|
|
649
|
+
1. Mentions the key changes made
|
|
650
|
+
2. Asks the reviewer to verify specific aspects that were addressed
|
|
651
|
+
3. Is conversational and clear
|
|
652
|
+
|
|
653
|
+
Example format: "please review the updates to the reflection service integration and verify that the null checks properly handle missing configuration objects"
|
|
654
|
+
|
|
655
|
+
Return ONLY the review request text, without any prefix like "Please review" since @codex will already be prepended.`;
|
|
656
|
+
const completion = await client.chat.completions.create({
|
|
657
|
+
model: 'gpt-4o-mini',
|
|
658
|
+
messages: [
|
|
659
|
+
{
|
|
660
|
+
role: 'system',
|
|
661
|
+
content: 'You are a helpful assistant that generates specific code review requests based on git diffs.'
|
|
662
|
+
},
|
|
663
|
+
{
|
|
664
|
+
role: 'user',
|
|
665
|
+
content: prompt
|
|
666
|
+
}
|
|
667
|
+
],
|
|
668
|
+
temperature: 0.3,
|
|
669
|
+
max_tokens: 150
|
|
670
|
+
});
|
|
671
|
+
const reviewRequest = completion.choices[0]?.message?.content?.trim();
|
|
672
|
+
if (!reviewRequest) {
|
|
673
|
+
return 'please review the latest changes and verify all review comments have been properly addressed';
|
|
674
|
+
}
|
|
675
|
+
return reviewRequest;
|
|
676
|
+
}
|
|
677
|
+
catch (error) {
|
|
678
|
+
console.error('Error generating review instructions:', error);
|
|
679
|
+
// Fallback to a generic message if OpenAI fails
|
|
680
|
+
return 'please review the latest changes and verify all review comments have been properly addressed';
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
async tryCommitWithFixes(commitMessage, task, workingDir, spinner) {
|
|
684
|
+
let commitAttempts = 0;
|
|
685
|
+
const maxCommitAttempts = 3;
|
|
686
|
+
let commitSucceeded = false;
|
|
687
|
+
while (commitAttempts < maxCommitAttempts && !commitSucceeded) {
|
|
688
|
+
try {
|
|
689
|
+
if (!this.gitManager) {
|
|
690
|
+
throw new Error('GitManager not initialized');
|
|
691
|
+
}
|
|
692
|
+
await this.gitManager.commitChanges(commitMessage);
|
|
693
|
+
commitSucceeded = true;
|
|
694
|
+
// Stop the spinner if we're retrying
|
|
695
|
+
if (commitAttempts > 0 && spinner.isSpinning) {
|
|
696
|
+
spinner.succeed('Commit successful after retry');
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
catch (commitError) {
|
|
700
|
+
commitAttempts++;
|
|
701
|
+
const errorMessage = commitError instanceof Error ? commitError.message : String(commitError);
|
|
702
|
+
// Check if this is a pre-commit hook failure
|
|
703
|
+
if (errorMessage.includes('pre-commit') && commitAttempts < maxCommitAttempts) {
|
|
704
|
+
spinner.fail(`Pre-commit hook failed (attempt ${commitAttempts}/${maxCommitAttempts})`);
|
|
705
|
+
console.log(chalk.yellow('🔧 Running Claude to fix pre-commit errors...'));
|
|
706
|
+
// Extract the error details from the commit error
|
|
707
|
+
const errorDetails = errorMessage;
|
|
708
|
+
// Prepare prompt for Claude to fix the errors
|
|
709
|
+
const fixPrompt = `Fix the following pre-commit hook errors:\n\n${errorDetails}\n\nPlease fix all TypeScript errors, linting issues, and any other problems preventing the commit.`;
|
|
710
|
+
spinner = ora('Running Claude to fix pre-commit errors...').start();
|
|
711
|
+
try {
|
|
712
|
+
// Run Claude to fix the errors
|
|
713
|
+
const fixResult = await this.getClaudeExecutor().executeTask(fixPrompt, workingDir);
|
|
714
|
+
spinner.succeed('Claude attempted to fix the errors');
|
|
715
|
+
// Update the execution log with the fix attempt
|
|
716
|
+
const previousLog = await this.jobManager.getTaskExecutionLog(task.uuid);
|
|
717
|
+
await this.jobManager.updateTaskExecutionLog(task.uuid, `${previousLog}\n\n--- Pre-commit Fix Attempt ${commitAttempts} ---\n${fixResult.log}`);
|
|
718
|
+
// Try to commit again on the next iteration
|
|
719
|
+
spinner = ora('Retrying commit...').start();
|
|
720
|
+
}
|
|
721
|
+
catch (fixError) {
|
|
722
|
+
spinner.fail('Failed to run Claude to fix errors');
|
|
723
|
+
console.error(chalk.red('Claude fix attempt failed:'), fixError);
|
|
724
|
+
throw commitError; // Re-throw the original error
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
else {
|
|
728
|
+
// Not a pre-commit error or max attempts reached
|
|
729
|
+
throw commitError;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
return { succeeded: commitSucceeded };
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
//# sourceMappingURL=address-task-executor.js.map
|