@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.
- package/.env.aidev.example +81 -62
- package/CONTRIBUTING.md +78 -78
- package/LICENSE +21 -21
- package/README.md +499 -467
- package/dist/__tests__/clickup-format.test.d.ts +2 -0
- package/dist/__tests__/clickup-format.test.d.ts.map +1 -0
- package/dist/__tests__/clickup-format.test.js +256 -0
- package/dist/__tests__/clickup-format.test.js.map +1 -0
- package/dist/__tests__/hooks.test.d.ts +2 -0
- package/dist/__tests__/hooks.test.d.ts.map +1 -0
- package/dist/__tests__/hooks.test.js +329 -0
- package/dist/__tests__/hooks.test.js.map +1 -0
- package/dist/__tests__/init.test.js +33 -0
- package/dist/__tests__/init.test.js.map +1 -1
- package/dist/__tests__/local-provider.test.js +31 -31
- package/dist/__tests__/platform.test.js +5 -5
- package/dist/__tests__/providers.test.js +166 -0
- package/dist/__tests__/providers.test.js.map +1 -1
- package/dist/__tests__/run.test.js +21 -8
- package/dist/__tests__/run.test.js.map +1 -1
- package/dist/cli.js +5 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/help.d.ts.map +1 -1
- package/dist/commands/help.js +67 -61
- package/dist/commands/help.js.map +1 -1
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +125 -0
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/run.d.ts +2 -1
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +248 -119
- package/dist/commands/run.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +16 -1
- package/dist/config.js.map +1 -1
- package/dist/github.js +23 -23
- package/dist/hooks.d.ts +106 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +146 -0
- package/dist/hooks.js.map +1 -0
- package/dist/providers/clickup-format.d.ts +16 -0
- package/dist/providers/clickup-format.d.ts.map +1 -0
- package/dist/providers/clickup-format.js +255 -0
- package/dist/providers/clickup-format.js.map +1 -0
- package/dist/providers/clickup.d.ts.map +1 -1
- package/dist/providers/clickup.js +8 -6
- package/dist/providers/clickup.js.map +1 -1
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js +2 -1
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/linear.js +70 -70
- package/dist/providers/monday.js +39 -39
- package/dist/providers/trello.d.ts +32 -0
- package/dist/providers/trello.d.ts.map +1 -0
- package/dist/providers/trello.js +227 -0
- package/dist/providers/trello.js.map +1 -0
- package/dist/types.d.ts +12 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +51 -50
- package/scripts/run-tests.cjs +18 -18
package/dist/commands/run.d.ts
CHANGED
|
@@ -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":"AAEA,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/commands/run.ts"],"names":[],"mappings":"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"}
|
package/dist/commands/run.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 `
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
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
|
-
|
|
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
|
|
969
|
-
|
|
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(
|
|
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
|
-
|
|
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
|