@qelos/aidev 0.1.0 → 0.1.2

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 (83) hide show
  1. package/README.md +3 -3
  2. package/dist/__tests__/git.test.d.ts +2 -0
  3. package/dist/__tests__/git.test.d.ts.map +1 -0
  4. package/dist/__tests__/git.test.js +35 -0
  5. package/dist/__tests__/git.test.js.map +1 -0
  6. package/dist/__tests__/init.test.d.ts +2 -0
  7. package/dist/__tests__/init.test.d.ts.map +1 -0
  8. package/dist/__tests__/init.test.js +157 -0
  9. package/dist/__tests__/init.test.js.map +1 -0
  10. package/dist/__tests__/platform.test.d.ts +2 -0
  11. package/dist/__tests__/platform.test.d.ts.map +1 -0
  12. package/dist/__tests__/platform.test.js +31 -0
  13. package/dist/__tests__/platform.test.js.map +1 -0
  14. package/dist/__tests__/run.test.d.ts +2 -0
  15. package/dist/__tests__/run.test.d.ts.map +1 -0
  16. package/dist/__tests__/run.test.js +77 -0
  17. package/dist/__tests__/run.test.js.map +1 -0
  18. package/dist/__tests__/schedule.test.d.ts +2 -0
  19. package/dist/__tests__/schedule.test.d.ts.map +1 -0
  20. package/dist/__tests__/schedule.test.js +49 -0
  21. package/dist/__tests__/schedule.test.js.map +1 -0
  22. package/dist/ai/base.d.ts +11 -0
  23. package/dist/ai/base.js.map +1 -0
  24. package/dist/ai/claude.d.ts +7 -0
  25. package/dist/ai/claude.js.map +1 -0
  26. package/dist/ai/cursor.d.ts +7 -0
  27. package/dist/ai/cursor.js.map +1 -0
  28. package/dist/ai/index.d.ts +5 -0
  29. package/dist/ai/index.js.map +1 -0
  30. package/dist/cli.d.ts +3 -0
  31. package/dist/cli.js.map +1 -0
  32. package/dist/commands/help.d.ts +2 -0
  33. package/dist/commands/help.js.map +1 -0
  34. package/dist/commands/init.d.ts +19 -0
  35. package/dist/commands/init.d.ts.map +1 -1
  36. package/dist/commands/init.js +7 -3
  37. package/dist/commands/init.js.map +1 -0
  38. package/dist/commands/run.d.ts +9 -0
  39. package/dist/commands/run.d.ts.map +1 -1
  40. package/dist/commands/run.js +3 -0
  41. package/dist/commands/run.js.map +1 -0
  42. package/dist/commands/schedule.d.ts +10 -0
  43. package/dist/commands/schedule.d.ts.map +1 -1
  44. package/dist/commands/schedule.js +2 -0
  45. package/dist/commands/schedule.js.map +1 -0
  46. package/dist/config.d.ts +3 -0
  47. package/dist/config.js.map +1 -0
  48. package/dist/git.d.ts +12 -0
  49. package/dist/git.js.map +1 -0
  50. package/dist/logger.d.ts +10 -0
  51. package/dist/logger.js.map +1 -0
  52. package/dist/platform.d.ts +9 -0
  53. package/dist/platform.js.map +1 -0
  54. package/dist/providers/base.d.ts +8 -0
  55. package/dist/providers/base.js.map +1 -0
  56. package/dist/providers/clickup.d.ts +15 -0
  57. package/dist/providers/clickup.js.map +1 -0
  58. package/dist/providers/index.d.ts +5 -0
  59. package/dist/providers/index.js.map +1 -0
  60. package/dist/types.d.ts +31 -0
  61. package/dist/types.js.map +1 -0
  62. package/package.json +19 -5
  63. package/.claude/settings.local.json +0 -9
  64. package/.cursor/rules/aidev.mdc +0 -57
  65. package/CLAUDE.md +0 -32
  66. package/src/ai/base.ts +0 -11
  67. package/src/ai/claude.ts +0 -35
  68. package/src/ai/cursor.ts +0 -35
  69. package/src/ai/index.ts +0 -15
  70. package/src/cli.ts +0 -83
  71. package/src/commands/help.ts +0 -43
  72. package/src/commands/init.ts +0 -296
  73. package/src/commands/run.ts +0 -283
  74. package/src/commands/schedule.ts +0 -179
  75. package/src/config.ts +0 -59
  76. package/src/git.ts +0 -109
  77. package/src/logger.ts +0 -53
  78. package/src/platform.ts +0 -33
  79. package/src/providers/base.ts +0 -8
  80. package/src/providers/clickup.ts +0 -107
  81. package/src/providers/index.ts +0 -20
  82. package/src/types.ts +0 -33
  83. package/tsconfig.json +0 -19
@@ -1,296 +0,0 @@
1
- import * as fs from 'node:fs';
2
- import * as path from 'node:path';
3
- import * as readline from 'node:readline/promises';
4
- import { stdin as input, stdout as output } from 'node:process';
5
- import { logger } from '../logger';
6
- import { detectRemote } from '../git';
7
- import chalk from 'chalk';
8
-
9
- const VALID_AGENTS = ['claude', 'cursor'] as const;
10
-
11
- // Patterns we want guaranteed in .gitignore.
12
- // Each entry: [pattern to write, regex that matches equivalent existing lines]
13
- const GITIGNORE_RULES: Array<[string, RegExp]> = [
14
- ['.env.*', /^\.env[\.\*]/m],
15
- ['*.log', /^\*\.log/m],
16
- ];
17
-
18
- function ensureGitignore(): void {
19
- const gitignorePath = path.join(process.cwd(), '.gitignore');
20
- const existing = fs.existsSync(gitignorePath)
21
- ? fs.readFileSync(gitignorePath, 'utf8')
22
- : '';
23
-
24
- const missing = GITIGNORE_RULES
25
- .filter(([, regex]) => !regex.test(existing))
26
- .map(([pattern]) => pattern);
27
-
28
- if (missing.length === 0) return;
29
-
30
- const addition = (existing.endsWith('\n') || existing === '' ? '' : '\n')
31
- + missing.join('\n') + '\n';
32
- fs.appendFileSync(gitignorePath, addition, 'utf8');
33
- logger.info(`.gitignore — added: ${missing.join(', ')}`);
34
- }
35
-
36
- interface ClickUpMember {
37
- id: number;
38
- username: string;
39
- email: string;
40
- }
41
-
42
- interface Answers {
43
- clickupApiKey: string;
44
- clickupTeamId: string;
45
- clickupTag: string;
46
- clickupPendingStatus: string;
47
- clickupInReviewStatus: string;
48
- assigneeTag: string;
49
- gitRemote: string;
50
- githubBaseBranch: string;
51
- githubRepo: string;
52
- agents: string;
53
- devNotesMode: string;
54
- }
55
-
56
- function dim(s: string) {
57
- return chalk.dim(s);
58
- }
59
-
60
- function hint(s: string) {
61
- return chalk.dim(`(${s})`);
62
- }
63
-
64
- async function ask(
65
- rl: readline.Interface,
66
- question: string,
67
- defaultVal = '',
68
- required = false
69
- ): Promise<string> {
70
- const suffix = defaultVal ? chalk.dim(` [${defaultVal}]`) : '';
71
- while (true) {
72
- const raw = await rl.question(` ${question}${suffix}: `);
73
- const val = raw.trim() || defaultVal;
74
- if (required && !val) {
75
- console.log(chalk.yellow(` This field is required.`));
76
- continue;
77
- }
78
- return val;
79
- }
80
- }
81
-
82
- async function choose(
83
- rl: readline.Interface,
84
- question: string,
85
- options: string[],
86
- defaultVal: string
87
- ): Promise<string> {
88
- const opts = options
89
- .map((o) => (o === defaultVal ? chalk.cyan(o) : o))
90
- .join(chalk.dim(' | '));
91
- while (true) {
92
- const raw = await rl.question(` ${question} ${dim(`[${opts}]`)}: `);
93
- const val = raw.trim() || defaultVal;
94
- if (!options.includes(val)) {
95
- console.log(chalk.yellow(` Choose one of: ${options.join(', ')}`));
96
- continue;
97
- }
98
- return val;
99
- }
100
- }
101
-
102
- async function pickAgents(rl: readline.Interface): Promise<string> {
103
- const available = [...VALID_AGENTS];
104
-
105
- console.log(`\n Available agents:`);
106
- available.forEach((a, i) => console.log(` ${chalk.cyan(String(i + 1))}. ${a}`));
107
-
108
- console.log(
109
- `\n Enter agents ${hint('numbers or names, comma-separated — first = primary, rest = fallback')}`
110
- );
111
-
112
- while (true) {
113
- const raw = await rl.question(` Agents in order ${dim(`[${available.join(',')}]`)}: `);
114
-
115
- if (!raw.trim()) return available.join(',');
116
-
117
- const parts = raw.split(',').map((s) => s.trim().toLowerCase()).filter(Boolean);
118
- const resolved = parts.map((p) => {
119
- const idx = parseInt(p, 10);
120
- if (!isNaN(idx) && idx >= 1 && idx <= available.length) return available[idx - 1];
121
- return p;
122
- });
123
-
124
- const invalid = resolved.filter((r) => !available.includes(r as typeof available[number]));
125
- if (invalid.length) {
126
- console.log(chalk.yellow(` Unknown agent(s): ${invalid.join(', ')}. Valid: ${available.join(', ')}`));
127
- continue;
128
- }
129
-
130
- const unique = [...new Set(resolved)];
131
- if (unique.length !== resolved.length) {
132
- console.log(chalk.yellow(` Duplicate agents removed: ${unique.join(', ')}`));
133
- }
134
- return unique.join(',');
135
- }
136
- }
137
-
138
- async function fetchCurrentUser(apiKey: string): Promise<ClickUpMember | null> {
139
- try {
140
- const res = await fetch('https://api.clickup.com/api/v2/user', {
141
- headers: { Authorization: apiKey },
142
- });
143
- if (!res.ok) return null;
144
- const data = await res.json() as { user: ClickUpMember };
145
- return data.user;
146
- } catch {
147
- return null;
148
- }
149
- }
150
-
151
- async function pickAssignee(rl: readline.Interface, apiKey: string): Promise<string> {
152
- process.stdout.write(` ${chalk.dim('Fetching current user...')}\r`);
153
- const user = await fetchCurrentUser(apiKey);
154
- process.stdout.write(' \r');
155
-
156
- if (!user) {
157
- return ask(rl, `Assignee tag ${hint('optional — could not fetch user')}`, '');
158
- }
159
-
160
- const display = user.username && user.username !== 'null'
161
- ? `${user.username} <${user.email}>`
162
- : user.email;
163
- return ask(rl, `Assignee tag`, display);
164
- }
165
-
166
- function section(title: string) {
167
- console.log('\n' + chalk.bold.underline(title));
168
- }
169
-
170
- /** Wraps value in double quotes if it contains spaces or special chars. */
171
- function envVal(val: string): string {
172
- return /[\s#"']/.test(val) ? `"${val.replace(/"/g, '\\"')}"` : val;
173
- }
174
-
175
- function line(key: string, val: string): string | null {
176
- return val ? `${key}=${envVal(val)}` : null;
177
- }
178
-
179
- function renderEnv(a: Answers): string {
180
- const lines = [
181
- `PROVIDER=clickup`,
182
- line('CLICKUP_API_KEY', a.clickupApiKey),
183
- line('CLICKUP_TEAM_ID', a.clickupTeamId),
184
- line('CLICKUP_TAG', a.clickupTag),
185
- `CLICKUP_PENDING_STATUS=${envVal(a.clickupPendingStatus)}`,
186
- `CLICKUP_IN_REVIEW_STATUS=${envVal(a.clickupInReviewStatus)}`,
187
- ``,
188
- line('ASSIGNEE_TAG', a.assigneeTag),
189
- `GIT_REMOTE=${envVal(a.gitRemote)}`,
190
- `GITHUB_BASE_BRANCH=${envVal(a.githubBaseBranch)}`,
191
- line('GITHUB_REPO', a.githubRepo),
192
- ``,
193
- `# Agents to use, in fallback order (comma-separated: claude, cursor)`,
194
- `AGENTS=${a.agents}`,
195
- ``,
196
- `# DEV_NOTES_MODE: smart (only ask when unclear) | always (ask before every task)`,
197
- `DEV_NOTES_MODE=${a.devNotesMode}`,
198
- ``,
199
- ];
200
- return lines.filter((l) => l !== null).join('\n');
201
- }
202
-
203
- export async function initCommand(): Promise<void> {
204
- const dest = path.join(process.cwd(), '.env.aidev');
205
-
206
- if (fs.existsSync(dest)) {
207
- const rl0 = readline.createInterface({ input, output });
208
- const overwrite = await rl0.question(
209
- chalk.yellow('.env.aidev already exists. Reconfigure? ') + dim('[y/N] ')
210
- );
211
- rl0.close();
212
- if (overwrite.trim().toLowerCase() !== 'y') {
213
- logger.info('Keeping existing .env.aidev.');
214
- return;
215
- }
216
- console.log();
217
- }
218
-
219
- console.log(chalk.bold('\naidev setup') + dim(' — press Enter to accept defaults\n'));
220
-
221
- const rl = readline.createInterface({ input, output });
222
-
223
- try {
224
- // ── ClickUp ──────────────────────────────────────────────
225
- section('ClickUp');
226
- const globalEnvHint = hint('leave blank to use global env var');
227
- const clickupApiKey = await ask(rl, `API key ${globalEnvHint}`, '');
228
- const clickupTeamId = await ask(rl, `Team / workspace ID ${globalEnvHint}`, '');
229
- const folderName = path.basename(process.cwd());
230
- const clickupTag = await ask(
231
- rl,
232
- `Tag to filter tasks ${hint('tasks with this tag will be picked up')}`,
233
- folderName
234
- );
235
- const clickupPendingStatus = await ask(rl, 'Pending status name', 'pending');
236
- const clickupInReviewStatus = await ask(rl, 'In-review status name', 'review');
237
-
238
- // ── Git / GitHub ─────────────────────────────────────────
239
- section('Git & GitHub');
240
- const detectedRemote = detectRemote() ?? 'origin';
241
- const gitRemote = await ask(rl, 'Git remote', detectedRemote);
242
- const githubBaseBranch = await ask(rl, 'Base branch', 'main');
243
- const githubRepo = await ask(
244
- rl,
245
- `GitHub repo ${hint('owner/repo — used for PR links, optional')}`,
246
- ''
247
- );
248
-
249
- // ── AI agents ────────────────────────────────────────────
250
- section('AI agents');
251
- const agents = await pickAgents(rl);
252
- const devNotesMode = await choose(
253
- rl,
254
- `Dev notes mode ${hint('smart = ask AI if unclear, always = ask before every task')}`,
255
- ['smart', 'always'],
256
- 'smart'
257
- );
258
-
259
- // ── Assignee ─────────────────────────────────────────────
260
- section('Assignee');
261
- const effectiveApiKey = clickupApiKey || process.env.CLICKUP_API_KEY || '';
262
- let assigneeTag: string;
263
-
264
- if (effectiveApiKey) {
265
- assigneeTag = await pickAssignee(rl, effectiveApiKey);
266
- } else {
267
- assigneeTag = await ask(
268
- rl,
269
- `Assignee tag ${hint('optional — provide API key above to auto-detect')}`,
270
- ''
271
- );
272
- }
273
-
274
- const answers: Answers = {
275
- clickupApiKey,
276
- clickupTeamId,
277
- clickupTag,
278
- clickupPendingStatus,
279
- clickupInReviewStatus,
280
- assigneeTag,
281
- gitRemote,
282
- githubBaseBranch,
283
- githubRepo,
284
- agents,
285
- devNotesMode,
286
- };
287
-
288
- ensureGitignore();
289
- fs.writeFileSync(dest, renderEnv(answers), 'utf8');
290
- console.log();
291
- logger.success(`.env.aidev written to ${dest}`);
292
- logger.info(`Agents: ${agents} ${dim('(first = primary, rest = fallback)')}`);
293
- } finally {
294
- rl.close();
295
- }
296
- }
@@ -1,283 +0,0 @@
1
- import { Config, Task, Comment } from '../types';
2
- import { TaskProvider } from '../providers';
3
- import { AIRunner } from '../ai';
4
- import { logger, logRunStart } from '../logger';
5
- import * as git from '../git';
6
-
7
- const SKIP_STATUSES = new Set(['closed', 'done', 'cancelled', 'complete']);
8
-
9
- export type RunFilter = 'all' | 'open' | 'pending';
10
-
11
- export async function runCommand(
12
- filter: RunFilter,
13
- config: Config,
14
- provider: TaskProvider,
15
- runners: AIRunner[]
16
- ): Promise<void> {
17
- logRunStart();
18
- logger.info(`Fetching tasks (filter: ${filter})...`);
19
- const tasks = await provider.fetchTasks();
20
- logger.info(`Found ${tasks.length} tagged task(s)`);
21
-
22
- let processed = 0;
23
- let skipped = 0;
24
-
25
- for (const task of tasks) {
26
- const result = await processTask(task, filter, config, provider, runners);
27
- if (result === 'processed') processed++;
28
- else skipped++;
29
- }
30
-
31
- logger.success(`Done. Processed: ${processed}, Skipped: ${skipped}`);
32
- }
33
-
34
- async function processTask(
35
- task: Task,
36
- filter: RunFilter,
37
- config: Config,
38
- provider: TaskProvider,
39
- runners: AIRunner[]
40
- ): Promise<'processed' | 'skipped'> {
41
- const isPending = task.status.toLowerCase() === config.clickupPendingStatus.toLowerCase();
42
-
43
- logger.task(`[${task.id}] "${task.name}" (status: ${task.status})`);
44
-
45
- // Skip terminal statuses
46
- if (SKIP_STATUSES.has(task.status.toLowerCase())) {
47
- logger.debug(`Skipping — terminal status: ${task.status}`);
48
- return 'skipped';
49
- }
50
-
51
- // Skip if remote branch already exists for this task
52
- const branchName = `${task.id}/${git.slugify(task.name)}`;
53
- if (git.remoteBranchExists(config.gitRemote, branchName)) {
54
- logger.debug(`Skipping — branch already exists: ${branchName}`);
55
- return 'skipped';
56
- }
57
-
58
- // Apply filter
59
- if (filter === 'open' && isPending) {
60
- logger.debug('Skipping — filter=open, task is pending');
61
- return 'skipped';
62
- }
63
- if (filter === 'pending' && !isPending) {
64
- logger.debug('Skipping — filter=pending, task is not pending');
65
- return 'skipped';
66
- }
67
-
68
- // Handle pending tasks: check if a human replied
69
- if (isPending) {
70
- const hasReply = await checkForHumanReply(task, provider);
71
- if (!hasReply) {
72
- logger.debug('Skipping — pending task has no human reply yet');
73
- return 'skipped';
74
- }
75
- logger.info('Pending task has a human reply — proceeding');
76
- } else {
77
- // Check if task needs clarification
78
- const clarification = await checkNeedsClarification(task, config, provider, runners);
79
- if (clarification) {
80
- await provider.postComment(task.id, clarification);
81
- await provider.updateStatus(task.id, config.clickupPendingStatus);
82
- logger.info(`Posted clarification question, set status to ${config.clickupPendingStatus}`);
83
- return 'skipped';
84
- }
85
- }
86
-
87
- // Implement the task
88
- await implementTask(task, branchName, config, provider, runners);
89
- return 'processed';
90
- }
91
-
92
- async function checkForHumanReply(task: Task, provider: TaskProvider): Promise<boolean> {
93
- const comments = await provider.getComments(task.id);
94
- if (comments.length < 2) return false;
95
-
96
- // The last comment should be from a human (not a bot — heuristic: not containing "[aidev]")
97
- const lastComment = comments[comments.length - 1];
98
- return !lastComment.text.includes('[aidev]');
99
- }
100
-
101
- async function checkNeedsClarification(
102
- task: Task,
103
- config: Config,
104
- provider: TaskProvider,
105
- runners: AIRunner[]
106
- ): Promise<string | null> {
107
- if (config.devNotesMode === 'always') {
108
- return `Any dev notes or implementation preferences for this task?\n\nTask: ${task.name}`;
109
- }
110
-
111
- // smart mode: ask AI if the task is clear
112
- const runner = runners.find((r) => r.isAvailable());
113
- if (!runner) {
114
- logger.warn('No AI runner available — skipping clarification check');
115
- return null;
116
- }
117
-
118
- const clarificationPrompt = `You are a senior software developer reviewing a task.
119
- Determine if the following task has enough information to implement without further clarification.
120
-
121
- Task name: ${task.name}
122
- Task description: ${task.description || '(no description)'}
123
-
124
- Respond with valid JSON only:
125
- {
126
- "clear": true|false,
127
- "question": "question to ask if not clear, or null"
128
- }`;
129
-
130
- const result = await runner.run(clarificationPrompt);
131
- if (!result.success) {
132
- logger.warn('Clarification check failed — proceeding without clarification');
133
- return null;
134
- }
135
-
136
- try {
137
- const jsonMatch = result.output.match(/\{[\s\S]*\}/);
138
- if (!jsonMatch) return null;
139
- const parsed = JSON.parse(jsonMatch[0]) as { clear: boolean; question?: string | null };
140
- if (!parsed.clear && parsed.question) {
141
- return parsed.question;
142
- }
143
- } catch {
144
- logger.debug('Could not parse clarification response — proceeding');
145
- }
146
-
147
- return null;
148
- }
149
-
150
- async function implementTask(
151
- task: Task,
152
- branchName: string,
153
- config: Config,
154
- provider: TaskProvider,
155
- runners: AIRunner[]
156
- ): Promise<void> {
157
- logger.info(`Implementing task: ${task.name}`);
158
-
159
- // Mark as in progress
160
- try {
161
- await provider.updateStatus(task.id, 'in progress');
162
- await provider.postComment(task.id, `[aidev] Starting implementation on branch \`${branchName}\``);
163
- } catch (err) {
164
- logger.warn(`Could not update task status: ${err}`);
165
- }
166
-
167
- // Prepare git branch
168
- if (!git.fetchAndCheckout(config.gitRemote, config.githubBaseBranch)) {
169
- logger.error('Failed to prepare base branch');
170
- await provider.postComment(task.id, '[aidev] Failed to prepare git branch. Manual intervention needed.');
171
- return;
172
- }
173
-
174
- if (!git.createBranch(branchName)) {
175
- logger.error(`Failed to create branch ${branchName}`);
176
- return;
177
- }
178
-
179
- // Get conversation context for pending tasks
180
- let context = '';
181
- try {
182
- const comments = await provider.getComments(task.id);
183
- if (comments.length > 0) {
184
- context = '\n\nConversation context:\n' + comments.map((c) => `${c.author}: ${c.text}`).join('\n');
185
- }
186
- } catch {
187
- // ignore
188
- }
189
-
190
- const implementPrompt = buildImplementPrompt(task, context);
191
-
192
- // Run AI runners in order with fallback
193
- let implemented = false;
194
- let previousNotes = '';
195
-
196
- for (const runner of runners) {
197
- if (!runner.isAvailable()) {
198
- logger.debug(`${runner.name} not available, skipping`);
199
- continue;
200
- }
201
-
202
- logger.info(`Running ${runner.name}...`);
203
- const result = await runner.run(implementPrompt, previousNotes || undefined);
204
-
205
- if (result.success) {
206
- implemented = true;
207
- break;
208
- }
209
-
210
- logger.warn(`${runner.name} failed — trying next runner`);
211
- previousNotes = `Previous runner (${runner.name}) output:\n${result.output}\nErrors:\n${result.error}`;
212
- }
213
-
214
- if (!implemented) {
215
- logger.error('All AI runners failed');
216
- await provider.postComment(task.id, '[aidev] All AI runners failed. Manual implementation needed.');
217
- git.deleteBranch(branchName);
218
- return;
219
- }
220
-
221
- // Check if AI made changes
222
- if (!git.hasChanges()) {
223
- logger.warn('AI runner produced no file changes');
224
- await provider.postComment(
225
- task.id,
226
- '[aidev] AI runner completed but made no code changes. More information may be needed.'
227
- );
228
- git.deleteBranch(branchName);
229
- return;
230
- }
231
-
232
- // Commit and push
233
- if (!git.addAll() || !git.commit(`[aidev] Implement: ${task.name}\n\nTask: ${task.url}`)) {
234
- logger.error('Failed to commit changes');
235
- return;
236
- }
237
-
238
- if (!git.push(config.gitRemote, branchName)) {
239
- logger.error('Failed to push branch');
240
- return;
241
- }
242
-
243
- // Post completion comment
244
- const prUrl = buildPRUrl(config, branchName);
245
- const comment = buildCompletionComment(branchName, prUrl, config);
246
- await provider.postComment(task.id, comment);
247
- await provider.updateStatus(task.id, config.clickupInReviewStatus);
248
-
249
- logger.success(`Task implemented: branch ${branchName} pushed`);
250
- }
251
-
252
- function buildImplementPrompt(task: Task, context: string): string {
253
- return `You are implementing a software development task. Make the necessary code changes to complete the task described below.
254
-
255
- Task: ${task.name}
256
-
257
- Description:
258
- ${task.description || '(no description provided)'}
259
- ${context}
260
-
261
- Please implement the required changes. Focus on correctness and follow the existing code style in the project.`;
262
- }
263
-
264
- function buildPRUrl(config: Config, branch: string): string {
265
- if (!config.githubRepo) return '';
266
- const encoded = encodeURIComponent(branch);
267
- return `https://github.com/${config.githubRepo}/compare/${config.githubBaseBranch}...${encoded}?expand=1`;
268
- }
269
-
270
- function buildCompletionComment(branch: string, prUrl: string, config: Config): string {
271
- const lines = [
272
- `[aidev] Implementation complete!`,
273
- ``,
274
- `Branch: \`${branch}\``,
275
- ];
276
-
277
- if (prUrl) {
278
- lines.push(`Open PR: ${prUrl}`);
279
- }
280
-
281
- lines.push(``, `Status set to: ${config.clickupInReviewStatus}`);
282
- return lines.join('\n');
283
- }