@qelos/aidev 0.3.0 → 0.4.0

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 (61) hide show
  1. package/.env.aidev.example +81 -62
  2. package/CONTRIBUTING.md +78 -78
  3. package/LICENSE +21 -21
  4. package/README.md +499 -467
  5. package/dist/__tests__/clickup-format.test.d.ts +2 -0
  6. package/dist/__tests__/clickup-format.test.d.ts.map +1 -0
  7. package/dist/__tests__/clickup-format.test.js +256 -0
  8. package/dist/__tests__/clickup-format.test.js.map +1 -0
  9. package/dist/__tests__/hooks.test.d.ts +2 -0
  10. package/dist/__tests__/hooks.test.d.ts.map +1 -0
  11. package/dist/__tests__/hooks.test.js +329 -0
  12. package/dist/__tests__/hooks.test.js.map +1 -0
  13. package/dist/__tests__/init.test.js +33 -0
  14. package/dist/__tests__/init.test.js.map +1 -1
  15. package/dist/__tests__/local-provider.test.js +31 -31
  16. package/dist/__tests__/platform.test.js +5 -5
  17. package/dist/__tests__/providers.test.js +166 -0
  18. package/dist/__tests__/providers.test.js.map +1 -1
  19. package/dist/__tests__/run.test.js +21 -8
  20. package/dist/__tests__/run.test.js.map +1 -1
  21. package/dist/cli.js +5 -1
  22. package/dist/cli.js.map +1 -1
  23. package/dist/commands/help.d.ts.map +1 -1
  24. package/dist/commands/help.js +67 -61
  25. package/dist/commands/help.js.map +1 -1
  26. package/dist/commands/init.d.ts +1 -0
  27. package/dist/commands/init.d.ts.map +1 -1
  28. package/dist/commands/init.js +125 -0
  29. package/dist/commands/init.js.map +1 -1
  30. package/dist/commands/run.d.ts +2 -1
  31. package/dist/commands/run.d.ts.map +1 -1
  32. package/dist/commands/run.js +248 -119
  33. package/dist/commands/run.js.map +1 -1
  34. package/dist/config.d.ts.map +1 -1
  35. package/dist/config.js +16 -1
  36. package/dist/config.js.map +1 -1
  37. package/dist/github.js +23 -23
  38. package/dist/hooks.d.ts +106 -0
  39. package/dist/hooks.d.ts.map +1 -0
  40. package/dist/hooks.js +146 -0
  41. package/dist/hooks.js.map +1 -0
  42. package/dist/providers/clickup-format.d.ts +16 -0
  43. package/dist/providers/clickup-format.d.ts.map +1 -0
  44. package/dist/providers/clickup-format.js +255 -0
  45. package/dist/providers/clickup-format.js.map +1 -0
  46. package/dist/providers/clickup.d.ts.map +1 -1
  47. package/dist/providers/clickup.js +8 -6
  48. package/dist/providers/clickup.js.map +1 -1
  49. package/dist/providers/index.d.ts.map +1 -1
  50. package/dist/providers/index.js +2 -1
  51. package/dist/providers/index.js.map +1 -1
  52. package/dist/providers/linear.js +70 -70
  53. package/dist/providers/monday.js +39 -39
  54. package/dist/providers/trello.d.ts +32 -0
  55. package/dist/providers/trello.d.ts.map +1 -0
  56. package/dist/providers/trello.js +227 -0
  57. package/dist/providers/trello.js.map +1 -0
  58. package/dist/types.d.ts +12 -0
  59. package/dist/types.d.ts.map +1 -1
  60. package/package.json +51 -50
  61. package/scripts/run-tests.cjs +18 -18
@@ -1,6 +1,7 @@
1
1
  import { Config, Task, Comment } from '../types';
2
2
  import { TaskProvider } from '../providers';
3
3
  import { AIRunner } from '../ai';
4
+ import { AidevHooks, HookVM } from '../hooks';
4
5
  export declare const DEFAULT_TRIGGER_WORD = "aidev-continue";
5
6
  export declare function getPendingStatus(config: Config): string;
6
7
  export declare function getOpenStatus(config: Config): string;
@@ -19,7 +20,7 @@ export interface ThinkingTaskPlan {
19
20
  }
20
21
  export declare function getRunSkipReason(status: string, filter: RunFilter, pendingStatus: string, openStatus?: string): string | null;
21
22
  export declare function sortTasksByPriority(tasks: Task[]): Task[];
22
- export declare function runCommand(filter: RunFilter, config: Config, provider: TaskProvider, runners: AIRunner[], nonCodeProvider?: TaskProvider): Promise<void>;
23
+ export declare function runCommand(filter: RunFilter, config: Config, provider: TaskProvider, runners: AIRunner[], nonCodeProvider?: TaskProvider, hooks?: AidevHooks, vm?: HookVM): Promise<void>;
23
24
  export declare function hasHumanReply(comments: Comment[], commentPrefix?: string): boolean;
24
25
  /**
25
26
  * Returns true if the last comment contains the trigger word.
@@ -1 +1 @@
1
- {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/commands/run.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/commands/run.ts"],"names":[],"mappings":"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"}
@@ -60,6 +60,7 @@ const git = __importStar(require("../git"));
60
60
  const github_1 = require("../github");
61
61
  const diagnostics_1 = require("../diagnostics");
62
62
  const lockfile_1 = require("../lockfile");
63
+ const hooks_1 = require("../hooks");
63
64
  const SKIP_STATUSES = new Set(['closed', 'done', 'cancelled', 'complete']);
64
65
  const NO_PRIORITY = Number.MAX_SAFE_INTEGER;
65
66
  const SLEEPING_MARKER = 'machine appears to be asleep';
@@ -72,6 +73,8 @@ function getPendingStatus(config) {
72
73
  return config.linearPendingStatus;
73
74
  if (p === 'notion')
74
75
  return config.notionPendingStatus;
76
+ if (p === 'trello')
77
+ return config.trelloPendingStatus;
75
78
  return config.clickupPendingStatus;
76
79
  }
77
80
  function getOpenStatus(config) {
@@ -80,6 +83,8 @@ function getOpenStatus(config) {
80
83
  return 'open';
81
84
  if (p === 'linear')
82
85
  return 'open';
86
+ if (p === 'trello')
87
+ return config.trelloOpenStatus || 'open';
83
88
  return config.clickupOpenStatus || 'open';
84
89
  }
85
90
  function getInReviewStatus(config) {
@@ -90,6 +95,8 @@ function getInReviewStatus(config) {
90
95
  return config.linearInReviewStatus;
91
96
  if (p === 'notion')
92
97
  return config.notionInReviewStatus;
98
+ if (p === 'trello')
99
+ return config.trelloInReviewStatus;
93
100
  return config.clickupInReviewStatus;
94
101
  }
95
102
  function getRunSkipReason(status, filter, pendingStatus, openStatus = 'open') {
@@ -160,7 +167,7 @@ function formatSubtaskList(plan) {
160
167
  .map((s) => `${icons[s.status]} **${s.id}.** ${s.title} — _${s.status}_`)
161
168
  .join('\n');
162
169
  }
163
- async function runCommand(filter, config, provider, runners, nonCodeProvider) {
170
+ async function runCommand(filter, config, provider, runners, nonCodeProvider, hooks = {}, vm) {
164
171
  const cwd = process.cwd();
165
172
  if (!(0, lockfile_1.acquireLock)(cwd)) {
166
173
  const pid = (0, lockfile_1.readLock)(cwd);
@@ -176,10 +183,15 @@ async function runCommand(filter, config, provider, runners, nonCodeProvider) {
176
183
  logger_1.logger.info(`Fetching tasks (filter: ${filter})...`);
177
184
  const tasks = sortTasksByPriority(await provider.fetchTasks());
178
185
  logger_1.logger.info(`Found ${tasks.length} tagged task(s)`);
186
+ // beforeRun hook
187
+ if (vm) {
188
+ const runCtx = { config, filter, taskCount: tasks.length };
189
+ await (0, hooks_1.executeHook)(hooks, 'beforeRun', runCtx, vm);
190
+ }
179
191
  let processed = 0;
180
192
  let skipped = 0;
181
193
  for (const task of tasks) {
182
- const result = await processTask(task, filter, config, provider, runners, screenAvailable);
194
+ const result = await processTask(task, filter, config, provider, runners, screenAvailable, hooks, vm);
183
195
  if (result === 'processed')
184
196
  processed++;
185
197
  else
@@ -190,20 +202,27 @@ async function runCommand(filter, config, provider, runners, nonCodeProvider) {
190
202
  const nonCodeTasks = sortTasksByPriority(await nonCodeProvider.fetchTasks());
191
203
  logger_1.logger.info(`Found ${nonCodeTasks.length} non-code task(s)`);
192
204
  for (const task of nonCodeTasks) {
193
- const result = await processNonCodeTask(task, filter, config, nonCodeProvider, runners, screenAvailable);
205
+ const result = await processNonCodeTask(task, filter, config, nonCodeProvider, runners, screenAvailable, hooks, vm);
194
206
  if (result === 'processed')
195
207
  processed++;
196
208
  else
197
209
  skipped++;
198
210
  }
199
211
  }
212
+ // afterRun hook
213
+ if (vm) {
214
+ const afterCtx = {
215
+ config, filter, taskCount: tasks.length, processed, skipped,
216
+ };
217
+ await (0, hooks_1.executeHook)(hooks, 'afterRun', afterCtx, vm);
218
+ }
200
219
  logger_1.logger.success(`Done. Processed: ${processed}, Skipped: ${skipped}`);
201
220
  }
202
221
  finally {
203
222
  (0, lockfile_1.releaseLock)(cwd);
204
223
  }
205
224
  }
206
- async function processTask(task, filter, config, provider, runners, screenAvailable) {
225
+ async function processTask(task, filter, config, provider, runners, screenAvailable, hooks = {}, vm) {
207
226
  const pendingStatus = getPendingStatus(config);
208
227
  const openStatus = getOpenStatus(config);
209
228
  const isPending = task.status.toLowerCase() === pendingStatus.toLowerCase();
@@ -254,10 +273,10 @@ async function processTask(task, filter, config, provider, runners, screenAvaila
254
273
  }
255
274
  }
256
275
  if (isThinkingTask(task, config)) {
257
- await implementThinkingTask(task, branchName, branchExists, config, provider, runners);
276
+ await implementThinkingTask(task, branchName, branchExists, config, provider, runners, hooks, vm);
258
277
  }
259
278
  else {
260
- await implementTask(task, branchName, branchExists, config, provider, runners);
279
+ await implementTask(task, branchName, branchExists, config, provider, runners, hooks, vm);
261
280
  }
262
281
  return 'processed';
263
282
  }
@@ -313,16 +332,16 @@ async function checkNeedsClarification(task, config, provider, runners) {
313
332
  logger_1.logger.warn('No AI runner available — skipping clarification check');
314
333
  return null;
315
334
  }
316
- const clarificationPrompt = `You are a senior software developer reviewing a task.
317
- Determine if the following task has enough information to implement without further clarification.
318
-
319
- Task name: ${task.name}
320
- Task description: ${task.description || '(no description)'}
321
-
322
- Respond with valid JSON only:
323
- {
324
- "clear": true|false,
325
- "question": "question to ask if not clear, or null"
335
+ const clarificationPrompt = `You are a senior software developer reviewing a task.
336
+ Determine if the following task has enough information to implement without further clarification.
337
+
338
+ Task name: ${task.name}
339
+ Task description: ${task.description || '(no description)'}
340
+
341
+ Respond with valid JSON only:
342
+ {
343
+ "clear": true|false,
344
+ "question": "question to ask if not clear, or null"
326
345
  }`;
327
346
  for (const runner of availableRunners) {
328
347
  const result = await runner.run(clarificationPrompt);
@@ -350,33 +369,33 @@ Respond with valid JSON only:
350
369
  return null;
351
370
  }
352
371
  function buildConflictResolutionPrompt(task, conflictFiles, context) {
353
- return `You are resolving merge conflicts in a software development task branch.
354
-
355
- The task branch has fallen behind the base branch and has merge conflicts that need to be resolved.
356
-
357
- ## Task context (DO NOT break this — the task must still work after conflict resolution)
358
-
359
- Task: ${task.name}
360
-
361
- Description:
362
- ${task.description || '(no description provided)'}
363
- ${context}
364
-
365
- ## Merge conflicts
366
-
367
- The following files have merge conflicts with conflict markers (<<<<<<< HEAD, =======, >>>>>>> ...):
368
- ${conflictFiles.map((f) => `- ${f}`).join('\n')}
369
-
370
- ## Instructions
371
-
372
- 1. Open each conflicting file and resolve the conflict markers
373
- 2. Keep BOTH the task's changes AND the base branch updates where possible
374
- 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
375
- 4. Remove all conflict markers (<<<<<<< HEAD, =======, >>>>>>> ...)
376
- 5. Make sure the code compiles and is consistent after resolution
372
+ return `You are resolving merge conflicts in a software development task branch.
373
+
374
+ The task branch has fallen behind the base branch and has merge conflicts that need to be resolved.
375
+
376
+ ## Task context (DO NOT break this — the task must still work after conflict resolution)
377
+
378
+ Task: ${task.name}
379
+
380
+ Description:
381
+ ${task.description || '(no description provided)'}
382
+ ${context}
383
+
384
+ ## Merge conflicts
385
+
386
+ The following files have merge conflicts with conflict markers (<<<<<<< HEAD, =======, >>>>>>> ...):
387
+ ${conflictFiles.map((f) => `- ${f}`).join('\n')}
388
+
389
+ ## Instructions
390
+
391
+ 1. Open each conflicting file and resolve the conflict markers
392
+ 2. Keep BOTH the task's changes AND the base branch updates where possible
393
+ 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
394
+ 4. Remove all conflict markers (<<<<<<< HEAD, =======, >>>>>>> ...)
395
+ 5. Make sure the code compiles and is consistent after resolution
377
396
  6. Do NOT make any changes beyond what is needed to resolve the conflicts`;
378
397
  }
379
- async function resolveConflictsWithAI(task, config, provider, runners, context, branchName) {
398
+ async function resolveConflictsWithAI(task, config, provider, runners, context, hooks, vm, branchName) {
380
399
  const check = git.checkConflictsWithBase(config.gitRemote, config.githubBaseBranch);
381
400
  if (check.behindCommits === 0) {
382
401
  logger_1.logger.debug('Branch is up to date with base — no merge needed');
@@ -401,7 +420,13 @@ async function resolveConflictsWithAI(task, config, provider, runners, context,
401
420
  }
402
421
  catch { /* ignore */ }
403
422
  if (!git.mergeBaseBranch(config.gitRemote, config.githubBaseBranch)) {
404
- const prompt = buildConflictResolutionPrompt(task, check.conflictFiles, context);
423
+ let prompt = buildConflictResolutionPrompt(task, check.conflictFiles, context);
424
+ // beforeResolveConflicts hook
425
+ if (vm) {
426
+ const conflictCtx = { task, config, branchName, conflictFiles: check.conflictFiles, prompt };
427
+ const modified = await (0, hooks_1.executeHook)(hooks, 'beforeResolveConflicts', conflictCtx, vm);
428
+ prompt = modified.prompt;
429
+ }
405
430
  let resolved = false;
406
431
  let previousNotes = '';
407
432
  for (const runner of runners) {
@@ -423,7 +448,22 @@ async function resolveConflictsWithAI(task, config, provider, runners, context,
423
448
  }
424
449
  if (!resolved) {
425
450
  logger_1.logger.error('All AI runners failed to resolve merge conflicts');
426
- git.abortMerge();
451
+ try {
452
+ if (vm) {
453
+ const afterFailCtx = {
454
+ task,
455
+ config,
456
+ branchName,
457
+ conflictFiles: check.conflictFiles,
458
+ prompt,
459
+ resolved: false,
460
+ };
461
+ await (0, hooks_1.executeHook)(hooks, 'afterResolveConflicts', afterFailCtx, vm);
462
+ }
463
+ }
464
+ finally {
465
+ git.abortMerge();
466
+ }
427
467
  try {
428
468
  await provider.postComment(task.id, `${config.commentPrefix} Failed to automatically resolve merge conflicts. Manual intervention needed to rebase/merge the branch.`);
429
469
  }
@@ -439,6 +479,13 @@ async function resolveConflictsWithAI(task, config, provider, runners, context,
439
479
  logger_1.logger.warn('Failed to push conflict resolution — continuing anyway');
440
480
  }
441
481
  logger_1.logger.success('Merge conflicts resolved successfully');
482
+ // afterResolveConflicts hook
483
+ if (vm) {
484
+ const afterCtx = {
485
+ task, config, branchName, conflictFiles: check.conflictFiles, prompt, resolved: true,
486
+ };
487
+ await (0, hooks_1.executeHook)(hooks, 'afterResolveConflicts', afterCtx, vm);
488
+ }
442
489
  try {
443
490
  await provider.postComment(task.id, `${config.commentPrefix} Merge conflicts resolved automatically.`);
444
491
  }
@@ -489,7 +536,7 @@ function resolveHandledThreads(threads) {
489
536
  logger_1.logger.info(`Resolved ${resolved}/${threads.length} review thread(s) on GitHub`);
490
537
  }
491
538
  }
492
- async function implementTask(task, branchName, branchExists, config, provider, runners) {
539
+ async function implementTask(task, branchName, branchExists, config, provider, runners, hooks = {}, vm) {
493
540
  logger_1.logger.info(`Implementing task: ${task.name}`);
494
541
  try {
495
542
  await provider.updateStatus(task.id, 'in progress');
@@ -526,7 +573,7 @@ async function implementTask(task, branchName, branchExists, config, provider, r
526
573
  // ignore
527
574
  }
528
575
  if (branchExists) {
529
- const conflictsOk = await resolveConflictsWithAI(task, config, provider, runners, context, branchName);
576
+ const conflictsOk = await resolveConflictsWithAI(task, config, provider, runners, context, hooks, vm, branchName);
530
577
  if (!conflictsOk) {
531
578
  logger_1.logger.error('Cannot proceed — merge conflicts could not be resolved');
532
579
  return;
@@ -540,7 +587,13 @@ async function implementTask(task, branchName, branchExists, config, provider, r
540
587
  logger_1.logger.info(`Found ${reviewThreads.length} unresolved review comment(s) to address`);
541
588
  context += formatReviewComments(reviewThreads);
542
589
  }
543
- const implementPrompt = buildImplementPrompt(task, context);
590
+ let implementPrompt = buildImplementPrompt(task, context);
591
+ // beforeEachTask hook — may modify context (e.g. improve the prompt)
592
+ if (vm) {
593
+ const taskCtx = { task, config, branchName, prompt: implementPrompt };
594
+ const modified = await (0, hooks_1.executeHook)(hooks, 'beforeEachTask', taskCtx, vm);
595
+ implementPrompt = modified.prompt;
596
+ }
544
597
  // Run AI runners in order with fallback
545
598
  let implemented = false;
546
599
  let previousNotes = '';
@@ -602,17 +655,22 @@ async function implementTask(task, branchName, branchExists, config, provider, r
602
655
  catch (err) {
603
656
  logger_1.logger.warn(`Branch pushed but failed to update task: ${err instanceof Error ? err.message : err}`);
604
657
  }
658
+ // afterEachTask hook
659
+ if (vm) {
660
+ const afterCtx = { task, config, branchName, prompt: implementPrompt, success: true };
661
+ await (0, hooks_1.executeHook)(hooks, 'afterEachTask', afterCtx, vm);
662
+ }
605
663
  logger_1.logger.success(`Task implemented: branch ${branchName} pushed`);
606
664
  }
607
665
  function buildImplementPrompt(task, context) {
608
- return `You are implementing a software development task. Make the necessary code changes to complete the task described below.
609
-
610
- Task: ${task.name}
611
-
612
- Description:
613
- ${task.description || '(no description provided)'}
614
- ${context}
615
-
666
+ return `You are implementing a software development task. Make the necessary code changes to complete the task described below.
667
+
668
+ Task: ${task.name}
669
+
670
+ Description:
671
+ ${task.description || '(no description provided)'}
672
+ ${context}
673
+
616
674
  Please implement the required changes. Focus on correctness and follow the existing code style in the project.`;
617
675
  }
618
676
  async function analyzeAndPlan(task, context, runners) {
@@ -621,28 +679,28 @@ async function analyzeAndPlan(task, context, runners) {
621
679
  logger_1.logger.error('No AI runner available for task analysis');
622
680
  return null;
623
681
  }
624
- const analysisPrompt = `You are a senior software architect breaking down a development task into smaller, sequential implementation steps.
625
-
626
- Task name: ${task.name}
627
-
628
- Description:
629
- ${task.description || '(no description provided)'}
630
- ${context}
631
-
632
- 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.
633
-
634
- Respond with valid JSON only — no markdown fences, no extra text:
635
- {
636
- "instructions": "Detailed implementation instructions in markdown covering the full task — architecture decisions, key files to modify, edge cases to handle, testing approach",
637
- "subtasks": [
638
- {
639
- "id": 1,
640
- "title": "Short title for the sub-task",
641
- "description": "Detailed description of what to implement in this step, including specific files and functions to change"
642
- }
643
- ]
644
- }
645
-
682
+ const analysisPrompt = `You are a senior software architect breaking down a development task into smaller, sequential implementation steps.
683
+
684
+ Task name: ${task.name}
685
+
686
+ Description:
687
+ ${task.description || '(no description provided)'}
688
+ ${context}
689
+
690
+ 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.
691
+
692
+ Respond with valid JSON only — no markdown fences, no extra text:
693
+ {
694
+ "instructions": "Detailed implementation instructions in markdown covering the full task — architecture decisions, key files to modify, edge cases to handle, testing approach",
695
+ "subtasks": [
696
+ {
697
+ "id": 1,
698
+ "title": "Short title for the sub-task",
699
+ "description": "Detailed description of what to implement in this step, including specific files and functions to change"
700
+ }
701
+ ]
702
+ }
703
+
646
704
  Keep sub-tasks focused: 2-6 sub-tasks is ideal. Order them by dependency (foundation first).`;
647
705
  logger_1.logger.info('Analyzing task and creating implementation plan...');
648
706
  const result = await runner.run(analysisPrompt);
@@ -689,20 +747,20 @@ async function executeSubTask(subtask, task, plan, config, runners, reviewContex
689
747
  .filter((s) => s.status === 'done')
690
748
  .map((s) => ` - [done] ${s.id}. ${s.title}`)
691
749
  .join('\n');
692
- const prompt = `You are implementing step ${subtask.id} of a multi-step task.
693
-
694
- Overall task: ${task.name}
695
- ${task.description ? `\nTask description:\n${task.description}` : ''}
696
-
697
- ## Full implementation instructions
698
- ${instructions}
699
- ${reviewContext || ''}
700
- ## Progress
701
- ${completedSteps || '(no steps completed yet)'}
702
-
703
- ## Current step: ${subtask.id}. ${subtask.title}
704
- ${subtask.description}
705
-
750
+ const prompt = `You are implementing step ${subtask.id} of a multi-step task.
751
+
752
+ Overall task: ${task.name}
753
+ ${task.description ? `\nTask description:\n${task.description}` : ''}
754
+
755
+ ## Full implementation instructions
756
+ ${instructions}
757
+ ${reviewContext || ''}
758
+ ## Progress
759
+ ${completedSteps || '(no steps completed yet)'}
760
+
761
+ ## Current step: ${subtask.id}. ${subtask.title}
762
+ ${subtask.description}
763
+
706
764
  Implement ONLY this step. Focus on correctness and follow the existing code style.`;
707
765
  let implemented = false;
708
766
  let previousNotes = '';
@@ -726,7 +784,7 @@ Implement ONLY this step. Focus on correctness and follow the existing code styl
726
784
  }
727
785
  return implemented;
728
786
  }
729
- async function implementThinkingTask(task, branchName, branchExists, config, provider, runners) {
787
+ async function implementThinkingTask(task, branchName, branchExists, config, provider, runners, hooks = {}, vm) {
730
788
  logger_1.logger.info(`Implementing thinking task: ${task.name}`);
731
789
  try {
732
790
  await provider.updateStatus(task.id, 'in progress');
@@ -761,7 +819,7 @@ async function implementThinkingTask(task, branchName, branchExists, config, pro
761
819
  }
762
820
  catch { /* ignore */ }
763
821
  if (branchExists) {
764
- const conflictsOk = await resolveConflictsWithAI(task, config, provider, runners, context, branchName);
822
+ const conflictsOk = await resolveConflictsWithAI(task, config, provider, runners, context, hooks, vm, branchName);
765
823
  if (!conflictsOk) {
766
824
  logger_1.logger.error('Cannot proceed — merge conflicts could not be resolved');
767
825
  return;
@@ -801,6 +859,32 @@ async function implementThinkingTask(task, branchName, branchExists, config, pro
801
859
  logger_1.logger.warn(`Failed to post breakdown comment: ${err}`);
802
860
  }
803
861
  }
862
+ // beforeThinkingTask hook — may adjust subtask titles/descriptions before execution
863
+ if (vm) {
864
+ const thinkCtx = {
865
+ task,
866
+ config,
867
+ branchName,
868
+ subtasks: plan.subtasks.map((s) => ({
869
+ id: s.id,
870
+ title: s.title,
871
+ description: s.description,
872
+ status: s.status,
873
+ })),
874
+ };
875
+ const modified = await (0, hooks_1.executeHook)(hooks, 'beforeThinkingTask', thinkCtx, vm);
876
+ const prevById = new Map(plan.subtasks.map((s) => [s.id, s]));
877
+ plan.subtasks = modified.subtasks.map((s) => {
878
+ const prev = prevById.get(s.id);
879
+ return {
880
+ id: s.id,
881
+ title: s.title,
882
+ description: s.description,
883
+ status: (prev?.status ?? s.status),
884
+ };
885
+ });
886
+ writeTaskPlan(plan);
887
+ }
804
888
  let allSucceeded = true;
805
889
  for (const subtask of plan.subtasks) {
806
890
  if (subtask.status === 'done') {
@@ -867,6 +951,13 @@ async function implementThinkingTask(task, branchName, branchExists, config, pro
867
951
  catch (err) {
868
952
  logger_1.logger.warn(`Branch pushed but failed to update task: ${err instanceof Error ? err.message : err}`);
869
953
  }
954
+ // afterThinkingTask hook
955
+ if (vm) {
956
+ const afterCtx = {
957
+ task, config, branchName, subtasks: plan.subtasks.map((s) => ({ ...s })), success: true,
958
+ };
959
+ await (0, hooks_1.executeHook)(hooks, 'afterThinkingTask', afterCtx, vm);
960
+ }
870
961
  logger_1.logger.success(`Thinking task implemented: branch ${branchName} pushed`);
871
962
  }
872
963
  /**
@@ -903,25 +994,29 @@ function buildCompletionComment(branch, prUrl, config) {
903
994
  function buildNonCodePrompt(task, context) {
904
995
  const hasComments = context.trim().length > 0;
905
996
  if (hasComments) {
906
- return `You are handling a non-code task. This task does NOT require code changes — it requires a thoughtful, verbal response.
907
-
908
- Task: ${task.name}
909
-
910
- Original description:
911
- ${task.description || '(no description provided)'}
912
- ${context}
913
-
914
- IMPORTANT: The conversation above contains follow-up comments. Focus on the LATEST comment as the primary request to address — it may refine, override, or follow up on the original description. Use the original description and earlier comments only as background context.
915
-
916
- Please provide a clear, detailed response. 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.`;
917
- }
918
- return `You are handling a non-code task. This task does NOT require code changes — it requires a thoughtful, verbal response.
919
-
920
- Task: ${task.name}
921
-
922
- Description:
923
- ${task.description || '(no description provided)'}
924
-
997
+ return `Task: ${task.name}
998
+
999
+ Original description:
1000
+ ${task.description || '(no description provided)'}
1001
+ ${context}
1002
+
1003
+ ⚠️ CRITICAL: This is a FOLLOW-UP request. The conversation above contains new comments from the user.
1004
+ YOUR PRIMARY TASK is to address the LATEST comment at the bottom of the conversation - this is the user's current request.
1005
+ The latest comment may:
1006
+ - Ask for something completely different from the original task
1007
+ - Request modifications to what was already done
1008
+ - Add new requirements
1009
+
1010
+ DO NOT repeat what was already done. DO NOT re-execute the original task unless explicitly asked.
1011
+ Focus ENTIRELY on addressing the latest comment as your main instruction.
1012
+
1013
+ 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.`;
1014
+ }
1015
+ return `Task: ${task.name}
1016
+
1017
+ Description:
1018
+ ${task.description || '(no description provided)'}
1019
+
925
1020
  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.`;
926
1021
  }
927
1022
  function buildNonCodeCompletionComment(config, agentResponse) {
@@ -929,7 +1024,24 @@ function buildNonCodeCompletionComment(config, agentResponse) {
929
1024
  `${config.commentPrefix} Non-code task complete!`,
930
1025
  ];
931
1026
  if (agentResponse) {
932
- lines.push(``, `---`, ``, agentResponse);
1027
+ // Filter out instructional text like "Here's text you can paste as the **task ticket comment**"
1028
+ // and extract only the actual content
1029
+ let cleanedResponse = agentResponse;
1030
+ // Look for the content after the first "---" separator, as the actual content usually follows
1031
+ const separatorIndex = agentResponse.indexOf('---');
1032
+ if (separatorIndex !== -1) {
1033
+ const afterSeparator = agentResponse.substring(separatorIndex + 3).trim();
1034
+ // Look for the start of actual content (skip any remaining instructional text)
1035
+ const lines = afterSeparator.split('\n');
1036
+ const contentStartIndex = lines.findIndex(line => line.trim() &&
1037
+ !line.toLowerCase().includes('here\'s text you can paste') &&
1038
+ !line.toLowerCase().includes('task ticket comment') &&
1039
+ !line.toLowerCase().includes('addresses your latest ask'));
1040
+ if (contentStartIndex !== -1) {
1041
+ cleanedResponse = lines.slice(contentStartIndex).join('\n').trim();
1042
+ }
1043
+ }
1044
+ lines.push(``, `---`, ``, cleanedResponse);
933
1045
  }
934
1046
  lines.push(``, `Status set to: ${getInReviewStatus(config)}`);
935
1047
  return lines.join('\n');
@@ -940,7 +1052,7 @@ function hasAidevComment(comments, commentPrefix = '[aidev]') {
940
1052
  function filterAutomatedComments(comments, commentPrefix = '[aidev]') {
941
1053
  return comments.filter((c) => !c.text.includes(commentPrefix));
942
1054
  }
943
- async function processNonCodeTask(task, filter, config, provider, runners, screenAvailable) {
1055
+ async function processNonCodeTask(task, filter, config, provider, runners, screenAvailable, hooks = {}, vm) {
944
1056
  const pendingStatus = getPendingStatus(config);
945
1057
  const openStatus = getOpenStatus(config);
946
1058
  const isPending = task.status.toLowerCase() === pendingStatus.toLowerCase();
@@ -954,6 +1066,7 @@ async function processNonCodeTask(task, filter, config, provider, runners, scree
954
1066
  const wasProcessed = hasAidevComment(comments, config.commentPrefix);
955
1067
  if (isPending || wasProcessed) {
956
1068
  const trigger = hasTriggerWord(comments, config.triggerWord);
1069
+ const hasHumanFollowUp = hasHumanReply(comments, config.commentPrefix);
957
1070
  if (isPending) {
958
1071
  const reply = hasHumanReply(comments, config.commentPrefix);
959
1072
  if (!reply && !trigger) {
@@ -965,11 +1078,14 @@ async function processNonCodeTask(task, filter, config, provider, runners, scree
965
1078
  : 'Pending non-code task has a human reply — proceeding');
966
1079
  }
967
1080
  else {
968
- if (!trigger) {
969
- logger_1.logger.info(`[${task.id}] "${task.name}" skipped — already processed, no trigger word`);
1081
+ // For already processed tasks, check if there's a human follow-up or trigger word
1082
+ if (!trigger && !hasHumanFollowUp) {
1083
+ logger_1.logger.info(`[${task.id}] "${task.name}" skipped — already processed, no trigger word or human follow-up`);
970
1084
  return 'skipped';
971
1085
  }
972
- logger_1.logger.info(`Trigger word "${config.triggerWord}" found — re-processing non-code task`);
1086
+ logger_1.logger.info(trigger
1087
+ ? `Trigger word "${config.triggerWord}" found — re-processing non-code task`
1088
+ : 'Human follow-up comment found — processing non-code task');
973
1089
  }
974
1090
  if (!screenAvailable) {
975
1091
  await notifySleeping(task, provider, config.commentPrefix);
@@ -989,10 +1105,10 @@ async function processNonCodeTask(task, filter, config, provider, runners, scree
989
1105
  return 'skipped';
990
1106
  }
991
1107
  }
992
- await implementNonCodeTask(task, config, provider, runners);
1108
+ await implementNonCodeTask(task, config, provider, runners, hooks, vm);
993
1109
  return 'processed';
994
1110
  }
995
- async function implementNonCodeTask(task, config, provider, runners) {
1111
+ async function implementNonCodeTask(task, config, provider, runners, hooks = {}, vm) {
996
1112
  logger_1.logger.info(`Implementing non-code task: ${task.name}`);
997
1113
  try {
998
1114
  await provider.updateStatus(task.id, 'in progress');
@@ -1012,7 +1128,13 @@ async function implementNonCodeTask(task, config, provider, runners) {
1012
1128
  catch {
1013
1129
  // ignore
1014
1130
  }
1015
- const nonCodePrompt = buildNonCodePrompt(task, context);
1131
+ let nonCodePrompt = buildNonCodePrompt(task, context);
1132
+ // beforeNonCodeTask hook
1133
+ if (vm) {
1134
+ const ncCtx = { task, config, prompt: nonCodePrompt };
1135
+ const modified = await (0, hooks_1.executeHook)(hooks, 'beforeNonCodeTask', ncCtx, vm);
1136
+ nonCodePrompt = modified.prompt;
1137
+ }
1016
1138
  let implemented = false;
1017
1139
  let agentOutput = '';
1018
1140
  let previousNotes = '';
@@ -1045,6 +1167,13 @@ async function implementNonCodeTask(task, config, provider, runners) {
1045
1167
  catch (err) {
1046
1168
  logger_1.logger.warn(`Failed to update task: ${err instanceof Error ? err.message : err}`);
1047
1169
  }
1170
+ // afterNonCodeTask hook
1171
+ if (vm) {
1172
+ const afterCtx = {
1173
+ task, config, prompt: nonCodePrompt, success: true, output: agentOutput,
1174
+ };
1175
+ await (0, hooks_1.executeHook)(hooks, 'afterNonCodeTask', afterCtx, vm);
1176
+ }
1048
1177
  logger_1.logger.success(`Non-code task complete: ${task.name}`);
1049
1178
  }
1050
1179
  //# sourceMappingURL=run.js.map