task-o-matic 0.0.11 → 0.0.13

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