@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.
Files changed (106) hide show
  1. package/.aidev/assets/86c8yjxrr/9ea11c36-311c-4022-889c-1bb0915122dc.jpg-40e6939e3b68e864260f7ae7e85bd623_exif.jpg +0 -0
  2. package/.env.aidev.example +92 -84
  3. package/CONTRIBUTING.md +78 -78
  4. package/LICENSE +21 -21
  5. package/README.md +735 -622
  6. package/dist/autoCompress.d.ts +54 -0
  7. package/dist/autoCompress.d.ts.map +1 -0
  8. package/dist/autoCompress.js +300 -0
  9. package/dist/autoCompress.js.map +1 -0
  10. package/dist/cli.js +13 -0
  11. package/dist/cli.js.map +1 -1
  12. package/dist/commands/accepted.d.ts +7 -2
  13. package/dist/commands/accepted.d.ts.map +1 -1
  14. package/dist/commands/accepted.js +17 -2
  15. package/dist/commands/accepted.js.map +1 -1
  16. package/dist/commands/help.d.ts.map +1 -1
  17. package/dist/commands/help.js +80 -67
  18. package/dist/commands/help.js.map +1 -1
  19. package/dist/commands/init.js +105 -105
  20. package/dist/commands/run.d.ts +9 -1
  21. package/dist/commands/run.d.ts.map +1 -1
  22. package/dist/commands/run.js +307 -140
  23. package/dist/commands/run.js.map +1 -1
  24. package/dist/commands/tasks.d.ts +1 -0
  25. package/dist/commands/tasks.d.ts.map +1 -1
  26. package/dist/commands/tasks.js +29 -0
  27. package/dist/commands/tasks.js.map +1 -1
  28. package/dist/config.d.ts.map +1 -1
  29. package/dist/config.js +6 -0
  30. package/dist/config.js.map +1 -1
  31. package/dist/github.js +27 -27
  32. package/dist/hooks.d.ts +1 -1
  33. package/dist/hooks.d.ts.map +1 -1
  34. package/dist/providers/linear.d.ts +4 -1
  35. package/dist/providers/linear.d.ts.map +1 -1
  36. package/dist/providers/linear.js +142 -78
  37. package/dist/providers/linear.js.map +1 -1
  38. package/dist/providers/monday.js +39 -39
  39. package/dist/sessions.d.ts +20 -0
  40. package/dist/sessions.d.ts.map +1 -0
  41. package/dist/sessions.js +171 -0
  42. package/dist/sessions.js.map +1 -0
  43. package/dist/types.d.ts +2 -0
  44. package/dist/types.d.ts.map +1 -1
  45. package/package.json +51 -51
  46. package/scripts/run-tests.cjs +18 -18
  47. package/dist/__tests__/ai-runners.test.d.ts +0 -2
  48. package/dist/__tests__/ai-runners.test.d.ts.map +0 -1
  49. package/dist/__tests__/ai-runners.test.js +0 -367
  50. package/dist/__tests__/ai-runners.test.js.map +0 -1
  51. package/dist/__tests__/clickup-format.test.d.ts +0 -2
  52. package/dist/__tests__/clickup-format.test.d.ts.map +0 -1
  53. package/dist/__tests__/clickup-format.test.js +0 -256
  54. package/dist/__tests__/clickup-format.test.js.map +0 -1
  55. package/dist/__tests__/config.test.d.ts +0 -2
  56. package/dist/__tests__/config.test.d.ts.map +0 -1
  57. package/dist/__tests__/config.test.js +0 -418
  58. package/dist/__tests__/config.test.js.map +0 -1
  59. package/dist/__tests__/git.test.d.ts +0 -2
  60. package/dist/__tests__/git.test.d.ts.map +0 -1
  61. package/dist/__tests__/git.test.js +0 -697
  62. package/dist/__tests__/git.test.js.map +0 -1
  63. package/dist/__tests__/github.test.d.ts +0 -2
  64. package/dist/__tests__/github.test.d.ts.map +0 -1
  65. package/dist/__tests__/github.test.js +0 -273
  66. package/dist/__tests__/github.test.js.map +0 -1
  67. package/dist/__tests__/help.test.d.ts +0 -2
  68. package/dist/__tests__/help.test.d.ts.map +0 -1
  69. package/dist/__tests__/help.test.js +0 -34
  70. package/dist/__tests__/help.test.js.map +0 -1
  71. package/dist/__tests__/hooks.test.d.ts +0 -2
  72. package/dist/__tests__/hooks.test.d.ts.map +0 -1
  73. package/dist/__tests__/hooks.test.js +0 -381
  74. package/dist/__tests__/hooks.test.js.map +0 -1
  75. package/dist/__tests__/init.test.d.ts +0 -2
  76. package/dist/__tests__/init.test.d.ts.map +0 -1
  77. package/dist/__tests__/init.test.js +0 -596
  78. package/dist/__tests__/init.test.js.map +0 -1
  79. package/dist/__tests__/local-provider.test.d.ts +0 -2
  80. package/dist/__tests__/local-provider.test.d.ts.map +0 -1
  81. package/dist/__tests__/local-provider.test.js +0 -631
  82. package/dist/__tests__/local-provider.test.js.map +0 -1
  83. package/dist/__tests__/lockfile.test.d.ts +0 -2
  84. package/dist/__tests__/lockfile.test.d.ts.map +0 -1
  85. package/dist/__tests__/lockfile.test.js +0 -144
  86. package/dist/__tests__/lockfile.test.js.map +0 -1
  87. package/dist/__tests__/permissions.test.d.ts +0 -2
  88. package/dist/__tests__/permissions.test.d.ts.map +0 -1
  89. package/dist/__tests__/permissions.test.js +0 -151
  90. package/dist/__tests__/permissions.test.js.map +0 -1
  91. package/dist/__tests__/platform.test.d.ts +0 -2
  92. package/dist/__tests__/platform.test.d.ts.map +0 -1
  93. package/dist/__tests__/platform.test.js +0 -329
  94. package/dist/__tests__/platform.test.js.map +0 -1
  95. package/dist/__tests__/providers.test.d.ts +0 -2
  96. package/dist/__tests__/providers.test.d.ts.map +0 -1
  97. package/dist/__tests__/providers.test.js +0 -925
  98. package/dist/__tests__/providers.test.js.map +0 -1
  99. package/dist/__tests__/run.test.d.ts +0 -2
  100. package/dist/__tests__/run.test.d.ts.map +0 -1
  101. package/dist/__tests__/run.test.js +0 -767
  102. package/dist/__tests__/run.test.js.map +0 -1
  103. package/dist/__tests__/schedule.test.d.ts +0 -2
  104. package/dist/__tests__/schedule.test.d.ts.map +0 -1
  105. package/dist/__tests__/schedule.test.js +0 -258
  106. package/dist/__tests__/schedule.test.js.map +0 -1
@@ -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
- return JSON.parse(fs.readFileSync(p, 'utf8'));
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}.** ${s.title} — _${s.status}_`)
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
- const humanComments = filterAutomatedComments(comments, config.commentPrefix);
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 executeSubTask(subtask, task, plan, config, runners, reviewContext) {
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 prompt = `You are implementing step ${subtask.id} of a multi-step task.
789
-
790
- Overall task: ${task.name}
791
- ${task.description ? `\nTask description:\n${task.description}` : ''}
792
-
793
- ## Full implementation instructions
794
- ${instructions}
795
- ${reviewContext || ''}
796
- ## Progress
797
- ${completedSteps || '(no steps completed yet)'}
798
-
799
- ## Current step: ${subtask.id}. ${subtask.title}
800
- ${subtask.description}
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
- const humanComments = filterAutomatedComments(comments, config.commentPrefix);
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 (const subtask of plan.subtasks) {
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
- const humanComments = filterAutomatedComments(comments, config.commentPrefix);
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
  }