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.
Files changed (150) hide show
  1. package/dist/commands/init.js +41 -8
  2. package/dist/commands/tasks/create.d.ts.map +1 -1
  3. package/dist/commands/tasks/create.js +6 -13
  4. package/dist/commands/tasks/document/add.d.ts +3 -0
  5. package/dist/commands/tasks/document/add.d.ts.map +1 -0
  6. package/dist/commands/tasks/document/add.js +35 -0
  7. package/dist/commands/tasks/document/analyze.d.ts +3 -0
  8. package/dist/commands/tasks/document/analyze.d.ts.map +1 -0
  9. package/dist/commands/tasks/document/analyze.js +49 -0
  10. package/dist/commands/tasks/document/get.d.ts +3 -0
  11. package/dist/commands/tasks/document/get.d.ts.map +1 -0
  12. package/dist/commands/tasks/document/get.js +29 -0
  13. package/dist/commands/tasks/document/index.d.ts +8 -0
  14. package/dist/commands/tasks/document/index.d.ts.map +1 -0
  15. package/dist/commands/tasks/document/index.js +13 -0
  16. package/dist/commands/tasks/enhance.d.ts.map +1 -1
  17. package/dist/commands/tasks/enhance.js +64 -61
  18. package/dist/commands/tasks/execute-loop.d.ts.map +1 -1
  19. package/dist/commands/tasks/execute-loop.js +64 -90
  20. package/dist/commands/tasks/execute.d.ts.map +1 -1
  21. package/dist/commands/tasks/execute.js +52 -16
  22. package/dist/commands/tasks/list.js +2 -2
  23. package/dist/commands/tasks/next.js +4 -4
  24. package/dist/commands/tasks/plan/create.d.ts +3 -0
  25. package/dist/commands/tasks/plan/create.d.ts.map +1 -0
  26. package/dist/commands/tasks/plan/create.js +37 -0
  27. package/dist/commands/tasks/plan/delete.d.ts +3 -0
  28. package/dist/commands/tasks/plan/delete.d.ts.map +1 -0
  29. package/dist/commands/tasks/plan/delete.js +14 -0
  30. package/dist/commands/tasks/plan/get.d.ts +3 -0
  31. package/dist/commands/tasks/plan/get.d.ts.map +1 -0
  32. package/dist/commands/tasks/plan/get.js +24 -0
  33. package/dist/commands/tasks/plan/index.d.ts +10 -0
  34. package/dist/commands/tasks/plan/index.d.ts.map +1 -0
  35. package/dist/commands/tasks/plan/index.js +17 -0
  36. package/dist/commands/tasks/plan/list.d.ts +3 -0
  37. package/dist/commands/tasks/plan/list.d.ts.map +1 -0
  38. package/dist/commands/tasks/plan/list.js +21 -0
  39. package/dist/commands/tasks/plan/set.d.ts +3 -0
  40. package/dist/commands/tasks/plan/set.d.ts.map +1 -0
  41. package/dist/commands/tasks/plan/set.js +33 -0
  42. package/dist/commands/tasks/split.d.ts.map +1 -1
  43. package/dist/commands/tasks/split.js +65 -60
  44. package/dist/commands/tasks/status.js +2 -2
  45. package/dist/lib/ai-service/ai-operations.d.ts +1 -1
  46. package/dist/lib/ai-service/ai-operations.d.ts.map +1 -1
  47. package/dist/lib/ai-service/base-operations.d.ts +22 -0
  48. package/dist/lib/ai-service/base-operations.d.ts.map +1 -1
  49. package/dist/lib/ai-service/base-operations.js +29 -1
  50. package/dist/lib/ai-service/task-operations.d.ts +1 -1
  51. package/dist/lib/ai-service/task-operations.d.ts.map +1 -1
  52. package/dist/lib/better-t-stack-cli.d.ts +36 -21
  53. package/dist/lib/better-t-stack-cli.d.ts.map +1 -1
  54. package/dist/lib/better-t-stack-cli.js +212 -33
  55. package/dist/lib/bootstrap/cli-bootstrap.d.ts +14 -0
  56. package/dist/lib/bootstrap/cli-bootstrap.d.ts.map +1 -0
  57. package/dist/lib/bootstrap/cli-bootstrap.js +325 -0
  58. package/dist/lib/bootstrap/index.d.ts +4 -0
  59. package/dist/lib/bootstrap/index.d.ts.map +1 -0
  60. package/dist/lib/bootstrap/index.js +19 -0
  61. package/dist/lib/bootstrap/medusa-bootstrap.d.ts +14 -0
  62. package/dist/lib/bootstrap/medusa-bootstrap.d.ts.map +1 -0
  63. package/dist/lib/bootstrap/medusa-bootstrap.js +218 -0
  64. package/dist/lib/bootstrap/opentui-bootstrap.d.ts +11 -0
  65. package/dist/lib/bootstrap/opentui-bootstrap.d.ts.map +1 -0
  66. package/dist/lib/bootstrap/opentui-bootstrap.js +342 -0
  67. package/dist/lib/config.d.ts +14 -0
  68. package/dist/lib/config.d.ts.map +1 -1
  69. package/dist/lib/config.js +18 -0
  70. package/dist/lib/git-utils.d.ts +45 -0
  71. package/dist/lib/git-utils.d.ts.map +1 -0
  72. package/dist/lib/git-utils.js +160 -0
  73. package/dist/lib/task-execution-core.d.ts +7 -0
  74. package/dist/lib/task-execution-core.d.ts.map +1 -0
  75. package/dist/lib/task-execution-core.js +360 -0
  76. package/dist/lib/task-execution.d.ts +4 -0
  77. package/dist/lib/task-execution.d.ts.map +1 -1
  78. package/dist/lib/task-execution.js +31 -149
  79. package/dist/lib/task-loop-execution.d.ts +1 -19
  80. package/dist/lib/task-loop-execution.d.ts.map +1 -1
  81. package/dist/lib/task-loop-execution.js +50 -585
  82. package/dist/lib/task-planning.d.ts +28 -0
  83. package/dist/lib/task-planning.d.ts.map +1 -0
  84. package/dist/lib/task-planning.js +109 -0
  85. package/dist/lib/task-review.d.ts +27 -0
  86. package/dist/lib/task-review.d.ts.map +1 -0
  87. package/dist/lib/task-review.js +106 -0
  88. package/dist/lib/validation.d.ts +20 -3
  89. package/dist/lib/validation.d.ts.map +1 -1
  90. package/dist/lib/validation.js +39 -10
  91. package/dist/services/prd.d.ts.map +1 -1
  92. package/dist/services/prd.js +18 -45
  93. package/dist/services/tasks.d.ts +2 -2
  94. package/dist/services/tasks.d.ts.map +1 -1
  95. package/dist/services/tasks.js +55 -85
  96. package/dist/test/task-loop-git.test.js +6 -6
  97. package/dist/types/cli-options.d.ts +138 -0
  98. package/dist/types/cli-options.d.ts.map +1 -0
  99. package/dist/types/cli-options.js +6 -0
  100. package/dist/types/index.d.ts +74 -1
  101. package/dist/types/index.d.ts.map +1 -1
  102. package/dist/types/results.d.ts +60 -6
  103. package/dist/types/results.d.ts.map +1 -1
  104. package/dist/utils/bulk-operations.d.ts +51 -0
  105. package/dist/utils/bulk-operations.d.ts.map +1 -0
  106. package/dist/utils/bulk-operations.js +68 -0
  107. package/dist/utils/cli-validators.d.ts +54 -0
  108. package/dist/utils/cli-validators.d.ts.map +1 -0
  109. package/dist/utils/cli-validators.js +75 -0
  110. package/dist/utils/command-error-handler.d.ts +32 -0
  111. package/dist/utils/command-error-handler.d.ts.map +1 -0
  112. package/dist/utils/command-error-handler.js +52 -0
  113. package/dist/utils/confirmation.d.ts +19 -0
  114. package/dist/utils/confirmation.d.ts.map +1 -0
  115. package/dist/utils/confirmation.js +39 -0
  116. package/dist/utils/display-helpers.d.ts +81 -0
  117. package/dist/utils/display-helpers.d.ts.map +1 -0
  118. package/dist/utils/display-helpers.js +109 -0
  119. package/dist/utils/error-utils.d.ts +70 -0
  120. package/dist/utils/error-utils.d.ts.map +1 -0
  121. package/dist/utils/error-utils.js +103 -0
  122. package/dist/utils/file-utils.d.ts +49 -0
  123. package/dist/utils/file-utils.d.ts.map +1 -0
  124. package/dist/utils/file-utils.js +77 -0
  125. package/dist/utils/id-generator.d.ts +92 -0
  126. package/dist/utils/id-generator.d.ts.map +1 -0
  127. package/dist/utils/id-generator.js +140 -0
  128. package/dist/utils/model-executor-parser.d.ts +38 -0
  129. package/dist/utils/model-executor-parser.d.ts.map +1 -0
  130. package/dist/utils/model-executor-parser.js +67 -0
  131. package/dist/utils/progress-tracking.d.ts +28 -0
  132. package/dist/utils/progress-tracking.d.ts.map +1 -0
  133. package/dist/utils/progress-tracking.js +43 -0
  134. package/dist/utils/stack-formatter.d.ts +2 -1
  135. package/dist/utils/stack-formatter.d.ts.map +1 -1
  136. package/dist/utils/stack-formatter.js +8 -2
  137. package/dist/utils/storage-utils.d.ts +49 -0
  138. package/dist/utils/storage-utils.d.ts.map +1 -0
  139. package/dist/utils/storage-utils.js +79 -0
  140. package/dist/utils/streaming-utils.d.ts +38 -0
  141. package/dist/utils/streaming-utils.d.ts.map +1 -0
  142. package/dist/utils/streaming-utils.js +56 -0
  143. package/docs/agents/cli.md +58 -149
  144. package/package.json +1 -1
  145. package/dist/commands/tasks/document.d.ts +0 -5
  146. package/dist/commands/tasks/document.d.ts.map +0 -1
  147. package/dist/commands/tasks/document.js +0 -118
  148. package/dist/commands/tasks/plan.d.ts +0 -7
  149. package/dist/commands/tasks/plan.d.ts.map +0 -1
  150. 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 executor_factory_1 = require("./executors/executor-factory");
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
- // Execute task with retry logic
607
- const attempts = await executeTaskWithRetry(task, tool, config, dry);
608
- // Check if task succeeded
609
- const lastAttempt = attempts[attempts.length - 1];
610
- const succeeded = lastAttempt.success;
611
- if (succeeded) {
612
- completedTasks++;
613
- console.log(chalk_1.default.green.bold(`\n✅ Task ${task.title} completed successfully after ${attempts.length} attempt(s)\n`));
614
- }
615
- else {
616
- failedTasks++;
617
- console.log(chalk_1.default.red.bold(`\n❌ Task ${task.title} failed after ${attempts.length} attempt(s)\n`));
618
- }
619
- taskResults.push({
620
- taskId: task.id,
621
- taskTitle: task.title,
622
- attempts,
623
- finalStatus: succeeded ? "completed" : "failed",
624
- });
625
- // Auto-commit if enabled and task succeeded
626
- if (autoCommit && succeeded && !dry && lastAttempt.commitInfo) {
627
- try {
628
- const { message, files } = lastAttempt.commitInfo;
629
- if (files.length > 0) {
630
- // Stage specific files
631
- const gitAdd = `git add ${files.join(" ")}`;
632
- console.log(chalk_1.default.blue(`📦 Staging files: ${gitAdd}`));
633
- await execAsync(gitAdd);
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
- catch (error) {
647
- 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`));
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;