@qelos/aidev 0.5.1 → 0.5.3
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/.aidev/assets/86c8yjxrr/9ea11c36-311c-4022-889c-1bb0915122dc.jpg-40e6939e3b68e864260f7ae7e85bd623_exif.jpg +0 -0
- package/.env.aidev.example +92 -84
- package/CONTRIBUTING.md +78 -78
- package/LICENSE +21 -21
- package/README.md +735 -622
- package/dist/autoCompress.d.ts +54 -0
- package/dist/autoCompress.d.ts.map +1 -0
- package/dist/autoCompress.js +300 -0
- package/dist/autoCompress.js.map +1 -0
- package/dist/cli.js +13 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/accepted.d.ts +7 -2
- package/dist/commands/accepted.d.ts.map +1 -1
- package/dist/commands/accepted.js +17 -2
- package/dist/commands/accepted.js.map +1 -1
- package/dist/commands/help.d.ts.map +1 -1
- package/dist/commands/help.js +80 -67
- package/dist/commands/help.js.map +1 -1
- package/dist/commands/init.js +105 -105
- package/dist/commands/run.d.ts +9 -1
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +307 -140
- package/dist/commands/run.js.map +1 -1
- package/dist/commands/tasks.d.ts +1 -0
- package/dist/commands/tasks.d.ts.map +1 -1
- package/dist/commands/tasks.js +29 -0
- package/dist/commands/tasks.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +6 -0
- package/dist/config.js.map +1 -1
- package/dist/github.js +27 -27
- package/dist/hooks.d.ts +1 -1
- package/dist/hooks.d.ts.map +1 -1
- package/dist/providers/linear.d.ts +4 -1
- package/dist/providers/linear.d.ts.map +1 -1
- package/dist/providers/linear.js +142 -78
- package/dist/providers/linear.js.map +1 -1
- package/dist/providers/monday.js +39 -39
- package/dist/sessions.d.ts +20 -0
- package/dist/sessions.d.ts.map +1 -0
- package/dist/sessions.js +171 -0
- package/dist/sessions.js.map +1 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +51 -51
- package/scripts/run-tests.cjs +18 -18
- package/dist/__tests__/ai-runners.test.d.ts +0 -2
- package/dist/__tests__/ai-runners.test.d.ts.map +0 -1
- package/dist/__tests__/ai-runners.test.js +0 -367
- package/dist/__tests__/ai-runners.test.js.map +0 -1
- package/dist/__tests__/clickup-format.test.d.ts +0 -2
- package/dist/__tests__/clickup-format.test.d.ts.map +0 -1
- package/dist/__tests__/clickup-format.test.js +0 -256
- package/dist/__tests__/clickup-format.test.js.map +0 -1
- package/dist/__tests__/config.test.d.ts +0 -2
- package/dist/__tests__/config.test.d.ts.map +0 -1
- package/dist/__tests__/config.test.js +0 -418
- package/dist/__tests__/config.test.js.map +0 -1
- package/dist/__tests__/git.test.d.ts +0 -2
- package/dist/__tests__/git.test.d.ts.map +0 -1
- package/dist/__tests__/git.test.js +0 -697
- package/dist/__tests__/git.test.js.map +0 -1
- package/dist/__tests__/github.test.d.ts +0 -2
- package/dist/__tests__/github.test.d.ts.map +0 -1
- package/dist/__tests__/github.test.js +0 -273
- package/dist/__tests__/github.test.js.map +0 -1
- package/dist/__tests__/help.test.d.ts +0 -2
- package/dist/__tests__/help.test.d.ts.map +0 -1
- package/dist/__tests__/help.test.js +0 -34
- package/dist/__tests__/help.test.js.map +0 -1
- package/dist/__tests__/hooks.test.d.ts +0 -2
- package/dist/__tests__/hooks.test.d.ts.map +0 -1
- package/dist/__tests__/hooks.test.js +0 -381
- package/dist/__tests__/hooks.test.js.map +0 -1
- package/dist/__tests__/init.test.d.ts +0 -2
- package/dist/__tests__/init.test.d.ts.map +0 -1
- package/dist/__tests__/init.test.js +0 -596
- package/dist/__tests__/init.test.js.map +0 -1
- package/dist/__tests__/local-provider.test.d.ts +0 -2
- package/dist/__tests__/local-provider.test.d.ts.map +0 -1
- package/dist/__tests__/local-provider.test.js +0 -631
- package/dist/__tests__/local-provider.test.js.map +0 -1
- package/dist/__tests__/lockfile.test.d.ts +0 -2
- package/dist/__tests__/lockfile.test.d.ts.map +0 -1
- package/dist/__tests__/lockfile.test.js +0 -144
- package/dist/__tests__/lockfile.test.js.map +0 -1
- package/dist/__tests__/permissions.test.d.ts +0 -2
- package/dist/__tests__/permissions.test.d.ts.map +0 -1
- package/dist/__tests__/permissions.test.js +0 -151
- package/dist/__tests__/permissions.test.js.map +0 -1
- package/dist/__tests__/platform.test.d.ts +0 -2
- package/dist/__tests__/platform.test.d.ts.map +0 -1
- package/dist/__tests__/platform.test.js +0 -329
- package/dist/__tests__/platform.test.js.map +0 -1
- package/dist/__tests__/providers.test.d.ts +0 -2
- package/dist/__tests__/providers.test.d.ts.map +0 -1
- package/dist/__tests__/providers.test.js +0 -925
- package/dist/__tests__/providers.test.js.map +0 -1
- package/dist/__tests__/run.test.d.ts +0 -2
- package/dist/__tests__/run.test.d.ts.map +0 -1
- package/dist/__tests__/run.test.js +0 -767
- package/dist/__tests__/run.test.js.map +0 -1
- package/dist/__tests__/schedule.test.d.ts +0 -2
- package/dist/__tests__/schedule.test.d.ts.map +0 -1
- package/dist/__tests__/schedule.test.js +0 -258
- package/dist/__tests__/schedule.test.js.map +0 -1
package/dist/commands/run.js
CHANGED
|
@@ -39,12 +39,18 @@ exports.getOpenStatus = getOpenStatus;
|
|
|
39
39
|
exports.getInReviewStatus = getInReviewStatus;
|
|
40
40
|
exports.getRunSkipReason = getRunSkipReason;
|
|
41
41
|
exports.sortTasksByPriority = sortTasksByPriority;
|
|
42
|
+
exports.writeTaskPlan = writeTaskPlan;
|
|
43
|
+
exports.readTaskPlan = readTaskPlan;
|
|
44
|
+
exports.formatSubtaskId = formatSubtaskId;
|
|
45
|
+
exports.subtaskDepth = subtaskDepth;
|
|
46
|
+
exports.formatSubtaskList = formatSubtaskList;
|
|
42
47
|
exports.runCommand = runCommand;
|
|
43
48
|
exports.hasHumanReply = hasHumanReply;
|
|
44
49
|
exports.hasTriggerWord = hasTriggerWord;
|
|
45
50
|
exports.checkNeedsClarification = checkNeedsClarification;
|
|
46
51
|
exports.buildConflictResolutionPrompt = buildConflictResolutionPrompt;
|
|
47
52
|
exports.buildImplementPrompt = buildImplementPrompt;
|
|
53
|
+
exports.splitFailedSubtask = splitFailedSubtask;
|
|
48
54
|
exports.buildPRBody = buildPRBody;
|
|
49
55
|
exports.tryCreatePR = tryCreatePR;
|
|
50
56
|
exports.buildPRUrl = buildPRUrl;
|
|
@@ -65,6 +71,7 @@ const github_1 = require("../github");
|
|
|
65
71
|
const diagnostics_1 = require("../diagnostics");
|
|
66
72
|
const lockfile_1 = require("../lockfile");
|
|
67
73
|
const hooks_1 = require("../hooks");
|
|
74
|
+
const sessions_1 = require("../sessions");
|
|
68
75
|
const SKIP_STATUSES = new Set(['closed', 'done', 'cancelled', 'complete']);
|
|
69
76
|
const NO_PRIORITY = Number.MAX_SAFE_INTEGER;
|
|
70
77
|
const SLEEPING_MARKER = 'machine appears to be asleep';
|
|
@@ -149,17 +156,43 @@ function cleanupThinkingFiles(taskId) {
|
|
|
149
156
|
function writeTaskPlan(plan) {
|
|
150
157
|
fs.writeFileSync(taskPlanPath(plan.taskId), JSON.stringify(plan, null, 2), 'utf8');
|
|
151
158
|
}
|
|
159
|
+
function truncateError(msg) {
|
|
160
|
+
const MAX_LEN = 4096;
|
|
161
|
+
if (msg.length <= MAX_LEN)
|
|
162
|
+
return msg;
|
|
163
|
+
return msg.slice(0, MAX_LEN) + '\n... (truncated)';
|
|
164
|
+
}
|
|
152
165
|
function readTaskPlan(taskId) {
|
|
153
166
|
const p = taskPlanPath(taskId);
|
|
154
167
|
if (!fs.existsSync(p))
|
|
155
168
|
return null;
|
|
156
169
|
try {
|
|
157
|
-
|
|
170
|
+
const raw = JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
171
|
+
// Backward compat: older plans may be missing `attempts` / `lastError`.
|
|
172
|
+
raw.subtasks = (raw.subtasks || []).map((s) => ({
|
|
173
|
+
id: s.id,
|
|
174
|
+
title: s.title,
|
|
175
|
+
description: s.description,
|
|
176
|
+
status: s.status,
|
|
177
|
+
attempts: typeof s.attempts === 'number' ? s.attempts : 0,
|
|
178
|
+
lastError: s.lastError,
|
|
179
|
+
}));
|
|
180
|
+
return raw;
|
|
158
181
|
}
|
|
159
182
|
catch {
|
|
160
183
|
return null;
|
|
161
184
|
}
|
|
162
185
|
}
|
|
186
|
+
function formatSubtaskId(id) {
|
|
187
|
+
// Decimal string IDs like "3.1" already contain a dot; plain numeric IDs get
|
|
188
|
+
// a trailing dot so the list reads "1. Title", "2. Title", "3.1 Title".
|
|
189
|
+
return typeof id === 'string' ? id : `${id}.`;
|
|
190
|
+
}
|
|
191
|
+
function subtaskDepth(id) {
|
|
192
|
+
if (typeof id === 'number')
|
|
193
|
+
return 0;
|
|
194
|
+
return (id.match(/\./g) || []).length;
|
|
195
|
+
}
|
|
163
196
|
function formatSubtaskList(plan) {
|
|
164
197
|
const icons = {
|
|
165
198
|
pending: '⬜',
|
|
@@ -168,7 +201,7 @@ function formatSubtaskList(plan) {
|
|
|
168
201
|
failed: '❌',
|
|
169
202
|
};
|
|
170
203
|
return plan.subtasks
|
|
171
|
-
.map((s) => `${icons[s.status]} **${s.id}
|
|
204
|
+
.map((s) => `${icons[s.status]} **${formatSubtaskId(s.id)}** ${s.title} — _${s.status}_`)
|
|
172
205
|
.join('\n');
|
|
173
206
|
}
|
|
174
207
|
async function runCommand(filter, config, provider, runners, nonCodeProvider, hooks = {}, vm) {
|
|
@@ -370,16 +403,16 @@ async function checkNeedsClarification(task, config, provider, runners) {
|
|
|
370
403
|
logger_1.logger.warn('No AI runner available — skipping clarification check');
|
|
371
404
|
return null;
|
|
372
405
|
}
|
|
373
|
-
const clarificationPrompt = `You are a senior software developer reviewing a task.
|
|
374
|
-
Determine if the following task has enough information to implement without further clarification.
|
|
375
|
-
|
|
376
|
-
Task name: ${task.name}
|
|
377
|
-
Task description: ${task.description || '(no description)'}
|
|
378
|
-
|
|
379
|
-
Respond with valid JSON only:
|
|
380
|
-
{
|
|
381
|
-
"clear": true|false,
|
|
382
|
-
"question": "question to ask if not clear, or null"
|
|
406
|
+
const clarificationPrompt = `You are a senior software developer reviewing a task.
|
|
407
|
+
Determine if the following task has enough information to implement without further clarification.
|
|
408
|
+
|
|
409
|
+
Task name: ${task.name}
|
|
410
|
+
Task description: ${task.description || '(no description)'}
|
|
411
|
+
|
|
412
|
+
Respond with valid JSON only:
|
|
413
|
+
{
|
|
414
|
+
"clear": true|false,
|
|
415
|
+
"question": "question to ask if not clear, or null"
|
|
383
416
|
}`;
|
|
384
417
|
for (const runner of availableRunners) {
|
|
385
418
|
const result = await runner.run(clarificationPrompt);
|
|
@@ -407,30 +440,30 @@ Respond with valid JSON only:
|
|
|
407
440
|
return null;
|
|
408
441
|
}
|
|
409
442
|
function buildConflictResolutionPrompt(task, conflictFiles, context) {
|
|
410
|
-
return `You are resolving merge conflicts in a software development task branch.
|
|
411
|
-
|
|
412
|
-
The task branch has fallen behind the base branch and has merge conflicts that need to be resolved.
|
|
413
|
-
|
|
414
|
-
## Task context (DO NOT break this — the task must still work after conflict resolution)
|
|
415
|
-
|
|
416
|
-
Task: ${task.name}
|
|
417
|
-
|
|
418
|
-
Description:
|
|
419
|
-
${task.description || '(no description provided)'}
|
|
420
|
-
${context}
|
|
421
|
-
|
|
422
|
-
## Merge conflicts
|
|
423
|
-
|
|
424
|
-
The following files have merge conflicts with conflict markers (<<<<<<< HEAD, =======, >>>>>>> ...):
|
|
425
|
-
${conflictFiles.map((f) => `- ${f}`).join('\n')}
|
|
426
|
-
|
|
427
|
-
## Instructions
|
|
428
|
-
|
|
429
|
-
1. Open each conflicting file and resolve the conflict markers
|
|
430
|
-
2. Keep BOTH the task's changes AND the base branch updates where possible
|
|
431
|
-
3. If the base branch changed something the task also changed, prefer the task's intent but make sure it works with the new base branch code
|
|
432
|
-
4. Remove all conflict markers (<<<<<<< HEAD, =======, >>>>>>> ...)
|
|
433
|
-
5. Make sure the code compiles and is consistent after resolution
|
|
443
|
+
return `You are resolving merge conflicts in a software development task branch.
|
|
444
|
+
|
|
445
|
+
The task branch has fallen behind the base branch and has merge conflicts that need to be resolved.
|
|
446
|
+
|
|
447
|
+
## Task context (DO NOT break this — the task must still work after conflict resolution)
|
|
448
|
+
|
|
449
|
+
Task: ${task.name}
|
|
450
|
+
|
|
451
|
+
Description:
|
|
452
|
+
${task.description || '(no description provided)'}
|
|
453
|
+
${context}
|
|
454
|
+
|
|
455
|
+
## Merge conflicts
|
|
456
|
+
|
|
457
|
+
The following files have merge conflicts with conflict markers (<<<<<<< HEAD, =======, >>>>>>> ...):
|
|
458
|
+
${conflictFiles.map((f) => `- ${f}`).join('\n')}
|
|
459
|
+
|
|
460
|
+
## Instructions
|
|
461
|
+
|
|
462
|
+
1. Open each conflicting file and resolve the conflict markers
|
|
463
|
+
2. Keep BOTH the task's changes AND the base branch updates where possible
|
|
464
|
+
3. If the base branch changed something the task also changed, prefer the task's intent but make sure it works with the new base branch code
|
|
465
|
+
4. Remove all conflict markers (<<<<<<< HEAD, =======, >>>>>>> ...)
|
|
466
|
+
5. Make sure the code compiles and is consistent after resolution
|
|
434
467
|
6. Do NOT make any changes beyond what is needed to resolve the conflicts`;
|
|
435
468
|
}
|
|
436
469
|
async function resolveConflictsWithAI(task, config, provider, runners, context, hooks, vm, branchName) {
|
|
@@ -602,10 +635,7 @@ async function implementTask(task, branchName, branchExists, config, provider, r
|
|
|
602
635
|
let context = '';
|
|
603
636
|
try {
|
|
604
637
|
const comments = await provider.getComments(task.id);
|
|
605
|
-
|
|
606
|
-
if (humanComments.length > 0) {
|
|
607
|
-
context = '\n\nConversation context:\n' + humanComments.map((c) => `${c.author}: ${c.text}`).join('\n');
|
|
608
|
-
}
|
|
638
|
+
context = await buildConversationContext(task.id, comments, config, runners);
|
|
609
639
|
}
|
|
610
640
|
catch {
|
|
611
641
|
// ignore
|
|
@@ -701,14 +731,14 @@ async function implementTask(task, branchName, branchExists, config, provider, r
|
|
|
701
731
|
logger_1.logger.success(`Task implemented: branch ${branchName} pushed`);
|
|
702
732
|
}
|
|
703
733
|
function buildImplementPrompt(task, context) {
|
|
704
|
-
return `You are implementing a software development task. Make the necessary code changes to complete the task described below.
|
|
705
|
-
|
|
706
|
-
Task: ${task.name}
|
|
707
|
-
|
|
708
|
-
Description:
|
|
709
|
-
${task.description || '(no description provided)'}
|
|
710
|
-
${context}
|
|
711
|
-
|
|
734
|
+
return `You are implementing a software development task. Make the necessary code changes to complete the task described below.
|
|
735
|
+
|
|
736
|
+
Task: ${task.name}
|
|
737
|
+
|
|
738
|
+
Description:
|
|
739
|
+
${task.description || '(no description provided)'}
|
|
740
|
+
${context}
|
|
741
|
+
|
|
712
742
|
Please implement the required changes. Focus on correctness and follow the existing code style in the project.`;
|
|
713
743
|
}
|
|
714
744
|
async function analyzeAndPlan(task, context, runners) {
|
|
@@ -717,28 +747,28 @@ async function analyzeAndPlan(task, context, runners) {
|
|
|
717
747
|
logger_1.logger.error('No AI runner available for task analysis');
|
|
718
748
|
return null;
|
|
719
749
|
}
|
|
720
|
-
const analysisPrompt = `You are a senior software architect breaking down a development task into smaller, sequential implementation steps.
|
|
721
|
-
|
|
722
|
-
Task name: ${task.name}
|
|
723
|
-
|
|
724
|
-
Description:
|
|
725
|
-
${task.description || '(no description provided)'}
|
|
726
|
-
${context}
|
|
727
|
-
|
|
728
|
-
Analyze this task and break it into smaller, independently implementable sub-tasks that should be executed sequentially. Each sub-task should be a coherent unit of work that can be committed separately.
|
|
729
|
-
|
|
730
|
-
Respond with valid JSON only — no markdown fences, no extra text:
|
|
731
|
-
{
|
|
732
|
-
"instructions": "Detailed implementation instructions in markdown covering the full task — architecture decisions, key files to modify, edge cases to handle, testing approach",
|
|
733
|
-
"subtasks": [
|
|
734
|
-
{
|
|
735
|
-
"id": 1,
|
|
736
|
-
"title": "Short title for the sub-task",
|
|
737
|
-
"description": "Detailed description of what to implement in this step, including specific files and functions to change"
|
|
738
|
-
}
|
|
739
|
-
]
|
|
740
|
-
}
|
|
741
|
-
|
|
750
|
+
const analysisPrompt = `You are a senior software architect breaking down a development task into smaller, sequential implementation steps.
|
|
751
|
+
|
|
752
|
+
Task name: ${task.name}
|
|
753
|
+
|
|
754
|
+
Description:
|
|
755
|
+
${task.description || '(no description provided)'}
|
|
756
|
+
${context}
|
|
757
|
+
|
|
758
|
+
Analyze this task and break it into smaller, independently implementable sub-tasks that should be executed sequentially. Each sub-task should be a coherent unit of work that can be committed separately.
|
|
759
|
+
|
|
760
|
+
Respond with valid JSON only — no markdown fences, no extra text:
|
|
761
|
+
{
|
|
762
|
+
"instructions": "Detailed implementation instructions in markdown covering the full task — architecture decisions, key files to modify, edge cases to handle, testing approach",
|
|
763
|
+
"subtasks": [
|
|
764
|
+
{
|
|
765
|
+
"id": 1,
|
|
766
|
+
"title": "Short title for the sub-task",
|
|
767
|
+
"description": "Detailed description of what to implement in this step, including specific files and functions to change"
|
|
768
|
+
}
|
|
769
|
+
]
|
|
770
|
+
}
|
|
771
|
+
|
|
742
772
|
Keep sub-tasks focused: 2-6 sub-tasks is ideal. Order them by dependency (foundation first).`;
|
|
743
773
|
logger_1.logger.info('Analyzing task and creating implementation plan...');
|
|
744
774
|
const result = await runner.run(analysisPrompt);
|
|
@@ -765,6 +795,7 @@ Keep sub-tasks focused: 2-6 sub-tasks is ideal. Order them by dependency (founda
|
|
|
765
795
|
title: s.title,
|
|
766
796
|
description: s.description,
|
|
767
797
|
status: 'pending',
|
|
798
|
+
attempts: 0,
|
|
768
799
|
})),
|
|
769
800
|
};
|
|
770
801
|
fs.writeFileSync(taskInstructionsPath(task.id), parsed.instructions || `# Implementation Plan: ${task.name}\n\nSee ${task.id}.aidev.task.json for sub-tasks.`, 'utf8');
|
|
@@ -776,7 +807,87 @@ Keep sub-tasks focused: 2-6 sub-tasks is ideal. Order them by dependency (founda
|
|
|
776
807
|
return null;
|
|
777
808
|
}
|
|
778
809
|
}
|
|
779
|
-
async function
|
|
810
|
+
async function splitFailedSubtask(parentTask, plan, failedSubtask, runners) {
|
|
811
|
+
const runner = runners.find((r) => r.isAvailable());
|
|
812
|
+
if (!runner) {
|
|
813
|
+
logger_1.logger.error('No AI runner available for sub-task split');
|
|
814
|
+
return null;
|
|
815
|
+
}
|
|
816
|
+
const siblings = plan.subtasks
|
|
817
|
+
.filter((s) => s.id !== failedSubtask.id)
|
|
818
|
+
.map((s) => ` - [${s.status}] ${formatSubtaskId(s.id)} ${s.title}`)
|
|
819
|
+
.join('\n');
|
|
820
|
+
const diagnostics = failedSubtask.lastError && failedSubtask.lastError !== '__git__'
|
|
821
|
+
? failedSubtask.lastError
|
|
822
|
+
: '(no diagnostics captured)';
|
|
823
|
+
const splitPrompt = `You are a senior software architect helping recover a stalled implementation step by splitting it into exactly two smaller, sequential sub-tasks.
|
|
824
|
+
|
|
825
|
+
Overall task: ${parentTask.name}
|
|
826
|
+
${parentTask.description ? `\nTask description:\n${parentTask.description}\n` : ''}
|
|
827
|
+
## Surrounding plan
|
|
828
|
+
${siblings || '(no sibling sub-tasks)'}
|
|
829
|
+
|
|
830
|
+
## Failed sub-task
|
|
831
|
+
ID: ${formatSubtaskId(failedSubtask.id)}
|
|
832
|
+
Title: ${failedSubtask.title}
|
|
833
|
+
Description: ${failedSubtask.description}
|
|
834
|
+
|
|
835
|
+
## Previous failure diagnostics
|
|
836
|
+
${diagnostics}
|
|
837
|
+
|
|
838
|
+
Split the failed sub-task above into exactly two smaller, independently implementable sub-tasks that together achieve the original goal. Each new sub-task should be a coherent unit of work that can be committed separately, ordered by dependency (foundation first). Take the diagnostics into account so the split actually addresses what broke.
|
|
839
|
+
|
|
840
|
+
Respond with valid JSON only — no markdown fences, no extra text:
|
|
841
|
+
{
|
|
842
|
+
"subtasks": [
|
|
843
|
+
{ "title": "Short title for the first new sub-task", "description": "Detailed description of what to implement in this step" },
|
|
844
|
+
{ "title": "Short title for the second new sub-task", "description": "Detailed description of what to implement in this step" }
|
|
845
|
+
]
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
Exactly two entries — no more, no fewer.`;
|
|
849
|
+
logger_1.logger.info(`Splitting failed sub-task ${formatSubtaskId(failedSubtask.id)} into two smaller steps...`);
|
|
850
|
+
const result = await runner.run(splitPrompt);
|
|
851
|
+
if (!result.success) {
|
|
852
|
+
logger_1.logger.error('Sub-task split failed');
|
|
853
|
+
return null;
|
|
854
|
+
}
|
|
855
|
+
try {
|
|
856
|
+
const jsonMatch = result.output.match(/\{[\s\S]*\}/);
|
|
857
|
+
if (!jsonMatch) {
|
|
858
|
+
logger_1.logger.error('Could not parse split response — no JSON found');
|
|
859
|
+
return null;
|
|
860
|
+
}
|
|
861
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
862
|
+
if (!parsed.subtasks || parsed.subtasks.length !== 2) {
|
|
863
|
+
logger_1.logger.error(`Split response must contain exactly 2 sub-tasks (got ${parsed.subtasks?.length ?? 0})`);
|
|
864
|
+
return null;
|
|
865
|
+
}
|
|
866
|
+
const newSubtasks = [];
|
|
867
|
+
for (let i = 0; i < 2; i++) {
|
|
868
|
+
const entry = parsed.subtasks[i];
|
|
869
|
+
const title = (entry?.title || '').trim();
|
|
870
|
+
const description = (entry?.description || '').trim();
|
|
871
|
+
if (!title || !description) {
|
|
872
|
+
logger_1.logger.error(`Split response sub-task ${i + 1} has empty title or description`);
|
|
873
|
+
return null;
|
|
874
|
+
}
|
|
875
|
+
newSubtasks.push({
|
|
876
|
+
id: `${failedSubtask.id}.${i + 1}`,
|
|
877
|
+
title,
|
|
878
|
+
description,
|
|
879
|
+
status: 'pending',
|
|
880
|
+
attempts: 0,
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
return newSubtasks;
|
|
884
|
+
}
|
|
885
|
+
catch (err) {
|
|
886
|
+
logger_1.logger.error(`Failed to parse split response: ${err}`);
|
|
887
|
+
return null;
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
async function executeSubTask(subtask, task, plan, config, runners, reviewContext, previousError) {
|
|
780
891
|
const instructionsPath = taskInstructionsPath(task.id);
|
|
781
892
|
const instructions = fs.existsSync(instructionsPath)
|
|
782
893
|
? fs.readFileSync(instructionsPath, 'utf8')
|
|
@@ -785,20 +896,23 @@ async function executeSubTask(subtask, task, plan, config, runners, reviewContex
|
|
|
785
896
|
.filter((s) => s.status === 'done')
|
|
786
897
|
.map((s) => ` - [done] ${s.id}. ${s.title}`)
|
|
787
898
|
.join('\n');
|
|
788
|
-
const
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
${
|
|
795
|
-
|
|
796
|
-
##
|
|
797
|
-
${
|
|
798
|
-
|
|
799
|
-
##
|
|
800
|
-
${
|
|
801
|
-
|
|
899
|
+
const retrySection = previousError && previousError !== '__git__'
|
|
900
|
+
? `\n## Previous attempt failure diagnostics\nThis step failed on a previous attempt. Diagnostics below — please take them into account and avoid repeating the same failure.\n\n${previousError}\n`
|
|
901
|
+
: '';
|
|
902
|
+
const prompt = `You are implementing step ${subtask.id} of a multi-step task.
|
|
903
|
+
|
|
904
|
+
Overall task: ${task.name}
|
|
905
|
+
${task.description ? `\nTask description:\n${task.description}` : ''}
|
|
906
|
+
|
|
907
|
+
## Full implementation instructions
|
|
908
|
+
${instructions}
|
|
909
|
+
${reviewContext || ''}${retrySection}
|
|
910
|
+
## Progress
|
|
911
|
+
${completedSteps || '(no steps completed yet)'}
|
|
912
|
+
|
|
913
|
+
## Current step: ${subtask.id}. ${subtask.title}
|
|
914
|
+
${subtask.description}
|
|
915
|
+
|
|
802
916
|
Implement ONLY this step. Focus on correctness and follow the existing code style.`;
|
|
803
917
|
let implemented = false;
|
|
804
918
|
let previousNotes = '';
|
|
@@ -850,10 +964,7 @@ async function implementThinkingTask(task, branchName, branchExists, config, pro
|
|
|
850
964
|
let context = '';
|
|
851
965
|
try {
|
|
852
966
|
const comments = await provider.getComments(task.id);
|
|
853
|
-
|
|
854
|
-
if (humanComments.length > 0) {
|
|
855
|
-
context = '\n\nConversation context:\n' + humanComments.map((c) => `${c.author}: ${c.text}`).join('\n');
|
|
856
|
-
}
|
|
967
|
+
context = await buildConversationContext(task.id, comments, config, runners);
|
|
857
968
|
}
|
|
858
969
|
catch { /* ignore */ }
|
|
859
970
|
if (branchExists) {
|
|
@@ -919,26 +1030,68 @@ async function implementThinkingTask(task, branchName, branchExists, config, pro
|
|
|
919
1030
|
title: s.title,
|
|
920
1031
|
description: s.description,
|
|
921
1032
|
status: (prev?.status ?? s.status),
|
|
1033
|
+
attempts: prev?.attempts ?? 0,
|
|
1034
|
+
lastError: prev?.lastError,
|
|
922
1035
|
};
|
|
923
1036
|
});
|
|
924
1037
|
writeTaskPlan(plan);
|
|
925
1038
|
}
|
|
926
1039
|
let allSucceeded = true;
|
|
927
|
-
for (
|
|
1040
|
+
for (let i = 0; i < plan.subtasks.length; i++) {
|
|
1041
|
+
const subtask = plan.subtasks[i];
|
|
928
1042
|
if (subtask.status === 'done') {
|
|
929
1043
|
logger_1.logger.info(` Step ${subtask.id} already done — skipping`);
|
|
930
1044
|
continue;
|
|
931
1045
|
}
|
|
1046
|
+
// Failure recovery on resume: a sub-task that ended in 'failed' on a prior run
|
|
1047
|
+
// gets a chance to be split into two smaller steps before we retry it.
|
|
1048
|
+
if (subtask.status === 'failed') {
|
|
1049
|
+
const isGitFailure = subtask.lastError === '__git__';
|
|
1050
|
+
const depth = subtaskDepth(subtask.id);
|
|
1051
|
+
const attempts = subtask.attempts ?? 0;
|
|
1052
|
+
const shouldSplit = !isGitFailure && attempts >= 2 && depth < 2;
|
|
1053
|
+
if (shouldSplit) {
|
|
1054
|
+
const failedId = subtask.id;
|
|
1055
|
+
const newSubtasks = await splitFailedSubtask(task, plan, subtask, runners);
|
|
1056
|
+
if (newSubtasks) {
|
|
1057
|
+
plan.subtasks.splice(i, 1, ...newSubtasks);
|
|
1058
|
+
writeTaskPlan(plan);
|
|
1059
|
+
const newIds = newSubtasks.map((s) => s.id).join(', ');
|
|
1060
|
+
logger_1.logger.info(` Step ${failedId} was split into ${newIds}`);
|
|
1061
|
+
try {
|
|
1062
|
+
await provider.postComment(task.id, `${config.commentPrefix} Step ${failedId} was split into ${newIds}:\n\n${formatSubtaskList(plan)}`);
|
|
1063
|
+
}
|
|
1064
|
+
catch { /* ignore */ }
|
|
1065
|
+
i--; // re-process this index — it now points at the first new sub-task
|
|
1066
|
+
continue;
|
|
1067
|
+
}
|
|
1068
|
+
logger_1.logger.warn(` Could not split failed step ${failedId} — falling back to plain retry`);
|
|
1069
|
+
try {
|
|
1070
|
+
await provider.postComment(task.id, `${config.commentPrefix} Failed to automatically split step ${failedId}. Retrying as-is.`);
|
|
1071
|
+
}
|
|
1072
|
+
catch { /* ignore */ }
|
|
1073
|
+
}
|
|
1074
|
+
else if (!isGitFailure && attempts >= 2 && depth >= 2) {
|
|
1075
|
+
logger_1.logger.warn(` Step ${subtask.id} has reached the split-depth cap — manual intervention may be needed`);
|
|
1076
|
+
try {
|
|
1077
|
+
await provider.postComment(task.id, `${config.commentPrefix} Step ${subtask.id} has already been split twice and is still failing. Retrying as-is — please consider manual intervention.`);
|
|
1078
|
+
}
|
|
1079
|
+
catch { /* ignore */ }
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
const previousError = subtask.lastError;
|
|
932
1083
|
subtask.status = 'running';
|
|
1084
|
+
subtask.attempts = (subtask.attempts ?? 0) + 1;
|
|
933
1085
|
writeTaskPlan(plan);
|
|
934
|
-
logger_1.logger.info(` Starting step ${subtask.id}: ${subtask.title}`);
|
|
935
|
-
const success = await executeSubTask(subtask, task, plan, config, runners, reviewContext);
|
|
1086
|
+
logger_1.logger.info(` Starting step ${subtask.id}: ${subtask.title} (attempt ${subtask.attempts})`);
|
|
1087
|
+
const success = await executeSubTask(subtask, task, plan, config, runners, reviewContext, previousError);
|
|
936
1088
|
if (!success) {
|
|
1089
|
+
const diagnostics = (0, diagnostics_1.collectAndLogDiagnostics)();
|
|
937
1090
|
subtask.status = 'failed';
|
|
1091
|
+
subtask.lastError = truncateError(diagnostics);
|
|
938
1092
|
writeTaskPlan(plan);
|
|
939
1093
|
allSucceeded = false;
|
|
940
1094
|
logger_1.logger.error(` Step ${subtask.id} failed: ${subtask.title}`);
|
|
941
|
-
const diagnostics = (0, diagnostics_1.collectAndLogDiagnostics)();
|
|
942
1095
|
try {
|
|
943
1096
|
await provider.postComment(task.id, `${config.commentPrefix} Step ${subtask.id} failed: ${subtask.title}\n\n${formatSubtaskList(plan)}\n\n${diagnostics}`);
|
|
944
1097
|
}
|
|
@@ -947,6 +1100,7 @@ async function implementThinkingTask(task, branchName, branchExists, config, pro
|
|
|
947
1100
|
}
|
|
948
1101
|
if (!git.addAll() || !git.commit(`${config.commentPrefix} Step ${subtask.id}: ${subtask.title}\n\nTask: ${task.url}`, branchName)) {
|
|
949
1102
|
subtask.status = 'failed';
|
|
1103
|
+
subtask.lastError = '__git__';
|
|
950
1104
|
writeTaskPlan(plan);
|
|
951
1105
|
allSucceeded = false;
|
|
952
1106
|
logger_1.logger.error(` Failed to commit step ${subtask.id}`);
|
|
@@ -954,6 +1108,7 @@ async function implementThinkingTask(task, branchName, branchExists, config, pro
|
|
|
954
1108
|
}
|
|
955
1109
|
if (!git.push(config.gitRemote, branchName)) {
|
|
956
1110
|
subtask.status = 'failed';
|
|
1111
|
+
subtask.lastError = '__git__';
|
|
957
1112
|
writeTaskPlan(plan);
|
|
958
1113
|
allSucceeded = false;
|
|
959
1114
|
logger_1.logger.error(` Failed to push step ${subtask.id}`);
|
|
@@ -1036,29 +1191,29 @@ function buildCompletionComment(branch, prUrl, config) {
|
|
|
1036
1191
|
function buildNonCodePrompt(task, context) {
|
|
1037
1192
|
const hasComments = context.trim().length > 0;
|
|
1038
1193
|
if (hasComments) {
|
|
1039
|
-
return `Task: ${task.name}
|
|
1040
|
-
|
|
1041
|
-
Original description:
|
|
1042
|
-
${task.description || '(no description provided)'}
|
|
1043
|
-
${context}
|
|
1044
|
-
|
|
1045
|
-
⚠️ CRITICAL: This is a FOLLOW-UP request. The conversation above contains new comments from the user.
|
|
1046
|
-
YOUR PRIMARY TASK is to address the LATEST comment at the bottom of the conversation - this is the user's current request.
|
|
1047
|
-
The latest comment may:
|
|
1048
|
-
- Ask for something completely different from the original task
|
|
1049
|
-
- Request modifications to what was already done
|
|
1050
|
-
- Add new requirements
|
|
1051
|
-
|
|
1052
|
-
DO NOT repeat what was already done. DO NOT re-execute the original task unless explicitly asked.
|
|
1053
|
-
Focus ENTIRELY on addressing the latest comment as your main instruction.
|
|
1054
|
-
|
|
1194
|
+
return `Task: ${task.name}
|
|
1195
|
+
|
|
1196
|
+
Original description:
|
|
1197
|
+
${task.description || '(no description provided)'}
|
|
1198
|
+
${context}
|
|
1199
|
+
|
|
1200
|
+
⚠️ CRITICAL: This is a FOLLOW-UP request. The conversation above contains new comments from the user.
|
|
1201
|
+
YOUR PRIMARY TASK is to address the LATEST comment at the bottom of the conversation - this is the user's current request.
|
|
1202
|
+
The latest comment may:
|
|
1203
|
+
- Ask for something completely different from the original task
|
|
1204
|
+
- Request modifications to what was already done
|
|
1205
|
+
- Add new requirements
|
|
1206
|
+
|
|
1207
|
+
DO NOT repeat what was already done. DO NOT re-execute the original task unless explicitly asked.
|
|
1208
|
+
Focus ENTIRELY on addressing the latest comment as your main instruction.
|
|
1209
|
+
|
|
1055
1210
|
Please provide a clear, detailed response to the LATEST comment. Your response will be posted as a comment on the task ticket, so write it as a direct answer or explanation addressed to the person who wrote the latest comment.`;
|
|
1056
1211
|
}
|
|
1057
|
-
return `Task: ${task.name}
|
|
1058
|
-
|
|
1059
|
-
Description:
|
|
1060
|
-
${task.description || '(no description provided)'}
|
|
1061
|
-
|
|
1212
|
+
return `Task: ${task.name}
|
|
1213
|
+
|
|
1214
|
+
Description:
|
|
1215
|
+
${task.description || '(no description provided)'}
|
|
1216
|
+
|
|
1062
1217
|
Please provide a clear, detailed response to this task. Your response will be posted as a comment on the task ticket, so write it as a direct answer or explanation addressed to the person who created the task.`;
|
|
1063
1218
|
}
|
|
1064
1219
|
function buildNonCodeCompletionComment(config, agentResponse) {
|
|
@@ -1094,6 +1249,21 @@ function hasAidevComment(comments, commentPrefix = '[aidev]') {
|
|
|
1094
1249
|
function filterAutomatedComments(comments, commentPrefix = '[aidev]') {
|
|
1095
1250
|
return comments.filter((c) => !c.text.includes(commentPrefix));
|
|
1096
1251
|
}
|
|
1252
|
+
async function buildConversationContext(taskId, comments, config, runners) {
|
|
1253
|
+
const humanComments = filterAutomatedComments(comments, config.commentPrefix);
|
|
1254
|
+
if (humanComments.length === 0)
|
|
1255
|
+
return '';
|
|
1256
|
+
const rawLength = '\n\nConversation context:\n'.length +
|
|
1257
|
+
humanComments.reduce((n, c, i) => n + c.author.length + 2 + c.text.length + (i > 0 ? 1 : 0), 0);
|
|
1258
|
+
const context = await (0, sessions_1.buildCompressedContext)(humanComments, taskId, runners, config);
|
|
1259
|
+
if (config.autoCompress &&
|
|
1260
|
+
humanComments.length > 1 &&
|
|
1261
|
+
rawLength > config.compressThreshold &&
|
|
1262
|
+
context.startsWith('\n\nSummary of earlier conversation')) {
|
|
1263
|
+
logger_1.logger.info(`[${taskId}] Auto-compressed conversation context: ${rawLength} → ${context.length} chars`);
|
|
1264
|
+
}
|
|
1265
|
+
return context;
|
|
1266
|
+
}
|
|
1097
1267
|
async function processNonCodeTask(task, filter, config, provider, runners, screenAvailable, hooks = {}, vm) {
|
|
1098
1268
|
const pendingStatus = getPendingStatus(config);
|
|
1099
1269
|
const openStatus = getOpenStatus(config);
|
|
@@ -1162,10 +1332,7 @@ async function implementNonCodeTask(task, config, provider, runners, hooks = {},
|
|
|
1162
1332
|
let context = '';
|
|
1163
1333
|
try {
|
|
1164
1334
|
const comments = await provider.getComments(task.id);
|
|
1165
|
-
|
|
1166
|
-
if (humanComments.length > 0) {
|
|
1167
|
-
context = '\n\nConversation context:\n' + humanComments.map((c) => `${c.author}: ${c.text}`).join('\n');
|
|
1168
|
-
}
|
|
1335
|
+
context = await buildConversationContext(task.id, comments, config, runners);
|
|
1169
1336
|
}
|
|
1170
1337
|
catch {
|
|
1171
1338
|
// ignore
|
|
@@ -1356,22 +1523,22 @@ async function implementReviewTask(task, branchName, config, provider, runners,
|
|
|
1356
1523
|
logger_1.logger.success(`Review comments addressed for: ${task.name}`);
|
|
1357
1524
|
}
|
|
1358
1525
|
function buildReviewPrompt(task, threads) {
|
|
1359
|
-
let prompt = `You are addressing code review comments on a pull request for a software development task.
|
|
1360
|
-
|
|
1361
|
-
Task: ${task.name}
|
|
1362
|
-
|
|
1363
|
-
Description:
|
|
1364
|
-
${task.description || '(no description provided)'}
|
|
1365
|
-
|
|
1366
|
-
## Unresolved Code Review Threads
|
|
1367
|
-
|
|
1368
|
-
The following review threads need to be addressed. For each thread, either:
|
|
1369
|
-
- Fix the code as requested (make the changes directly in the files)
|
|
1370
|
-
- Or, if it's a discussion/question that doesn't require code changes, output a REPLY block:
|
|
1371
|
-
<!-- AIDEV-REPLY thread_id -->Your reply here<!-- /AIDEV-REPLY -->
|
|
1372
|
-
|
|
1373
|
-
Replace "thread_id" with the actual thread ID shown below.
|
|
1374
|
-
|
|
1526
|
+
let prompt = `You are addressing code review comments on a pull request for a software development task.
|
|
1527
|
+
|
|
1528
|
+
Task: ${task.name}
|
|
1529
|
+
|
|
1530
|
+
Description:
|
|
1531
|
+
${task.description || '(no description provided)'}
|
|
1532
|
+
|
|
1533
|
+
## Unresolved Code Review Threads
|
|
1534
|
+
|
|
1535
|
+
The following review threads need to be addressed. For each thread, either:
|
|
1536
|
+
- Fix the code as requested (make the changes directly in the files)
|
|
1537
|
+
- Or, if it's a discussion/question that doesn't require code changes, output a REPLY block:
|
|
1538
|
+
<!-- AIDEV-REPLY thread_id -->Your reply here<!-- /AIDEV-REPLY -->
|
|
1539
|
+
|
|
1540
|
+
Replace "thread_id" with the actual thread ID shown below.
|
|
1541
|
+
|
|
1375
1542
|
`;
|
|
1376
1543
|
for (const thread of threads) {
|
|
1377
1544
|
const location = thread.line
|
|
@@ -1383,12 +1550,12 @@ Replace "thread_id" with the actual thread ID shown below.
|
|
|
1383
1550
|
}
|
|
1384
1551
|
prompt += '\n';
|
|
1385
1552
|
}
|
|
1386
|
-
prompt += `## Instructions
|
|
1387
|
-
|
|
1388
|
-
1. Read each review thread carefully
|
|
1389
|
-
2. For code change requests: make the fix directly in the relevant file(s)
|
|
1390
|
-
3. For questions or discussions: output a REPLY block with a clear, helpful response
|
|
1391
|
-
4. You may handle multiple threads — some with code fixes, others with replies
|
|
1553
|
+
prompt += `## Instructions
|
|
1554
|
+
|
|
1555
|
+
1. Read each review thread carefully
|
|
1556
|
+
2. For code change requests: make the fix directly in the relevant file(s)
|
|
1557
|
+
3. For questions or discussions: output a REPLY block with a clear, helpful response
|
|
1558
|
+
4. You may handle multiple threads — some with code fixes, others with replies
|
|
1392
1559
|
5. Focus on correctness and follow the existing code style`;
|
|
1393
1560
|
return prompt;
|
|
1394
1561
|
}
|