vibeman 0.0.1 → 0.0.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.
- package/dist/index.js +5 -7
- package/dist/runtime/api/.tsbuildinfo +1 -1
- package/dist/runtime/api/agent/agent-service.d.ts +11 -13
- package/dist/runtime/api/agent/agent-service.js +25 -31
- package/dist/runtime/api/agent/ai-providers/claude-code-adapter.d.ts +2 -2
- package/dist/runtime/api/agent/ai-providers/claude-code-adapter.js +25 -36
- package/dist/runtime/api/agent/ai-providers/codex-cli-provider.js +48 -14
- package/dist/runtime/api/agent/ai-providers/types.d.ts +2 -0
- package/dist/runtime/api/agent/codex-cli-provider.test.js +37 -0
- package/dist/runtime/api/agent/parsers.d.ts +1 -0
- package/dist/runtime/api/agent/parsers.js +75 -8
- package/dist/runtime/api/agent/prompt-service.d.ts +14 -1
- package/dist/runtime/api/agent/prompt-service.js +123 -14
- package/dist/runtime/api/agent/prompt-service.test.d.ts +1 -0
- package/dist/runtime/api/agent/prompt-service.test.js +230 -0
- package/dist/runtime/api/agent/routing-policy.d.ts +14 -14
- package/dist/runtime/api/api/routers/ai.d.ts +6 -6
- package/dist/runtime/api/api/routers/ai.js +2 -17
- package/dist/runtime/api/api/routers/executions.d.ts +5 -5
- package/dist/runtime/api/api/routers/executions.js +12 -21
- package/dist/runtime/api/api/routers/provider-config.d.ts +165 -0
- package/dist/runtime/api/api/routers/provider-config.js +252 -0
- package/dist/runtime/api/api/routers/tasks.d.ts +10 -10
- package/dist/runtime/api/api/routers/workflows.d.ts +15 -16
- package/dist/runtime/api/api/routers/workflows.js +28 -26
- package/dist/runtime/api/api/routers/worktrees.d.ts +4 -5
- package/dist/runtime/api/api/routers/worktrees.js +11 -11
- package/dist/runtime/api/api/trpc.d.ts +18 -18
- package/dist/runtime/api/index.js +2 -10
- package/dist/runtime/api/lib/local-config.d.ts +245 -0
- package/dist/runtime/api/lib/local-config.js +288 -0
- package/dist/runtime/api/lib/provider-detection.d.ts +59 -0
- package/dist/runtime/api/lib/provider-detection.js +244 -0
- package/dist/runtime/api/lib/server/bootstrap.d.ts +38 -0
- package/dist/runtime/api/lib/server/bootstrap.js +197 -0
- package/dist/runtime/api/lib/server/project-root.js +24 -1
- package/dist/runtime/api/lib/trpc/server.d.ts +124 -31
- package/dist/runtime/api/lib/trpc/server.js +8 -8
- package/dist/runtime/api/lib/trpc/ws-server.js +2 -2
- package/dist/runtime/api/router.d.ts +125 -32
- package/dist/runtime/api/router.js +9 -31
- package/dist/runtime/api/settings-service.js +2 -0
- package/dist/runtime/api/workflows/vibing-orchestrator.d.ts +8 -3
- package/dist/runtime/api/workflows/vibing-orchestrator.js +182 -183
- package/dist/runtime/web/.next/BUILD_ID +1 -1
- package/dist/runtime/web/.next/app-build-manifest.json +2 -2
- package/dist/runtime/web/.next/build-manifest.json +2 -2
- package/dist/runtime/web/.next/prerender-manifest.json +3 -3
- package/dist/runtime/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dist/runtime/web/.next/server/app/_not-found.html +2 -2
- package/dist/runtime/web/.next/server/app/_not-found.rsc +5 -5
- package/dist/runtime/web/.next/server/app/api/health/route.js +1 -1
- package/dist/runtime/web/.next/server/app/api/health/route_client-reference-manifest.js +1 -1
- package/dist/runtime/web/.next/server/app/api/images/[...path]/route_client-reference-manifest.js +1 -1
- package/dist/runtime/web/.next/server/app/api/upload/route_client-reference-manifest.js +1 -1
- package/dist/runtime/web/.next/server/app/index.html +2 -2
- package/dist/runtime/web/.next/server/app/index.rsc +6 -6
- package/dist/runtime/web/.next/server/app/page.js +3 -3
- package/dist/runtime/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/runtime/web/.next/server/chunks/458.js +1 -1
- package/dist/runtime/web/.next/server/pages/404.html +2 -2
- package/dist/runtime/web/.next/server/pages/500.html +1 -1
- package/dist/runtime/web/.next/server/pages-manifest.json +1 -1
- package/dist/runtime/web/.next/server/server-reference-manifest.json +1 -1
- package/dist/runtime/web/.next/static/chunks/app/{layout-dc0cfd29075b2160.js → layout-8435322f09fd0975.js} +1 -1
- package/dist/runtime/web/.next/static/chunks/app/page-8c3ba579efc6f918.js +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -1
- package/dist/runtime/web/.next/static/chunks/app/page-f34a8b196b18850b.js +0 -1
- /package/dist/runtime/web/.next/static/{1HR8N0rJkCvFRtbTPJMyH → mRpNgPfbYR_0wrODzlg_4}/_buildManifest.js +0 -0
- /package/dist/runtime/web/.next/static/{1HR8N0rJkCvFRtbTPJMyH → mRpNgPfbYR_0wrODzlg_4}/_ssgManifest.js +0 -0
|
@@ -4,13 +4,26 @@ export declare class PromptService {
|
|
|
4
4
|
private projectRoot;
|
|
5
5
|
private taskService;
|
|
6
6
|
constructor(projectRoot: string, taskService: TaskService);
|
|
7
|
+
/**
|
|
8
|
+
* Generate absolute path to task file
|
|
9
|
+
*/
|
|
10
|
+
private generateTaskFilePath;
|
|
11
|
+
/**
|
|
12
|
+
* Validate that task file exists at the given path
|
|
13
|
+
*/
|
|
14
|
+
private validateTaskFile;
|
|
7
15
|
generateImprovementPrompt(task: Task, taskData: {
|
|
8
16
|
title: string;
|
|
9
17
|
type: string;
|
|
10
18
|
priority: string;
|
|
11
19
|
content: string;
|
|
12
20
|
}): Promise<string>;
|
|
13
|
-
generateTaskPrompt(task: Task): Promise<string>;
|
|
21
|
+
generateTaskPrompt(task: Task, workflowConfig?: import('../types/index.js').VibingConfig): Promise<string>;
|
|
22
|
+
/**
|
|
23
|
+
* Build a prominent, clearly formatted instruction section derived from workflow options.
|
|
24
|
+
* If no options are provided or none apply, returns a minimal guidance line.
|
|
25
|
+
*/
|
|
26
|
+
private buildWorkflowInstructions;
|
|
14
27
|
generateAIMergePrompt(task: Task, worktree: WorktreeInfo, baseBranch?: string): Promise<string>;
|
|
15
28
|
generateReviewPrompt(task: Task, worktree: WorktreeInfo, reviewContext?: string): Promise<string>;
|
|
16
29
|
}
|
|
@@ -70,8 +70,11 @@ If you recommend changes to type or priority, include them at the top of your re
|
|
|
70
70
|
Return a JSON object with:
|
|
71
71
|
- "type": one of [feature, bug, chore, refactor, test, doc]
|
|
72
72
|
- "priority": one of [high, medium, low]
|
|
73
|
+
- "title": a concise, descriptive title for the task (optional, but preferred)
|
|
73
74
|
- "content": the improved task specification in markdown format with proper JSON escaping
|
|
74
75
|
|
|
76
|
+
If you include a title in the JSON response, do NOT include an H1 heading at the beginning of the content field to avoid duplication.
|
|
77
|
+
|
|
75
78
|
## Project Context
|
|
76
79
|
|
|
77
80
|
<ProjectContext>
|
|
@@ -95,6 +98,10 @@ const TASK_TEMPLATE = `## Task Assignment
|
|
|
95
98
|
|
|
96
99
|
Deliver the following task.
|
|
97
100
|
|
|
101
|
+
## Workflow Instructions
|
|
102
|
+
|
|
103
|
+
{{workflowInstructions}}
|
|
104
|
+
|
|
98
105
|
## Responsibilities
|
|
99
106
|
|
|
100
107
|
1. **Analyze requirements** — study the acceptance criteria to clarify what must be delivered.
|
|
@@ -132,6 +139,8 @@ Begin by examining the codebase; then proceed with the implementation.
|
|
|
132
139
|
- **Status:** {{taskStatus}}
|
|
133
140
|
- **Tags:** {{tagsList}}
|
|
134
141
|
|
|
142
|
+
{{taskFilePathInfo}}
|
|
143
|
+
|
|
135
144
|
#### Description
|
|
136
145
|
|
|
137
146
|
{{taskContent}}
|
|
@@ -155,7 +164,7 @@ If conflicts:
|
|
|
155
164
|
`;
|
|
156
165
|
const REVIEW_TEMPLATE = `## Code Review Task
|
|
157
166
|
|
|
158
|
-
Review the changes made for this task comprehensively.
|
|
167
|
+
Review the changes made for this task comprehensively. Return a JSON string with Fenced JSON.
|
|
159
168
|
|
|
160
169
|
1. **Analyze the implemented changes** by examining the modified files, some of them might be committed in the recent commits
|
|
161
170
|
2. **Evaluate code quality** including:
|
|
@@ -171,17 +180,17 @@ Review the changes made for this task comprehensively.
|
|
|
171
180
|
- Implementation matches the task requirements
|
|
172
181
|
- No unrelated changes were introduced
|
|
173
182
|
|
|
174
|
-
4. **Provide structured feedback** using this JSON structure:
|
|
183
|
+
4. **Provide structured feedback** using this JSON structure with following fields:
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
"reviewSummary": "Brief overview of the implementation and overall assessment",
|
|
187
|
+
"recommendations": [
|
|
188
|
+
"Specific recommendation 1",
|
|
189
|
+
"Specific recommendation 2",
|
|
190
|
+
"Specific recommendation 3"
|
|
191
|
+
],
|
|
192
|
+
"qualityScore": 85
|
|
175
193
|
|
|
176
|
-
{
|
|
177
|
-
"reviewSummary": "Brief overview of the implementation and overall assessment",
|
|
178
|
-
"recommendations": [
|
|
179
|
-
"Specific recommendation 1",
|
|
180
|
-
"Specific recommendation 2",
|
|
181
|
-
"Specific recommendation 3"
|
|
182
|
-
],
|
|
183
|
-
"qualityScore": 85
|
|
184
|
-
}
|
|
185
194
|
|
|
186
195
|
## Review Guidelines
|
|
187
196
|
|
|
@@ -194,7 +203,7 @@ Review the changes made for this task comprehensively.
|
|
|
194
203
|
- Testing & coverage: 0-20
|
|
195
204
|
- Security & performance considerations: 0-15
|
|
196
205
|
|
|
197
|
-
If you identify any critical bugs or potential improvements, provide detailed notes,
|
|
206
|
+
If you identify any critical bugs or potential improvements, provide detailed notes, acceptance criteria, implementation details, todo list, etc. in the Recommendations section and return a score below 70. This will trigger a reimplementation based on your feedback.
|
|
198
207
|
|
|
199
208
|
---
|
|
200
209
|
|
|
@@ -258,6 +267,32 @@ export class PromptService {
|
|
|
258
267
|
this.projectRoot = projectRoot;
|
|
259
268
|
this.taskService = taskService;
|
|
260
269
|
}
|
|
270
|
+
/**
|
|
271
|
+
* Generate absolute path to task file
|
|
272
|
+
*/
|
|
273
|
+
generateTaskFilePath(taskId) {
|
|
274
|
+
try {
|
|
275
|
+
const vibeDir = getVibeDir();
|
|
276
|
+
const taskFilePath = path.resolve(vibeDir, 'tasks', `${taskId}.md`);
|
|
277
|
+
return taskFilePath;
|
|
278
|
+
}
|
|
279
|
+
catch (error) {
|
|
280
|
+
log.error(`Failed to generate task file path for ${taskId}`, error, 'prompt-service');
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Validate that task file exists at the given path
|
|
286
|
+
*/
|
|
287
|
+
async validateTaskFile(filePath) {
|
|
288
|
+
try {
|
|
289
|
+
await fs.access(filePath, fs.constants.F_OK);
|
|
290
|
+
return true;
|
|
291
|
+
}
|
|
292
|
+
catch {
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
261
296
|
async generateImprovementPrompt(task, taskData) {
|
|
262
297
|
// Read project context
|
|
263
298
|
const productOverviewPath = path.join(getVibeDir(), 'product_overview.md');
|
|
@@ -268,6 +303,12 @@ export class PromptService {
|
|
|
268
303
|
catch {
|
|
269
304
|
log.warn('Could not read product_overview.md for improvement prompt', undefined, 'prompt-service');
|
|
270
305
|
}
|
|
306
|
+
// Add task file path to project context for improvement prompts
|
|
307
|
+
const taskFilePath = this.generateTaskFilePath(task.id);
|
|
308
|
+
if (taskFilePath && (await this.validateTaskFile(taskFilePath))) {
|
|
309
|
+
productContext += `\n\n**Current task file location:** ${taskFilePath}`;
|
|
310
|
+
log.info(`Task file path included in improvement prompt`, { taskId: task.id, path: taskFilePath }, 'prompt-service');
|
|
311
|
+
}
|
|
271
312
|
return render(IMPROVEMENT_TEMPLATE, {
|
|
272
313
|
projectContext: productContext,
|
|
273
314
|
taskId: task.id,
|
|
@@ -278,8 +319,28 @@ export class PromptService {
|
|
|
278
319
|
taskContent: taskData.content,
|
|
279
320
|
});
|
|
280
321
|
}
|
|
281
|
-
async generateTaskPrompt(task) {
|
|
322
|
+
async generateTaskPrompt(task, workflowConfig) {
|
|
282
323
|
const tagsList = task.tags.join(', ');
|
|
324
|
+
// Generate and validate task file path
|
|
325
|
+
const taskFilePath = this.generateTaskFilePath(task.id);
|
|
326
|
+
let taskFilePathInfo = '';
|
|
327
|
+
if (taskFilePath) {
|
|
328
|
+
const isValid = await this.validateTaskFile(taskFilePath);
|
|
329
|
+
if (isValid) {
|
|
330
|
+
taskFilePathInfo = `- **Current task file:** ${taskFilePath}`;
|
|
331
|
+
log.info(`Task file path included in prompt`, { taskId: task.id, path: taskFilePath }, 'prompt-service');
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
taskFilePathInfo = `- **Task file:** (not accessible at expected path: ${taskFilePath})`;
|
|
335
|
+
log.warn(`Task file not accessible`, { taskId: task.id, path: taskFilePath }, 'prompt-service');
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
else {
|
|
339
|
+
taskFilePathInfo = `- **Task file:** (path could not be resolved)`;
|
|
340
|
+
log.error(`Could not generate task file path`, { taskId: task.id }, 'prompt-service');
|
|
341
|
+
}
|
|
342
|
+
// Build workflow instruction prompts based on workflow configuration
|
|
343
|
+
const workflowInstructions = this.buildWorkflowInstructions(workflowConfig);
|
|
283
344
|
return render(TASK_TEMPLATE, {
|
|
284
345
|
taskId: task.id,
|
|
285
346
|
taskTitle: task.title,
|
|
@@ -288,8 +349,50 @@ export class PromptService {
|
|
|
288
349
|
taskStatus: task.status,
|
|
289
350
|
taskContent: task.content,
|
|
290
351
|
tagsList,
|
|
352
|
+
taskFilePathInfo,
|
|
353
|
+
workflowInstructions,
|
|
291
354
|
});
|
|
292
355
|
}
|
|
356
|
+
/**
|
|
357
|
+
* Build a prominent, clearly formatted instruction section derived from workflow options.
|
|
358
|
+
* If no options are provided or none apply, returns a minimal guidance line.
|
|
359
|
+
*/
|
|
360
|
+
buildWorkflowInstructions(cfg) {
|
|
361
|
+
const lines = [];
|
|
362
|
+
if (!cfg) {
|
|
363
|
+
// Default lightweight guidance when workflow config is unavailable
|
|
364
|
+
return '- Follow project conventions and ask before destructive actions.';
|
|
365
|
+
}
|
|
366
|
+
// Auto Commit disabled prompt
|
|
367
|
+
if (cfg.autoCommit === false) {
|
|
368
|
+
lines.push('IMPORTANT: AUTO COMMIT IS DISABLED');
|
|
369
|
+
lines.push('- DO NOT commit any changes');
|
|
370
|
+
lines.push('- DO NOT run git commit commands');
|
|
371
|
+
lines.push('- Leave all changes staged for manual review');
|
|
372
|
+
lines.push('');
|
|
373
|
+
}
|
|
374
|
+
// PR creation disabled prompt
|
|
375
|
+
if (cfg.createPR === false) {
|
|
376
|
+
lines.push('IMPORTANT: PR CREATION IS DISABLED');
|
|
377
|
+
lines.push('- DO NOT create pull requests');
|
|
378
|
+
lines.push('- DO NOT run gh pr create commands');
|
|
379
|
+
lines.push('- Prepare changes for manual PR creation');
|
|
380
|
+
lines.push('');
|
|
381
|
+
}
|
|
382
|
+
// Auto merge disabled prompt
|
|
383
|
+
if (cfg.autoMerge === false) {
|
|
384
|
+
lines.push('IMPORTANT: AUTO MERGE IS DISABLED');
|
|
385
|
+
lines.push('- DO NOT merge pull requests automatically');
|
|
386
|
+
lines.push('- DO NOT run git merge commands');
|
|
387
|
+
lines.push('- Prepare changes for manual merge approval');
|
|
388
|
+
lines.push('');
|
|
389
|
+
}
|
|
390
|
+
// If nothing matched, provide a simple fallback
|
|
391
|
+
if (lines.length === 0) {
|
|
392
|
+
return '- No special workflow constraints. Proceed with standard process.';
|
|
393
|
+
}
|
|
394
|
+
return lines.join('\n');
|
|
395
|
+
}
|
|
293
396
|
async generateAIMergePrompt(task, worktree, baseBranch) {
|
|
294
397
|
const repoPath = this.projectRoot;
|
|
295
398
|
const featureBr = worktree.branchName;
|
|
@@ -321,7 +424,13 @@ export class PromptService {
|
|
|
321
424
|
});
|
|
322
425
|
}
|
|
323
426
|
async generateReviewPrompt(task, worktree, reviewContext) {
|
|
324
|
-
|
|
427
|
+
let additionalContext = reviewContext ? `\n**Additional Context:** ${reviewContext}` : '';
|
|
428
|
+
// Add task file path to additional context
|
|
429
|
+
const taskFilePath = this.generateTaskFilePath(task.id);
|
|
430
|
+
if (taskFilePath && (await this.validateTaskFile(taskFilePath))) {
|
|
431
|
+
additionalContext += `\n**Current task file:** ${taskFilePath}`;
|
|
432
|
+
log.info(`Task file path included in review prompt`, { taskId: task.id, path: taskFilePath }, 'prompt-service');
|
|
433
|
+
}
|
|
325
434
|
return render(REVIEW_TEMPLATE, {
|
|
326
435
|
taskId: task.id,
|
|
327
436
|
taskTitle: task.title,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
import { promises as fs } from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { PromptService } from './prompt-service.js';
|
|
5
|
+
const TEST_DATA_DIR = path.resolve(process.cwd(), '.test-prompt-service');
|
|
6
|
+
const TEST_TASKS_DIR = path.join(TEST_DATA_DIR, 'tasks');
|
|
7
|
+
// Mock getVibeDir to return test directory
|
|
8
|
+
vi.mock('../lib/server/project-root', () => ({
|
|
9
|
+
getVibeDir: () => TEST_DATA_DIR,
|
|
10
|
+
getProjectRoot: () => TEST_DATA_DIR,
|
|
11
|
+
}));
|
|
12
|
+
// Mock TaskService
|
|
13
|
+
const mockTaskService = {
|
|
14
|
+
getTask: vi.fn(),
|
|
15
|
+
getAllTasks: vi.fn(),
|
|
16
|
+
};
|
|
17
|
+
describe('PromptService Task File Path Integration', () => {
|
|
18
|
+
let promptService;
|
|
19
|
+
beforeEach(async () => {
|
|
20
|
+
promptService = new PromptService(TEST_DATA_DIR, mockTaskService);
|
|
21
|
+
// Create test directories
|
|
22
|
+
await fs.mkdir(TEST_TASKS_DIR, { recursive: true });
|
|
23
|
+
});
|
|
24
|
+
afterEach(async () => {
|
|
25
|
+
// Clean up test data
|
|
26
|
+
try {
|
|
27
|
+
await fs.rm(TEST_DATA_DIR, { recursive: true, force: true });
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// Ignore cleanup errors
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
describe('generateTaskPrompt', () => {
|
|
34
|
+
it('should include absolute path when task file exists', async () => {
|
|
35
|
+
const mockTask = {
|
|
36
|
+
id: 'TEST-TASK-001',
|
|
37
|
+
title: 'Test Task',
|
|
38
|
+
type: 'feature',
|
|
39
|
+
status: 'in-progress',
|
|
40
|
+
priority: 'high',
|
|
41
|
+
tags: ['test'],
|
|
42
|
+
content: 'Test task content',
|
|
43
|
+
created_at: '2024-09-04T12:00:00.000Z',
|
|
44
|
+
updated_at: '2024-09-04T12:00:00.000Z',
|
|
45
|
+
assignee: [],
|
|
46
|
+
comments: [],
|
|
47
|
+
};
|
|
48
|
+
// Create task file
|
|
49
|
+
const taskFilePath = path.join(TEST_TASKS_DIR, 'TEST-TASK-001.md');
|
|
50
|
+
const taskFileContent = `---
|
|
51
|
+
id: TEST-TASK-001
|
|
52
|
+
title: Test Task
|
|
53
|
+
type: feature
|
|
54
|
+
status: in-progress
|
|
55
|
+
priority: high
|
|
56
|
+
tags: test
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
Test task content`;
|
|
60
|
+
await fs.writeFile(taskFilePath, taskFileContent, 'utf-8');
|
|
61
|
+
const prompt = await promptService.generateTaskPrompt(mockTask);
|
|
62
|
+
expect(prompt).toContain('Current task file:');
|
|
63
|
+
expect(prompt).toContain(taskFilePath);
|
|
64
|
+
expect(prompt).toContain('TEST-TASK-001');
|
|
65
|
+
expect(prompt).toContain('Test Task');
|
|
66
|
+
expect(prompt).toContain('Test task content');
|
|
67
|
+
});
|
|
68
|
+
it('should handle missing task file gracefully', async () => {
|
|
69
|
+
const mockTask = {
|
|
70
|
+
id: 'NONEXISTENT-TASK',
|
|
71
|
+
title: 'Nonexistent Task',
|
|
72
|
+
type: 'bug',
|
|
73
|
+
status: 'backlog',
|
|
74
|
+
priority: 'medium',
|
|
75
|
+
tags: [],
|
|
76
|
+
content: 'This task file does not exist',
|
|
77
|
+
created_at: '2024-09-04T12:00:00.000Z',
|
|
78
|
+
updated_at: '2024-09-04T12:00:00.000Z',
|
|
79
|
+
assignee: [],
|
|
80
|
+
comments: [],
|
|
81
|
+
};
|
|
82
|
+
const prompt = await promptService.generateTaskPrompt(mockTask);
|
|
83
|
+
expect(prompt).toContain('not accessible at expected path');
|
|
84
|
+
expect(prompt).toContain('NONEXISTENT-TASK.md');
|
|
85
|
+
expect(prompt).toContain('Nonexistent Task');
|
|
86
|
+
expect(prompt).toContain('This task file does not exist');
|
|
87
|
+
});
|
|
88
|
+
it('should handle path resolution errors', async () => {
|
|
89
|
+
// Mock generateTaskFilePath to return null (simulating path resolution error)
|
|
90
|
+
const originalGenerateTaskFilePath = promptService.generateTaskFilePath;
|
|
91
|
+
promptService.generateTaskFilePath = vi.fn().mockReturnValue(null);
|
|
92
|
+
const mockTask = {
|
|
93
|
+
id: 'ERROR-TASK',
|
|
94
|
+
title: 'Error Task',
|
|
95
|
+
type: 'chore',
|
|
96
|
+
status: 'review',
|
|
97
|
+
priority: 'low',
|
|
98
|
+
tags: ['error'],
|
|
99
|
+
content: 'Task with path resolution error',
|
|
100
|
+
created_at: '2024-09-04T12:00:00.000Z',
|
|
101
|
+
updated_at: '2024-09-04T12:00:00.000Z',
|
|
102
|
+
assignee: [],
|
|
103
|
+
comments: [],
|
|
104
|
+
};
|
|
105
|
+
const prompt = await promptService.generateTaskPrompt(mockTask);
|
|
106
|
+
expect(prompt).toContain('path could not be resolved');
|
|
107
|
+
expect(prompt).toContain('ERROR-TASK');
|
|
108
|
+
expect(prompt).toContain('Task with path resolution error');
|
|
109
|
+
// Restore original method
|
|
110
|
+
promptService.generateTaskFilePath = originalGenerateTaskFilePath;
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
describe('generateImprovementPrompt', () => {
|
|
114
|
+
it('should include task file path in project context', async () => {
|
|
115
|
+
const mockTask = {
|
|
116
|
+
id: 'IMPROVE-TASK-001',
|
|
117
|
+
title: 'Improvement Task',
|
|
118
|
+
type: 'feature',
|
|
119
|
+
status: 'backlog',
|
|
120
|
+
priority: 'medium',
|
|
121
|
+
tags: [],
|
|
122
|
+
content: 'Original content',
|
|
123
|
+
created_at: '2024-09-04T12:00:00.000Z',
|
|
124
|
+
updated_at: '2024-09-04T12:00:00.000Z',
|
|
125
|
+
assignee: [],
|
|
126
|
+
comments: [],
|
|
127
|
+
};
|
|
128
|
+
const taskData = {
|
|
129
|
+
title: 'Improved Task',
|
|
130
|
+
type: 'feature',
|
|
131
|
+
priority: 'high',
|
|
132
|
+
content: 'Improved content',
|
|
133
|
+
};
|
|
134
|
+
// Create task file
|
|
135
|
+
const taskFilePath = path.join(TEST_TASKS_DIR, 'IMPROVE-TASK-001.md');
|
|
136
|
+
await fs.writeFile(taskFilePath, 'Task file content', 'utf-8');
|
|
137
|
+
const prompt = await promptService.generateImprovementPrompt(mockTask, taskData);
|
|
138
|
+
expect(prompt).toContain('Current task file location:');
|
|
139
|
+
expect(prompt).toContain(taskFilePath);
|
|
140
|
+
expect(prompt).toContain('IMPROVE-TASK-001');
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
describe('generateReviewPrompt', () => {
|
|
144
|
+
it('should include task file path in additional context', async () => {
|
|
145
|
+
const mockTask = {
|
|
146
|
+
id: 'REVIEW-TASK-001',
|
|
147
|
+
title: 'Review Task',
|
|
148
|
+
type: 'bug',
|
|
149
|
+
status: 'review',
|
|
150
|
+
priority: 'high',
|
|
151
|
+
tags: [],
|
|
152
|
+
content: 'Task under review',
|
|
153
|
+
created_at: '2024-09-04T12:00:00.000Z',
|
|
154
|
+
updated_at: '2024-09-04T12:00:00.000Z',
|
|
155
|
+
assignee: [],
|
|
156
|
+
comments: [],
|
|
157
|
+
};
|
|
158
|
+
const mockWorktree = {
|
|
159
|
+
taskId: 'REVIEW-TASK-001',
|
|
160
|
+
path: '/test/worktree',
|
|
161
|
+
branchName: 'feature/review-task-001',
|
|
162
|
+
};
|
|
163
|
+
// Create task file
|
|
164
|
+
const taskFilePath = path.join(TEST_TASKS_DIR, 'REVIEW-TASK-001.md');
|
|
165
|
+
await fs.writeFile(taskFilePath, 'Review task content', 'utf-8');
|
|
166
|
+
const prompt = await promptService.generateReviewPrompt(mockTask, mockWorktree, 'Custom review context');
|
|
167
|
+
expect(prompt).toContain('Current task file:');
|
|
168
|
+
expect(prompt).toContain(taskFilePath);
|
|
169
|
+
expect(prompt).toContain('Custom review context');
|
|
170
|
+
expect(prompt).toContain('REVIEW-TASK-001');
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
describe('cross-platform path resolution', () => {
|
|
174
|
+
it('should generate valid absolute paths on different platforms', async () => {
|
|
175
|
+
const taskId = 'CROSS-PLATFORM-TEST';
|
|
176
|
+
// Test the private method through a task prompt generation
|
|
177
|
+
const mockTask = {
|
|
178
|
+
id: taskId,
|
|
179
|
+
title: 'Cross Platform Test',
|
|
180
|
+
type: 'test',
|
|
181
|
+
status: 'in-progress',
|
|
182
|
+
priority: 'medium',
|
|
183
|
+
tags: [],
|
|
184
|
+
content: 'Cross platform path test',
|
|
185
|
+
created_at: '2024-09-04T12:00:00.000Z',
|
|
186
|
+
updated_at: '2024-09-04T12:00:00.000Z',
|
|
187
|
+
assignee: [],
|
|
188
|
+
comments: [],
|
|
189
|
+
};
|
|
190
|
+
// Create task file
|
|
191
|
+
const taskFilePath = path.join(TEST_TASKS_DIR, `${taskId}.md`);
|
|
192
|
+
await fs.writeFile(taskFilePath, 'Cross platform test content', 'utf-8');
|
|
193
|
+
const prompt = await promptService.generateTaskPrompt(mockTask);
|
|
194
|
+
// Verify the path is absolute and uses correct separators
|
|
195
|
+
const pathMatch = prompt.match(/- \*\*Current task file:\*\* (.+)/);
|
|
196
|
+
expect(pathMatch).toBeTruthy();
|
|
197
|
+
if (pathMatch) {
|
|
198
|
+
const extractedPath = pathMatch[1];
|
|
199
|
+
expect(path.isAbsolute(extractedPath)).toBe(true);
|
|
200
|
+
expect(extractedPath).toContain(taskId);
|
|
201
|
+
expect(extractedPath.endsWith('.md')).toBe(true);
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
describe('error handling', () => {
|
|
206
|
+
it('should handle file system permission errors gracefully', async () => {
|
|
207
|
+
const mockTask = {
|
|
208
|
+
id: 'PERMISSION-TEST',
|
|
209
|
+
title: 'Permission Test',
|
|
210
|
+
type: 'chore',
|
|
211
|
+
status: 'backlog',
|
|
212
|
+
priority: 'low',
|
|
213
|
+
tags: [],
|
|
214
|
+
content: 'Permission test content',
|
|
215
|
+
created_at: '2024-09-04T12:00:00.000Z',
|
|
216
|
+
updated_at: '2024-09-04T12:00:00.000Z',
|
|
217
|
+
assignee: [],
|
|
218
|
+
comments: [],
|
|
219
|
+
};
|
|
220
|
+
// Mock validateTaskFile to simulate permission error
|
|
221
|
+
const originalValidateTaskFile = promptService.validateTaskFile;
|
|
222
|
+
promptService.validateTaskFile = vi.fn().mockResolvedValueOnce(false);
|
|
223
|
+
const prompt = await promptService.generateTaskPrompt(mockTask);
|
|
224
|
+
expect(prompt).toContain('not accessible at expected path');
|
|
225
|
+
expect(prompt).toContain('PERMISSION-TEST');
|
|
226
|
+
// Restore original function
|
|
227
|
+
promptService.validateTaskFile = originalValidateTaskFile;
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
});
|
|
@@ -15,13 +15,13 @@ export declare const GenerationOptionsSchema: z.ZodObject<{
|
|
|
15
15
|
maxTokens: z.ZodOptional<z.ZodNumber>;
|
|
16
16
|
tools: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
17
17
|
}, "strip", z.ZodTypeAny, {
|
|
18
|
+
temperature?: number | undefined;
|
|
18
19
|
maxTokens?: number | undefined;
|
|
19
20
|
tools?: string[] | undefined;
|
|
20
|
-
temperature?: number | undefined;
|
|
21
21
|
}, {
|
|
22
|
+
temperature?: number | undefined;
|
|
22
23
|
maxTokens?: number | undefined;
|
|
23
24
|
tools?: string[] | undefined;
|
|
24
|
-
temperature?: number | undefined;
|
|
25
25
|
}>;
|
|
26
26
|
export type GenerationOptions = z.infer<typeof GenerationOptionsSchema>;
|
|
27
27
|
/**
|
|
@@ -35,30 +35,30 @@ export declare const OperationConfigSchema: z.ZodObject<{
|
|
|
35
35
|
maxTokens: z.ZodOptional<z.ZodNumber>;
|
|
36
36
|
tools: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
37
37
|
}, "strip", z.ZodTypeAny, {
|
|
38
|
+
temperature?: number | undefined;
|
|
38
39
|
maxTokens?: number | undefined;
|
|
39
40
|
tools?: string[] | undefined;
|
|
40
|
-
temperature?: number | undefined;
|
|
41
41
|
}, {
|
|
42
|
+
temperature?: number | undefined;
|
|
42
43
|
maxTokens?: number | undefined;
|
|
43
44
|
tools?: string[] | undefined;
|
|
44
|
-
temperature?: number | undefined;
|
|
45
45
|
}>>;
|
|
46
46
|
fallback: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
47
47
|
}, "strip", z.ZodTypeAny, {
|
|
48
48
|
provider: string;
|
|
49
49
|
options?: {
|
|
50
|
+
temperature?: number | undefined;
|
|
50
51
|
maxTokens?: number | undefined;
|
|
51
52
|
tools?: string[] | undefined;
|
|
52
|
-
temperature?: number | undefined;
|
|
53
53
|
} | undefined;
|
|
54
54
|
model?: string | undefined;
|
|
55
55
|
fallback?: string[] | undefined;
|
|
56
56
|
}, {
|
|
57
57
|
provider: string;
|
|
58
58
|
options?: {
|
|
59
|
+
temperature?: number | undefined;
|
|
59
60
|
maxTokens?: number | undefined;
|
|
60
61
|
tools?: string[] | undefined;
|
|
61
|
-
temperature?: number | undefined;
|
|
62
62
|
} | undefined;
|
|
63
63
|
model?: string | undefined;
|
|
64
64
|
fallback?: string[] | undefined;
|
|
@@ -77,54 +77,54 @@ export declare const RoutingPolicySchema: z.ZodObject<{
|
|
|
77
77
|
maxTokens: z.ZodOptional<z.ZodNumber>;
|
|
78
78
|
tools: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
79
79
|
}, "strip", z.ZodTypeAny, {
|
|
80
|
+
temperature?: number | undefined;
|
|
80
81
|
maxTokens?: number | undefined;
|
|
81
82
|
tools?: string[] | undefined;
|
|
82
|
-
temperature?: number | undefined;
|
|
83
83
|
}, {
|
|
84
|
+
temperature?: number | undefined;
|
|
84
85
|
maxTokens?: number | undefined;
|
|
85
86
|
tools?: string[] | undefined;
|
|
86
|
-
temperature?: number | undefined;
|
|
87
87
|
}>>;
|
|
88
88
|
fallback: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
89
89
|
}, "strip", z.ZodTypeAny, {
|
|
90
90
|
provider: string;
|
|
91
91
|
options?: {
|
|
92
|
+
temperature?: number | undefined;
|
|
92
93
|
maxTokens?: number | undefined;
|
|
93
94
|
tools?: string[] | undefined;
|
|
94
|
-
temperature?: number | undefined;
|
|
95
95
|
} | undefined;
|
|
96
96
|
model?: string | undefined;
|
|
97
97
|
fallback?: string[] | undefined;
|
|
98
98
|
}, {
|
|
99
99
|
provider: string;
|
|
100
100
|
options?: {
|
|
101
|
+
temperature?: number | undefined;
|
|
101
102
|
maxTokens?: number | undefined;
|
|
102
103
|
tools?: string[] | undefined;
|
|
103
|
-
temperature?: number | undefined;
|
|
104
104
|
} | undefined;
|
|
105
105
|
model?: string | undefined;
|
|
106
106
|
fallback?: string[] | undefined;
|
|
107
107
|
}>>>;
|
|
108
108
|
}, "strip", z.ZodTypeAny, {
|
|
109
109
|
defaultProvider: string;
|
|
110
|
-
operations?: Partial<Record<"execute_task" | "improve_task" | "
|
|
110
|
+
operations?: Partial<Record<"execute_task" | "improve_task" | "ai_merge" | "ai_codereview", {
|
|
111
111
|
provider: string;
|
|
112
112
|
options?: {
|
|
113
|
+
temperature?: number | undefined;
|
|
113
114
|
maxTokens?: number | undefined;
|
|
114
115
|
tools?: string[] | undefined;
|
|
115
|
-
temperature?: number | undefined;
|
|
116
116
|
} | undefined;
|
|
117
117
|
model?: string | undefined;
|
|
118
118
|
fallback?: string[] | undefined;
|
|
119
119
|
}>> | undefined;
|
|
120
120
|
}, {
|
|
121
121
|
defaultProvider: string;
|
|
122
|
-
operations?: Partial<Record<"execute_task" | "improve_task" | "
|
|
122
|
+
operations?: Partial<Record<"execute_task" | "improve_task" | "ai_merge" | "ai_codereview", {
|
|
123
123
|
provider: string;
|
|
124
124
|
options?: {
|
|
125
|
+
temperature?: number | undefined;
|
|
125
126
|
maxTokens?: number | undefined;
|
|
126
127
|
tools?: string[] | undefined;
|
|
127
|
-
temperature?: number | undefined;
|
|
128
128
|
} | undefined;
|
|
129
129
|
model?: string | undefined;
|
|
130
130
|
fallback?: string[] | undefined;
|
|
@@ -8,7 +8,7 @@ import { type RoutingPolicy, type RoutableOperation } from '../../agent/routing-
|
|
|
8
8
|
* Build AI router with required dependencies
|
|
9
9
|
*/
|
|
10
10
|
export declare function buildAIRoutes(options: {
|
|
11
|
-
|
|
11
|
+
agentService: AgentService;
|
|
12
12
|
}): {
|
|
13
13
|
/**
|
|
14
14
|
* List all registered AI providers with health status
|
|
@@ -50,12 +50,12 @@ export declare function buildAIRoutes(options: {
|
|
|
50
50
|
input: {
|
|
51
51
|
policy: {
|
|
52
52
|
defaultProvider?: string | undefined;
|
|
53
|
-
operations?: Partial<Record<"execute_task" | "improve_task" | "
|
|
53
|
+
operations?: Partial<Record<"execute_task" | "improve_task" | "ai_merge" | "ai_codereview", {
|
|
54
54
|
provider: string;
|
|
55
55
|
options?: {
|
|
56
|
+
temperature?: number | undefined;
|
|
56
57
|
maxTokens?: number | undefined;
|
|
57
58
|
tools?: string[] | undefined;
|
|
58
|
-
temperature?: number | undefined;
|
|
59
59
|
} | undefined;
|
|
60
60
|
model?: string | undefined;
|
|
61
61
|
fallback?: string[] | undefined;
|
|
@@ -86,17 +86,17 @@ export declare function buildAIRoutes(options: {
|
|
|
86
86
|
*/
|
|
87
87
|
setOperationConfig: import("@trpc/server").TRPCMutationProcedure<{
|
|
88
88
|
input: {
|
|
89
|
+
operation: "execute_task" | "improve_task" | "ai_merge" | "ai_codereview";
|
|
89
90
|
config: {
|
|
90
91
|
provider: string;
|
|
91
92
|
options?: {
|
|
93
|
+
temperature?: number | undefined;
|
|
92
94
|
maxTokens?: number | undefined;
|
|
93
95
|
tools?: string[] | undefined;
|
|
94
|
-
temperature?: number | undefined;
|
|
95
96
|
} | undefined;
|
|
96
97
|
model?: string | undefined;
|
|
97
98
|
fallback?: string[] | undefined;
|
|
98
99
|
};
|
|
99
|
-
operation: "execute_task" | "improve_task" | "ai_codereview" | "ai_merge";
|
|
100
100
|
};
|
|
101
101
|
output: {
|
|
102
102
|
success: boolean;
|
|
@@ -130,7 +130,7 @@ export declare function buildAIRoutes(options: {
|
|
|
130
130
|
* Useful for unit tests that call functions directly.
|
|
131
131
|
*/
|
|
132
132
|
export declare function buildAIRouteHandlers(options: {
|
|
133
|
-
|
|
133
|
+
agentService: AgentService;
|
|
134
134
|
}): {
|
|
135
135
|
readonly listProviders: () => Promise<{
|
|
136
136
|
name: string;
|