@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,717 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { JobManager } from './job-manager.js';
|
|
4
|
+
import { GitManager } from './git-manager.js';
|
|
5
|
+
import { ExecutorFactory } from './executor-factory.js';
|
|
6
|
+
import { OpenAIService } from './openai-service.js';
|
|
7
|
+
import { RepositoryManager } from './repository-manager.js';
|
|
8
|
+
import { ConfigManager } from '../config.js';
|
|
9
|
+
import { AddressTaskExecutor } from './address-task-executor.js';
|
|
10
|
+
import { PRService } from './pr-service.js';
|
|
11
|
+
export class TaskExecutor {
|
|
12
|
+
jobManager;
|
|
13
|
+
gitManager = null;
|
|
14
|
+
claudeExecutor = null;
|
|
15
|
+
openaiService = null;
|
|
16
|
+
repositoryManager;
|
|
17
|
+
configManager;
|
|
18
|
+
workingDir;
|
|
19
|
+
repoInstructions;
|
|
20
|
+
constructor() {
|
|
21
|
+
this.jobManager = new JobManager();
|
|
22
|
+
this.repositoryManager = new RepositoryManager();
|
|
23
|
+
this.configManager = new ConfigManager();
|
|
24
|
+
this.workingDir = '';
|
|
25
|
+
this.repoInstructions = undefined;
|
|
26
|
+
}
|
|
27
|
+
getClaudeExecutor() {
|
|
28
|
+
if (!this.claudeExecutor) {
|
|
29
|
+
this.claudeExecutor = ExecutorFactory.getExecutor();
|
|
30
|
+
}
|
|
31
|
+
return this.claudeExecutor;
|
|
32
|
+
}
|
|
33
|
+
getOpenAIService() {
|
|
34
|
+
if (!this.openaiService) {
|
|
35
|
+
this.openaiService = new OpenAIService();
|
|
36
|
+
}
|
|
37
|
+
return this.openaiService;
|
|
38
|
+
}
|
|
39
|
+
async executeWorkflow() {
|
|
40
|
+
try {
|
|
41
|
+
console.log(chalk.blue.bold('🚀 Starting Ivan workflow'));
|
|
42
|
+
console.log('');
|
|
43
|
+
console.log(chalk.blue('🔍 Validating dependencies...'));
|
|
44
|
+
await this.getClaudeExecutor().validateClaudeCodeInstallation();
|
|
45
|
+
console.log(chalk.green('✅ Claude Code SDK configured'));
|
|
46
|
+
this.workingDir = await this.repositoryManager.getValidWorkingDirectory();
|
|
47
|
+
this.gitManager = new GitManager(this.workingDir);
|
|
48
|
+
if (!this.gitManager) {
|
|
49
|
+
throw new Error('GitManager not initialized');
|
|
50
|
+
}
|
|
51
|
+
this.gitManager.validateGitHubCliInstallation();
|
|
52
|
+
console.log(chalk.green('✅ GitHub CLI is installed'));
|
|
53
|
+
if (!this.gitManager) {
|
|
54
|
+
throw new Error('GitManager not initialized');
|
|
55
|
+
}
|
|
56
|
+
this.gitManager.validateGitHubCliAuthentication();
|
|
57
|
+
console.log(chalk.green('✅ GitHub CLI is authenticated'));
|
|
58
|
+
const repoInfo = this.repositoryManager.getRepositoryInfo(this.workingDir);
|
|
59
|
+
console.log(chalk.blue(`📂 Working in: ${repoInfo.name} (${repoInfo.branch})`));
|
|
60
|
+
console.log('');
|
|
61
|
+
// Check for repository-specific instructions
|
|
62
|
+
this.repoInstructions = await this.configManager.getRepoInstructions(this.workingDir);
|
|
63
|
+
if (!this.repoInstructions) {
|
|
64
|
+
// Only prompt if the user hasn't previously declined for this repo
|
|
65
|
+
const hasDeclined = await this.configManager.hasDeclinedRepoInstructions(this.workingDir);
|
|
66
|
+
if (!hasDeclined) {
|
|
67
|
+
console.log(chalk.yellow('⚠️ No repository-specific instructions found for this repository.'));
|
|
68
|
+
const inquirer = (await import('inquirer')).default;
|
|
69
|
+
const { shouldSetInstructions } = await inquirer.prompt([
|
|
70
|
+
{
|
|
71
|
+
type: 'confirm',
|
|
72
|
+
name: 'shouldSetInstructions',
|
|
73
|
+
message: 'Would you like to set repository-specific instructions now?',
|
|
74
|
+
default: false
|
|
75
|
+
}
|
|
76
|
+
]);
|
|
77
|
+
if (shouldSetInstructions) {
|
|
78
|
+
this.repoInstructions = await this.configManager.promptForRepoInstructions(this.workingDir);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
// Mark that the user declined so we don't ask again
|
|
82
|
+
await this.configManager.markRepoInstructionsDeclined(this.workingDir);
|
|
83
|
+
console.log(chalk.gray('You can configure instructions later with: ivan edit-repo-instructions'));
|
|
84
|
+
}
|
|
85
|
+
console.log('');
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
console.log(chalk.green('✅ Repository-specific instructions loaded'));
|
|
90
|
+
}
|
|
91
|
+
const { tasks, prStrategy } = await this.jobManager.promptForTasks(this.workingDir);
|
|
92
|
+
console.log('');
|
|
93
|
+
// Check if these are address tasks
|
|
94
|
+
const addressTasks = tasks.filter(t => t.type === 'address');
|
|
95
|
+
const buildTasks = tasks.filter(t => t.type === 'build');
|
|
96
|
+
// Handle address tasks separately
|
|
97
|
+
if (addressTasks.length > 0) {
|
|
98
|
+
const addressExecutor = new AddressTaskExecutor();
|
|
99
|
+
await addressExecutor.executeAddressTasks(addressTasks);
|
|
100
|
+
}
|
|
101
|
+
// Handle build tasks
|
|
102
|
+
if (buildTasks.length > 0) {
|
|
103
|
+
// Ask about PR comment monitoring upfront
|
|
104
|
+
let shouldWaitForComments = false;
|
|
105
|
+
const inquirer = (await import('inquirer')).default;
|
|
106
|
+
const { waitForComments } = await inquirer.prompt([
|
|
107
|
+
{
|
|
108
|
+
type: 'confirm',
|
|
109
|
+
name: 'waitForComments',
|
|
110
|
+
message: 'After completing tasks, would you like to wait 30 minutes for PR reviews and automatically address any comments?',
|
|
111
|
+
default: false
|
|
112
|
+
}
|
|
113
|
+
]);
|
|
114
|
+
shouldWaitForComments = waitForComments;
|
|
115
|
+
// Ask if user wants to confirm before each task
|
|
116
|
+
let confirmBeforeEach = false;
|
|
117
|
+
if (buildTasks.length > 1) {
|
|
118
|
+
const { shouldConfirm } = await inquirer.prompt([
|
|
119
|
+
{
|
|
120
|
+
type: 'confirm',
|
|
121
|
+
name: 'shouldConfirm',
|
|
122
|
+
message: 'Would you like to confirm before executing each task?',
|
|
123
|
+
default: false
|
|
124
|
+
}
|
|
125
|
+
]);
|
|
126
|
+
confirmBeforeEach = shouldConfirm;
|
|
127
|
+
}
|
|
128
|
+
console.log(chalk.blue.bold('📋 Executing tasks...'));
|
|
129
|
+
// Handle single PR strategy
|
|
130
|
+
if (prStrategy === 'single' && buildTasks.length > 1) {
|
|
131
|
+
await this.executeTasksWithSinglePR(buildTasks, confirmBeforeEach);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
// Multiple PRs (default behavior)
|
|
135
|
+
for (let i = 0; i < buildTasks.length; i++) {
|
|
136
|
+
const task = buildTasks[i];
|
|
137
|
+
if (confirmBeforeEach) {
|
|
138
|
+
console.log('');
|
|
139
|
+
console.log(chalk.yellow(`Task ${i + 1} of ${buildTasks.length}: ${task.description}`));
|
|
140
|
+
const inquirer = (await import('inquirer')).default;
|
|
141
|
+
const { shouldExecute } = await inquirer.prompt([
|
|
142
|
+
{
|
|
143
|
+
type: 'confirm',
|
|
144
|
+
name: 'shouldExecute',
|
|
145
|
+
message: 'Execute this task?',
|
|
146
|
+
default: true
|
|
147
|
+
}
|
|
148
|
+
]);
|
|
149
|
+
if (!shouldExecute) {
|
|
150
|
+
console.log(chalk.gray('⏭️ Skipping task'));
|
|
151
|
+
await this.jobManager.updateTaskStatus(task.uuid, 'not_started');
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
await this.executeTask(task);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
console.log('');
|
|
159
|
+
console.log(chalk.green.bold('🎉 All initial tasks completed successfully!'));
|
|
160
|
+
// Collect PR URLs created during this session by reloading tasks from DB
|
|
161
|
+
const createdPRUrls = [];
|
|
162
|
+
for (const task of buildTasks) {
|
|
163
|
+
const updatedTask = await this.jobManager.getTask(task.uuid);
|
|
164
|
+
if (updatedTask && updatedTask.pr_link) {
|
|
165
|
+
createdPRUrls.push(updatedTask.pr_link);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (createdPRUrls.length > 0) {
|
|
169
|
+
// Show PRs created
|
|
170
|
+
console.log('');
|
|
171
|
+
console.log(chalk.blue('📋 PRs created:'));
|
|
172
|
+
createdPRUrls.forEach(url => console.log(chalk.cyan(` - ${url}`)));
|
|
173
|
+
if (shouldWaitForComments) {
|
|
174
|
+
// Wait 30 minutes for reviewers to comment
|
|
175
|
+
console.log('');
|
|
176
|
+
console.log(chalk.blue('⏰ Waiting 30 minutes for PR reviews...'));
|
|
177
|
+
console.log(chalk.gray('PRs being monitored:'));
|
|
178
|
+
createdPRUrls.forEach(url => console.log(chalk.gray(` - ${url}`)));
|
|
179
|
+
console.log('');
|
|
180
|
+
const waitTime = 30 * 60 * 1000; // 30 minutes in milliseconds
|
|
181
|
+
const startTime = Date.now();
|
|
182
|
+
const interval = setInterval(() => {
|
|
183
|
+
const elapsed = Date.now() - startTime;
|
|
184
|
+
const remaining = waitTime - elapsed;
|
|
185
|
+
const minutes = Math.floor(remaining / 60000);
|
|
186
|
+
const seconds = Math.floor((remaining % 60000) / 1000);
|
|
187
|
+
process.stdout.write(`\r${chalk.blue('⏰')} Time remaining: ${minutes}:${seconds.toString().padStart(2, '0')} `);
|
|
188
|
+
}, 1000);
|
|
189
|
+
await new Promise(resolve => setTimeout(resolve, waitTime));
|
|
190
|
+
clearInterval(interval);
|
|
191
|
+
console.log('\n');
|
|
192
|
+
// Check for unaddressed comments on created PRs
|
|
193
|
+
console.log(chalk.blue('🔍 Checking for PR review comments...'));
|
|
194
|
+
const prService = new PRService(this.workingDir);
|
|
195
|
+
const addressTasks = await this.checkAndCreateAddressTasks(createdPRUrls, prService);
|
|
196
|
+
if (addressTasks.length > 0) {
|
|
197
|
+
console.log('');
|
|
198
|
+
console.log(chalk.blue.bold(`📋 Found ${addressTasks.length} comments to address`));
|
|
199
|
+
// Execute address tasks automatically
|
|
200
|
+
const addressExecutor = new AddressTaskExecutor();
|
|
201
|
+
await addressExecutor.executeAddressTasks(addressTasks);
|
|
202
|
+
console.log('');
|
|
203
|
+
console.log(chalk.green.bold('🎉 All PR comments addressed successfully!'));
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
console.log(chalk.green('✨ No unaddressed comments found on PRs!'));
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
console.log(chalk.blue('💡 You can run "ivan address" later to handle any PR comments'));
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
console.error(chalk.red.bold('❌ Workflow failed:'), error);
|
|
217
|
+
throw error;
|
|
218
|
+
}
|
|
219
|
+
finally {
|
|
220
|
+
this.jobManager.close();
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
async checkAndCreateAddressTasks(prUrls, prService) {
|
|
224
|
+
const addressTasks = [];
|
|
225
|
+
for (const prUrl of prUrls) {
|
|
226
|
+
// Extract PR number from URL
|
|
227
|
+
const prMatch = prUrl.match(/\/pull\/(\d+)/);
|
|
228
|
+
if (!prMatch)
|
|
229
|
+
continue;
|
|
230
|
+
const prNumber = parseInt(prMatch[1]);
|
|
231
|
+
// Get unaddressed comments for this PR
|
|
232
|
+
const comments = await prService.getUnaddressedComments(prNumber);
|
|
233
|
+
if (comments.length > 0) {
|
|
234
|
+
// Get the branch name for this PR
|
|
235
|
+
if (!this.gitManager) {
|
|
236
|
+
throw new Error('GitManager not initialized');
|
|
237
|
+
}
|
|
238
|
+
const prInfo = await this.gitManager.getPRInfo(prNumber);
|
|
239
|
+
const branch = prInfo.headRefName;
|
|
240
|
+
// Create address tasks for each comment
|
|
241
|
+
for (const comment of comments) {
|
|
242
|
+
let description = `Address PR #${prNumber} comment from @${comment.author}: "${comment.body.substring(0, 100)}${comment.body.length > 100 ? '...' : ''}"`;
|
|
243
|
+
if (comment.path) {
|
|
244
|
+
description += ` (in ${comment.path}${comment.line ? `:${comment.line}` : ''})`;
|
|
245
|
+
}
|
|
246
|
+
// Get the current job ID from one of the build tasks
|
|
247
|
+
const jobUuid = this.jobManager.getCurrentJobUuid() || (await this.jobManager.getLatestJobId(this.workingDir));
|
|
248
|
+
// Create the address task
|
|
249
|
+
const taskUuid = await this.jobManager.createTask(jobUuid, description, 'address');
|
|
250
|
+
await this.jobManager.updateTaskBranch(taskUuid, branch);
|
|
251
|
+
const task = await this.jobManager.getTask(taskUuid);
|
|
252
|
+
if (task) {
|
|
253
|
+
addressTasks.push(task);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return addressTasks;
|
|
259
|
+
}
|
|
260
|
+
async executeTask(task) {
|
|
261
|
+
console.log('');
|
|
262
|
+
console.log(chalk.cyan.bold(`📝 Task: ${task.description}`));
|
|
263
|
+
let spinner = ora('Updating task status...').start();
|
|
264
|
+
let worktreePath = null;
|
|
265
|
+
let branchName = null;
|
|
266
|
+
try {
|
|
267
|
+
await this.jobManager.updateTaskStatus(task.uuid, 'active');
|
|
268
|
+
spinner.succeed('Task marked as active');
|
|
269
|
+
spinner = ora('Cleaning up and syncing with main branch...').start();
|
|
270
|
+
if (!this.gitManager) {
|
|
271
|
+
throw new Error('GitManager not initialized');
|
|
272
|
+
}
|
|
273
|
+
await this.gitManager.cleanupAndSyncMain();
|
|
274
|
+
spinner.succeed('Repository cleaned and synced with main');
|
|
275
|
+
if (!this.gitManager) {
|
|
276
|
+
throw new Error('GitManager not initialized');
|
|
277
|
+
}
|
|
278
|
+
branchName = this.gitManager.generateBranchName(task.description);
|
|
279
|
+
spinner = ora(`Creating worktree for branch: ${branchName}`).start();
|
|
280
|
+
worktreePath = await this.gitManager.createWorktree(branchName);
|
|
281
|
+
this.gitManager.switchToWorktree(worktreePath);
|
|
282
|
+
spinner.succeed(`Worktree created: ${worktreePath}`);
|
|
283
|
+
await this.jobManager.updateTaskBranch(task.uuid, branchName);
|
|
284
|
+
spinner = ora('Executing task with Claude Code...').start();
|
|
285
|
+
// Append repository-specific instructions to the task if they exist
|
|
286
|
+
let taskWithInstructions = task.description;
|
|
287
|
+
if (this.repoInstructions) {
|
|
288
|
+
taskWithInstructions = `${task.description}\n\nRepository-specific instructions:\n${this.repoInstructions}`;
|
|
289
|
+
}
|
|
290
|
+
// Use worktree path for Claude execution, falling back to workingDir if needed
|
|
291
|
+
const executionPath = worktreePath || this.workingDir;
|
|
292
|
+
const result = await this.getClaudeExecutor().executeTask(taskWithInstructions, executionPath);
|
|
293
|
+
spinner.succeed('Claude Code execution completed');
|
|
294
|
+
spinner = ora('Storing execution log...').start();
|
|
295
|
+
await this.jobManager.updateTaskExecutionLog(task.uuid, result.log);
|
|
296
|
+
spinner.succeed('Execution log stored');
|
|
297
|
+
if (!this.gitManager) {
|
|
298
|
+
throw new Error('GitManager not initialized');
|
|
299
|
+
}
|
|
300
|
+
const changedFiles = this.gitManager.getChangedFiles();
|
|
301
|
+
if (changedFiles.length === 0) {
|
|
302
|
+
console.log(chalk.yellow('⚠️ No changes detected, skipping commit and PR creation'));
|
|
303
|
+
await this.jobManager.updateTaskStatus(task.uuid, 'completed');
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
if (!this.gitManager) {
|
|
307
|
+
throw new Error('GitManager not initialized');
|
|
308
|
+
}
|
|
309
|
+
const diff = this.gitManager.getDiff();
|
|
310
|
+
spinner = ora('Generating commit message...').start();
|
|
311
|
+
const commitMessage = await this.getOpenAIService().generateCommitMessage(diff, changedFiles);
|
|
312
|
+
spinner.succeed(`Commit message generated: ${commitMessage}`);
|
|
313
|
+
spinner = ora('Committing changes...').start();
|
|
314
|
+
if (!this.gitManager) {
|
|
315
|
+
throw new Error('GitManager not initialized');
|
|
316
|
+
}
|
|
317
|
+
// Try to commit, handling pre-commit hook failures
|
|
318
|
+
let commitAttempts = 0;
|
|
319
|
+
const maxCommitAttempts = 3;
|
|
320
|
+
let commitSucceeded = false;
|
|
321
|
+
while (commitAttempts < maxCommitAttempts && !commitSucceeded) {
|
|
322
|
+
try {
|
|
323
|
+
await this.gitManager.commitChanges(commitMessage);
|
|
324
|
+
// Only show success message if spinner is running (first attempt)
|
|
325
|
+
if (commitAttempts === 0) {
|
|
326
|
+
spinner.succeed('Changes committed');
|
|
327
|
+
}
|
|
328
|
+
else if (spinner.isSpinning) {
|
|
329
|
+
spinner.succeed('Commit successful after retry');
|
|
330
|
+
}
|
|
331
|
+
commitSucceeded = true;
|
|
332
|
+
}
|
|
333
|
+
catch (commitError) {
|
|
334
|
+
commitAttempts++;
|
|
335
|
+
const errorMessage = commitError instanceof Error ? commitError.message : String(commitError);
|
|
336
|
+
// Check if this is a pre-commit hook failure
|
|
337
|
+
if (errorMessage.includes('pre-commit') && commitAttempts < maxCommitAttempts) {
|
|
338
|
+
spinner.fail(`Pre-commit hook failed (attempt ${commitAttempts}/${maxCommitAttempts})`);
|
|
339
|
+
console.log(chalk.yellow('🔧 Running Claude to fix pre-commit errors...'));
|
|
340
|
+
// Extract the error details from the commit error
|
|
341
|
+
const errorDetails = errorMessage;
|
|
342
|
+
// Prepare prompt for Claude to fix the errors
|
|
343
|
+
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.`;
|
|
344
|
+
spinner = ora('Running Claude to fix pre-commit errors...').start();
|
|
345
|
+
try {
|
|
346
|
+
// Run Claude to fix the errors
|
|
347
|
+
const fixResult = await this.getClaudeExecutor().executeTask(fixPrompt, worktreePath || this.workingDir);
|
|
348
|
+
spinner.succeed('Claude attempted to fix the errors');
|
|
349
|
+
// Update the execution log with the fix attempt
|
|
350
|
+
const previousLog = await this.jobManager.getTaskExecutionLog(task.uuid);
|
|
351
|
+
await this.jobManager.updateTaskExecutionLog(task.uuid, `${previousLog}\n\n--- Pre-commit Fix Attempt ${commitAttempts} ---\n${fixResult.log}`);
|
|
352
|
+
// Try to commit again on the next iteration
|
|
353
|
+
spinner = ora('Retrying commit...').start();
|
|
354
|
+
}
|
|
355
|
+
catch (fixError) {
|
|
356
|
+
spinner.fail('Failed to run Claude to fix errors');
|
|
357
|
+
console.error(chalk.red('Claude fix attempt failed:'), fixError);
|
|
358
|
+
throw commitError; // Re-throw the original error
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
// Not a pre-commit error or max attempts reached
|
|
363
|
+
throw commitError;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
if (!commitSucceeded) {
|
|
368
|
+
throw new Error(`Failed to commit after ${maxCommitAttempts} attempts due to pre-commit hook failures`);
|
|
369
|
+
}
|
|
370
|
+
spinner = ora('Pushing branch...').start();
|
|
371
|
+
if (!this.gitManager) {
|
|
372
|
+
throw new Error('GitManager not initialized');
|
|
373
|
+
}
|
|
374
|
+
await this.gitManager.pushBranch(branchName);
|
|
375
|
+
spinner.succeed('Branch pushed to origin');
|
|
376
|
+
spinner = ora('Generating PR description...').start();
|
|
377
|
+
const { title, body } = await this.getOpenAIService().generatePullRequestDescription(task.description, diff, changedFiles);
|
|
378
|
+
spinner.succeed('PR description generated');
|
|
379
|
+
spinner = ora('Creating pull request...').start();
|
|
380
|
+
if (!this.gitManager) {
|
|
381
|
+
throw new Error('GitManager not initialized');
|
|
382
|
+
}
|
|
383
|
+
const prUrl = await this.gitManager.createPullRequest(title, body);
|
|
384
|
+
spinner.succeed(`Pull request created: ${prUrl}`);
|
|
385
|
+
await this.jobManager.updateTaskPrLink(task.uuid, prUrl);
|
|
386
|
+
await this.jobManager.updateTaskStatus(task.uuid, 'completed');
|
|
387
|
+
console.log(chalk.green(`✅ Task completed: ${task.description}`));
|
|
388
|
+
console.log(chalk.cyan(`🔗 PR: ${prUrl}`));
|
|
389
|
+
}
|
|
390
|
+
catch (error) {
|
|
391
|
+
if (spinner.isSpinning) {
|
|
392
|
+
spinner.fail('Task execution failed');
|
|
393
|
+
}
|
|
394
|
+
// Store error log if execution failed
|
|
395
|
+
try {
|
|
396
|
+
const errorLog = error instanceof Error ? error.message : String(error);
|
|
397
|
+
await this.jobManager.updateTaskExecutionLog(task.uuid, `ERROR: ${errorLog}`);
|
|
398
|
+
console.log(chalk.gray('Error log stored to database'));
|
|
399
|
+
}
|
|
400
|
+
catch (logError) {
|
|
401
|
+
console.error(chalk.red('Failed to store error log:'), logError);
|
|
402
|
+
}
|
|
403
|
+
console.error(chalk.red(`❌ Failed to execute task: ${task.description}`), error);
|
|
404
|
+
throw error;
|
|
405
|
+
}
|
|
406
|
+
finally {
|
|
407
|
+
// Switch back to original directory and clean up worktree
|
|
408
|
+
if (this.gitManager && worktreePath && branchName) {
|
|
409
|
+
this.gitManager.switchToOriginalDir();
|
|
410
|
+
await this.gitManager.removeWorktree(branchName);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
async executeTasksWithSinglePR(tasks, confirmBeforeEach) {
|
|
415
|
+
console.log('');
|
|
416
|
+
console.log(chalk.blue('📦 Creating single branch for all tasks...'));
|
|
417
|
+
let spinner = ora('Cleaning up and syncing with main branch...').start();
|
|
418
|
+
let worktreePath = null;
|
|
419
|
+
let branchName = null;
|
|
420
|
+
let sessionId;
|
|
421
|
+
try {
|
|
422
|
+
if (!this.gitManager) {
|
|
423
|
+
throw new Error('GitManager not initialized');
|
|
424
|
+
}
|
|
425
|
+
await this.gitManager.cleanupAndSyncMain();
|
|
426
|
+
spinner.succeed('Repository cleaned and synced with main');
|
|
427
|
+
// Generate branch name based on all tasks
|
|
428
|
+
const combinedDescription = tasks.length === 1
|
|
429
|
+
? tasks[0].description
|
|
430
|
+
: `Multiple tasks: ${tasks.slice(0, 2).map(t => t.description).join(', ')}${tasks.length > 2 ? '...' : ''}`;
|
|
431
|
+
branchName = this.gitManager.generateBranchName(combinedDescription);
|
|
432
|
+
spinner = ora(`Creating worktree for branch: ${branchName}`).start();
|
|
433
|
+
worktreePath = await this.gitManager.createWorktree(branchName);
|
|
434
|
+
this.gitManager.switchToWorktree(worktreePath);
|
|
435
|
+
spinner.succeed(`Worktree created: ${worktreePath}`);
|
|
436
|
+
// Update all tasks with the same branch
|
|
437
|
+
for (const task of tasks) {
|
|
438
|
+
await this.jobManager.updateTaskBranch(task.uuid, branchName);
|
|
439
|
+
}
|
|
440
|
+
// Execute each task on the same branch
|
|
441
|
+
for (let i = 0; i < tasks.length; i++) {
|
|
442
|
+
const task = tasks[i];
|
|
443
|
+
if (confirmBeforeEach) {
|
|
444
|
+
console.log('');
|
|
445
|
+
console.log(chalk.yellow(`Task ${i + 1} of ${tasks.length}: ${task.description}`));
|
|
446
|
+
const inquirer = (await import('inquirer')).default;
|
|
447
|
+
const { shouldExecute } = await inquirer.prompt([
|
|
448
|
+
{
|
|
449
|
+
type: 'confirm',
|
|
450
|
+
name: 'shouldExecute',
|
|
451
|
+
message: 'Execute this task?',
|
|
452
|
+
default: true
|
|
453
|
+
}
|
|
454
|
+
]);
|
|
455
|
+
if (!shouldExecute) {
|
|
456
|
+
console.log(chalk.gray('⏭️ Skipping task'));
|
|
457
|
+
await this.jobManager.updateTaskStatus(task.uuid, 'not_started');
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
console.log('');
|
|
462
|
+
console.log(chalk.cyan.bold(`📝 Task ${i + 1}/${tasks.length}: ${task.description}`));
|
|
463
|
+
spinner = ora('Updating task status...').start();
|
|
464
|
+
await this.jobManager.updateTaskStatus(task.uuid, 'active');
|
|
465
|
+
spinner.succeed('Task marked as active');
|
|
466
|
+
spinner = ora('Executing task with Claude Code...').start();
|
|
467
|
+
// Append repository-specific instructions to the task if they exist
|
|
468
|
+
let taskWithInstructions = task.description;
|
|
469
|
+
if (this.repoInstructions) {
|
|
470
|
+
taskWithInstructions = `${task.description}\n\nRepository-specific instructions:\n${this.repoInstructions}`;
|
|
471
|
+
}
|
|
472
|
+
// Use worktree path for Claude execution
|
|
473
|
+
const executionPath = worktreePath || this.workingDir;
|
|
474
|
+
// Pass session ID to maintain context between tasks
|
|
475
|
+
const result = await this.getClaudeExecutor().executeTask(taskWithInstructions, executionPath, sessionId);
|
|
476
|
+
// Store the session ID for the next task
|
|
477
|
+
sessionId = result.sessionId;
|
|
478
|
+
spinner.succeed('Claude Code execution completed');
|
|
479
|
+
spinner = ora('Storing execution log...').start();
|
|
480
|
+
await this.jobManager.updateTaskExecutionLog(task.uuid, result.log);
|
|
481
|
+
spinner.succeed('Execution log stored');
|
|
482
|
+
// Commit changes after each task (but don't create PR yet)
|
|
483
|
+
if (!this.gitManager) {
|
|
484
|
+
throw new Error('GitManager not initialized');
|
|
485
|
+
}
|
|
486
|
+
const changedFiles = this.gitManager.getChangedFiles();
|
|
487
|
+
if (changedFiles.length > 0) {
|
|
488
|
+
const diff = this.gitManager.getDiff();
|
|
489
|
+
spinner = ora('Generating commit message...').start();
|
|
490
|
+
const commitMessage = await this.getOpenAIService().generateCommitMessage(diff, changedFiles);
|
|
491
|
+
spinner.succeed(`Commit message generated: ${commitMessage}`);
|
|
492
|
+
spinner = ora('Committing changes...').start();
|
|
493
|
+
// Try to commit, handling pre-commit hook failures
|
|
494
|
+
let commitAttempts = 0;
|
|
495
|
+
const maxCommitAttempts = 3;
|
|
496
|
+
let commitSucceeded = false;
|
|
497
|
+
while (commitAttempts < maxCommitAttempts && !commitSucceeded) {
|
|
498
|
+
try {
|
|
499
|
+
await this.gitManager.commitChanges(commitMessage);
|
|
500
|
+
// Only show success message if spinner is running (first attempt)
|
|
501
|
+
if (commitAttempts === 0) {
|
|
502
|
+
spinner.succeed('Changes committed');
|
|
503
|
+
}
|
|
504
|
+
else if (spinner.isSpinning) {
|
|
505
|
+
spinner.succeed('Commit successful after retry');
|
|
506
|
+
}
|
|
507
|
+
commitSucceeded = true;
|
|
508
|
+
}
|
|
509
|
+
catch (commitError) {
|
|
510
|
+
commitAttempts++;
|
|
511
|
+
const errorMessage = commitError instanceof Error ? commitError.message : String(commitError);
|
|
512
|
+
// Check if this is a pre-commit hook failure
|
|
513
|
+
if (errorMessage.includes('pre-commit') && commitAttempts < maxCommitAttempts) {
|
|
514
|
+
spinner.fail(`Pre-commit hook failed (attempt ${commitAttempts}/${maxCommitAttempts})`);
|
|
515
|
+
console.log(chalk.yellow('🔧 Running Claude to fix pre-commit errors...'));
|
|
516
|
+
// Extract the error details from the commit error
|
|
517
|
+
const errorDetails = errorMessage;
|
|
518
|
+
// Prepare prompt for Claude to fix the errors
|
|
519
|
+
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.`;
|
|
520
|
+
spinner = ora('Running Claude to fix pre-commit errors...').start();
|
|
521
|
+
try {
|
|
522
|
+
// Run Claude to fix the errors (pass session ID to maintain context)
|
|
523
|
+
const fixResult = await this.getClaudeExecutor().executeTask(fixPrompt, executionPath, sessionId);
|
|
524
|
+
// Update session ID
|
|
525
|
+
sessionId = fixResult.sessionId;
|
|
526
|
+
spinner.succeed('Claude attempted to fix the errors');
|
|
527
|
+
// Update the execution log with the fix attempt
|
|
528
|
+
const previousLog = await this.jobManager.getTaskExecutionLog(task.uuid);
|
|
529
|
+
await this.jobManager.updateTaskExecutionLog(task.uuid, `${previousLog}\n\n--- Pre-commit Fix Attempt ${commitAttempts} ---\n${fixResult.log}`);
|
|
530
|
+
// Try to commit again on the next iteration
|
|
531
|
+
spinner = ora('Retrying commit...').start();
|
|
532
|
+
}
|
|
533
|
+
catch (fixError) {
|
|
534
|
+
spinner.fail('Failed to run Claude to fix errors');
|
|
535
|
+
console.error(chalk.red('Claude fix attempt failed:'), fixError);
|
|
536
|
+
throw commitError; // Re-throw the original error
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
else {
|
|
540
|
+
// Not a pre-commit error or max attempts reached
|
|
541
|
+
throw commitError;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
if (!commitSucceeded) {
|
|
546
|
+
throw new Error(`Failed to commit after ${maxCommitAttempts} attempts due to pre-commit hook failures`);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
else {
|
|
550
|
+
console.log(chalk.yellow('⚠️ No changes detected for this task'));
|
|
551
|
+
}
|
|
552
|
+
await this.jobManager.updateTaskStatus(task.uuid, 'completed');
|
|
553
|
+
console.log(chalk.green(`✅ Task ${i + 1}/${tasks.length} completed`));
|
|
554
|
+
}
|
|
555
|
+
// After all tasks are complete, create a single PR
|
|
556
|
+
spinner = ora('Pushing branch...').start();
|
|
557
|
+
if (!this.gitManager) {
|
|
558
|
+
throw new Error('GitManager not initialized');
|
|
559
|
+
}
|
|
560
|
+
await this.gitManager.pushBranch(branchName);
|
|
561
|
+
spinner.succeed('Branch pushed to origin');
|
|
562
|
+
// Generate PR description based on all tasks
|
|
563
|
+
spinner = ora('Generating pull request description...').start();
|
|
564
|
+
const allTaskDescriptions = tasks.map(t => `- ${t.description}`).join('\n');
|
|
565
|
+
const prTaskDescription = `Completed ${tasks.length} tasks:\n\n${allTaskDescriptions}`;
|
|
566
|
+
// Get combined diff for PR description
|
|
567
|
+
const finalDiff = this.gitManager.getDiff('origin/main', 'HEAD');
|
|
568
|
+
const allChangedFiles = this.gitManager.getChangedFiles('origin/main');
|
|
569
|
+
const { title, body } = await this.getOpenAIService().generatePullRequestDescription(prTaskDescription, finalDiff, allChangedFiles);
|
|
570
|
+
spinner.succeed('PR description generated');
|
|
571
|
+
spinner = ora('Creating pull request...').start();
|
|
572
|
+
const prUrl = await this.gitManager.createPullRequest(title, body);
|
|
573
|
+
spinner.succeed(`Pull request created: ${prUrl}`);
|
|
574
|
+
// Update all tasks with the same PR link
|
|
575
|
+
for (const task of tasks) {
|
|
576
|
+
await this.jobManager.updateTaskPrLink(task.uuid, prUrl);
|
|
577
|
+
}
|
|
578
|
+
console.log('');
|
|
579
|
+
console.log(chalk.green.bold(`✅ All ${tasks.length} tasks completed in a single PR!`));
|
|
580
|
+
console.log(chalk.cyan(`🔗 PR: ${prUrl}`));
|
|
581
|
+
}
|
|
582
|
+
catch (error) {
|
|
583
|
+
if (spinner && spinner.isSpinning) {
|
|
584
|
+
spinner.fail('Batch task execution failed');
|
|
585
|
+
}
|
|
586
|
+
console.error(chalk.red('❌ Failed to execute tasks with single PR:'), error);
|
|
587
|
+
throw error;
|
|
588
|
+
}
|
|
589
|
+
finally {
|
|
590
|
+
// Switch back to original directory and clean up worktree
|
|
591
|
+
if (this.gitManager && worktreePath && branchName) {
|
|
592
|
+
this.gitManager.switchToOriginalDir();
|
|
593
|
+
await this.gitManager.removeWorktree(branchName);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
async executeNonInteractiveWorkflow(config) {
|
|
598
|
+
try {
|
|
599
|
+
console.log(chalk.blue.bold('🚀 Starting non-interactive workflow'));
|
|
600
|
+
console.log('');
|
|
601
|
+
console.log(chalk.blue('🔍 Validating dependencies...'));
|
|
602
|
+
await this.getClaudeExecutor().validateClaudeCodeInstallation();
|
|
603
|
+
console.log(chalk.green('✅ Claude Code SDK configured'));
|
|
604
|
+
this.workingDir = await this.repositoryManager.getValidWorkingDirectory();
|
|
605
|
+
this.gitManager = new GitManager(this.workingDir);
|
|
606
|
+
if (!this.gitManager) {
|
|
607
|
+
throw new Error('GitManager not initialized');
|
|
608
|
+
}
|
|
609
|
+
this.gitManager.validateGitHubCliInstallation();
|
|
610
|
+
console.log(chalk.green('✅ GitHub CLI is installed'));
|
|
611
|
+
if (!this.gitManager) {
|
|
612
|
+
throw new Error('GitManager not initialized');
|
|
613
|
+
}
|
|
614
|
+
this.gitManager.validateGitHubCliAuthentication();
|
|
615
|
+
console.log(chalk.green('✅ GitHub CLI is authenticated'));
|
|
616
|
+
const repoInfo = this.repositoryManager.getRepositoryInfo(this.workingDir);
|
|
617
|
+
console.log(chalk.blue(`📂 Working in: ${repoInfo.name} (${repoInfo.branch})`));
|
|
618
|
+
console.log('');
|
|
619
|
+
// Load repository-specific instructions
|
|
620
|
+
this.repoInstructions = await this.configManager.getRepoInstructions(this.workingDir);
|
|
621
|
+
if (this.repoInstructions) {
|
|
622
|
+
console.log(chalk.green('✅ Repository-specific instructions loaded'));
|
|
623
|
+
}
|
|
624
|
+
// Process tasks based on config
|
|
625
|
+
let finalTasks = config.tasks;
|
|
626
|
+
const prStrategy = config.prStrategy || 'multiple';
|
|
627
|
+
// If single task and generateSubtasks is true, break it down
|
|
628
|
+
if (config.tasks.length === 1 && config.generateSubtasks) {
|
|
629
|
+
console.log(chalk.blue('🔍 Generating subtasks...'));
|
|
630
|
+
finalTasks = await this.jobManager.generateTaskBreakdownWithClaude(config.tasks[0], this.workingDir);
|
|
631
|
+
console.log(chalk.green(`✅ Generated ${finalTasks.length} subtasks`));
|
|
632
|
+
}
|
|
633
|
+
// Create job and tasks
|
|
634
|
+
const jobDescription = config.tasks.length === 1 ? config.tasks[0] : `Multiple tasks: ${config.tasks.slice(0, 2).join(', ')}${config.tasks.length > 2 ? '...' : ''}`;
|
|
635
|
+
const jobUuid = await this.jobManager.createJob(jobDescription, this.workingDir);
|
|
636
|
+
const tasks = [];
|
|
637
|
+
for (const taskDescription of finalTasks) {
|
|
638
|
+
const taskUuid = await this.jobManager.createTask(jobUuid, taskDescription, 'build');
|
|
639
|
+
const task = await this.jobManager.getTask(taskUuid);
|
|
640
|
+
if (task) {
|
|
641
|
+
tasks.push(task);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
console.log('');
|
|
645
|
+
console.log(chalk.blue.bold(`📋 Executing ${tasks.length} task(s)...`));
|
|
646
|
+
const shouldWaitForComments = config.waitForComments || false;
|
|
647
|
+
// Execute tasks based on PR strategy
|
|
648
|
+
if (prStrategy === 'single' && tasks.length > 1) {
|
|
649
|
+
await this.executeTasksWithSinglePR(tasks, false);
|
|
650
|
+
}
|
|
651
|
+
else {
|
|
652
|
+
for (const task of tasks) {
|
|
653
|
+
await this.executeTask(task);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
console.log('');
|
|
657
|
+
console.log(chalk.green.bold('🎉 All initial tasks completed successfully!'));
|
|
658
|
+
// Collect PR URLs
|
|
659
|
+
const createdPRUrls = [];
|
|
660
|
+
for (const task of tasks) {
|
|
661
|
+
const updatedTask = await this.jobManager.getTask(task.uuid);
|
|
662
|
+
if (updatedTask && updatedTask.pr_link) {
|
|
663
|
+
createdPRUrls.push(updatedTask.pr_link);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
if (createdPRUrls.length > 0) {
|
|
667
|
+
console.log('');
|
|
668
|
+
console.log(chalk.blue('📋 PRs created:'));
|
|
669
|
+
createdPRUrls.forEach(url => console.log(chalk.cyan(` - ${url}`)));
|
|
670
|
+
if (shouldWaitForComments) {
|
|
671
|
+
// Wait 30 minutes for reviewers to comment
|
|
672
|
+
console.log('');
|
|
673
|
+
console.log(chalk.blue('⏰ Waiting 30 minutes for PR reviews...'));
|
|
674
|
+
console.log(chalk.gray('PRs being monitored:'));
|
|
675
|
+
createdPRUrls.forEach(url => console.log(chalk.gray(` - ${url}`)));
|
|
676
|
+
console.log('');
|
|
677
|
+
const waitTime = 30 * 60 * 1000; // 30 minutes in milliseconds
|
|
678
|
+
const startTime = Date.now();
|
|
679
|
+
const interval = setInterval(() => {
|
|
680
|
+
const elapsed = Date.now() - startTime;
|
|
681
|
+
const remaining = waitTime - elapsed;
|
|
682
|
+
const minutes = Math.floor(remaining / 60000);
|
|
683
|
+
const seconds = Math.floor((remaining % 60000) / 1000);
|
|
684
|
+
process.stdout.write(`\r${chalk.blue('⏰')} Time remaining: ${minutes}:${seconds.toString().padStart(2, '0')} `);
|
|
685
|
+
}, 1000);
|
|
686
|
+
await new Promise(resolve => setTimeout(resolve, waitTime));
|
|
687
|
+
clearInterval(interval);
|
|
688
|
+
console.log('\n');
|
|
689
|
+
// Check for unaddressed comments on created PRs
|
|
690
|
+
console.log(chalk.blue('🔍 Checking for PR review comments...'));
|
|
691
|
+
const prService = new PRService(this.workingDir);
|
|
692
|
+
const addressTasks = await this.checkAndCreateAddressTasks(createdPRUrls, prService);
|
|
693
|
+
if (addressTasks.length > 0) {
|
|
694
|
+
console.log('');
|
|
695
|
+
console.log(chalk.blue.bold(`📋 Found ${addressTasks.length} comments to address`));
|
|
696
|
+
// Execute address tasks automatically
|
|
697
|
+
const addressExecutor = new AddressTaskExecutor();
|
|
698
|
+
await addressExecutor.executeAddressTasks(addressTasks);
|
|
699
|
+
console.log('');
|
|
700
|
+
console.log(chalk.green.bold('🎉 All PR comments addressed successfully!'));
|
|
701
|
+
}
|
|
702
|
+
else {
|
|
703
|
+
console.log(chalk.green('✨ No unaddressed comments found on PRs!'));
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
catch (error) {
|
|
709
|
+
console.error(chalk.red.bold('❌ Non-interactive workflow failed:'), error);
|
|
710
|
+
throw error;
|
|
711
|
+
}
|
|
712
|
+
finally {
|
|
713
|
+
this.jobManager.close();
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
//# sourceMappingURL=task-executor.js.map
|