vibeman 0.0.1 → 0.0.3
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 +18 -19
- package/dist/runtime/api/agent/agent-service.js +61 -58
- 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.d.ts +2 -0
- package/dist/runtime/api/agent/ai-providers/codex-cli-provider.js +109 -43
- package/dist/runtime/api/agent/ai-providers/types.d.ts +2 -0
- package/dist/runtime/api/agent/codex-cli-provider.test.js +83 -1
- 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.js +230 -0
- package/dist/runtime/api/agent/routing-policy.d.ts +25 -42
- package/dist/runtime/api/agent/routing-policy.js +82 -132
- package/dist/runtime/api/agent/routing-policy.test.js +63 -0
- package/dist/runtime/api/api/routers/ai.d.ts +19 -7
- package/dist/runtime/api/api/routers/ai.js +9 -23
- package/dist/runtime/api/api/routers/executions.d.ts +4 -4
- 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 +9 -9
- package/dist/runtime/api/api/routers/workflows.d.ts +23 -16
- package/dist/runtime/api/api/routers/workflows.js +30 -27
- 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 +16 -16
- 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 +143 -30
- 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 +144 -31
- package/dist/runtime/api/router.js +9 -31
- package/dist/runtime/api/settings-service.js +51 -1
- package/dist/runtime/api/types/index.d.ts +8 -1
- package/dist/runtime/api/types/settings.d.ts +15 -2
- package/dist/runtime/api/workflows/vibing-orchestrator.d.ts +8 -3
- package/dist/runtime/api/workflows/vibing-orchestrator.js +214 -184
- package/dist/runtime/web/.next/BUILD_ID +1 -1
- package/dist/runtime/web/.next/app-build-manifest.json +19 -12
- package/dist/runtime/web/.next/app-path-routes-manifest.json +2 -1
- package/dist/runtime/web/.next/build-manifest.json +2 -2
- package/dist/runtime/web/.next/prerender-manifest.json +10 -10
- package/dist/runtime/web/.next/routes-manifest.json +8 -0
- package/dist/runtime/web/.next/server/app/.vibeman/assets/images/[...path]/route.js +1 -0
- package/dist/runtime/web/.next/server/app/.vibeman/assets/images/[...path]/route.js.nft.json +1 -0
- package/dist/runtime/web/.next/server/app/.vibeman/assets/images/[...path]/route_client-reference-manifest.js +1 -0
- 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.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.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 +21 -21
- package/dist/runtime/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/runtime/web/.next/server/app-paths-manifest.json +2 -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/5_15u1WQCxN1_eHZpldCv/_buildManifest.js +1 -0
- package/dist/runtime/web/.next/static/chunks/{277-0142a939f08738c3.js → 823-6f371a6e829adbba.js} +1 -1
- package/dist/runtime/web/.next/static/chunks/app/.vibeman/assets/images/[...path]/route-751c9265a65409e5.js +1 -0
- package/dist/runtime/web/.next/static/chunks/app/api/health/route-751c9265a65409e5.js +1 -0
- package/dist/runtime/web/.next/static/chunks/app/api/images/[...path]/route-751c9265a65409e5.js +1 -0
- package/dist/runtime/web/.next/static/chunks/app/api/upload/route-751c9265a65409e5.js +1 -0
- 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-9fe7d75095b4ccec.js +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -1
- package/dist/runtime/api/lib/image-paste-drop-extension.d.ts +0 -26
- package/dist/runtime/api/lib/image-paste-drop-extension.js +0 -125
- package/dist/runtime/api/lib/markdown-utils.d.ts +0 -8
- package/dist/runtime/api/lib/markdown-utils.js +0 -282
- package/dist/runtime/api/lib/markdown-utils.test.js +0 -348
- package/dist/runtime/api/lib/tiptap-utils.clamp-selection.test.js +0 -27
- package/dist/runtime/api/lib/tiptap-utils.d.ts +0 -130
- package/dist/runtime/api/lib/tiptap-utils.js +0 -327
- package/dist/runtime/web/.next/static/1HR8N0rJkCvFRtbTPJMyH/_buildManifest.js +0 -1
- package/dist/runtime/web/.next/static/chunks/app/api/health/route-105a61ae865ba536.js +0 -1
- package/dist/runtime/web/.next/static/chunks/app/api/images/[...path]/route-105a61ae865ba536.js +0 -1
- package/dist/runtime/web/.next/static/chunks/app/api/upload/route-105a61ae865ba536.js +0 -1
- package/dist/runtime/web/.next/static/chunks/app/page-f34a8b196b18850b.js +0 -1
- /package/dist/runtime/api/{lib/markdown-utils.test.d.ts → agent/prompt-service.test.d.ts} +0 -0
- /package/dist/runtime/api/{lib/tiptap-utils.clamp-selection.test.d.ts → agent/routing-policy.test.d.ts} +0 -0
- /package/dist/runtime/web/.next/static/{1HR8N0rJkCvFRtbTPJMyH → 5_15u1WQCxN1_eHZpldCv}/_ssgManifest.js +0 -0
|
@@ -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,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
|
+
});
|
|
@@ -6,7 +6,8 @@ import { z } from 'zod';
|
|
|
6
6
|
/**
|
|
7
7
|
* Operation types that support AI routing
|
|
8
8
|
*/
|
|
9
|
-
|
|
9
|
+
declare const ROUTABLE_OPERATIONS: readonly ["execute_task", "quality_checks", "ai_codereview", "ai_merge", "improve_task"];
|
|
10
|
+
export type RoutableOperation = (typeof ROUTABLE_OPERATIONS)[number];
|
|
10
11
|
/**
|
|
11
12
|
* Generation options for AI execution
|
|
12
13
|
*/
|
|
@@ -15,13 +16,13 @@ export declare const GenerationOptionsSchema: z.ZodObject<{
|
|
|
15
16
|
maxTokens: z.ZodOptional<z.ZodNumber>;
|
|
16
17
|
tools: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
17
18
|
}, "strip", z.ZodTypeAny, {
|
|
19
|
+
temperature?: number | undefined;
|
|
18
20
|
maxTokens?: number | undefined;
|
|
19
21
|
tools?: string[] | undefined;
|
|
20
|
-
temperature?: number | undefined;
|
|
21
22
|
}, {
|
|
23
|
+
temperature?: number | undefined;
|
|
22
24
|
maxTokens?: number | undefined;
|
|
23
25
|
tools?: string[] | undefined;
|
|
24
|
-
temperature?: number | undefined;
|
|
25
26
|
}>;
|
|
26
27
|
export type GenerationOptions = z.infer<typeof GenerationOptionsSchema>;
|
|
27
28
|
/**
|
|
@@ -35,30 +36,30 @@ export declare const OperationConfigSchema: z.ZodObject<{
|
|
|
35
36
|
maxTokens: z.ZodOptional<z.ZodNumber>;
|
|
36
37
|
tools: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
37
38
|
}, "strip", z.ZodTypeAny, {
|
|
39
|
+
temperature?: number | undefined;
|
|
38
40
|
maxTokens?: number | undefined;
|
|
39
41
|
tools?: string[] | undefined;
|
|
40
|
-
temperature?: number | undefined;
|
|
41
42
|
}, {
|
|
43
|
+
temperature?: number | undefined;
|
|
42
44
|
maxTokens?: number | undefined;
|
|
43
45
|
tools?: string[] | undefined;
|
|
44
|
-
temperature?: number | undefined;
|
|
45
46
|
}>>;
|
|
46
47
|
fallback: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
47
48
|
}, "strip", z.ZodTypeAny, {
|
|
48
49
|
provider: string;
|
|
49
50
|
options?: {
|
|
51
|
+
temperature?: number | undefined;
|
|
50
52
|
maxTokens?: number | undefined;
|
|
51
53
|
tools?: string[] | undefined;
|
|
52
|
-
temperature?: number | undefined;
|
|
53
54
|
} | undefined;
|
|
54
55
|
model?: string | undefined;
|
|
55
56
|
fallback?: string[] | undefined;
|
|
56
57
|
}, {
|
|
57
58
|
provider: string;
|
|
58
59
|
options?: {
|
|
60
|
+
temperature?: number | undefined;
|
|
59
61
|
maxTokens?: number | undefined;
|
|
60
62
|
tools?: string[] | undefined;
|
|
61
|
-
temperature?: number | undefined;
|
|
62
63
|
} | undefined;
|
|
63
64
|
model?: string | undefined;
|
|
64
65
|
fallback?: string[] | undefined;
|
|
@@ -69,7 +70,7 @@ export type OperationConfig = z.infer<typeof OperationConfigSchema>;
|
|
|
69
70
|
*/
|
|
70
71
|
export declare const RoutingPolicySchema: z.ZodObject<{
|
|
71
72
|
defaultProvider: z.ZodString;
|
|
72
|
-
operations: z.ZodOptional<z.ZodRecord<z.ZodEnum<["execute_task", "
|
|
73
|
+
operations: z.ZodOptional<z.ZodRecord<z.ZodEnum<["execute_task", "quality_checks", "ai_codereview", "ai_merge", "improve_task"]>, z.ZodObject<{
|
|
73
74
|
provider: z.ZodString;
|
|
74
75
|
model: z.ZodOptional<z.ZodString>;
|
|
75
76
|
options: z.ZodOptional<z.ZodObject<{
|
|
@@ -77,60 +78,61 @@ export declare const RoutingPolicySchema: z.ZodObject<{
|
|
|
77
78
|
maxTokens: z.ZodOptional<z.ZodNumber>;
|
|
78
79
|
tools: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
79
80
|
}, "strip", z.ZodTypeAny, {
|
|
81
|
+
temperature?: number | undefined;
|
|
80
82
|
maxTokens?: number | undefined;
|
|
81
83
|
tools?: string[] | undefined;
|
|
82
|
-
temperature?: number | undefined;
|
|
83
84
|
}, {
|
|
85
|
+
temperature?: number | undefined;
|
|
84
86
|
maxTokens?: number | undefined;
|
|
85
87
|
tools?: string[] | undefined;
|
|
86
|
-
temperature?: number | undefined;
|
|
87
88
|
}>>;
|
|
88
89
|
fallback: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
89
90
|
}, "strip", z.ZodTypeAny, {
|
|
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
100
|
provider: string;
|
|
100
101
|
options?: {
|
|
102
|
+
temperature?: number | undefined;
|
|
101
103
|
maxTokens?: number | undefined;
|
|
102
104
|
tools?: string[] | undefined;
|
|
103
|
-
temperature?: number | undefined;
|
|
104
105
|
} | undefined;
|
|
105
106
|
model?: string | undefined;
|
|
106
107
|
fallback?: string[] | undefined;
|
|
107
108
|
}>>>;
|
|
108
109
|
}, "strip", z.ZodTypeAny, {
|
|
109
110
|
defaultProvider: string;
|
|
110
|
-
operations?: Partial<Record<"execute_task" | "
|
|
111
|
+
operations?: Partial<Record<"execute_task" | "quality_checks" | "improve_task" | "ai_merge" | "ai_codereview", {
|
|
111
112
|
provider: string;
|
|
112
113
|
options?: {
|
|
114
|
+
temperature?: number | undefined;
|
|
113
115
|
maxTokens?: number | undefined;
|
|
114
116
|
tools?: string[] | undefined;
|
|
115
|
-
temperature?: number | undefined;
|
|
116
117
|
} | undefined;
|
|
117
118
|
model?: string | undefined;
|
|
118
119
|
fallback?: string[] | undefined;
|
|
119
120
|
}>> | undefined;
|
|
120
121
|
}, {
|
|
121
122
|
defaultProvider: string;
|
|
122
|
-
operations?: Partial<Record<"execute_task" | "
|
|
123
|
+
operations?: Partial<Record<"execute_task" | "quality_checks" | "improve_task" | "ai_merge" | "ai_codereview", {
|
|
123
124
|
provider: string;
|
|
124
125
|
options?: {
|
|
126
|
+
temperature?: number | undefined;
|
|
125
127
|
maxTokens?: number | undefined;
|
|
126
128
|
tools?: string[] | undefined;
|
|
127
|
-
temperature?: number | undefined;
|
|
128
129
|
} | undefined;
|
|
129
130
|
model?: string | undefined;
|
|
130
131
|
fallback?: string[] | undefined;
|
|
131
132
|
}>> | undefined;
|
|
132
133
|
}>;
|
|
133
134
|
export type RoutingPolicy = z.infer<typeof RoutingPolicySchema>;
|
|
135
|
+
export declare const ROUTABLE_OPERATION_LIST: readonly ["execute_task", "quality_checks", "ai_codereview", "ai_merge", "improve_task"];
|
|
134
136
|
/**
|
|
135
137
|
* Resolved provider configuration for execution
|
|
136
138
|
*/
|
|
@@ -142,47 +144,28 @@ export interface ResolvedProvider {
|
|
|
142
144
|
}
|
|
143
145
|
/**
|
|
144
146
|
* Routing Policy Manager
|
|
145
|
-
* Manages AI provider routing policies
|
|
147
|
+
* Manages AI provider routing policies stored inside settings.json
|
|
146
148
|
*/
|
|
147
149
|
export declare class RoutingPolicyManager {
|
|
148
150
|
private policy;
|
|
149
|
-
private
|
|
150
|
-
private lastModified;
|
|
151
|
+
private readonly settingsService;
|
|
151
152
|
constructor();
|
|
152
153
|
/**
|
|
153
|
-
* Get current effective policy
|
|
154
|
+
* Get current effective policy (lazy loads from settings)
|
|
154
155
|
*/
|
|
155
156
|
getPolicy(): Promise<RoutingPolicy>;
|
|
156
157
|
/**
|
|
157
|
-
* Update routing policy and persist
|
|
158
|
+
* Update routing policy and persist via settings service
|
|
158
159
|
*/
|
|
159
160
|
updatePolicy(updates: Partial<RoutingPolicy>): Promise<void>;
|
|
160
161
|
/**
|
|
161
162
|
* Resolve provider for a specific operation
|
|
162
163
|
*/
|
|
164
|
+
getEffectivePolicy(): Promise<RoutingPolicy>;
|
|
163
165
|
resolveProviderForOperation(operation: RoutableOperation, overrides?: Partial<ResolvedProvider>): Promise<ResolvedProvider>;
|
|
164
|
-
/**
|
|
165
|
-
* Set default provider
|
|
166
|
-
*/
|
|
167
166
|
setDefaultProvider(provider: string): Promise<void>;
|
|
168
|
-
/**
|
|
169
|
-
* Set operation-specific routing
|
|
170
|
-
*/
|
|
171
167
|
setOperationConfig(operation: RoutableOperation, config: OperationConfig): Promise<void>;
|
|
172
|
-
/**
|
|
173
|
-
* Validate policy against available providers
|
|
174
|
-
*/
|
|
175
168
|
validatePolicy(policy: RoutingPolicy, availableProviders: Set<string>): string[];
|
|
176
|
-
|
|
177
|
-
* Load policy from file if it has changed
|
|
178
|
-
*/
|
|
179
|
-
private loadPolicyIfChanged;
|
|
180
|
-
/**
|
|
181
|
-
* Create example policy file if it doesn't exist
|
|
182
|
-
*/
|
|
183
|
-
createExamplePolicy(): Promise<void>;
|
|
184
|
-
/**
|
|
185
|
-
* Get policy file path for external access
|
|
186
|
-
*/
|
|
187
|
-
getPolicyFilePath(): string;
|
|
169
|
+
private buildPolicyFromSettings;
|
|
188
170
|
}
|
|
171
|
+
export {};
|