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.
Files changed (102) hide show
  1. package/dist/index.js +5 -7
  2. package/dist/runtime/api/.tsbuildinfo +1 -1
  3. package/dist/runtime/api/agent/agent-service.d.ts +18 -19
  4. package/dist/runtime/api/agent/agent-service.js +61 -58
  5. package/dist/runtime/api/agent/ai-providers/claude-code-adapter.d.ts +2 -2
  6. package/dist/runtime/api/agent/ai-providers/claude-code-adapter.js +25 -36
  7. package/dist/runtime/api/agent/ai-providers/codex-cli-provider.d.ts +2 -0
  8. package/dist/runtime/api/agent/ai-providers/codex-cli-provider.js +109 -43
  9. package/dist/runtime/api/agent/ai-providers/types.d.ts +2 -0
  10. package/dist/runtime/api/agent/codex-cli-provider.test.js +83 -1
  11. package/dist/runtime/api/agent/parsers.d.ts +1 -0
  12. package/dist/runtime/api/agent/parsers.js +75 -8
  13. package/dist/runtime/api/agent/prompt-service.d.ts +14 -1
  14. package/dist/runtime/api/agent/prompt-service.js +123 -14
  15. package/dist/runtime/api/agent/prompt-service.test.js +230 -0
  16. package/dist/runtime/api/agent/routing-policy.d.ts +25 -42
  17. package/dist/runtime/api/agent/routing-policy.js +82 -132
  18. package/dist/runtime/api/agent/routing-policy.test.js +63 -0
  19. package/dist/runtime/api/api/routers/ai.d.ts +19 -7
  20. package/dist/runtime/api/api/routers/ai.js +9 -23
  21. package/dist/runtime/api/api/routers/executions.d.ts +4 -4
  22. package/dist/runtime/api/api/routers/executions.js +12 -21
  23. package/dist/runtime/api/api/routers/provider-config.d.ts +165 -0
  24. package/dist/runtime/api/api/routers/provider-config.js +252 -0
  25. package/dist/runtime/api/api/routers/tasks.d.ts +9 -9
  26. package/dist/runtime/api/api/routers/workflows.d.ts +23 -16
  27. package/dist/runtime/api/api/routers/workflows.js +30 -27
  28. package/dist/runtime/api/api/routers/worktrees.d.ts +4 -5
  29. package/dist/runtime/api/api/routers/worktrees.js +11 -11
  30. package/dist/runtime/api/api/trpc.d.ts +16 -16
  31. package/dist/runtime/api/index.js +2 -10
  32. package/dist/runtime/api/lib/local-config.d.ts +245 -0
  33. package/dist/runtime/api/lib/local-config.js +288 -0
  34. package/dist/runtime/api/lib/provider-detection.d.ts +59 -0
  35. package/dist/runtime/api/lib/provider-detection.js +244 -0
  36. package/dist/runtime/api/lib/server/bootstrap.d.ts +38 -0
  37. package/dist/runtime/api/lib/server/bootstrap.js +197 -0
  38. package/dist/runtime/api/lib/server/project-root.js +24 -1
  39. package/dist/runtime/api/lib/trpc/server.d.ts +143 -30
  40. package/dist/runtime/api/lib/trpc/server.js +8 -8
  41. package/dist/runtime/api/lib/trpc/ws-server.js +2 -2
  42. package/dist/runtime/api/router.d.ts +144 -31
  43. package/dist/runtime/api/router.js +9 -31
  44. package/dist/runtime/api/settings-service.js +51 -1
  45. package/dist/runtime/api/types/index.d.ts +8 -1
  46. package/dist/runtime/api/types/settings.d.ts +15 -2
  47. package/dist/runtime/api/workflows/vibing-orchestrator.d.ts +8 -3
  48. package/dist/runtime/api/workflows/vibing-orchestrator.js +214 -184
  49. package/dist/runtime/web/.next/BUILD_ID +1 -1
  50. package/dist/runtime/web/.next/app-build-manifest.json +19 -12
  51. package/dist/runtime/web/.next/app-path-routes-manifest.json +2 -1
  52. package/dist/runtime/web/.next/build-manifest.json +2 -2
  53. package/dist/runtime/web/.next/prerender-manifest.json +10 -10
  54. package/dist/runtime/web/.next/routes-manifest.json +8 -0
  55. package/dist/runtime/web/.next/server/app/.vibeman/assets/images/[...path]/route.js +1 -0
  56. package/dist/runtime/web/.next/server/app/.vibeman/assets/images/[...path]/route.js.nft.json +1 -0
  57. package/dist/runtime/web/.next/server/app/.vibeman/assets/images/[...path]/route_client-reference-manifest.js +1 -0
  58. package/dist/runtime/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  59. package/dist/runtime/web/.next/server/app/_not-found.html +2 -2
  60. package/dist/runtime/web/.next/server/app/_not-found.rsc +5 -5
  61. package/dist/runtime/web/.next/server/app/api/health/route.js +1 -1
  62. package/dist/runtime/web/.next/server/app/api/health/route_client-reference-manifest.js +1 -1
  63. package/dist/runtime/web/.next/server/app/api/images/[...path]/route.js +1 -1
  64. package/dist/runtime/web/.next/server/app/api/images/[...path]/route_client-reference-manifest.js +1 -1
  65. package/dist/runtime/web/.next/server/app/api/upload/route.js +1 -1
  66. package/dist/runtime/web/.next/server/app/api/upload/route_client-reference-manifest.js +1 -1
  67. package/dist/runtime/web/.next/server/app/index.html +2 -2
  68. package/dist/runtime/web/.next/server/app/index.rsc +6 -6
  69. package/dist/runtime/web/.next/server/app/page.js +21 -21
  70. package/dist/runtime/web/.next/server/app/page_client-reference-manifest.js +1 -1
  71. package/dist/runtime/web/.next/server/app-paths-manifest.json +2 -1
  72. package/dist/runtime/web/.next/server/chunks/458.js +1 -1
  73. package/dist/runtime/web/.next/server/pages/404.html +2 -2
  74. package/dist/runtime/web/.next/server/pages/500.html +1 -1
  75. package/dist/runtime/web/.next/server/pages-manifest.json +1 -1
  76. package/dist/runtime/web/.next/server/server-reference-manifest.json +1 -1
  77. package/dist/runtime/web/.next/static/5_15u1WQCxN1_eHZpldCv/_buildManifest.js +1 -0
  78. package/dist/runtime/web/.next/static/chunks/{277-0142a939f08738c3.js → 823-6f371a6e829adbba.js} +1 -1
  79. package/dist/runtime/web/.next/static/chunks/app/.vibeman/assets/images/[...path]/route-751c9265a65409e5.js +1 -0
  80. package/dist/runtime/web/.next/static/chunks/app/api/health/route-751c9265a65409e5.js +1 -0
  81. package/dist/runtime/web/.next/static/chunks/app/api/images/[...path]/route-751c9265a65409e5.js +1 -0
  82. package/dist/runtime/web/.next/static/chunks/app/api/upload/route-751c9265a65409e5.js +1 -0
  83. package/dist/runtime/web/.next/static/chunks/app/{layout-dc0cfd29075b2160.js → layout-8435322f09fd0975.js} +1 -1
  84. package/dist/runtime/web/.next/static/chunks/app/page-9fe7d75095b4ccec.js +1 -0
  85. package/dist/tsconfig.tsbuildinfo +1 -1
  86. package/package.json +5 -1
  87. package/dist/runtime/api/lib/image-paste-drop-extension.d.ts +0 -26
  88. package/dist/runtime/api/lib/image-paste-drop-extension.js +0 -125
  89. package/dist/runtime/api/lib/markdown-utils.d.ts +0 -8
  90. package/dist/runtime/api/lib/markdown-utils.js +0 -282
  91. package/dist/runtime/api/lib/markdown-utils.test.js +0 -348
  92. package/dist/runtime/api/lib/tiptap-utils.clamp-selection.test.js +0 -27
  93. package/dist/runtime/api/lib/tiptap-utils.d.ts +0 -130
  94. package/dist/runtime/api/lib/tiptap-utils.js +0 -327
  95. package/dist/runtime/web/.next/static/1HR8N0rJkCvFRtbTPJMyH/_buildManifest.js +0 -1
  96. package/dist/runtime/web/.next/static/chunks/app/api/health/route-105a61ae865ba536.js +0 -1
  97. package/dist/runtime/web/.next/static/chunks/app/api/images/[...path]/route-105a61ae865ba536.js +0 -1
  98. package/dist/runtime/web/.next/static/chunks/app/api/upload/route-105a61ae865ba536.js +0 -1
  99. package/dist/runtime/web/.next/static/chunks/app/page-f34a8b196b18850b.js +0 -1
  100. /package/dist/runtime/api/{lib/markdown-utils.test.d.ts → agent/prompt-service.test.d.ts} +0 -0
  101. /package/dist/runtime/api/{lib/tiptap-utils.clamp-selection.test.d.ts → agent/routing-policy.test.d.ts} +0 -0
  102. /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, accpetance 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.
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
- const additionalContext = reviewContext ? `\n**Additional Context:** ${reviewContext}` : '';
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
- export type RoutableOperation = 'execute_task' | 'improve_task' | 'ai_codereview' | 'ai_merge';
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", "improve_task", "ai_codereview", "ai_merge"]>, z.ZodObject<{
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" | "improve_task" | "ai_codereview" | "ai_merge", {
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" | "improve_task" | "ai_codereview" | "ai_merge", {
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 with hot-reload capability
147
+ * Manages AI provider routing policies stored inside settings.json
146
148
  */
147
149
  export declare class RoutingPolicyManager {
148
150
  private policy;
149
- private policyFilePath;
150
- private lastModified;
151
+ private readonly settingsService;
151
152
  constructor();
152
153
  /**
153
- * Get current effective policy with hot-reload
154
+ * Get current effective policy (lazy loads from settings)
154
155
  */
155
156
  getPolicy(): Promise<RoutingPolicy>;
156
157
  /**
157
- * Update routing policy and persist to disk
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 {};