vibeman 0.0.2 → 0.0.5

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 (198) hide show
  1. package/dist/index.js +3 -3
  2. package/dist/runtime/api/.tsbuildinfo +1 -1
  3. package/dist/runtime/api/agent/agent-service.d.ts +11 -6
  4. package/dist/runtime/api/agent/agent-service.js +97 -29
  5. package/dist/runtime/api/agent/ai-providers/amp-cli-provider.d.ts +38 -0
  6. package/dist/runtime/api/agent/ai-providers/amp-cli-provider.js +268 -0
  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 +92 -32
  9. package/dist/runtime/api/agent/ai-providers/gemini-cli-provider.d.ts +24 -0
  10. package/dist/runtime/api/agent/ai-providers/gemini-cli-provider.js +291 -0
  11. package/dist/runtime/api/agent/ai-providers/index.d.ts +3 -3
  12. package/dist/runtime/api/agent/ai-providers/index.js +3 -1
  13. package/dist/runtime/api/agent/ai-providers/types.d.ts +5 -2
  14. package/dist/runtime/api/agent/amp-cli-provider.test.js +99 -0
  15. package/dist/runtime/api/agent/codex-cli-provider.test.js +54 -7
  16. package/dist/runtime/api/agent/prompt-service.js +108 -105
  17. package/dist/runtime/api/agent/prompt-service.test.js +35 -0
  18. package/dist/runtime/api/agent/routing-policy.d.ts +13 -30
  19. package/dist/runtime/api/agent/routing-policy.js +82 -132
  20. package/dist/runtime/api/agent/routing-policy.test.js +63 -0
  21. package/dist/runtime/api/api/routers/ai.d.ts +15 -3
  22. package/dist/runtime/api/api/routers/ai.js +7 -6
  23. package/dist/runtime/api/api/routers/executions.d.ts +3 -8
  24. package/dist/runtime/api/api/routers/executions.js +2 -2
  25. package/dist/runtime/api/api/routers/provider-config.d.ts +34 -0
  26. package/dist/runtime/api/api/routers/settings.d.ts +19 -0
  27. package/dist/runtime/api/api/routers/settings.js +16 -0
  28. package/dist/runtime/api/api/routers/tasks.d.ts +10 -10
  29. package/dist/runtime/api/api/routers/workflows.d.ts +20 -12
  30. package/dist/runtime/api/api/routers/workflows.js +2 -1
  31. package/dist/runtime/api/api/routers/worktrees.d.ts +2 -2
  32. package/dist/runtime/api/api/trpc.d.ts +18 -18
  33. package/dist/runtime/api/lib/local-config.d.ts +94 -4
  34. package/dist/runtime/api/lib/local-config.js +16 -0
  35. package/dist/runtime/api/lib/provider-detection.d.ts +2 -0
  36. package/dist/runtime/api/lib/provider-detection.js +83 -1
  37. package/dist/runtime/api/lib/server/vibeman-info.d.ts +5 -0
  38. package/dist/runtime/api/lib/server/vibeman-info.js +85 -0
  39. package/dist/runtime/api/lib/trpc/server.d.ts +85 -35
  40. package/dist/runtime/api/persistence/execution-log-persistence.d.ts +1 -1
  41. package/dist/runtime/api/persistence/execution-log-persistence.js +19 -3
  42. package/dist/runtime/api/router.d.ts +85 -35
  43. package/dist/runtime/api/settings-service.js +70 -5
  44. package/dist/runtime/api/tasks/task-file-parser.d.ts +1 -0
  45. package/dist/runtime/api/tasks/task-file-parser.js +20 -1
  46. package/dist/runtime/api/tasks/task-updater.d.ts +62 -0
  47. package/dist/runtime/api/tasks/task-updater.js +260 -0
  48. package/dist/runtime/api/tasks/task-updater.test.d.ts +1 -0
  49. package/dist/runtime/api/tasks/task-updater.test.js +303 -0
  50. package/dist/runtime/api/types/index.d.ts +9 -2
  51. package/dist/runtime/api/types/settings.d.ts +29 -5
  52. package/dist/runtime/api/vcs/git-service.d.ts +9 -0
  53. package/dist/runtime/api/vcs/git-service.js +23 -0
  54. package/dist/runtime/api/vcs/worktree-service.d.ts +1 -1
  55. package/dist/runtime/api/vcs/worktree-service.js +22 -10
  56. package/dist/runtime/api/workflows/quality-pipeline.js +2 -1
  57. package/dist/runtime/api/workflows/vibing-orchestrator.d.ts +93 -5
  58. package/dist/runtime/api/workflows/vibing-orchestrator.js +806 -204
  59. package/dist/runtime/api/workflows/workflow-effects.d.ts +45 -0
  60. package/dist/runtime/api/workflows/workflow-effects.js +49 -0
  61. package/dist/runtime/api/workflows/workflow-reconciler.d.ts +65 -0
  62. package/dist/runtime/api/workflows/workflow-reconciler.js +226 -0
  63. package/dist/runtime/api/workflows/workflow-reducer.d.ts +26 -0
  64. package/dist/runtime/api/workflows/workflow-reducer.js +288 -0
  65. package/dist/runtime/api/workflows/workflow-reducer.test.d.ts +1 -0
  66. package/dist/runtime/api/workflows/workflow-reducer.test.js +247 -0
  67. package/dist/runtime/api/workflows/workflow-schema.d.ts +546 -0
  68. package/dist/runtime/api/workflows/workflow-schema.js +256 -0
  69. package/dist/runtime/web/.next/BUILD_ID +1 -1
  70. package/dist/runtime/web/.next/app-build-manifest.json +51 -44
  71. package/dist/runtime/web/.next/app-path-routes-manifest.json +2 -1
  72. package/dist/runtime/web/.next/build-manifest.json +14 -14
  73. package/dist/runtime/web/.next/prerender-manifest.json +10 -10
  74. package/dist/runtime/web/.next/react-loadable-manifest.json +2 -33
  75. package/dist/runtime/web/.next/required-server-files.json +5 -5
  76. package/dist/runtime/web/.next/routes-manifest.json +8 -0
  77. package/dist/runtime/web/.next/server/app/.vibeman/assets/images/[...path]/route.js +1 -0
  78. package/dist/runtime/web/.next/server/app/.vibeman/assets/images/[...path]/route.js.nft.json +1 -0
  79. package/dist/runtime/web/.next/server/app/.vibeman/assets/images/[...path]/route_client-reference-manifest.js +1 -0
  80. package/dist/runtime/web/.next/server/app/_not-found/page.js +2 -2
  81. package/dist/runtime/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  82. package/dist/runtime/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  83. package/dist/runtime/web/.next/server/app/_not-found.html +2 -2
  84. package/dist/runtime/web/.next/server/app/_not-found.rsc +12 -12
  85. package/dist/runtime/web/.next/server/app/api/health/route.js +1 -1
  86. package/dist/runtime/web/.next/server/app/api/health/route.js.nft.json +1 -1
  87. package/dist/runtime/web/.next/server/app/api/health/route_client-reference-manifest.js +1 -1
  88. package/dist/runtime/web/.next/server/app/api/images/[...path]/route.js +1 -1
  89. package/dist/runtime/web/.next/server/app/api/images/[...path]/route.js.nft.json +1 -1
  90. package/dist/runtime/web/.next/server/app/api/images/[...path]/route_client-reference-manifest.js +1 -1
  91. package/dist/runtime/web/.next/server/app/api/upload/route.js +1 -1
  92. package/dist/runtime/web/.next/server/app/api/upload/route.js.nft.json +1 -1
  93. package/dist/runtime/web/.next/server/app/api/upload/route_client-reference-manifest.js +1 -1
  94. package/dist/runtime/web/.next/server/app/index.html +2 -2
  95. package/dist/runtime/web/.next/server/app/index.rsc +15 -15
  96. package/dist/runtime/web/.next/server/app/page.js +27 -62
  97. package/dist/runtime/web/.next/server/app/page.js.nft.json +1 -1
  98. package/dist/runtime/web/.next/server/app/page_client-reference-manifest.js +1 -1
  99. package/dist/runtime/web/.next/server/app-paths-manifest.json +2 -1
  100. package/dist/runtime/web/.next/server/chunks/210.js +1 -0
  101. package/dist/runtime/web/.next/server/chunks/291.js +18 -0
  102. package/dist/runtime/web/.next/server/chunks/552.js +22 -0
  103. package/dist/runtime/web/.next/server/chunks/780.js +1 -0
  104. package/dist/runtime/web/.next/server/chunks/905.js +6 -0
  105. package/dist/runtime/web/.next/server/chunks/98.js +1 -0
  106. package/dist/runtime/web/.next/server/middleware-build-manifest.js +1 -1
  107. package/dist/runtime/web/.next/server/middleware-react-loadable-manifest.js +1 -1
  108. package/dist/runtime/web/.next/server/pages/404.html +2 -2
  109. package/dist/runtime/web/.next/server/pages/500.html +1 -1
  110. package/dist/runtime/web/.next/server/pages/_app.js +1 -1
  111. package/dist/runtime/web/.next/server/pages/_app.js.nft.json +1 -1
  112. package/dist/runtime/web/.next/server/pages/_document.js +1 -1
  113. package/dist/runtime/web/.next/server/pages/_document.js.nft.json +1 -1
  114. package/dist/runtime/web/.next/server/pages/_error.js +9 -9
  115. package/dist/runtime/web/.next/server/pages/_error.js.nft.json +1 -1
  116. package/dist/runtime/web/.next/server/pages-manifest.json +1 -1
  117. package/dist/runtime/web/.next/server/server-reference-manifest.json +1 -1
  118. package/dist/runtime/web/.next/server/webpack-runtime.js +1 -1
  119. package/dist/runtime/web/.next/static/LJFZk_8tvKFN_Ee4HqUuM/_buildManifest.js +1 -0
  120. package/dist/runtime/web/.next/static/chunks/05c91ade-7d09b2b280adffd1.js +1 -0
  121. package/dist/runtime/web/.next/static/chunks/201-51bef3fa8c832e2e.js +1 -0
  122. package/dist/runtime/web/.next/static/chunks/524-89747ed9b0294f8a.js +1 -0
  123. package/dist/runtime/web/.next/static/chunks/554-8bec6e9cca6acc67.js +1 -0
  124. package/dist/runtime/web/.next/static/chunks/764.86e9503a69d45a85.js +1 -0
  125. package/dist/runtime/web/.next/static/chunks/{87c73c54-09e1ba5c70e60a51.js → 7ab4dc20-239138e0ae7af24a.js} +1 -1
  126. package/dist/runtime/web/.next/static/chunks/905-342391e3d3a3678f.js +20 -0
  127. package/dist/runtime/web/.next/static/chunks/a8a5ce16-4edea7df2d9b544a.js +79 -0
  128. package/dist/runtime/web/.next/static/chunks/{8bb4d8db-3e2aa02b0a2384b9.js → ad74d572-4c1b162e2c15acaa.js} +1 -1
  129. package/dist/runtime/web/.next/static/chunks/app/.vibeman/assets/images/[...path]/route-7b752a8641f96c1f.js +1 -0
  130. package/dist/runtime/web/.next/static/chunks/app/_not-found/page-34e66b251c2b5044.js +1 -0
  131. package/dist/runtime/web/.next/static/chunks/app/api/health/route-7b752a8641f96c1f.js +1 -0
  132. package/dist/runtime/web/.next/static/chunks/app/api/images/[...path]/route-7b752a8641f96c1f.js +1 -0
  133. package/dist/runtime/web/.next/static/chunks/app/api/upload/route-7b752a8641f96c1f.js +1 -0
  134. package/dist/runtime/web/.next/static/chunks/app/layout-df9ac93cb02b2385.js +1 -0
  135. package/dist/runtime/web/.next/static/chunks/app/page-6610743f7de5f92a.js +1 -0
  136. package/dist/runtime/web/.next/static/chunks/c25e0690-e9b798b8de667da1.js +1 -0
  137. package/dist/runtime/web/.next/static/chunks/framework-57157ec4d37f64aa.js +1 -0
  138. package/dist/runtime/web/.next/static/chunks/main-app-156cc0c60371bd78.js +1 -0
  139. package/dist/runtime/web/.next/static/chunks/main-df25d367c47b1fec.js +1 -0
  140. package/dist/runtime/web/.next/static/chunks/pages/_app-9f629a5e1131d19f.js +1 -0
  141. package/dist/runtime/web/.next/static/chunks/pages/_error-9238238274c7efcd.js +1 -0
  142. package/dist/runtime/web/.next/static/chunks/webpack-cd50e39b423d1808.js +1 -0
  143. package/dist/runtime/web/.next/static/css/4fbf378a264bd4ea.css +1 -0
  144. package/dist/runtime/web/package.json +8 -8
  145. package/dist/runtime/web/server.js +1 -1
  146. package/dist/tsconfig.tsbuildinfo +1 -1
  147. package/package.json +3 -37
  148. package/dist/runtime/api/lib/image-paste-drop-extension.d.ts +0 -26
  149. package/dist/runtime/api/lib/image-paste-drop-extension.js +0 -125
  150. package/dist/runtime/api/lib/markdown-utils.d.ts +0 -8
  151. package/dist/runtime/api/lib/markdown-utils.js +0 -282
  152. package/dist/runtime/api/lib/markdown-utils.test.js +0 -348
  153. package/dist/runtime/api/lib/tiptap-utils.clamp-selection.test.js +0 -27
  154. package/dist/runtime/api/lib/tiptap-utils.d.ts +0 -130
  155. package/dist/runtime/api/lib/tiptap-utils.js +0 -327
  156. package/dist/runtime/api/lib/trpc/client.d.ts +0 -1
  157. package/dist/runtime/api/lib/trpc/client.js +0 -5
  158. package/dist/runtime/web/.next/server/chunks/217.js +0 -1
  159. package/dist/runtime/web/.next/server/chunks/383.js +0 -6
  160. package/dist/runtime/web/.next/server/chunks/458.js +0 -1
  161. package/dist/runtime/web/.next/server/chunks/576.js +0 -18
  162. package/dist/runtime/web/.next/server/chunks/635.js +0 -22
  163. package/dist/runtime/web/.next/server/chunks/761.js +0 -1
  164. package/dist/runtime/web/.next/server/chunks/777.js +0 -3
  165. package/dist/runtime/web/.next/server/chunks/825.js +0 -1
  166. package/dist/runtime/web/.next/server/chunks/838.js +0 -1
  167. package/dist/runtime/web/.next/server/chunks/973.js +0 -15
  168. package/dist/runtime/web/.next/static/chunks/18-15c10d3288afef2e.js +0 -1
  169. package/dist/runtime/web/.next/static/chunks/1c0ca389.537bbe362e3ffbd9.js +0 -3
  170. package/dist/runtime/web/.next/static/chunks/22747d63-ad5da0c19f4cfe41.js +0 -71
  171. package/dist/runtime/web/.next/static/chunks/277-0142a939f08738c3.js +0 -63
  172. package/dist/runtime/web/.next/static/chunks/355.056c2645878a799a.js +0 -1
  173. package/dist/runtime/web/.next/static/chunks/420.a5ccf151c9e2b2f1.js +0 -1
  174. package/dist/runtime/web/.next/static/chunks/439.1be0c6242fd248d5.js +0 -15
  175. package/dist/runtime/web/.next/static/chunks/440.c52e7c0f797e22b2.js +0 -1
  176. package/dist/runtime/web/.next/static/chunks/575-e2478287c27da87b.js +0 -1
  177. package/dist/runtime/web/.next/static/chunks/691.920d88c115087314.js +0 -1
  178. package/dist/runtime/web/.next/static/chunks/765-e838910065b50c3d.js +0 -1
  179. package/dist/runtime/web/.next/static/chunks/891cff7f.0f71fc028f87e683.js +0 -1
  180. package/dist/runtime/web/.next/static/chunks/9af238c7-271a911d4e99ab18.js +0 -1
  181. package/dist/runtime/web/.next/static/chunks/app/_not-found/page-1cb74d1cba27d0ab.js +0 -1
  182. package/dist/runtime/web/.next/static/chunks/app/api/health/route-105a61ae865ba536.js +0 -1
  183. package/dist/runtime/web/.next/static/chunks/app/api/images/[...path]/route-105a61ae865ba536.js +0 -1
  184. package/dist/runtime/web/.next/static/chunks/app/api/upload/route-105a61ae865ba536.js +0 -1
  185. package/dist/runtime/web/.next/static/chunks/app/layout-8435322f09fd0975.js +0 -1
  186. package/dist/runtime/web/.next/static/chunks/app/page-8c3ba579efc6f918.js +0 -1
  187. package/dist/runtime/web/.next/static/chunks/cac567b0-5b77dd12911823cd.js +0 -1
  188. package/dist/runtime/web/.next/static/chunks/framework-2518f1345b5b2806.js +0 -1
  189. package/dist/runtime/web/.next/static/chunks/main-17665e5e39de9a8a.js +0 -1
  190. package/dist/runtime/web/.next/static/chunks/main-app-c0b0f5ba4f7f9d75.js +0 -1
  191. package/dist/runtime/web/.next/static/chunks/pages/_app-d6f6b3bbc3d81ee1.js +0 -1
  192. package/dist/runtime/web/.next/static/chunks/pages/_error-75a96cf1997cc3b9.js +0 -1
  193. package/dist/runtime/web/.next/static/chunks/webpack-c8de37305b4635cf.js +0 -1
  194. package/dist/runtime/web/.next/static/css/08c950681f1a9a92.css +0 -1
  195. package/dist/runtime/web/.next/static/mRpNgPfbYR_0wrODzlg_4/_buildManifest.js +0 -1
  196. /package/dist/runtime/api/{lib/markdown-utils.test.d.ts → agent/amp-cli-provider.test.d.ts} +0 -0
  197. /package/dist/runtime/api/{lib/tiptap-utils.clamp-selection.test.d.ts → agent/routing-policy.test.d.ts} +0 -0
  198. /package/dist/runtime/web/.next/static/{mRpNgPfbYR_0wrODzlg_4 → LJFZk_8tvKFN_Ee4HqUuM}/_ssgManifest.js +0 -0
@@ -0,0 +1,303 @@
1
+ import { describe, beforeEach, afterEach, test, expect, vi } from 'vitest';
2
+ import fs from 'fs/promises';
3
+ import path from 'path';
4
+ import { TaskUpdater } from './task-updater.js';
5
+ // Mock fs and path
6
+ vi.mock('fs/promises');
7
+ vi.mock('../lib/logger.js', () => ({
8
+ log: {
9
+ info: vi.fn(),
10
+ error: vi.fn(),
11
+ debug: vi.fn(),
12
+ },
13
+ }));
14
+ vi.mock('../lib/server/project-root.js', () => ({
15
+ getVibeDir: vi.fn(() => '/test/vibe'),
16
+ }));
17
+ const mockFs = fs;
18
+ describe('TaskUpdater', () => {
19
+ let taskUpdater;
20
+ const testTasksDir = '/test/vibe/tasks';
21
+ beforeEach(() => {
22
+ vi.clearAllMocks();
23
+ taskUpdater = new TaskUpdater('/test/vibe');
24
+ });
25
+ afterEach(() => {
26
+ vi.restoreAllMocks();
27
+ });
28
+ describe('updateTaskForReviewReadiness', () => {
29
+ const sampleTaskContent = `---
30
+ id: TEST-TASK-123
31
+ title: Test Task
32
+ type: feature
33
+ status: in-progress
34
+ tags: ''
35
+ priority: medium
36
+ ---
37
+
38
+ ## Description
39
+ This is a test task.
40
+
41
+ ## Implementation Tasks
42
+ - [ ] Implement feature A
43
+ - [ ] Add tests for feature A
44
+ - [x] Already completed task
45
+
46
+ ## Acceptance Criteria
47
+ - [ ] Feature works correctly
48
+ - [ ] Tests pass
49
+ `;
50
+ beforeEach(() => {
51
+ mockFs.access = vi.fn().mockResolvedValue(undefined);
52
+ mockFs.readFile = vi.fn().mockResolvedValue(sampleTaskContent);
53
+ mockFs.writeFile = vi.fn().mockResolvedValue(undefined);
54
+ mockFs.rename = vi.fn().mockResolvedValue(undefined);
55
+ mockFs.unlink = vi.fn().mockResolvedValue(undefined);
56
+ });
57
+ test('should update task status to review', async () => {
58
+ const context = {
59
+ filesModified: ['src/feature.ts', 'tests/feature.test.ts'],
60
+ keyChanges: ['Added new feature A', 'Implemented core logic'],
61
+ testsPassed: true,
62
+ qualityChecksPassed: true,
63
+ };
64
+ await taskUpdater.updateTaskForReviewReadiness('TEST-TASK-123', context);
65
+ expect(mockFs.access).toHaveBeenCalledWith(path.join(testTasksDir, 'TEST-TASK-123.md'));
66
+ expect(mockFs.readFile).toHaveBeenCalledWith(path.join(testTasksDir, 'TEST-TASK-123.md'), 'utf-8');
67
+ expect(mockFs.writeFile).toHaveBeenCalled();
68
+ expect(mockFs.rename).toHaveBeenCalled();
69
+ const writeCall = mockFs.writeFile.mock.calls[0];
70
+ const writtenContent = writeCall[1];
71
+ // Verify status changed to Review
72
+ expect(writtenContent).toContain('status: Review');
73
+ // Verify todos are marked as completed
74
+ expect(writtenContent).toContain('- [x] Implement feature A');
75
+ expect(writtenContent).toContain('- [x] Add tests for feature A');
76
+ // Verify acceptance criteria unchanged
77
+ expect(writtenContent).toContain('- [ ] Feature works correctly');
78
+ expect(writtenContent).toContain('- [ ] Tests pass');
79
+ // Verify implementation summary added
80
+ expect(writtenContent).toContain('## Implementation Summary');
81
+ expect(writtenContent).toContain('IMPLEMENTATION_SUMMARY_START');
82
+ expect(writtenContent).toContain('IMPLEMENTATION_SUMMARY_END');
83
+ });
84
+ test('should not change status if already done', async () => {
85
+ const doneTaskContent = sampleTaskContent.replace('status: in-progress', 'status: done');
86
+ mockFs.readFile = vi.fn().mockResolvedValue(doneTaskContent);
87
+ await taskUpdater.updateTaskForReviewReadiness('TEST-TASK-123', {});
88
+ const writeCall = mockFs.writeFile.mock.calls[0];
89
+ const writtenContent = writeCall[1];
90
+ expect(writtenContent).toContain('status: done');
91
+ expect(writtenContent).not.toContain('status: Review');
92
+ });
93
+ test('should not change status if already Done (case insensitive)', async () => {
94
+ const doneTaskContent = sampleTaskContent.replace('status: in-progress', 'status: Done');
95
+ mockFs.readFile = vi.fn().mockResolvedValue(doneTaskContent);
96
+ await taskUpdater.updateTaskForReviewReadiness('TEST-TASK-123', {});
97
+ const writeCall = mockFs.writeFile.mock.calls[0];
98
+ const writtenContent = writeCall[1];
99
+ expect(writtenContent).toContain('status: Done');
100
+ expect(writtenContent).not.toContain('status: Review');
101
+ });
102
+ test('should handle missing task file', async () => {
103
+ mockFs.access = vi.fn().mockRejectedValue(new Error('File not found'));
104
+ await expect(taskUpdater.updateTaskForReviewReadiness('NONEXISTENT-TASK', {})).rejects.toThrow('Task file not found: NONEXISTENT-TASK');
105
+ });
106
+ test('should mark only implementation todos, not acceptance criteria', async () => {
107
+ const taskWithMixedTodos = `---
108
+ id: TEST-TASK-456
109
+ title: Mixed Todos Task
110
+ type: feature
111
+ status: in-progress
112
+ tags: ''
113
+ priority: medium
114
+ ---
115
+
116
+ ## Implementation
117
+ - [ ] Task 1
118
+ - [ ] Task 2
119
+
120
+ ## Implementation Tasks
121
+ - [ ] Task 3
122
+ - [ ] Task 4
123
+
124
+ ## Acceptance Criteria
125
+ - [ ] AC 1
126
+ - [ ] AC 2
127
+
128
+ ## Random Section
129
+ - [ ] Should not be marked
130
+ `;
131
+ mockFs.readFile = vi.fn().mockResolvedValue(taskWithMixedTodos);
132
+ await taskUpdater.updateTaskForReviewReadiness('TEST-TASK-456', {});
133
+ const writeCall = mockFs.writeFile.mock.calls[0];
134
+ const writtenContent = writeCall[1];
135
+ // Implementation todos should be marked
136
+ expect(writtenContent).toContain('- [x] Task 1');
137
+ expect(writtenContent).toContain('- [x] Task 2');
138
+ expect(writtenContent).toContain('- [x] Task 3');
139
+ expect(writtenContent).toContain('- [x] Task 4');
140
+ // Acceptance criteria should remain unchanged
141
+ expect(writtenContent).toContain('- [ ] AC 1');
142
+ expect(writtenContent).toContain('- [ ] AC 2');
143
+ // Random section should remain unchanged
144
+ expect(writtenContent).toContain('- [ ] Should not be marked');
145
+ });
146
+ test('should update existing implementation summary', async () => {
147
+ const taskWithExistingSummary = `---
148
+ id: TEST-TASK-789
149
+ title: Task with Existing Summary
150
+ type: feature
151
+ status: in-progress
152
+ tags: ''
153
+ priority: medium
154
+ ---
155
+
156
+ ## Description
157
+ Task description
158
+
159
+ <!-- IMPLEMENTATION_SUMMARY_START -->
160
+ ## Implementation Summary
161
+ Old summary content
162
+ <!-- IMPLEMENTATION_SUMMARY_END -->
163
+
164
+ ## Implementation
165
+ - [ ] Task 1
166
+ `;
167
+ mockFs.readFile = vi.fn().mockResolvedValue(taskWithExistingSummary);
168
+ await taskUpdater.updateTaskForReviewReadiness('TEST-TASK-789', {
169
+ filesModified: ['newfile.ts'],
170
+ keyChanges: ['New implementation'],
171
+ });
172
+ const writeCall = mockFs.writeFile.mock.calls[0];
173
+ const writtenContent = writeCall[1];
174
+ // Should have new summary, not old
175
+ expect(writtenContent).not.toContain('Old summary content');
176
+ expect(writtenContent).toContain('**Files Modified:** 1 files updated');
177
+ expect(writtenContent).toContain('newfile.ts');
178
+ expect(writtenContent).toContain('New implementation');
179
+ });
180
+ test('should handle atomic write failure by cleaning up temp file', async () => {
181
+ mockFs.writeFile = vi.fn().mockRejectedValue(new Error('Write failed'));
182
+ mockFs.unlink = vi.fn().mockResolvedValue(undefined);
183
+ await expect(taskUpdater.updateTaskForReviewReadiness('TEST-TASK-123', {})).rejects.toThrow('Write failed');
184
+ expect(mockFs.unlink).toHaveBeenCalledWith(path.join(testTasksDir, 'TEST-TASK-123.md.tmp'));
185
+ });
186
+ test('should generate comprehensive implementation summary', async () => {
187
+ const context = {
188
+ filesModified: ['src/feature.ts', 'tests/feature.test.ts', 'docs/feature.md'],
189
+ keyChanges: ['Added feature A', 'Improved performance', 'Fixed edge case'],
190
+ testsAdded: true,
191
+ testsPassed: true,
192
+ buildPassed: true,
193
+ qualityChecksPassed: true,
194
+ aiReviewScore: 85,
195
+ workflowId: 'workflow-123',
196
+ };
197
+ await taskUpdater.updateTaskForReviewReadiness('TEST-TASK-123', context);
198
+ const writeCall = mockFs.writeFile.mock.calls[0];
199
+ const writtenContent = writeCall[1];
200
+ // Verify summary content
201
+ expect(writtenContent).toContain('**Files Modified:** 3 files updated');
202
+ expect(writtenContent).toContain('src/feature.ts');
203
+ expect(writtenContent).toContain('tests/feature.test.ts');
204
+ expect(writtenContent).toContain('docs/feature.md');
205
+ expect(writtenContent).toContain('**Key Changes:**');
206
+ expect(writtenContent).toContain('Added feature A');
207
+ expect(writtenContent).toContain('Improved performance');
208
+ expect(writtenContent).toContain('Fixed edge case');
209
+ expect(writtenContent).toContain('**Tests:** New tests added and passing');
210
+ expect(writtenContent).toContain('**Quality Checks:** All quality checks passed');
211
+ expect(writtenContent).toContain('AI review score: 85');
212
+ expect(writtenContent).toContain('Ready for manual review and approval');
213
+ });
214
+ test('should identify review risks when quality checks fail', async () => {
215
+ const context = {
216
+ filesModified: ['src/feature.ts'],
217
+ testsPassed: false,
218
+ buildPassed: false,
219
+ qualityChecksPassed: false,
220
+ aiReviewScore: 45,
221
+ };
222
+ await taskUpdater.updateTaskForReviewReadiness('TEST-TASK-123', context);
223
+ const writeCall = mockFs.writeFile.mock.calls[0];
224
+ const writtenContent = writeCall[1];
225
+ expect(writtenContent).toContain('**Review Notes:** Issues requiring attention:');
226
+ expect(writtenContent).toContain('⚠️ Test failures need investigation');
227
+ expect(writtenContent).toContain('⚠️ Quality check issues need resolution');
228
+ expect(writtenContent).toContain('⚠️ Build issues need fixing');
229
+ expect(writtenContent).toContain('⚠️ AI review score below threshold');
230
+ });
231
+ test('should identify AI review score of 0 as risk', async () => {
232
+ const context = {
233
+ filesModified: ['src/feature.ts'],
234
+ testsPassed: true,
235
+ buildPassed: true,
236
+ qualityChecksPassed: true,
237
+ aiReviewScore: 0,
238
+ };
239
+ await taskUpdater.updateTaskForReviewReadiness('TEST-TASK-123', context);
240
+ const writeCall = mockFs.writeFile.mock.calls[0];
241
+ const writtenContent = writeCall[1];
242
+ expect(writtenContent).toContain('**Review Notes:** Issues requiring attention:');
243
+ expect(writtenContent).toContain('⚠️ AI review score below threshold');
244
+ });
245
+ test('should handle edge case with no eligible todo sections', async () => {
246
+ const taskWithoutTodos = `---
247
+ id: TEST-TASK-NO-TODOS
248
+ title: Task Without Implementation Todos
249
+ type: feature
250
+ status: in-progress
251
+ tags: ''
252
+ priority: medium
253
+ ---
254
+
255
+ ## Description
256
+ This task has no implementation todos.
257
+
258
+ ## Acceptance Criteria
259
+ - [ ] Should work
260
+ - [ ] Should be tested
261
+ `;
262
+ mockFs.readFile = vi.fn().mockResolvedValue(taskWithoutTodos);
263
+ await taskUpdater.updateTaskForReviewReadiness('TEST-TASK-NO-TODOS', {});
264
+ const writeCall = mockFs.writeFile.mock.calls[0];
265
+ const writtenContent = writeCall[1];
266
+ // Status should still be updated
267
+ expect(writtenContent).toContain('status: Review');
268
+ // Implementation summary should still be added
269
+ expect(writtenContent).toContain('## Implementation Summary');
270
+ // Acceptance criteria should remain unchanged
271
+ expect(writtenContent).toContain('- [ ] Should work');
272
+ expect(writtenContent).toContain('- [ ] Should be tested');
273
+ });
274
+ test('should handle nested todo lists in implementation sections', async () => {
275
+ const taskWithNestedTodos = `---
276
+ id: TEST-NESTED
277
+ title: Task with Nested Todos
278
+ type: feature
279
+ status: in-progress
280
+ tags: ''
281
+ priority: medium
282
+ ---
283
+
284
+ ## Implementation Tasks
285
+ - [ ] Main task 1
286
+ - [ ] Subtask 1.1
287
+ - [ ] Subtask 1.2
288
+ - [ ] Main task 2
289
+ - [ ] Subtask 2.1
290
+ `;
291
+ mockFs.readFile = vi.fn().mockResolvedValue(taskWithNestedTodos);
292
+ await taskUpdater.updateTaskForReviewReadiness('TEST-NESTED', {});
293
+ const writeCall = mockFs.writeFile.mock.calls[0];
294
+ const writtenContent = writeCall[1];
295
+ // All todos should be marked, preserving indentation
296
+ expect(writtenContent).toContain('- [x] Main task 1');
297
+ expect(writtenContent).toContain(' - [x] Subtask 1.1');
298
+ expect(writtenContent).toContain(' - [x] Subtask 1.2');
299
+ expect(writtenContent).toContain('- [x] Main task 2');
300
+ expect(writtenContent).toContain(' - [x] Subtask 2.1');
301
+ });
302
+ });
303
+ });
@@ -109,6 +109,13 @@ export interface VibingExecution {
109
109
  previousExecutionId?: string;
110
110
  }>;
111
111
  qualityResults?: QualityResults;
112
+ aiReviewResult?: {
113
+ executionId: string;
114
+ reviewSummary: string;
115
+ recommendations: string[];
116
+ qualityScore: number;
117
+ timestamp?: string;
118
+ };
112
119
  links?: {
113
120
  prUrl?: string;
114
121
  prNumber?: number;
@@ -164,7 +171,7 @@ export interface VibingConfig {
164
171
  timeout?: number;
165
172
  enabled?: boolean;
166
173
  }>;
167
- aiRoutingOverrides?: Partial<Record<'execute_task' | 'improve_task' | 'ai_codereview' | 'ai_merge', {
174
+ aiRoutingOverrides?: Partial<Record<'execute_task' | 'quality_checks' | 'ai_codereview' | 'ai_merge' | 'improve_task', {
168
175
  provider?: string;
169
176
  model?: string;
170
177
  }>>;
@@ -176,4 +183,4 @@ export interface VibingConfig {
176
183
  timestamp?: string;
177
184
  };
178
185
  }
179
- export type { VibingStage, VibingStatus, LegacyVibingPhase, VibingWorkflowV2, StageToStatusMapping, } from '@vibeman/types';
186
+ export type { VibingStage, VibingStatus, VibingWorkflowV2, StageToStatusMapping, } from '@vibeman/types';
@@ -1,14 +1,18 @@
1
+ type ClaudeModel = 'claude-sonnet-4-20250514' | 'claude-opus-4-1-20250805' | 'claude-3-5-haiku-20241022';
2
+ type CodexModel = 'gpt-5.1-codex-max' | 'gpt-5.1-codex-max-low' | 'gpt-5.1-codex-max-medium' | 'gpt-5.1-codex-max-high' | 'gpt-5.1-codex-max-extra-high' | 'gpt-5.1-codex' | 'gpt-5.1-codex-low' | 'gpt-5.1-codex-medium' | 'gpt-5.1-codex-high' | 'gpt-5.1-codex-extra-high' | 'gpt-5.1-codex-mini' | 'gpt-5.1-codex-mini-low' | 'gpt-5.1-codex-mini-medium' | 'gpt-5.1-codex-mini-high' | 'gpt-5.1-codex-mini-extra-high' | 'gpt-5.2' | 'gpt-5.2-low' | 'gpt-5.2-medium' | 'gpt-5.2-high' | 'gpt-5.2-extra-high' | 'gpt-5.1' | 'gpt-5.1-low' | 'gpt-5.1-medium' | 'gpt-5.1-high' | 'gpt-5.1-extra-high';
3
+ type GeminiModel = 'gemini-3-pro-preview' | 'gemini-3-flash-preview' | 'gemini-2.5-pro' | 'gemini-2.5-flash' | 'gemini-2.5-flash-lite';
4
+ type AllowedModel = ClaudeModel | CodexModel | GeminiModel | 'auto';
1
5
  export interface VibemanSettings {
2
6
  agents: {
3
- defaultProvider: 'claude-code' | 'codex';
7
+ defaultProvider: 'claude-code' | 'codex' | 'gemini' | 'amp';
4
8
  codingAgent: {
5
- provider: 'claude-code' | 'codex';
6
- model: 'claude-sonnet-4-20250514' | 'claude-opus-4-1-20250805' | 'claude-3-5-haiku-20241022' | 'gpt-5';
9
+ provider: 'claude-code' | 'codex' | 'gemini' | 'amp';
10
+ model: AllowedModel;
7
11
  maxTokens?: number;
8
12
  };
9
13
  judgeAgent: {
10
- provider: 'claude-code' | 'codex';
11
- model: 'claude-sonnet-4-20250514' | 'claude-opus-4-1-20250805' | 'claude-3-5-haiku-20241022' | 'gpt-5';
14
+ provider: 'claude-code' | 'codex' | 'gemini' | 'amp';
15
+ model: AllowedModel;
12
16
  maxTokens?: number;
13
17
  reviewThresholdScore: number;
14
18
  };
@@ -23,6 +27,25 @@ export interface VibemanSettings {
23
27
  codex?: {
24
28
  binPath?: string;
25
29
  };
30
+ gemini?: {
31
+ binPath?: string;
32
+ };
33
+ amp?: {
34
+ binPath?: string;
35
+ };
36
+ };
37
+ routingPolicy?: {
38
+ defaultProvider?: 'claude-code' | 'codex' | 'gemini' | 'amp';
39
+ operations?: Partial<Record<'execute_task' | 'quality_checks' | 'ai_codereview' | 'ai_merge' | 'improve_task', {
40
+ provider: string;
41
+ model?: string;
42
+ options?: {
43
+ temperature?: number;
44
+ maxTokens?: number;
45
+ tools?: string[];
46
+ };
47
+ fallback?: string[];
48
+ }>>;
26
49
  };
27
50
  };
28
51
  defaultWorkflow: {
@@ -79,3 +102,4 @@ export interface SettingsValidationError {
79
102
  expected?: string;
80
103
  received?: string;
81
104
  }
105
+ export {};
@@ -25,6 +25,7 @@ export declare class GitService {
25
25
  private config;
26
26
  private projectRoot;
27
27
  constructor(projectRoot?: string, config?: GitConfig);
28
+ getProjectRoot(): string;
28
29
  /**
29
30
  * Validate Git repository exists and is properly configured
30
31
  */
@@ -64,6 +65,14 @@ export declare class GitService {
64
65
  * Get Git version information
65
66
  */
66
67
  getGitVersion(): Promise<VersionResult>;
68
+ /**
69
+ * Check whether a path is ignored by git.
70
+ */
71
+ isIgnored(relativePath: string): Promise<boolean>;
72
+ /**
73
+ * Get the current HEAD commit hash
74
+ */
75
+ getHeadCommitHash(short?: boolean): Promise<string>;
67
76
  /**
68
77
  * List all worktrees in the repository
69
78
  */
@@ -7,6 +7,9 @@ export class GitService {
7
7
  this.config = config;
8
8
  this.git = simpleGit(projectRoot);
9
9
  }
10
+ getProjectRoot() {
11
+ return this.projectRoot;
12
+ }
10
13
  /**
11
14
  * Validate Git repository exists and is properly configured
12
15
  */
@@ -145,6 +148,26 @@ export class GitService {
145
148
  async getGitVersion() {
146
149
  return await this.git.version();
147
150
  }
151
+ /**
152
+ * Check whether a path is ignored by git.
153
+ */
154
+ async isIgnored(relativePath) {
155
+ try {
156
+ await this.git.raw(['check-ignore', '-q', relativePath]);
157
+ return true;
158
+ }
159
+ catch (error) {
160
+ return false;
161
+ }
162
+ }
163
+ /**
164
+ * Get the current HEAD commit hash
165
+ */
166
+ async getHeadCommitHash(short = true) {
167
+ const args = short ? ['--short', 'HEAD'] : ['HEAD'];
168
+ const hash = await this.git.revparse(args);
169
+ return hash.trim();
170
+ }
148
171
  /**
149
172
  * List all worktrees in the repository
150
173
  */
@@ -45,7 +45,7 @@ export declare class WorktreeService extends EventEmitter {
45
45
  isConnectedToTask: boolean;
46
46
  }>>;
47
47
  /**
48
- * Unified cleanup: remove branch first, then worktree
48
+ * Unified cleanup: remove worktree first, then branch
49
49
  * For connected worktrees, use taskId; for unconnected, use worktreePath and branchName
50
50
  */
51
51
  cleanupWorktree(params: {
@@ -252,6 +252,11 @@ export class WorktreeService extends EventEmitter {
252
252
  entries = [];
253
253
  }
254
254
  for (const name of entries) {
255
+ const isIgnored = await this.gitService.isIgnored(name);
256
+ if (!isIgnored) {
257
+ log.debug('Skipped non-ignored env file', { name }, 'worktree-service');
258
+ continue;
259
+ }
255
260
  const sourcePath = path.join(this.projectRoot, name);
256
261
  const targetPath = path.join(worktreePath, name);
257
262
  try {
@@ -269,8 +274,11 @@ export class WorktreeService extends EventEmitter {
269
274
  }
270
275
  }
271
276
  }
272
- catch {
273
- // target missing, proceed
277
+ catch (error) {
278
+ const err = error;
279
+ if (err?.code !== 'ENOENT') {
280
+ log.debug('Failed to inspect existing worktree env link', { name, error }, 'worktree-service');
281
+ }
274
282
  }
275
283
  if (shouldCreate) {
276
284
  // Remove any existing file/dir and create a symlink
@@ -280,8 +288,8 @@ export class WorktreeService extends EventEmitter {
280
288
  log.debug('Symlinked to worktree', { name }, 'worktree-service');
281
289
  }
282
290
  }
283
- catch {
284
- // Ignore missing source or failures
291
+ catch (error) {
292
+ log.debug('Failed to symlink env file', { name, error }, 'worktree-service');
285
293
  }
286
294
  }
287
295
  }
@@ -323,7 +331,7 @@ export class WorktreeService extends EventEmitter {
323
331
  }
324
332
  }
325
333
  /**
326
- * Unified cleanup: remove branch first, then worktree
334
+ * Unified cleanup: remove worktree first, then branch
327
335
  * For connected worktrees, use taskId; for unconnected, use worktreePath and branchName
328
336
  */
329
337
  async cleanupWorktree(params) {
@@ -355,7 +363,11 @@ export class WorktreeService extends EventEmitter {
355
363
  actualWorktreePath = worktreePath;
356
364
  actualBranchName = branchName;
357
365
  }
358
- // Step 1: Try to remove the branch first (if it's not main/master)
366
+ // Step 1: Remove the worktree
367
+ await this.gitService.removeWorktree(actualWorktreePath, force);
368
+ // Step 2: Prune worktrees to ensure git state is clean before branch deletion
369
+ await this.gitService.pruneWorktrees();
370
+ // Step 3: Try to remove the branch (if it's not main/master)
359
371
  if (actualBranchName !== 'main' && actualBranchName !== 'master') {
360
372
  try {
361
373
  await this.gitService.deleteLocalBranch(actualBranchName, true);
@@ -363,15 +375,13 @@ export class WorktreeService extends EventEmitter {
363
375
  }
364
376
  catch (error) {
365
377
  // Log but continue - branch might not exist or already deleted
366
- log.warn('Could not delete branch, continuing with worktree removal', {
378
+ log.warn('Could not delete branch after worktree removal', {
367
379
  branchName: actualBranchName,
368
380
  error: error instanceof Error ? error.message : String(error),
369
381
  }, 'worktree-service');
370
382
  }
371
383
  }
372
- // Step 2: Remove the worktree
373
- await this.gitService.removeWorktree(actualWorktreePath, force);
374
- // Step 3: Clean up tracking if it was a connected worktree
384
+ // Step 4: Clean up tracking if it was a connected worktree
375
385
  if (isConnectedToTask && taskId) {
376
386
  this.worktrees.delete(taskId);
377
387
  this.emit('worktreeDeleted', taskId);
@@ -406,6 +416,8 @@ export class WorktreeService extends EventEmitter {
406
416
  }
407
417
  // Remove worktree using git service
408
418
  await this.gitService.removeWorktree(worktree.path, force);
419
+ // Prune worktrees to ensure git state is clean before branch deletion
420
+ await this.gitService.pruneWorktrees();
409
421
  // Delete local branch if it exists and is not merged
410
422
  try {
411
423
  await this.gitService.deleteLocalBranch(worktree.branchName, force);
@@ -3,7 +3,7 @@ import { stripNextInjectedEnv } from '../utils/stripNextEnv.js';
3
3
  import { log } from '../lib/logger.js';
4
4
  import { getSettingsService } from '../settings-service.js';
5
5
  import { CoreAgentService } from '../agent/core-agent-service.js';
6
- import { ClaudeCodeAdapter, CodexCliProvider } from '../agent/ai-providers/index.js';
6
+ import { ClaudeCodeAdapter, CodexCliProvider, GeminiCliProvider, } from '../agent/ai-providers/index.js';
7
7
  import { getQualityChecksDetectionPrompt } from '../agent/prompt-service.js';
8
8
  export class QualityPipeline {
9
9
  // Expose configured checks for external orchestration/streaming
@@ -48,6 +48,7 @@ export class QualityPipeline {
48
48
  defaultModel: 'claude-sonnet-4-20250514',
49
49
  }));
50
50
  core.registerProvider('codex', new CodexCliProvider({ defaultWorkingDirectory: cwd }));
51
+ core.registerProvider('gemini', new GeminiCliProvider({ defaultWorkingDirectory: cwd }));
51
52
  const tools = ['Read', 'Grep', 'Glob'];
52
53
  const result = await core.execute({
53
54
  prompt,