@qelos/aidev 0.2.13 → 0.3.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 (74) hide show
  1. package/.env.aidev.example +62 -58
  2. package/CONTRIBUTING.md +78 -78
  3. package/LICENSE +21 -21
  4. package/README.md +467 -455
  5. package/dist/__tests__/ai-runners.test.js +44 -4
  6. package/dist/__tests__/ai-runners.test.js.map +1 -1
  7. package/dist/__tests__/config.test.js +6 -4
  8. package/dist/__tests__/config.test.js.map +1 -1
  9. package/dist/__tests__/init.test.js +2 -0
  10. package/dist/__tests__/init.test.js.map +1 -1
  11. package/dist/__tests__/local-provider.test.js +31 -31
  12. package/dist/__tests__/lockfile.test.js +1 -1
  13. package/dist/__tests__/lockfile.test.js.map +1 -1
  14. package/dist/__tests__/platform.test.js +16 -5
  15. package/dist/__tests__/platform.test.js.map +1 -1
  16. package/dist/__tests__/providers.test.js +128 -0
  17. package/dist/__tests__/providers.test.js.map +1 -1
  18. package/dist/__tests__/run.test.js +55 -0
  19. package/dist/__tests__/run.test.js.map +1 -1
  20. package/dist/ai/antigravity.d.ts +12 -0
  21. package/dist/ai/antigravity.d.ts.map +1 -0
  22. package/dist/ai/antigravity.js +64 -0
  23. package/dist/ai/antigravity.js.map +1 -0
  24. package/dist/ai/claude.d.ts.map +1 -1
  25. package/dist/ai/claude.js +1 -10
  26. package/dist/ai/claude.js.map +1 -1
  27. package/dist/ai/cursor.d.ts.map +1 -1
  28. package/dist/ai/cursor.js +1 -7
  29. package/dist/ai/cursor.js.map +1 -1
  30. package/dist/ai/index.d.ts.map +1 -1
  31. package/dist/ai/index.js +2 -0
  32. package/dist/ai/index.js.map +1 -1
  33. package/dist/ai/windsurf.d.ts.map +1 -1
  34. package/dist/ai/windsurf.js +1 -7
  35. package/dist/ai/windsurf.js.map +1 -1
  36. package/dist/cli.js +0 -0
  37. package/dist/commands/help.js +61 -61
  38. package/dist/commands/help.js.map +1 -1
  39. package/dist/commands/init.d.ts +1 -0
  40. package/dist/commands/init.d.ts.map +1 -1
  41. package/dist/commands/init.js +9 -2
  42. package/dist/commands/init.js.map +1 -1
  43. package/dist/commands/run.d.ts +3 -3
  44. package/dist/commands/run.d.ts.map +1 -1
  45. package/dist/commands/run.js +154 -134
  46. package/dist/commands/run.js.map +1 -1
  47. package/dist/config.d.ts.map +1 -1
  48. package/dist/config.js +3 -1
  49. package/dist/config.js.map +1 -1
  50. package/dist/git.d.ts +4 -0
  51. package/dist/git.d.ts.map +1 -1
  52. package/dist/git.js +11 -0
  53. package/dist/git.js.map +1 -1
  54. package/dist/github.js +23 -23
  55. package/dist/permissions.d.ts.map +1 -1
  56. package/dist/permissions.js +12 -0
  57. package/dist/permissions.js.map +1 -1
  58. package/dist/platform.d.ts +5 -0
  59. package/dist/platform.d.ts.map +1 -1
  60. package/dist/platform.js +21 -0
  61. package/dist/platform.js.map +1 -1
  62. package/dist/providers/clickup.d.ts.map +1 -1
  63. package/dist/providers/clickup.js +2 -1
  64. package/dist/providers/clickup.js.map +1 -1
  65. package/dist/providers/jira.d.ts.map +1 -1
  66. package/dist/providers/jira.js +2 -1
  67. package/dist/providers/jira.js.map +1 -1
  68. package/dist/providers/linear.js +71 -71
  69. package/dist/providers/linear.js.map +1 -1
  70. package/dist/providers/monday.js +39 -39
  71. package/dist/types.d.ts +2 -1
  72. package/dist/types.d.ts.map +1 -1
  73. package/package.json +50 -50
  74. package/scripts/run-tests.cjs +18 -0
@@ -219,7 +219,7 @@ async function processTask(task, filter, config, provider, runners, screenAvaila
219
219
  const comments = await provider.getComments(task.id);
220
220
  const trigger = hasTriggerWord(comments, config.triggerWord);
221
221
  if (isPending) {
222
- const reply = hasHumanReply(comments);
222
+ const reply = hasHumanReply(comments, config.commentPrefix);
223
223
  if (!reply && !trigger) {
224
224
  logger_1.logger.info(`[${task.id}] "${task.name}" skipped — pending task has no human reply or trigger word ("${config.triggerWord}")`);
225
225
  return 'skipped';
@@ -236,18 +236,18 @@ async function processTask(task, filter, config, provider, runners, screenAvaila
236
236
  logger_1.logger.info(`Trigger word "${config.triggerWord}" found — re-processing task`);
237
237
  }
238
238
  if (!screenAvailable) {
239
- await notifySleeping(task, provider);
239
+ await notifySleeping(task, provider, config.commentPrefix);
240
240
  return 'skipped';
241
241
  }
242
242
  }
243
243
  else {
244
244
  if (!screenAvailable) {
245
- await notifySleeping(task, provider);
245
+ await notifySleeping(task, provider, config.commentPrefix);
246
246
  return 'skipped';
247
247
  }
248
248
  const clarification = await checkNeedsClarification(task, config, provider, runners);
249
249
  if (clarification) {
250
- await provider.postComment(task.id, `[aidev] ${clarification}`);
250
+ await provider.postComment(task.id, `${config.commentPrefix} ${clarification}`);
251
251
  await provider.updateStatus(task.id, getPendingStatus(config));
252
252
  logger_1.logger.info(`Posted clarification question, set status to ${getPendingStatus(config)}`);
253
253
  return 'skipped';
@@ -261,11 +261,11 @@ async function processTask(task, filter, config, provider, runners, screenAvaila
261
261
  }
262
262
  return 'processed';
263
263
  }
264
- function hasHumanReply(comments) {
264
+ function hasHumanReply(comments, commentPrefix = '[aidev]') {
265
265
  if (comments.length < 2)
266
266
  return false;
267
267
  const lastComment = comments[comments.length - 1];
268
- return !lastComment.text.includes('[aidev]');
268
+ return !lastComment.text.includes(commentPrefix);
269
269
  }
270
270
  /**
271
271
  * Returns true if the last comment contains the trigger word.
@@ -282,7 +282,7 @@ function hasTriggerWord(comments, triggerWord) {
282
282
  return true;
283
283
  return false;
284
284
  }
285
- async function notifySleeping(task, provider) {
285
+ async function notifySleeping(task, provider, commentPrefix) {
286
286
  try {
287
287
  const comments = await provider.getComments(task.id);
288
288
  const lastComment = comments.length > 0 ? comments[comments.length - 1] : null;
@@ -295,7 +295,7 @@ async function notifySleeping(task, provider) {
295
295
  // If we can't check comments, still attempt to post
296
296
  }
297
297
  try {
298
- await provider.postComment(task.id, `[aidev] Cannot work on this task — the ${SLEEPING_MARKER} or the screen is locked. ` +
298
+ await provider.postComment(task.id, `${commentPrefix} Cannot work on this task — the ${SLEEPING_MARKER} or the screen is locked. ` +
299
299
  'AI agents require an active display session to operate. Please wake the machine and unlock the screen so I can continue.');
300
300
  logger_1.logger.info(`[${task.id}] Posted sleep notification`);
301
301
  }
@@ -313,16 +313,16 @@ async function checkNeedsClarification(task, config, provider, runners) {
313
313
  logger_1.logger.warn('No AI runner available — skipping clarification check');
314
314
  return null;
315
315
  }
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"
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"
326
326
  }`;
327
327
  for (const runner of availableRunners) {
328
328
  const result = await runner.run(clarificationPrompt);
@@ -350,30 +350,30 @@ Respond with valid JSON only:
350
350
  return null;
351
351
  }
352
352
  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
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
377
377
  6. Do NOT make any changes beyond what is needed to resolve the conflicts`;
378
378
  }
379
379
  async function resolveConflictsWithAI(task, config, provider, runners, context, branchName) {
@@ -396,7 +396,7 @@ async function resolveConflictsWithAI(task, config, provider, runners, context,
396
396
  logger_1.logger.warn(`Branch has conflicts with ${config.githubBaseBranch} in ${check.conflictFiles.length} file(s): ` +
397
397
  check.conflictFiles.join(', '));
398
398
  try {
399
- await provider.postComment(task.id, `[aidev] Branch \`${branchName}\` has merge conflicts with \`${config.githubBaseBranch}\` ` +
399
+ await provider.postComment(task.id, `${config.commentPrefix} Branch \`${branchName}\` has merge conflicts with \`${config.githubBaseBranch}\` ` +
400
400
  `in ${check.conflictFiles.length} file(s). Attempting automatic resolution...`);
401
401
  }
402
402
  catch { /* ignore */ }
@@ -425,7 +425,7 @@ async function resolveConflictsWithAI(task, config, provider, runners, context,
425
425
  logger_1.logger.error('All AI runners failed to resolve merge conflicts');
426
426
  git.abortMerge();
427
427
  try {
428
- await provider.postComment(task.id, '[aidev] Failed to automatically resolve merge conflicts. Manual intervention needed to rebase/merge the branch.');
428
+ await provider.postComment(task.id, `${config.commentPrefix} Failed to automatically resolve merge conflicts. Manual intervention needed to rebase/merge the branch.`);
429
429
  }
430
430
  catch { /* ignore */ }
431
431
  return false;
@@ -440,7 +440,7 @@ async function resolveConflictsWithAI(task, config, provider, runners, context,
440
440
  }
441
441
  logger_1.logger.success('Merge conflicts resolved successfully');
442
442
  try {
443
- await provider.postComment(task.id, '[aidev] Merge conflicts resolved automatically.');
443
+ await provider.postComment(task.id, `${config.commentPrefix} Merge conflicts resolved automatically.`);
444
444
  }
445
445
  catch { /* ignore */ }
446
446
  }
@@ -494,7 +494,7 @@ async function implementTask(task, branchName, branchExists, config, provider, r
494
494
  try {
495
495
  await provider.updateStatus(task.id, 'in progress');
496
496
  const verb = branchExists ? 'Continuing' : 'Starting';
497
- await provider.postComment(task.id, `[aidev] ${verb} implementation on branch \`${branchName}\``);
497
+ await provider.postComment(task.id, `${config.commentPrefix} ${verb} implementation on branch \`${branchName}\``);
498
498
  }
499
499
  catch (err) {
500
500
  logger_1.logger.warn(`Could not update task status: ${err}`);
@@ -502,7 +502,7 @@ async function implementTask(task, branchName, branchExists, config, provider, r
502
502
  if (branchExists) {
503
503
  if (!git.fetchAndCheckoutBranch(config.gitRemote, branchName)) {
504
504
  logger_1.logger.error(`Failed to checkout existing branch ${branchName}`);
505
- await provider.postComment(task.id, '[aidev] Failed to checkout existing branch. Manual intervention needed.');
505
+ await provider.postComment(task.id, `${config.commentPrefix} Failed to checkout existing branch. Manual intervention needed.`);
506
506
  return;
507
507
  }
508
508
  logger_1.logger.info(`Continuing on existing branch: ${branchName}`);
@@ -510,14 +510,14 @@ async function implementTask(task, branchName, branchExists, config, provider, r
510
510
  else {
511
511
  if (!git.createBranchFromRemote(config.gitRemote, config.githubBaseBranch, branchName)) {
512
512
  logger_1.logger.error(`Failed to create branch ${branchName} from ${config.gitRemote}/${config.githubBaseBranch}`);
513
- await provider.postComment(task.id, '[aidev] Failed to prepare git branch. Manual intervention needed.');
513
+ await provider.postComment(task.id, `${config.commentPrefix} Failed to prepare git branch. Manual intervention needed.`);
514
514
  return;
515
515
  }
516
516
  }
517
517
  let context = '';
518
518
  try {
519
519
  const comments = await provider.getComments(task.id);
520
- const humanComments = filterAutomatedComments(comments);
520
+ const humanComments = filterAutomatedComments(comments, config.commentPrefix);
521
521
  if (humanComments.length > 0) {
522
522
  context = '\n\nConversation context:\n' + humanComments.map((c) => `${c.author}: ${c.text}`).join('\n');
523
523
  }
@@ -555,6 +555,11 @@ async function implementTask(task, branchName, branchExists, config, provider, r
555
555
  implemented = true;
556
556
  break;
557
557
  }
558
+ if (result.success && !git.hasChanges() && git.hasCommitsAhead(config.gitRemote, config.githubBaseBranch)) {
559
+ logger_1.logger.info(`${runner.name} produced no new file changes, but branch already has commits ahead of ${config.githubBaseBranch}`);
560
+ implemented = true;
561
+ break;
562
+ }
558
563
  if (!result.success) {
559
564
  logger_1.logger.warn(`${runner.name} failed — trying next runner`);
560
565
  previousNotes = `Previous runner (${runner.name}) output:\n${result.output}\nErrors:\n${result.error}`;
@@ -567,20 +572,22 @@ async function implementTask(task, branchName, branchExists, config, provider, r
567
572
  if (!implemented) {
568
573
  logger_1.logger.error('All AI runners failed or produced no changes');
569
574
  const diagnostics = (0, diagnostics_1.collectAndLogDiagnostics)();
570
- await provider.postComment(task.id, `[aidev] All AI runners failed. Manual implementation needed.\n\n${diagnostics}`);
575
+ await provider.postComment(task.id, `${config.commentPrefix} All AI runners failed. Manual implementation needed.\n\n${diagnostics}`);
571
576
  if (!branchExists) {
572
577
  git.deleteBranch(branchName);
573
578
  }
574
579
  return;
575
580
  }
576
- // Commit and push
577
- if (!git.addAll() || !git.commit(`[aidev] Implement: ${task.name}\n\nTask: ${task.url}`, branchName)) {
578
- logger_1.logger.error('Failed to commit changes');
579
- return;
580
- }
581
- if (!git.push(config.gitRemote, branchName)) {
582
- logger_1.logger.error('Failed to push branch');
583
- return;
581
+ // Commit and push (only if there are new changes to commit)
582
+ if (git.hasChanges()) {
583
+ if (!git.addAll() || !git.commit(`${config.commentPrefix} Implement: ${task.name}\n\nTask: ${task.url}`, branchName)) {
584
+ logger_1.logger.error('Failed to commit changes');
585
+ return;
586
+ }
587
+ if (!git.push(config.gitRemote, branchName)) {
588
+ logger_1.logger.error('Failed to push branch');
589
+ return;
590
+ }
584
591
  }
585
592
  if (reviewThreads.length > 0) {
586
593
  resolveHandledThreads(reviewThreads);
@@ -598,14 +605,14 @@ async function implementTask(task, branchName, branchExists, config, provider, r
598
605
  logger_1.logger.success(`Task implemented: branch ${branchName} pushed`);
599
606
  }
600
607
  function buildImplementPrompt(task, context) {
601
- return `You are implementing a software development task. Make the necessary code changes to complete the task described below.
602
-
603
- Task: ${task.name}
604
-
605
- Description:
606
- ${task.description || '(no description provided)'}
607
- ${context}
608
-
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
+
609
616
  Please implement the required changes. Focus on correctness and follow the existing code style in the project.`;
610
617
  }
611
618
  async function analyzeAndPlan(task, context, runners) {
@@ -614,28 +621,28 @@ async function analyzeAndPlan(task, context, runners) {
614
621
  logger_1.logger.error('No AI runner available for task analysis');
615
622
  return null;
616
623
  }
617
- const analysisPrompt = `You are a senior software architect breaking down a development task into smaller, sequential implementation steps.
618
-
619
- Task name: ${task.name}
620
-
621
- Description:
622
- ${task.description || '(no description provided)'}
623
- ${context}
624
-
625
- 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.
626
-
627
- Respond with valid JSON only — no markdown fences, no extra text:
628
- {
629
- "instructions": "Detailed implementation instructions in markdown covering the full task — architecture decisions, key files to modify, edge cases to handle, testing approach",
630
- "subtasks": [
631
- {
632
- "id": 1,
633
- "title": "Short title for the sub-task",
634
- "description": "Detailed description of what to implement in this step, including specific files and functions to change"
635
- }
636
- ]
637
- }
638
-
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
+
639
646
  Keep sub-tasks focused: 2-6 sub-tasks is ideal. Order them by dependency (foundation first).`;
640
647
  logger_1.logger.info('Analyzing task and creating implementation plan...');
641
648
  const result = await runner.run(analysisPrompt);
@@ -682,20 +689,20 @@ async function executeSubTask(subtask, task, plan, config, runners, reviewContex
682
689
  .filter((s) => s.status === 'done')
683
690
  .map((s) => ` - [done] ${s.id}. ${s.title}`)
684
691
  .join('\n');
685
- const prompt = `You are implementing step ${subtask.id} of a multi-step task.
686
-
687
- Overall task: ${task.name}
688
- ${task.description ? `\nTask description:\n${task.description}` : ''}
689
-
690
- ## Full implementation instructions
691
- ${instructions}
692
- ${reviewContext || ''}
693
- ## Progress
694
- ${completedSteps || '(no steps completed yet)'}
695
-
696
- ## Current step: ${subtask.id}. ${subtask.title}
697
- ${subtask.description}
698
-
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
+
699
706
  Implement ONLY this step. Focus on correctness and follow the existing code style.`;
700
707
  let implemented = false;
701
708
  let previousNotes = '';
@@ -724,7 +731,7 @@ async function implementThinkingTask(task, branchName, branchExists, config, pro
724
731
  try {
725
732
  await provider.updateStatus(task.id, 'in progress');
726
733
  const verb = branchExists ? 'Continuing' : 'Starting';
727
- await provider.postComment(task.id, `[aidev] ${verb} implementation on branch \`${branchName}\` (thinking mode — will analyze and break into sub-tasks)`);
734
+ await provider.postComment(task.id, `${config.commentPrefix} ${verb} implementation on branch \`${branchName}\` (thinking mode — will analyze and break into sub-tasks)`);
728
735
  }
729
736
  catch (err) {
730
737
  logger_1.logger.warn(`Could not update task status: ${err}`);
@@ -732,7 +739,7 @@ async function implementThinkingTask(task, branchName, branchExists, config, pro
732
739
  if (branchExists) {
733
740
  if (!git.fetchAndCheckoutBranch(config.gitRemote, branchName)) {
734
741
  logger_1.logger.error(`Failed to checkout existing branch ${branchName}`);
735
- await provider.postComment(task.id, '[aidev] Failed to checkout existing branch. Manual intervention needed.');
742
+ await provider.postComment(task.id, `${config.commentPrefix} Failed to checkout existing branch. Manual intervention needed.`);
736
743
  return;
737
744
  }
738
745
  logger_1.logger.info(`Continuing on existing branch: ${branchName}`);
@@ -740,14 +747,14 @@ async function implementThinkingTask(task, branchName, branchExists, config, pro
740
747
  else {
741
748
  if (!git.createBranchFromRemote(config.gitRemote, config.githubBaseBranch, branchName)) {
742
749
  logger_1.logger.error(`Failed to create branch ${branchName} from ${config.gitRemote}/${config.githubBaseBranch}`);
743
- await provider.postComment(task.id, '[aidev] Failed to prepare git branch. Manual intervention needed.');
750
+ await provider.postComment(task.id, `${config.commentPrefix} Failed to prepare git branch. Manual intervention needed.`);
744
751
  return;
745
752
  }
746
753
  }
747
754
  let context = '';
748
755
  try {
749
756
  const comments = await provider.getComments(task.id);
750
- const humanComments = filterAutomatedComments(comments);
757
+ const humanComments = filterAutomatedComments(comments, config.commentPrefix);
751
758
  if (humanComments.length > 0) {
752
759
  context = '\n\nConversation context:\n' + humanComments.map((c) => `${c.author}: ${c.text}`).join('\n');
753
760
  }
@@ -780,7 +787,7 @@ async function implementThinkingTask(task, branchName, branchExists, config, pro
780
787
  plan = await analyzeAndPlan(task, context, runners);
781
788
  if (!plan) {
782
789
  logger_1.logger.error('Failed to create implementation plan');
783
- await provider.postComment(task.id, '[aidev] Failed to analyze and break down the task. Manual implementation needed.');
790
+ await provider.postComment(task.id, `${config.commentPrefix} Failed to analyze and break down the task. Manual implementation needed.`);
784
791
  cleanupThinkingFiles(task.id);
785
792
  if (!branchExists)
786
793
  git.deleteBranch(branchName);
@@ -788,7 +795,7 @@ async function implementThinkingTask(task, branchName, branchExists, config, pro
788
795
  }
789
796
  logger_1.logger.info(`Task broken into ${plan.subtasks.length} sub-tasks`);
790
797
  try {
791
- await provider.postComment(task.id, `[aidev] Task analyzed and broken into ${plan.subtasks.length} sub-tasks:\n\n${formatSubtaskList(plan)}`);
798
+ await provider.postComment(task.id, `${config.commentPrefix} Task analyzed and broken into ${plan.subtasks.length} sub-tasks:\n\n${formatSubtaskList(plan)}`);
792
799
  }
793
800
  catch (err) {
794
801
  logger_1.logger.warn(`Failed to post breakdown comment: ${err}`);
@@ -811,12 +818,12 @@ async function implementThinkingTask(task, branchName, branchExists, config, pro
811
818
  logger_1.logger.error(` Step ${subtask.id} failed: ${subtask.title}`);
812
819
  const diagnostics = (0, diagnostics_1.collectAndLogDiagnostics)();
813
820
  try {
814
- await provider.postComment(task.id, `[aidev] Step ${subtask.id} failed: ${subtask.title}\n\n${formatSubtaskList(plan)}\n\n${diagnostics}`);
821
+ await provider.postComment(task.id, `${config.commentPrefix} Step ${subtask.id} failed: ${subtask.title}\n\n${formatSubtaskList(plan)}\n\n${diagnostics}`);
815
822
  }
816
823
  catch { /* ignore */ }
817
824
  break;
818
825
  }
819
- if (!git.addAll() || !git.commit(`[aidev] Step ${subtask.id}: ${subtask.title}\n\nTask: ${task.url}`, branchName)) {
826
+ if (!git.addAll() || !git.commit(`${config.commentPrefix} Step ${subtask.id}: ${subtask.title}\n\nTask: ${task.url}`, branchName)) {
820
827
  subtask.status = 'failed';
821
828
  writeTaskPlan(plan);
822
829
  allSucceeded = false;
@@ -834,7 +841,7 @@ async function implementThinkingTask(task, branchName, branchExists, config, pro
834
841
  writeTaskPlan(plan);
835
842
  logger_1.logger.success(` Step ${subtask.id} complete: ${subtask.title}`);
836
843
  try {
837
- await provider.postComment(task.id, `[aidev] Step ${subtask.id} complete: ${subtask.title}\n\n${formatSubtaskList(plan)}`);
844
+ await provider.postComment(task.id, `${config.commentPrefix} Step ${subtask.id} complete: ${subtask.title}\n\n${formatSubtaskList(plan)}`);
838
845
  }
839
846
  catch { /* ignore */ }
840
847
  }
@@ -843,7 +850,7 @@ async function implementThinkingTask(task, branchName, branchExists, config, pro
843
850
  logger_1.logger.error('Thinking task did not complete all sub-tasks');
844
851
  try {
845
852
  const diagnostics = (0, diagnostics_1.collectAndLogDiagnostics)();
846
- await provider.postComment(task.id, `[aidev] Thinking task did not complete all sub-tasks. Manual intervention needed.\n\n${diagnostics}`);
853
+ await provider.postComment(task.id, `${config.commentPrefix} Thinking task did not complete all sub-tasks. Manual intervention needed.\n\n${diagnostics}`);
847
854
  }
848
855
  catch { /* ignore */ }
849
856
  return;
@@ -883,7 +890,7 @@ function buildPRUrl(config, branch) {
883
890
  }
884
891
  function buildCompletionComment(branch, prUrl, config) {
885
892
  const lines = [
886
- `[aidev] Implementation complete!`,
893
+ `${config.commentPrefix} Implementation complete!`,
887
894
  ``,
888
895
  `Branch: \`${branch}\``,
889
896
  ];
@@ -894,19 +901,32 @@ function buildCompletionComment(branch, prUrl, config) {
894
901
  return lines.join('\n');
895
902
  }
896
903
  function buildNonCodePrompt(task, context) {
897
- return `You are handling a non-code task. This task does NOT require code changes — it requires a thoughtful, verbal response.
898
-
899
- Task: ${task.name}
900
-
901
- Description:
902
- ${task.description || '(no description provided)'}
903
- ${context}
904
-
904
+ const hasComments = context.trim().length > 0;
905
+ 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
+
905
925
  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.`;
906
926
  }
907
927
  function buildNonCodeCompletionComment(config, agentResponse) {
908
928
  const lines = [
909
- `[aidev] Non-code task complete!`,
929
+ `${config.commentPrefix} Non-code task complete!`,
910
930
  ];
911
931
  if (agentResponse) {
912
932
  lines.push(``, `---`, ``, agentResponse);
@@ -914,11 +934,11 @@ function buildNonCodeCompletionComment(config, agentResponse) {
914
934
  lines.push(``, `Status set to: ${getInReviewStatus(config)}`);
915
935
  return lines.join('\n');
916
936
  }
917
- function hasAidevComment(comments) {
918
- return comments.some((c) => c.text.includes('[aidev]'));
937
+ function hasAidevComment(comments, commentPrefix = '[aidev]') {
938
+ return comments.some((c) => c.text.includes(commentPrefix));
919
939
  }
920
- function filterAutomatedComments(comments) {
921
- return comments.filter((c) => !c.text.includes('[aidev]'));
940
+ function filterAutomatedComments(comments, commentPrefix = '[aidev]') {
941
+ return comments.filter((c) => !c.text.includes(commentPrefix));
922
942
  }
923
943
  async function processNonCodeTask(task, filter, config, provider, runners, screenAvailable) {
924
944
  const pendingStatus = getPendingStatus(config);
@@ -931,11 +951,11 @@ async function processNonCodeTask(task, filter, config, provider, runners, scree
931
951
  return 'skipped';
932
952
  }
933
953
  const comments = await provider.getComments(task.id);
934
- const wasProcessed = hasAidevComment(comments);
954
+ const wasProcessed = hasAidevComment(comments, config.commentPrefix);
935
955
  if (isPending || wasProcessed) {
936
956
  const trigger = hasTriggerWord(comments, config.triggerWord);
937
957
  if (isPending) {
938
- const reply = hasHumanReply(comments);
958
+ const reply = hasHumanReply(comments, config.commentPrefix);
939
959
  if (!reply && !trigger) {
940
960
  logger_1.logger.info(`[${task.id}] "${task.name}" skipped — pending with no human reply or trigger word ("${config.triggerWord}")`);
941
961
  return 'skipped';
@@ -952,18 +972,18 @@ async function processNonCodeTask(task, filter, config, provider, runners, scree
952
972
  logger_1.logger.info(`Trigger word "${config.triggerWord}" found — re-processing non-code task`);
953
973
  }
954
974
  if (!screenAvailable) {
955
- await notifySleeping(task, provider);
975
+ await notifySleeping(task, provider, config.commentPrefix);
956
976
  return 'skipped';
957
977
  }
958
978
  }
959
979
  else {
960
980
  if (!screenAvailable) {
961
- await notifySleeping(task, provider);
981
+ await notifySleeping(task, provider, config.commentPrefix);
962
982
  return 'skipped';
963
983
  }
964
984
  const clarification = await checkNeedsClarification(task, config, provider, runners);
965
985
  if (clarification) {
966
- await provider.postComment(task.id, `[aidev] ${clarification}`);
986
+ await provider.postComment(task.id, `${config.commentPrefix} ${clarification}`);
967
987
  await provider.updateStatus(task.id, getPendingStatus(config));
968
988
  logger_1.logger.info(`Posted clarification question, set status to ${getPendingStatus(config)}`);
969
989
  return 'skipped';
@@ -976,7 +996,7 @@ async function implementNonCodeTask(task, config, provider, runners) {
976
996
  logger_1.logger.info(`Implementing non-code task: ${task.name}`);
977
997
  try {
978
998
  await provider.updateStatus(task.id, 'in progress');
979
- await provider.postComment(task.id, `[aidev] Starting non-code task execution`);
999
+ await provider.postComment(task.id, `${config.commentPrefix} Starting non-code task execution`);
980
1000
  }
981
1001
  catch (err) {
982
1002
  logger_1.logger.warn(`Could not update task status: ${err}`);
@@ -984,7 +1004,7 @@ async function implementNonCodeTask(task, config, provider, runners) {
984
1004
  let context = '';
985
1005
  try {
986
1006
  const comments = await provider.getComments(task.id);
987
- const humanComments = filterAutomatedComments(comments);
1007
+ const humanComments = filterAutomatedComments(comments, config.commentPrefix);
988
1008
  if (humanComments.length > 0) {
989
1009
  context = '\n\nConversation context:\n' + humanComments.map((c) => `${c.author}: ${c.text}`).join('\n');
990
1010
  }
@@ -1014,7 +1034,7 @@ async function implementNonCodeTask(task, config, provider, runners) {
1014
1034
  if (!implemented) {
1015
1035
  logger_1.logger.error('All AI runners failed');
1016
1036
  const diagnostics = (0, diagnostics_1.collectAndLogDiagnostics)();
1017
- await provider.postComment(task.id, `[aidev] All AI runners failed. Manual intervention needed.\n\n${diagnostics}`);
1037
+ await provider.postComment(task.id, `${config.commentPrefix} All AI runners failed. Manual intervention needed.\n\n${diagnostics}`);
1018
1038
  return;
1019
1039
  }
1020
1040
  try {