task-o-matic 0.0.12 → 0.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/init.js +41 -8
- package/dist/commands/tasks/create.d.ts.map +1 -1
- package/dist/commands/tasks/create.js +6 -13
- package/dist/commands/tasks/document/add.d.ts +3 -0
- package/dist/commands/tasks/document/add.d.ts.map +1 -0
- package/dist/commands/tasks/document/add.js +35 -0
- package/dist/commands/tasks/document/analyze.d.ts +3 -0
- package/dist/commands/tasks/document/analyze.d.ts.map +1 -0
- package/dist/commands/tasks/document/analyze.js +49 -0
- package/dist/commands/tasks/document/get.d.ts +3 -0
- package/dist/commands/tasks/document/get.d.ts.map +1 -0
- package/dist/commands/tasks/document/get.js +29 -0
- package/dist/commands/tasks/document/index.d.ts +8 -0
- package/dist/commands/tasks/document/index.d.ts.map +1 -0
- package/dist/commands/tasks/document/index.js +13 -0
- package/dist/commands/tasks/enhance.d.ts.map +1 -1
- package/dist/commands/tasks/enhance.js +64 -61
- package/dist/commands/tasks/execute-loop.d.ts.map +1 -1
- package/dist/commands/tasks/execute-loop.js +64 -90
- package/dist/commands/tasks/execute.d.ts.map +1 -1
- package/dist/commands/tasks/execute.js +52 -16
- package/dist/commands/tasks/list.js +2 -2
- package/dist/commands/tasks/next.js +4 -4
- package/dist/commands/tasks/plan/create.d.ts +3 -0
- package/dist/commands/tasks/plan/create.d.ts.map +1 -0
- package/dist/commands/tasks/plan/create.js +37 -0
- package/dist/commands/tasks/plan/delete.d.ts +3 -0
- package/dist/commands/tasks/plan/delete.d.ts.map +1 -0
- package/dist/commands/tasks/plan/delete.js +14 -0
- package/dist/commands/tasks/plan/get.d.ts +3 -0
- package/dist/commands/tasks/plan/get.d.ts.map +1 -0
- package/dist/commands/tasks/plan/get.js +24 -0
- package/dist/commands/tasks/plan/index.d.ts +10 -0
- package/dist/commands/tasks/plan/index.d.ts.map +1 -0
- package/dist/commands/tasks/plan/index.js +17 -0
- package/dist/commands/tasks/plan/list.d.ts +3 -0
- package/dist/commands/tasks/plan/list.d.ts.map +1 -0
- package/dist/commands/tasks/plan/list.js +21 -0
- package/dist/commands/tasks/plan/set.d.ts +3 -0
- package/dist/commands/tasks/plan/set.d.ts.map +1 -0
- package/dist/commands/tasks/plan/set.js +33 -0
- package/dist/commands/tasks/split.d.ts.map +1 -1
- package/dist/commands/tasks/split.js +65 -60
- package/dist/commands/tasks/status.js +2 -2
- package/dist/lib/ai-service/ai-operations.d.ts +1 -1
- package/dist/lib/ai-service/ai-operations.d.ts.map +1 -1
- package/dist/lib/ai-service/base-operations.d.ts +22 -0
- package/dist/lib/ai-service/base-operations.d.ts.map +1 -1
- package/dist/lib/ai-service/base-operations.js +29 -1
- package/dist/lib/ai-service/task-operations.d.ts +1 -1
- package/dist/lib/ai-service/task-operations.d.ts.map +1 -1
- package/dist/lib/better-t-stack-cli.d.ts +36 -21
- package/dist/lib/better-t-stack-cli.d.ts.map +1 -1
- package/dist/lib/better-t-stack-cli.js +212 -33
- package/dist/lib/bootstrap/cli-bootstrap.d.ts +14 -0
- package/dist/lib/bootstrap/cli-bootstrap.d.ts.map +1 -0
- package/dist/lib/bootstrap/cli-bootstrap.js +325 -0
- package/dist/lib/bootstrap/index.d.ts +4 -0
- package/dist/lib/bootstrap/index.d.ts.map +1 -0
- package/dist/lib/bootstrap/index.js +19 -0
- package/dist/lib/bootstrap/medusa-bootstrap.d.ts +14 -0
- package/dist/lib/bootstrap/medusa-bootstrap.d.ts.map +1 -0
- package/dist/lib/bootstrap/medusa-bootstrap.js +218 -0
- package/dist/lib/bootstrap/opentui-bootstrap.d.ts +11 -0
- package/dist/lib/bootstrap/opentui-bootstrap.d.ts.map +1 -0
- package/dist/lib/bootstrap/opentui-bootstrap.js +342 -0
- package/dist/lib/config.d.ts +14 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +18 -0
- package/dist/lib/git-utils.d.ts +45 -0
- package/dist/lib/git-utils.d.ts.map +1 -0
- package/dist/lib/git-utils.js +160 -0
- package/dist/lib/task-execution-core.d.ts +7 -0
- package/dist/lib/task-execution-core.d.ts.map +1 -0
- package/dist/lib/task-execution-core.js +360 -0
- package/dist/lib/task-execution.d.ts +4 -0
- package/dist/lib/task-execution.d.ts.map +1 -1
- package/dist/lib/task-execution.js +31 -149
- package/dist/lib/task-loop-execution.d.ts +1 -19
- package/dist/lib/task-loop-execution.d.ts.map +1 -1
- package/dist/lib/task-loop-execution.js +50 -585
- package/dist/lib/task-planning.d.ts +28 -0
- package/dist/lib/task-planning.d.ts.map +1 -0
- package/dist/lib/task-planning.js +109 -0
- package/dist/lib/task-review.d.ts +27 -0
- package/dist/lib/task-review.d.ts.map +1 -0
- package/dist/lib/task-review.js +106 -0
- package/dist/lib/validation.d.ts +20 -3
- package/dist/lib/validation.d.ts.map +1 -1
- package/dist/lib/validation.js +39 -10
- package/dist/services/prd.d.ts.map +1 -1
- package/dist/services/prd.js +18 -45
- package/dist/services/tasks.d.ts +2 -2
- package/dist/services/tasks.d.ts.map +1 -1
- package/dist/services/tasks.js +55 -85
- package/dist/test/task-loop-git.test.js +6 -6
- package/dist/types/cli-options.d.ts +138 -0
- package/dist/types/cli-options.d.ts.map +1 -0
- package/dist/types/cli-options.js +6 -0
- package/dist/types/index.d.ts +74 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/results.d.ts +60 -6
- package/dist/types/results.d.ts.map +1 -1
- package/dist/utils/bulk-operations.d.ts +51 -0
- package/dist/utils/bulk-operations.d.ts.map +1 -0
- package/dist/utils/bulk-operations.js +68 -0
- package/dist/utils/cli-validators.d.ts +54 -0
- package/dist/utils/cli-validators.d.ts.map +1 -0
- package/dist/utils/cli-validators.js +75 -0
- package/dist/utils/command-error-handler.d.ts +32 -0
- package/dist/utils/command-error-handler.d.ts.map +1 -0
- package/dist/utils/command-error-handler.js +52 -0
- package/dist/utils/confirmation.d.ts +19 -0
- package/dist/utils/confirmation.d.ts.map +1 -0
- package/dist/utils/confirmation.js +39 -0
- package/dist/utils/display-helpers.d.ts +81 -0
- package/dist/utils/display-helpers.d.ts.map +1 -0
- package/dist/utils/display-helpers.js +109 -0
- package/dist/utils/error-utils.d.ts +70 -0
- package/dist/utils/error-utils.d.ts.map +1 -0
- package/dist/utils/error-utils.js +103 -0
- package/dist/utils/file-utils.d.ts +49 -0
- package/dist/utils/file-utils.d.ts.map +1 -0
- package/dist/utils/file-utils.js +77 -0
- package/dist/utils/id-generator.d.ts +92 -0
- package/dist/utils/id-generator.d.ts.map +1 -0
- package/dist/utils/id-generator.js +140 -0
- package/dist/utils/model-executor-parser.d.ts +38 -0
- package/dist/utils/model-executor-parser.d.ts.map +1 -0
- package/dist/utils/model-executor-parser.js +67 -0
- package/dist/utils/progress-tracking.d.ts +28 -0
- package/dist/utils/progress-tracking.d.ts.map +1 -0
- package/dist/utils/progress-tracking.js +43 -0
- package/dist/utils/stack-formatter.d.ts +2 -1
- package/dist/utils/stack-formatter.d.ts.map +1 -1
- package/dist/utils/stack-formatter.js +8 -2
- package/dist/utils/storage-utils.d.ts +49 -0
- package/dist/utils/storage-utils.d.ts.map +1 -0
- package/dist/utils/storage-utils.js +79 -0
- package/dist/utils/streaming-utils.d.ts +38 -0
- package/dist/utils/streaming-utils.d.ts.map +1 -0
- package/dist/utils/streaming-utils.js +56 -0
- package/docs/agents/cli.md +58 -149
- package/package.json +1 -1
- package/dist/commands/tasks/document.d.ts +0 -5
- package/dist/commands/tasks/document.d.ts.map +0 -1
- package/dist/commands/tasks/document.js +0 -118
- package/dist/commands/tasks/plan.d.ts +0 -7
- package/dist/commands/tasks/plan.d.ts.map +0 -1
- package/dist/commands/tasks/plan.js +0 -131
|
@@ -3,559 +3,18 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.extractCommitInfo = extractCommitInfo;
|
|
7
6
|
exports.executeTaskLoop = executeTaskLoop;
|
|
8
7
|
const tasks_1 = require("../services/tasks");
|
|
9
|
-
const
|
|
10
|
-
const ai_service_factory_1 = require("../utils/ai-service-factory");
|
|
11
|
-
const hooks_1 = require("./hooks");
|
|
12
|
-
const prompt_builder_1 = require("./prompt-builder");
|
|
8
|
+
const task_execution_core_1 = require("./task-execution-core");
|
|
13
9
|
const chalk_1 = __importDefault(require("chalk"));
|
|
14
|
-
const child_process_1 = require("child_process");
|
|
15
|
-
const util_1 = require("util");
|
|
16
|
-
const ai_service_factory_2 = require("../utils/ai-service-factory");
|
|
17
|
-
const fs_1 = require("fs");
|
|
18
|
-
const inquirer_1 = __importDefault(require("inquirer"));
|
|
19
|
-
const execAsync = (0, util_1.promisify)(child_process_1.exec);
|
|
20
|
-
/**
|
|
21
|
-
* Extract commit message and file list from AI conversation/output
|
|
22
|
-
* This function analyzes the executor's work and generates appropriate commit info
|
|
23
|
-
*/
|
|
24
|
-
/**
|
|
25
|
-
* Extract commit message and file list from git state
|
|
26
|
-
* This function analyzes the actual git state to generate appropriate commit info
|
|
27
|
-
*/
|
|
28
|
-
async function extractCommitInfo(taskId, taskTitle, executionMessage, gitState, execFn = execAsync, aiOps = (0, ai_service_factory_2.getAIOperations)()) {
|
|
29
|
-
try {
|
|
30
|
-
// Case 1: Executor created a commit
|
|
31
|
-
if (gitState.beforeHead !== gitState.afterHead) {
|
|
32
|
-
console.log(chalk_1.default.blue("📝 Executor created a commit, extracting info..."));
|
|
33
|
-
const { stdout } = await execFn(`git show --stat --format="%s%n%b" ${gitState.afterHead}`);
|
|
34
|
-
const lines = stdout.trim().split("\n");
|
|
35
|
-
const message = lines[0].trim();
|
|
36
|
-
// Parse files from stat output (e.g. " src/file.ts | 10 +")
|
|
37
|
-
const files = lines
|
|
38
|
-
.slice(1)
|
|
39
|
-
.filter((line) => line.includes("|"))
|
|
40
|
-
.map((line) => line.split("|")[0].trim());
|
|
41
|
-
return {
|
|
42
|
-
message,
|
|
43
|
-
files,
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
// Case 2: Executor left uncommitted changes
|
|
47
|
-
if (gitState.hasUncommittedChanges) {
|
|
48
|
-
console.log(chalk_1.default.blue("📝 Uncommitted changes detected, generating commit message..."));
|
|
49
|
-
// Get the diff to send to AI
|
|
50
|
-
const { stdout: diff } = await execFn("git diff HEAD");
|
|
51
|
-
// Get list of changed files
|
|
52
|
-
const { stdout: status } = await execFn("git status --porcelain");
|
|
53
|
-
const files = status
|
|
54
|
-
.split("\n")
|
|
55
|
-
.filter((line) => line.length > 0)
|
|
56
|
-
.map((line) => line.substring(3).trim())
|
|
57
|
-
.filter((file) => file.length > 0);
|
|
58
|
-
// Use AI to generate commit message based on the diff
|
|
59
|
-
// const aiOps = getAIOperations(); // Injected
|
|
60
|
-
const prompt = `Based on the following git diff, generate a concise git commit message.
|
|
61
|
-
|
|
62
|
-
Task: ${taskTitle}
|
|
63
|
-
|
|
64
|
-
Git Diff:
|
|
65
|
-
${diff.substring(0, 10000)} // Limit diff size
|
|
66
|
-
|
|
67
|
-
Please respond in JSON format:
|
|
68
|
-
{
|
|
69
|
-
"message": "concise commit message following conventional commits format"
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
The commit message should:
|
|
73
|
-
- Follow conventional commits format (feat:, fix:, refactor:, etc.)
|
|
74
|
-
- Be concise and descriptive
|
|
75
|
-
- Focus on what changed
|
|
76
|
-
`;
|
|
77
|
-
const response = await aiOps.streamText(prompt, undefined, "You are a helpful assistant that generates git commit messages.");
|
|
78
|
-
// Try to parse JSON from response
|
|
79
|
-
const jsonMatch = response.match(/\{[\s\S]*\}/);
|
|
80
|
-
let message = `feat: complete task ${taskTitle}`;
|
|
81
|
-
if (jsonMatch) {
|
|
82
|
-
try {
|
|
83
|
-
const parsed = JSON.parse(jsonMatch[0]);
|
|
84
|
-
if (parsed.message) {
|
|
85
|
-
message = parsed.message;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
catch (e) {
|
|
89
|
-
// Ignore parse error
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
return {
|
|
93
|
-
message,
|
|
94
|
-
files,
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
// Case 3: No changes detected
|
|
98
|
-
return {
|
|
99
|
-
message: `feat: complete task ${taskTitle}`,
|
|
100
|
-
files: [],
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
catch (error) {
|
|
104
|
-
console.warn(chalk_1.default.yellow(`⚠️ Failed to extract commit info: ${error instanceof Error ? error.message : "Unknown error"}`));
|
|
105
|
-
// Fallback commit info
|
|
106
|
-
return {
|
|
107
|
-
message: `feat: complete task ${taskTitle}`,
|
|
108
|
-
files: [],
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* Run verification commands and return results
|
|
114
|
-
*/
|
|
115
|
-
async function runVerificationCommands(commands, dry) {
|
|
116
|
-
const results = [];
|
|
117
|
-
if (dry) {
|
|
118
|
-
console.log(chalk_1.default.yellow("🔍 DRY RUN - Verification commands:"));
|
|
119
|
-
commands.forEach((cmd) => {
|
|
120
|
-
console.log(chalk_1.default.cyan(` ${cmd}`));
|
|
121
|
-
results.push({
|
|
122
|
-
command: cmd,
|
|
123
|
-
success: true,
|
|
124
|
-
output: "DRY RUN - not executed",
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
return results;
|
|
128
|
-
}
|
|
129
|
-
for (const command of commands) {
|
|
130
|
-
console.log(chalk_1.default.blue(`🧪 Running verification: ${command}`));
|
|
131
|
-
try {
|
|
132
|
-
const { stdout, stderr } = await execAsync(command);
|
|
133
|
-
console.log(chalk_1.default.green(`✅ Verification passed: ${command}`));
|
|
134
|
-
results.push({
|
|
135
|
-
command,
|
|
136
|
-
success: true,
|
|
137
|
-
output: stdout.trim(),
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
catch (error) {
|
|
141
|
-
console.error(chalk_1.default.red(`❌ Verification failed: ${command}`));
|
|
142
|
-
const errorOutput = error.stderr || error.stdout || error.message;
|
|
143
|
-
console.error(chalk_1.default.red(` Error: ${errorOutput}`));
|
|
144
|
-
results.push({
|
|
145
|
-
command,
|
|
146
|
-
success: false,
|
|
147
|
-
error: errorOutput,
|
|
148
|
-
});
|
|
149
|
-
// Return early on first failure
|
|
150
|
-
break;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
return results;
|
|
154
|
-
}
|
|
155
|
-
/**
|
|
156
|
-
* Execute a single task with retry logic and error correction
|
|
157
|
-
*/
|
|
158
|
-
async function executeTaskWithRetry(task, tool, config, dry) {
|
|
159
|
-
const { maxRetries = 3, verificationCommands = [], tryModels, plan, planModel, reviewPlan, review, reviewModel, autoCommit, } = config;
|
|
160
|
-
const attempts = [];
|
|
161
|
-
let currentAttempt = 1;
|
|
162
|
-
let lastError;
|
|
163
|
-
let planContent;
|
|
164
|
-
// ----------------------------------------------------------------------
|
|
165
|
-
// PLANNING PHASE
|
|
166
|
-
// ----------------------------------------------------------------------
|
|
167
|
-
if (plan) {
|
|
168
|
-
console.log(chalk_1.default.blue.bold(`\n🧠 Starting Planning Phase for Task: ${task.title}`));
|
|
169
|
-
const planFileName = `task-${task.id}-plan.md`;
|
|
170
|
-
const planExecutor = planModel
|
|
171
|
-
? planModel.split(":")[0]
|
|
172
|
-
: tool;
|
|
173
|
-
const planModelName = planModel ? planModel.split(":")[1] : undefined;
|
|
174
|
-
let planningPrompt = `You are a senior software architect. Analyze the following task and create a detailed implementation plan.
|
|
175
|
-
|
|
176
|
-
Task: ${task.title}
|
|
177
|
-
Description: ${task.description || "No description provided."}
|
|
178
|
-
|
|
179
|
-
Requirements:
|
|
180
|
-
1. Analyze the task requirements.
|
|
181
|
-
2. Create a detailed step-by-step implementation plan.
|
|
182
|
-
3. Identify necessary file changes.
|
|
183
|
-
4. Write this plan to a file named "${planFileName}" in the current directory.
|
|
184
|
-
5. Do NOT implement the code yet, just create the plan file.
|
|
185
|
-
|
|
186
|
-
Please create the "${planFileName}" file now.`;
|
|
187
|
-
console.log(chalk_1.default.cyan(` Using executor for planning: ${planExecutor}${planModelName ? ` (${planModelName})` : ""}`));
|
|
188
|
-
// Create executor for planning
|
|
189
|
-
const planningConfig = {
|
|
190
|
-
model: planModelName,
|
|
191
|
-
continueLastSession: false,
|
|
192
|
-
};
|
|
193
|
-
const executor = executor_factory_1.ExecutorFactory.create(planExecutor, planningConfig);
|
|
194
|
-
try {
|
|
195
|
-
let planningComplete = false;
|
|
196
|
-
while (!planningComplete) {
|
|
197
|
-
await executor.execute(planningPrompt, dry, planningConfig);
|
|
198
|
-
if (!dry) {
|
|
199
|
-
// Verify plan file exists and read it
|
|
200
|
-
if ((0, fs_1.existsSync)(planFileName)) {
|
|
201
|
-
planContent = (0, fs_1.readFileSync)(planFileName, "utf-8");
|
|
202
|
-
console.log(chalk_1.default.green(`✅ Plan created successfully: ${planFileName}`));
|
|
203
|
-
// Human Review Loop
|
|
204
|
-
if (reviewPlan) {
|
|
205
|
-
console.log(chalk_1.default.yellow(`\n👀 Pausing for Human Review of the Plan: ${planFileName}`));
|
|
206
|
-
console.log(chalk_1.default.cyan("You can edit the file now."));
|
|
207
|
-
const { feedback } = await inquirer_1.default.prompt([
|
|
208
|
-
{
|
|
209
|
-
type: "input",
|
|
210
|
-
name: "feedback",
|
|
211
|
-
message: "Enter feedback to refine the plan (or press Enter to approve and continue):",
|
|
212
|
-
},
|
|
213
|
-
]);
|
|
214
|
-
if (feedback && feedback.trim() !== "") {
|
|
215
|
-
console.log(chalk_1.default.blue("🔄 Refining plan based on feedback..."));
|
|
216
|
-
planningPrompt = `The user provided the following feedback on the plan you just created:
|
|
217
|
-
|
|
218
|
-
"${feedback}"
|
|
219
|
-
|
|
220
|
-
Please update the plan file "${planFileName}" to incorporate this feedback.`;
|
|
221
|
-
// Continue loop to regenerate plan
|
|
222
|
-
continue;
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
// Auto-commit plan if enabled (only after approval)
|
|
226
|
-
if (autoCommit) {
|
|
227
|
-
try {
|
|
228
|
-
console.log(chalk_1.default.blue(`📦 Staging plan file: ${planFileName}`));
|
|
229
|
-
await execAsync(`git add ${planFileName}`);
|
|
230
|
-
await execAsync(`git commit -m "docs: create implementation plan for task ${task.id}"`);
|
|
231
|
-
console.log(chalk_1.default.green("✅ Plan committed successfully"));
|
|
232
|
-
}
|
|
233
|
-
catch (e) {
|
|
234
|
-
console.warn(chalk_1.default.yellow(`⚠️ Failed to commit plan: ${e instanceof Error ? e.message : "Unknown error"}`));
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
planningComplete = true;
|
|
238
|
-
}
|
|
239
|
-
else {
|
|
240
|
-
console.warn(chalk_1.default.yellow(`⚠️ Plan file ${planFileName} was not created by the executor.`));
|
|
241
|
-
planningComplete = true; // Exit loop to avoid infinite retry if file not created
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
else {
|
|
245
|
-
planningComplete = true; // Dry run
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
catch (error) {
|
|
250
|
-
console.error(chalk_1.default.red(`❌ Planning phase failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
// ----------------------------------------------------------------------
|
|
254
|
-
// EXECUTION PHASE
|
|
255
|
-
// ----------------------------------------------------------------------
|
|
256
|
-
while (currentAttempt <= maxRetries) {
|
|
257
|
-
// Determine which executor and model to use for this attempt
|
|
258
|
-
let currentExecutor = tool;
|
|
259
|
-
let currentModel;
|
|
260
|
-
if (tryModels && tryModels.length > 0) {
|
|
261
|
-
// Use the model config for this attempt (or last one if we've exceeded the list)
|
|
262
|
-
const modelConfigIndex = Math.min(currentAttempt - 1, tryModels.length - 1);
|
|
263
|
-
const modelConfig = tryModels[modelConfigIndex];
|
|
264
|
-
// Override executor if specified
|
|
265
|
-
if (modelConfig.executor) {
|
|
266
|
-
currentExecutor = modelConfig.executor;
|
|
267
|
-
}
|
|
268
|
-
// Store model name if specified
|
|
269
|
-
if (modelConfig.model) {
|
|
270
|
-
currentModel = modelConfig.model;
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
console.log(chalk_1.default.blue(`\n🎯 Attempt ${currentAttempt}/${maxRetries} for task: ${task.title} (${task.id})`));
|
|
274
|
-
if (currentModel) {
|
|
275
|
-
console.log(chalk_1.default.cyan(` Using executor: ${currentExecutor} with model: ${currentModel}`));
|
|
276
|
-
}
|
|
277
|
-
else {
|
|
278
|
-
console.log(chalk_1.default.cyan(` Using executor: ${currentExecutor}`));
|
|
279
|
-
}
|
|
280
|
-
const attemptStartTime = Date.now();
|
|
281
|
-
// Capture git state before execution
|
|
282
|
-
let beforeHead = "";
|
|
283
|
-
try {
|
|
284
|
-
const { stdout } = await execAsync("git rev-parse HEAD");
|
|
285
|
-
beforeHead = stdout.trim();
|
|
286
|
-
}
|
|
287
|
-
catch (e) {
|
|
288
|
-
// Git might not be initialized or no commits yet
|
|
289
|
-
}
|
|
290
|
-
try {
|
|
291
|
-
// Build execution message with context
|
|
292
|
-
const contextBuilder = (0, ai_service_factory_1.getContextBuilder)();
|
|
293
|
-
const taskContext = await contextBuilder.buildContext(task.id);
|
|
294
|
-
// Build retry context if this is a retry attempt
|
|
295
|
-
let retryContext = "";
|
|
296
|
-
if (currentAttempt > 1 && lastError) {
|
|
297
|
-
const retryParts = [];
|
|
298
|
-
retryParts.push(`# RETRY ATTEMPT ${currentAttempt}/${maxRetries}\n\n`);
|
|
299
|
-
// Add model escalation context
|
|
300
|
-
if (currentModel) {
|
|
301
|
-
retryParts.push(`**Note**: You are ${currentExecutor} using the ${currentModel} model. This is a more capable model than the previous attempt.\n\n`);
|
|
302
|
-
}
|
|
303
|
-
retryParts.push(`## Previous Attempt Failed With Error:\n\n${lastError}\n\n`);
|
|
304
|
-
retryParts.push(`Please analyze the error carefully and fix it. The error might be due to:\n`);
|
|
305
|
-
retryParts.push(`- Syntax errors\n`);
|
|
306
|
-
retryParts.push(`- Logic errors\n`);
|
|
307
|
-
retryParts.push(`- Missing dependencies or imports\n`);
|
|
308
|
-
retryParts.push(`- Incorrect configuration\n`);
|
|
309
|
-
retryParts.push(`- Build or test failures\n\n`);
|
|
310
|
-
retryParts.push(`Please fix the error above and complete the task successfully.\n\n`);
|
|
311
|
-
retryContext = retryParts.join("");
|
|
312
|
-
}
|
|
313
|
-
// Get task plan if available (from planning phase or service)
|
|
314
|
-
const storedPlanData = await tasks_1.taskService.getTaskPlan(task.id);
|
|
315
|
-
let finalPlan;
|
|
316
|
-
if (planContent) {
|
|
317
|
-
finalPlan = `${planContent}\n\nPlease follow this plan to implement the task.`;
|
|
318
|
-
}
|
|
319
|
-
else if (storedPlanData) {
|
|
320
|
-
finalPlan = storedPlanData.plan;
|
|
321
|
-
}
|
|
322
|
-
// Build execution prompt using PromptBuilder
|
|
323
|
-
const promptResult = prompt_builder_1.PromptBuilder.buildExecutionPrompt({
|
|
324
|
-
taskTitle: task.title,
|
|
325
|
-
taskDescription: task.description,
|
|
326
|
-
taskPlan: finalPlan,
|
|
327
|
-
stack: taskContext.stack,
|
|
328
|
-
documentation: taskContext.documentation,
|
|
329
|
-
retryContext,
|
|
330
|
-
});
|
|
331
|
-
if (!promptResult.success) {
|
|
332
|
-
throw new Error(`Failed to build execution prompt: ${promptResult.error}`);
|
|
333
|
-
}
|
|
334
|
-
const executionMessage = promptResult.prompt;
|
|
335
|
-
// Update task status to in-progress
|
|
336
|
-
if (!dry) {
|
|
337
|
-
await tasks_1.taskService.setTaskStatus(task.id, "in-progress");
|
|
338
|
-
console.log(chalk_1.default.yellow("⏳ Task status updated to in-progress"));
|
|
339
|
-
}
|
|
340
|
-
// Emit execution:start event
|
|
341
|
-
await hooks_1.hooks.emit("execution:start", {
|
|
342
|
-
taskId: task.id,
|
|
343
|
-
tool: currentExecutor,
|
|
344
|
-
});
|
|
345
|
-
// Create executor and run
|
|
346
|
-
// Build executor config
|
|
347
|
-
const executorConfig = {
|
|
348
|
-
model: currentModel,
|
|
349
|
-
continueLastSession: currentAttempt > 1, // Resume session on retries
|
|
350
|
-
};
|
|
351
|
-
// Log session resumption
|
|
352
|
-
if (currentAttempt > 1) {
|
|
353
|
-
console.log(chalk_1.default.cyan("🔄 Resuming previous session to provide error feedback to AI"));
|
|
354
|
-
}
|
|
355
|
-
const executor = executor_factory_1.ExecutorFactory.create(currentExecutor, executorConfig);
|
|
356
|
-
// Add model info to execution message if specified
|
|
357
|
-
let finalExecutionMessage = executionMessage;
|
|
358
|
-
if (currentModel) {
|
|
359
|
-
finalExecutionMessage =
|
|
360
|
-
`**Model Configuration**: Using ${currentModel}\n\n` +
|
|
361
|
-
executionMessage;
|
|
362
|
-
}
|
|
363
|
-
await executor.execute(finalExecutionMessage, dry, executorConfig);
|
|
364
|
-
// Run verification commands
|
|
365
|
-
const verificationResults = await runVerificationCommands(verificationCommands, dry);
|
|
366
|
-
// Check if all verifications passed
|
|
367
|
-
const allVerificationsPassed = verificationResults.every((r) => r.success);
|
|
368
|
-
if (!allVerificationsPassed) {
|
|
369
|
-
// Verification failed - prepare error message for retry
|
|
370
|
-
const failedVerification = verificationResults.find((r) => !r.success);
|
|
371
|
-
lastError = `Verification command "${failedVerification?.command}" failed:\n${failedVerification?.error}`;
|
|
372
|
-
attempts.push({
|
|
373
|
-
attemptNumber: currentAttempt,
|
|
374
|
-
success: false,
|
|
375
|
-
error: lastError,
|
|
376
|
-
executor: currentExecutor,
|
|
377
|
-
model: currentModel,
|
|
378
|
-
verificationResults,
|
|
379
|
-
timestamp: Date.now() - attemptStartTime,
|
|
380
|
-
});
|
|
381
|
-
console.log(chalk_1.default.red(`❌ Task execution failed verification on attempt ${currentAttempt}`));
|
|
382
|
-
currentAttempt++;
|
|
383
|
-
continue;
|
|
384
|
-
}
|
|
385
|
-
// ----------------------------------------------------------------------
|
|
386
|
-
// AI REVIEW PHASE
|
|
387
|
-
// ----------------------------------------------------------------------
|
|
388
|
-
if (review && !dry) {
|
|
389
|
-
console.log(chalk_1.default.blue.bold("\n🕵️ Starting AI Review Phase..."));
|
|
390
|
-
try {
|
|
391
|
-
// Get git diff
|
|
392
|
-
const { stdout: diff } = await execAsync("git diff HEAD");
|
|
393
|
-
if (!diff.trim()) {
|
|
394
|
-
console.log(chalk_1.default.yellow("⚠️ No changes detected to review."));
|
|
395
|
-
}
|
|
396
|
-
else {
|
|
397
|
-
const reviewExecutor = reviewModel
|
|
398
|
-
? reviewModel.split(":")[0]
|
|
399
|
-
: tool;
|
|
400
|
-
const reviewModelName = reviewModel
|
|
401
|
-
? reviewModel.split(":")[1]
|
|
402
|
-
: undefined;
|
|
403
|
-
console.log(chalk_1.default.cyan(` Using executor for review: ${reviewExecutor}${reviewModelName ? ` (${reviewModelName})` : ""}`));
|
|
404
|
-
const reviewPrompt = `You are a strict code reviewer. Review the following changes for the task.
|
|
405
|
-
|
|
406
|
-
Task: ${task.title}
|
|
407
|
-
Plan: ${planContent || "No plan provided."}
|
|
408
|
-
|
|
409
|
-
Git Diff:
|
|
410
|
-
${diff.substring(0, 10000)}
|
|
411
|
-
|
|
412
|
-
Analyze the changes for:
|
|
413
|
-
1. Correctness (does it solve the task?)
|
|
414
|
-
2. Code Quality (clean code, best practices)
|
|
415
|
-
3. Potential Bugs
|
|
416
|
-
|
|
417
|
-
Return a JSON object:
|
|
418
|
-
{
|
|
419
|
-
"approved": boolean,
|
|
420
|
-
"feedback": "Detailed feedback explaining why it was rejected or approved"
|
|
421
|
-
}
|
|
422
|
-
`;
|
|
423
|
-
const reviewConfig = {
|
|
424
|
-
model: reviewModelName,
|
|
425
|
-
continueLastSession: false,
|
|
426
|
-
};
|
|
427
|
-
// We use a separate executor instance for review to avoid polluting the main context
|
|
428
|
-
// But wait, ExecutorFactory.create returns a new instance usually.
|
|
429
|
-
// However, we need to capture the output which is usually streamed to stdout/file.
|
|
430
|
-
// The current Executor interface doesn't easily return the output string directly if it's designed for side-effects.
|
|
431
|
-
// But we can use getAIOperations().generateText directly for this since it's a simple Q&A.
|
|
432
|
-
const aiOps = (0, ai_service_factory_2.getAIOperations)();
|
|
433
|
-
// We need to construct a proper AI config for getAIOperations
|
|
434
|
-
// This is a bit hacky, ideally we'd use the executor abstraction, but we need the return value.
|
|
435
|
-
// Let's assume we can use the default AI provider for review for now, or try to respect the reviewModel.
|
|
436
|
-
// Actually, let's use the executor but we need to capture its output.
|
|
437
|
-
// The current executor implementation writes to files/stdout.
|
|
438
|
-
// Let's use aiOps directly for the review to get the JSON response.
|
|
439
|
-
const aiResponse = await aiOps.streamText(reviewPrompt);
|
|
440
|
-
const jsonMatch = aiResponse.match(/\{[\s\S]*\}/);
|
|
441
|
-
if (jsonMatch) {
|
|
442
|
-
const reviewResult = JSON.parse(jsonMatch[0]);
|
|
443
|
-
if (!reviewResult.approved) {
|
|
444
|
-
lastError = `AI Review Failed:\n${reviewResult.feedback}`;
|
|
445
|
-
attempts.push({
|
|
446
|
-
attemptNumber: currentAttempt,
|
|
447
|
-
success: false,
|
|
448
|
-
error: lastError,
|
|
449
|
-
executor: currentExecutor,
|
|
450
|
-
model: currentModel,
|
|
451
|
-
verificationResults,
|
|
452
|
-
timestamp: Date.now() - attemptStartTime,
|
|
453
|
-
});
|
|
454
|
-
console.log(chalk_1.default.red(`❌ AI Review Rejected Changes: ${reviewResult.feedback}`));
|
|
455
|
-
// Revert changes? Or just let the next attempt fix them?
|
|
456
|
-
// Usually better to let the next attempt see the bad code and fix it.
|
|
457
|
-
// But we might want to undo if it's really bad.
|
|
458
|
-
// For now, let's keep the changes so the AI can see what it did wrong.
|
|
459
|
-
currentAttempt++;
|
|
460
|
-
continue;
|
|
461
|
-
}
|
|
462
|
-
else {
|
|
463
|
-
console.log(chalk_1.default.green(`✅ AI Review Approved: ${reviewResult.feedback}`));
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
else {
|
|
467
|
-
console.warn(chalk_1.default.yellow("⚠️ Could not parse AI review response. Assuming approval."));
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
catch (error) {
|
|
472
|
-
console.error(chalk_1.default.red(`❌ AI Review failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
473
|
-
// If review crashes, do we fail the task? Maybe safe to warn and proceed.
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
// Success! Extract commit info
|
|
477
|
-
let commitInfo;
|
|
478
|
-
if (!dry) {
|
|
479
|
-
console.log(chalk_1.default.blue("📝 Extracting commit information..."));
|
|
480
|
-
// Capture git state after execution
|
|
481
|
-
let afterHead = "";
|
|
482
|
-
let hasUncommittedChanges = false;
|
|
483
|
-
try {
|
|
484
|
-
const { stdout: headStdout } = await execAsync("git rev-parse HEAD");
|
|
485
|
-
afterHead = headStdout.trim();
|
|
486
|
-
const { stdout: statusStdout } = await execAsync("git status --porcelain");
|
|
487
|
-
hasUncommittedChanges = statusStdout.trim().length > 0;
|
|
488
|
-
}
|
|
489
|
-
catch (e) {
|
|
490
|
-
// Git issues
|
|
491
|
-
}
|
|
492
|
-
commitInfo = await extractCommitInfo(task.id, task.title, executionMessage, {
|
|
493
|
-
beforeHead,
|
|
494
|
-
afterHead,
|
|
495
|
-
hasUncommittedChanges,
|
|
496
|
-
});
|
|
497
|
-
console.log(chalk_1.default.green(`✅ Commit message: ${commitInfo.message}`));
|
|
498
|
-
if (commitInfo.files.length > 0) {
|
|
499
|
-
console.log(chalk_1.default.green(`📁 Changed files: ${commitInfo.files.join(", ")}`));
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
// Update task status to completed
|
|
503
|
-
if (!dry) {
|
|
504
|
-
await tasks_1.taskService.setTaskStatus(task.id, "completed");
|
|
505
|
-
console.log(chalk_1.default.green("✅ Task execution completed successfully"));
|
|
506
|
-
}
|
|
507
|
-
// Record successful attempt
|
|
508
|
-
attempts.push({
|
|
509
|
-
attemptNumber: currentAttempt,
|
|
510
|
-
success: true,
|
|
511
|
-
executor: currentExecutor,
|
|
512
|
-
model: currentModel,
|
|
513
|
-
verificationResults,
|
|
514
|
-
commitInfo,
|
|
515
|
-
timestamp: Date.now() - attemptStartTime,
|
|
516
|
-
});
|
|
517
|
-
// Emit execution:end event
|
|
518
|
-
await hooks_1.hooks.emit("execution:end", { taskId: task.id, success: true });
|
|
519
|
-
return attempts; // Success - exit retry loop
|
|
520
|
-
}
|
|
521
|
-
catch (error) {
|
|
522
|
-
lastError = error instanceof Error ? error.message : String(error);
|
|
523
|
-
attempts.push({
|
|
524
|
-
attemptNumber: currentAttempt,
|
|
525
|
-
success: false,
|
|
526
|
-
error: lastError,
|
|
527
|
-
executor: currentExecutor,
|
|
528
|
-
model: currentModel,
|
|
529
|
-
timestamp: Date.now() - attemptStartTime,
|
|
530
|
-
});
|
|
531
|
-
// Emit execution:error event
|
|
532
|
-
await hooks_1.hooks.emit("execution:error", {
|
|
533
|
-
taskId: task.id,
|
|
534
|
-
error: error instanceof Error ? error : new Error(String(error)),
|
|
535
|
-
});
|
|
536
|
-
console.log(chalk_1.default.red(`❌ Task execution failed on attempt ${currentAttempt}: ${lastError}`));
|
|
537
|
-
if (!dry && currentAttempt < maxRetries) {
|
|
538
|
-
// Reset task status to todo for retry
|
|
539
|
-
await tasks_1.taskService.setTaskStatus(task.id, "todo");
|
|
540
|
-
console.log(chalk_1.default.yellow("⏸ Task status reset to todo for retry"));
|
|
541
|
-
}
|
|
542
|
-
currentAttempt++;
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
// All retries exhausted
|
|
546
|
-
if (!dry) {
|
|
547
|
-
await tasks_1.taskService.setTaskStatus(task.id, "todo");
|
|
548
|
-
console.log(chalk_1.default.red("❌ All retry attempts exhausted, task status reset to todo"));
|
|
549
|
-
}
|
|
550
|
-
return attempts;
|
|
551
|
-
}
|
|
552
10
|
/**
|
|
553
11
|
* Execute multiple tasks in a loop with retry and verification
|
|
12
|
+
* This delegates to the unified executeTaskCore for each task
|
|
554
13
|
*/
|
|
555
14
|
async function executeTaskLoop(options) {
|
|
556
15
|
const startTime = Date.now();
|
|
557
16
|
const { filters = {}, tool = "opencode", config = {}, dry = false } = options;
|
|
558
|
-
const { maxRetries = 3, verificationCommands = [], autoCommit = false, } = config;
|
|
17
|
+
const { maxRetries = 3, verificationCommands = [], autoCommit = false, tryModels, plan, planModel, reviewPlan, review, reviewModel, customMessage, continueSession, } = config;
|
|
559
18
|
console.log(chalk_1.default.blue.bold("\n🔄 Starting Task Loop Execution\n"));
|
|
560
19
|
console.log(chalk_1.default.cyan(`Executor Tool: ${tool}`));
|
|
561
20
|
console.log(chalk_1.default.cyan(`Max Retries per Task: ${maxRetries}`));
|
|
@@ -603,49 +62,55 @@ async function executeTaskLoop(options) {
|
|
|
603
62
|
for (let i = 0; i < tasksToExecute.length; i++) {
|
|
604
63
|
const task = tasksToExecute[i];
|
|
605
64
|
console.log(chalk_1.default.blue.bold(`\n${"=".repeat(60)}\n📌 Task ${i + 1}/${tasksToExecute.length}: ${task.title} (${task.id})\n${"=".repeat(60)}\n`));
|
|
606
|
-
//
|
|
607
|
-
const
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
}
|
|
635
|
-
else {
|
|
636
|
-
// Stage all changes
|
|
637
|
-
console.log(chalk_1.default.blue("📦 Staging all changes"));
|
|
638
|
-
await execAsync("git add .");
|
|
639
|
-
}
|
|
640
|
-
// Commit
|
|
641
|
-
const gitCommit = `git commit -m "${message}"`;
|
|
642
|
-
console.log(chalk_1.default.blue(`💾 Committing: ${message}`));
|
|
643
|
-
await execAsync(gitCommit);
|
|
644
|
-
console.log(chalk_1.default.green("✅ Changes committed successfully\n"));
|
|
65
|
+
// Build unified task execution config
|
|
66
|
+
const taskConfig = {
|
|
67
|
+
tool,
|
|
68
|
+
customMessage, // NEW: Support custom message override
|
|
69
|
+
executorConfig: {
|
|
70
|
+
continueLastSession: continueSession, // NEW: Support session continuation
|
|
71
|
+
},
|
|
72
|
+
verificationCommands,
|
|
73
|
+
enableRetry: true, // Always enable retry in loop
|
|
74
|
+
maxRetries,
|
|
75
|
+
tryModels,
|
|
76
|
+
enablePlanPhase: plan,
|
|
77
|
+
planModel,
|
|
78
|
+
reviewPlan,
|
|
79
|
+
enableReviewPhase: review,
|
|
80
|
+
reviewModel,
|
|
81
|
+
autoCommit,
|
|
82
|
+
executeSubtasks: true, // Now supports subtasks!
|
|
83
|
+
dry,
|
|
84
|
+
};
|
|
85
|
+
try {
|
|
86
|
+
// Execute task using unified core
|
|
87
|
+
const result = await (0, task_execution_core_1.executeTaskCore)(task.id, taskConfig);
|
|
88
|
+
// Check if task succeeded
|
|
89
|
+
const succeeded = result.success;
|
|
90
|
+
if (succeeded) {
|
|
91
|
+
completedTasks++;
|
|
92
|
+
console.log(chalk_1.default.green.bold(`\n✅ Task ${task.title} completed successfully after ${result.attempts.length} attempt(s)\n`));
|
|
645
93
|
}
|
|
646
|
-
|
|
647
|
-
|
|
94
|
+
else {
|
|
95
|
+
failedTasks++;
|
|
96
|
+
console.log(chalk_1.default.red.bold(`\n❌ Task ${task.title} failed after ${result.attempts.length} attempt(s)\n`));
|
|
648
97
|
}
|
|
98
|
+
taskResults.push({
|
|
99
|
+
taskId: task.id,
|
|
100
|
+
taskTitle: task.title,
|
|
101
|
+
attempts: result.attempts,
|
|
102
|
+
finalStatus: succeeded ? "completed" : "failed",
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
failedTasks++;
|
|
107
|
+
console.error(chalk_1.default.red.bold(`\n❌ Task ${task.title} failed with error: ${error instanceof Error ? error.message : "Unknown error"}\n`));
|
|
108
|
+
taskResults.push({
|
|
109
|
+
taskId: task.id,
|
|
110
|
+
taskTitle: task.title,
|
|
111
|
+
attempts: [],
|
|
112
|
+
finalStatus: "failed",
|
|
113
|
+
});
|
|
649
114
|
}
|
|
650
115
|
}
|
|
651
116
|
const duration = Date.now() - startTime;
|