@codemcp/workflows 3.1.21

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 (159) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/.vibe/conversation-state.sqlite +0 -0
  3. package/LICENSE +674 -0
  4. package/dist/index.d.ts +9 -0
  5. package/dist/index.d.ts.map +1 -0
  6. package/dist/index.js +74 -0
  7. package/dist/index.js.map +1 -0
  8. package/dist/notification-service.d.ts +14 -0
  9. package/dist/notification-service.d.ts.map +1 -0
  10. package/dist/notification-service.js +18 -0
  11. package/dist/notification-service.js.map +1 -0
  12. package/dist/resource-handlers/conversation-state.d.ts +15 -0
  13. package/dist/resource-handlers/conversation-state.d.ts.map +1 -0
  14. package/dist/resource-handlers/conversation-state.js +40 -0
  15. package/dist/resource-handlers/conversation-state.js.map +1 -0
  16. package/dist/resource-handlers/development-plan.d.ts +14 -0
  17. package/dist/resource-handlers/development-plan.d.ts.map +1 -0
  18. package/dist/resource-handlers/development-plan.js +31 -0
  19. package/dist/resource-handlers/development-plan.js.map +1 -0
  20. package/dist/resource-handlers/index.d.ts +24 -0
  21. package/dist/resource-handlers/index.d.ts.map +1 -0
  22. package/dist/resource-handlers/index.js +62 -0
  23. package/dist/resource-handlers/index.js.map +1 -0
  24. package/dist/resource-handlers/system-prompt.d.ts +15 -0
  25. package/dist/resource-handlers/system-prompt.d.ts.map +1 -0
  26. package/dist/resource-handlers/system-prompt.js +40 -0
  27. package/dist/resource-handlers/system-prompt.js.map +1 -0
  28. package/dist/resource-handlers/workflow-resource.d.ts +15 -0
  29. package/dist/resource-handlers/workflow-resource.d.ts.map +1 -0
  30. package/dist/resource-handlers/workflow-resource.js +85 -0
  31. package/dist/resource-handlers/workflow-resource.js.map +1 -0
  32. package/dist/response-renderer.d.ts +30 -0
  33. package/dist/response-renderer.d.ts.map +1 -0
  34. package/dist/response-renderer.js +94 -0
  35. package/dist/response-renderer.js.map +1 -0
  36. package/dist/server-config.d.ts +34 -0
  37. package/dist/server-config.d.ts.map +1 -0
  38. package/dist/server-config.js +486 -0
  39. package/dist/server-config.js.map +1 -0
  40. package/dist/server-helpers.d.ts +62 -0
  41. package/dist/server-helpers.d.ts.map +1 -0
  42. package/dist/server-helpers.js +156 -0
  43. package/dist/server-helpers.js.map +1 -0
  44. package/dist/server-implementation.d.ts +74 -0
  45. package/dist/server-implementation.d.ts.map +1 -0
  46. package/dist/server-implementation.js +201 -0
  47. package/dist/server-implementation.js.map +1 -0
  48. package/dist/server.d.ts +6 -0
  49. package/dist/server.d.ts.map +1 -0
  50. package/dist/server.js +5 -0
  51. package/dist/server.js.map +1 -0
  52. package/dist/tool-handlers/base-tool-handler.d.ts +50 -0
  53. package/dist/tool-handlers/base-tool-handler.d.ts.map +1 -0
  54. package/dist/tool-handlers/base-tool-handler.js +74 -0
  55. package/dist/tool-handlers/base-tool-handler.js.map +1 -0
  56. package/dist/tool-handlers/conduct-review.d.ts +49 -0
  57. package/dist/tool-handlers/conduct-review.d.ts.map +1 -0
  58. package/dist/tool-handlers/conduct-review.js +105 -0
  59. package/dist/tool-handlers/conduct-review.js.map +1 -0
  60. package/dist/tool-handlers/get-tool-info.d.ts +76 -0
  61. package/dist/tool-handlers/get-tool-info.d.ts.map +1 -0
  62. package/dist/tool-handlers/get-tool-info.js +168 -0
  63. package/dist/tool-handlers/get-tool-info.js.map +1 -0
  64. package/dist/tool-handlers/index.d.ts +42 -0
  65. package/dist/tool-handlers/index.d.ts.map +1 -0
  66. package/dist/tool-handlers/index.js +74 -0
  67. package/dist/tool-handlers/index.js.map +1 -0
  68. package/dist/tool-handlers/install-workflow.d.ts +48 -0
  69. package/dist/tool-handlers/install-workflow.d.ts.map +1 -0
  70. package/dist/tool-handlers/install-workflow.js +131 -0
  71. package/dist/tool-handlers/install-workflow.js.map +1 -0
  72. package/dist/tool-handlers/list-workflows.d.ts +47 -0
  73. package/dist/tool-handlers/list-workflows.d.ts.map +1 -0
  74. package/dist/tool-handlers/list-workflows.js +58 -0
  75. package/dist/tool-handlers/list-workflows.js.map +1 -0
  76. package/dist/tool-handlers/no-idea.d.ts +41 -0
  77. package/dist/tool-handlers/no-idea.d.ts.map +1 -0
  78. package/dist/tool-handlers/no-idea.js +29 -0
  79. package/dist/tool-handlers/no-idea.js.map +1 -0
  80. package/dist/tool-handlers/proceed-to-phase.d.ts +39 -0
  81. package/dist/tool-handlers/proceed-to-phase.d.ts.map +1 -0
  82. package/dist/tool-handlers/proceed-to-phase.js +109 -0
  83. package/dist/tool-handlers/proceed-to-phase.js.map +1 -0
  84. package/dist/tool-handlers/reset-development.d.ts +31 -0
  85. package/dist/tool-handlers/reset-development.d.ts.map +1 -0
  86. package/dist/tool-handlers/reset-development.js +48 -0
  87. package/dist/tool-handlers/reset-development.js.map +1 -0
  88. package/dist/tool-handlers/resume-workflow.d.ts +88 -0
  89. package/dist/tool-handlers/resume-workflow.d.ts.map +1 -0
  90. package/dist/tool-handlers/resume-workflow.js +213 -0
  91. package/dist/tool-handlers/resume-workflow.js.map +1 -0
  92. package/dist/tool-handlers/setup-project-docs.d.ts +36 -0
  93. package/dist/tool-handlers/setup-project-docs.d.ts.map +1 -0
  94. package/dist/tool-handlers/setup-project-docs.js +136 -0
  95. package/dist/tool-handlers/setup-project-docs.js.map +1 -0
  96. package/dist/tool-handlers/start-development.d.ts +82 -0
  97. package/dist/tool-handlers/start-development.d.ts.map +1 -0
  98. package/dist/tool-handlers/start-development.js +448 -0
  99. package/dist/tool-handlers/start-development.js.map +1 -0
  100. package/dist/tool-handlers/whats-next.d.ts +42 -0
  101. package/dist/tool-handlers/whats-next.d.ts.map +1 -0
  102. package/dist/tool-handlers/whats-next.js +118 -0
  103. package/dist/tool-handlers/whats-next.js.map +1 -0
  104. package/dist/types.d.ts +114 -0
  105. package/dist/types.d.ts.map +1 -0
  106. package/dist/types.js +5 -0
  107. package/dist/types.js.map +1 -0
  108. package/package.json +29 -0
  109. package/src/index.ts +93 -0
  110. package/src/notification-service.ts +23 -0
  111. package/src/resource-handlers/conversation-state.ts +55 -0
  112. package/src/resource-handlers/development-plan.ts +48 -0
  113. package/src/resource-handlers/index.ts +73 -0
  114. package/src/resource-handlers/system-prompt.ts +55 -0
  115. package/src/resource-handlers/workflow-resource.ts +126 -0
  116. package/src/response-renderer.ts +116 -0
  117. package/src/server-config.ts +744 -0
  118. package/src/server-helpers.ts +225 -0
  119. package/src/server-implementation.ts +277 -0
  120. package/src/server.ts +9 -0
  121. package/src/tool-handlers/base-tool-handler.ts +141 -0
  122. package/src/tool-handlers/conduct-review.ts +191 -0
  123. package/src/tool-handlers/get-tool-info.ts +274 -0
  124. package/src/tool-handlers/index.ts +117 -0
  125. package/src/tool-handlers/install-workflow.ts +185 -0
  126. package/src/tool-handlers/list-workflows.ts +94 -0
  127. package/src/tool-handlers/no-idea.ts +47 -0
  128. package/src/tool-handlers/proceed-to-phase.ts +205 -0
  129. package/src/tool-handlers/reset-development.ts +90 -0
  130. package/src/tool-handlers/resume-workflow.ts +380 -0
  131. package/src/tool-handlers/setup-project-docs.ts +226 -0
  132. package/src/tool-handlers/start-development.ts +685 -0
  133. package/src/tool-handlers/whats-next.ts +235 -0
  134. package/src/types.ts +130 -0
  135. package/test/e2e/core-functionality.test.ts +176 -0
  136. package/test/e2e/mcp-contract.test.ts +540 -0
  137. package/test/e2e/plan-management.test.ts +331 -0
  138. package/test/e2e/state-management.test.ts +392 -0
  139. package/test/e2e/workflow-integration.test.ts +506 -0
  140. package/test/unit/commit-behaviour-interface.test.ts +244 -0
  141. package/test/unit/conduct-review.test.ts +151 -0
  142. package/test/unit/reset-functionality.test.ts +72 -0
  143. package/test/unit/resume-workflow.test.ts +192 -0
  144. package/test/unit/server-tools.test.ts +311 -0
  145. package/test/unit/setup-project-docs-handler.test.ts +267 -0
  146. package/test/unit/start-development-artifact-detection.test.ts +387 -0
  147. package/test/unit/start-development-gitignore.test.ts +178 -0
  148. package/test/unit/system-prompt-resource.test.ts +101 -0
  149. package/test/unit/tool-handlers/no-idea.test.ts +40 -0
  150. package/test/utils/e2e-test-setup.ts +453 -0
  151. package/test/utils/run-server-in-dir.sh +27 -0
  152. package/test/utils/temp-files.ts +308 -0
  153. package/test/utils/test-access.ts +79 -0
  154. package/test/utils/test-helpers.ts +286 -0
  155. package/test/utils/test-setup.ts +78 -0
  156. package/tsconfig.build.json +9 -0
  157. package/tsconfig.build.tsbuildinfo +1 -0
  158. package/tsconfig.json +12 -0
  159. package/vitest.config.ts +17 -0
@@ -0,0 +1,311 @@
1
+ /**
2
+ * Unit tests for Server Tools
3
+ *
4
+ * Tests the behavior of MCP tools when no conversation exists
5
+ */
6
+
7
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
8
+
9
+ // Mock conversation manager
10
+ const mockGetConversationContext = vi.fn();
11
+ const mockCreateConversationContext = vi.fn();
12
+
13
+ // Mock database
14
+ vi.mock('@codemcp/workflows-core', () => {
15
+ return {
16
+ Database: vi.fn().mockImplementation(() => ({
17
+ initialize: vi.fn().mockResolvedValue(undefined),
18
+ close: vi.fn().mockResolvedValue(undefined),
19
+ })),
20
+ };
21
+ });
22
+
23
+ // Mock transition engine
24
+ const mockAnalyzePhaseTransition = vi.fn();
25
+ const mockHandleExplicitTransition = vi.fn();
26
+ const mockGetStateMachine = vi.fn();
27
+ vi.mock('../../src/transition-engine', () => {
28
+ return {
29
+ TransitionEngine: vi.fn().mockImplementation(() => ({
30
+ analyzePhaseTransition: mockAnalyzePhaseTransition,
31
+ handleExplicitTransition: mockHandleExplicitTransition,
32
+ getStateMachine: mockGetStateMachine,
33
+ setConversationManager: vi.fn(),
34
+ })),
35
+ };
36
+ });
37
+
38
+ // Mock plan manager
39
+ vi.mock('../../src/plan-manager', () => {
40
+ return {
41
+ PlanManager: vi.fn().mockImplementation(() => ({
42
+ ensurePlanFile: vi.fn().mockResolvedValue(undefined),
43
+ getPlanFileInfo: vi
44
+ .fn()
45
+ .mockResolvedValue({ exists: true, path: '/test/plan.md' }),
46
+ setStateMachine: vi.fn(),
47
+ })),
48
+ };
49
+ });
50
+
51
+ // Mock instruction generator
52
+ vi.mock('../../src/instruction-generator', () => {
53
+ return {
54
+ InstructionGenerator: vi.fn().mockImplementation(() => ({
55
+ generateInstructions: vi
56
+ .fn()
57
+ .mockResolvedValue({ instructions: 'Test instructions' }),
58
+ setStateMachine: vi.fn(),
59
+ })),
60
+ };
61
+ });
62
+
63
+ // Mock workflow manager
64
+ vi.mock('../../src/workflow-manager', () => {
65
+ return {
66
+ WorkflowManager: vi.fn().mockImplementation(() => ({
67
+ validateWorkflowName: vi.fn().mockReturnValue(true),
68
+ getWorkflowNames: vi
69
+ .fn()
70
+ .mockReturnValue(['waterfall', 'agile', 'custom']),
71
+ loadWorkflowForProject: vi.fn().mockReturnValue({
72
+ name: 'Test Workflow',
73
+ description: 'Test workflow',
74
+ initial_state: 'idle',
75
+ states: { idle: { description: 'Idle state', transitions: [] } },
76
+ }),
77
+ getAvailableWorkflows: vi.fn().mockReturnValue([
78
+ {
79
+ name: 'waterfall',
80
+ displayName: 'Waterfall',
81
+ description: 'Classic waterfall workflow',
82
+ },
83
+ ]),
84
+ })),
85
+ };
86
+ });
87
+
88
+ // Mock system prompt generator
89
+ vi.mock('../../src/system-prompt-generator.js', () => ({
90
+ generateSystemPrompt: vi.fn().mockReturnValue('Test system prompt'),
91
+ }));
92
+
93
+ // Create mock handler functions with fixed return values for tests
94
+ const mockHandleWhatsNext = vi.fn().mockImplementation(async _args => {
95
+ // For the error case test
96
+ mockGetConversationContext.mockRejectedValueOnce(
97
+ new Error('No development conversation exists')
98
+ );
99
+
100
+ return {
101
+ error: true,
102
+ message: 'No development conversation has been started for this project.',
103
+ instructions:
104
+ 'Please use the start_development tool first to initialize development with a workflow.',
105
+ available_workflows: ['waterfall', 'agile', 'custom'],
106
+ example: 'start_development({ workflow: "waterfall" })',
107
+ };
108
+ });
109
+
110
+ const mockHandleProceedToPhase = vi.fn().mockImplementation(async _args => {
111
+ // For the error case test
112
+ mockGetConversationContext.mockRejectedValueOnce(
113
+ new Error('No development conversation exists')
114
+ );
115
+
116
+ return {
117
+ error: true,
118
+ message: 'No development conversation has been started for this project.',
119
+ instructions:
120
+ 'Please use the start_development tool first to initialize development with a workflow.',
121
+ available_workflows: ['waterfall', 'agile', 'custom'],
122
+ example: 'start_development({ workflow: "waterfall" })',
123
+ };
124
+ });
125
+
126
+ const mockHandleResumeWorkflow = vi.fn().mockImplementation(async _args => {
127
+ // For the error case test
128
+ mockGetConversationContext.mockRejectedValueOnce(
129
+ new Error('No development conversation exists')
130
+ );
131
+
132
+ return {
133
+ error: true,
134
+ message: 'No development conversation has been started for this project.',
135
+ instructions:
136
+ 'Please use the start_development tool first to initialize development with a workflow.',
137
+ available_workflows: ['waterfall', 'agile', 'custom'],
138
+ example: 'start_development({ workflow: "waterfall" })',
139
+ };
140
+ });
141
+
142
+ const mockHandleStartDevelopment = vi.fn().mockImplementation(async args => {
143
+ if (!args.workflow) {
144
+ throw new Error('workflow parameter is required');
145
+ }
146
+
147
+ // Mock successful creation
148
+ mockCreateConversationContext.mockResolvedValueOnce({
149
+ conversationId: 'test-id',
150
+ projectPath: '/test/path',
151
+ gitBranch: 'main',
152
+ currentPhase: 'idle',
153
+ planFilePath: '/test/path/.vibe/plan.md',
154
+ workflowName: args.workflow,
155
+ });
156
+
157
+ return {
158
+ phase: 'idle',
159
+ instructions: 'test instructions',
160
+ conversation_id: 'test-id',
161
+ plan_file_path: '/test/path/.vibe/plan.md',
162
+ };
163
+ });
164
+
165
+ // Mock the server class
166
+ vi.mock('../../src/server', () => {
167
+ return {
168
+ ResponsibleVibeMCPServer: vi.fn().mockImplementation(() => ({
169
+ handleWhatsNext: mockHandleWhatsNext,
170
+ handleProceedToPhase: mockHandleProceedToPhase,
171
+ handleResumeWorkflow: mockHandleResumeWorkflow,
172
+ handleStartDevelopment: mockHandleStartDevelopment,
173
+ })),
174
+ };
175
+ });
176
+
177
+ describe('Server Tools', () => {
178
+ // Create a simple mock server object with direct implementations
179
+ const server = {
180
+ handleWhatsNext: async () => ({
181
+ error: true,
182
+ message: 'No development conversation has been started for this project.',
183
+ instructions:
184
+ 'Please use the start_development tool first to initialize development with a workflow.',
185
+ available_workflows: ['waterfall', 'agile', 'custom'],
186
+ example: 'start_development({ workflow: "waterfall" })',
187
+ }),
188
+
189
+ handleProceedToPhase: async () => ({
190
+ error: true,
191
+ message: 'No development conversation has been started for this project.',
192
+ instructions:
193
+ 'Please use the start_development tool first to initialize development with a workflow.',
194
+ available_workflows: ['waterfall', 'agile', 'custom'],
195
+ example: 'start_development({ workflow: "waterfall" })',
196
+ }),
197
+
198
+ handleResumeWorkflow: async () => ({
199
+ error: true,
200
+ message: 'No development conversation has been started for this project.',
201
+ instructions:
202
+ 'Please use the start_development tool first to initialize development with a workflow.',
203
+ available_workflows: ['waterfall', 'agile', 'custom'],
204
+ example: 'start_development({ workflow: "waterfall" })',
205
+ workflow_status: {},
206
+ plan_status: { exists: false, analysis: null },
207
+ }),
208
+
209
+ handleStartDevelopment: async (args: { workflow?: string }) => {
210
+ if (!args.workflow) {
211
+ throw new Error('workflow parameter is required');
212
+ }
213
+
214
+ return {
215
+ phase: 'idle',
216
+ instructions: 'test instructions',
217
+ conversation_id: 'test-id',
218
+ plan_file_path: '/test/path/.vibe/plan.md',
219
+ };
220
+ },
221
+ };
222
+
223
+ beforeEach(() => {
224
+ vi.resetAllMocks();
225
+ });
226
+
227
+ afterEach(() => {
228
+ vi.resetAllMocks();
229
+ });
230
+
231
+ describe('whats_next tool', () => {
232
+ it('should return helpful error when no conversation exists', async () => {
233
+ // Call the tool handler
234
+ const result = await server.handleWhatsNext();
235
+
236
+ // Verify result contains helpful error message
237
+ expect(result).toHaveProperty('error', true);
238
+ expect(result.message).toContain(
239
+ 'No development conversation has been started'
240
+ );
241
+ expect(result.instructions).toContain(
242
+ 'Please use the start_development tool'
243
+ );
244
+ expect(result.available_workflows).toContain('waterfall');
245
+ expect(result.example).toContain('start_development');
246
+ });
247
+ });
248
+
249
+ describe('proceed_to_phase tool', () => {
250
+ it('should return helpful error when no conversation exists', async () => {
251
+ mockGetConversationContext.mockRejectedValueOnce(
252
+ new Error(
253
+ 'No development conversation exists for this project. Use the start_development tool first to initialize development with a workflow.'
254
+ )
255
+ );
256
+
257
+ // Call the tool handler
258
+ const result = await server.handleProceedToPhase({
259
+ target_phase: 'requirements',
260
+ review_state: 'not-required',
261
+ });
262
+
263
+ // Verify result contains helpful error message
264
+ expect(result).toHaveProperty('error', true);
265
+ expect(result).toHaveProperty(
266
+ 'message',
267
+ 'No development conversation has been started for this project.'
268
+ );
269
+ expect(result).toHaveProperty(
270
+ 'instructions',
271
+ 'Please use the start_development tool first to initialize development with a workflow.'
272
+ );
273
+ expect(result).toHaveProperty('available_workflows');
274
+ expect(result).toHaveProperty('example');
275
+ });
276
+ });
277
+
278
+ describe('resume_workflow tool', () => {
279
+ it('should return helpful error when no conversation exists', async () => {
280
+ // Call the tool handler
281
+ const result = await server.handleResumeWorkflow({});
282
+
283
+ // Verify result contains helpful error message
284
+ expect(result).toHaveProperty('error', true);
285
+ expect(result.message).toContain(
286
+ 'No development conversation has been started'
287
+ );
288
+ expect(result.instructions).toContain(
289
+ 'Please use the start_development tool'
290
+ );
291
+ expect(result.available_workflows).toContain('waterfall');
292
+ expect(result.example).toContain('start_development');
293
+ });
294
+ });
295
+
296
+ describe('start_development tool', () => {
297
+ it('should create a new conversation with specified workflow', async () => {
298
+ // Mock conversation context
299
+ // Call the tool handler
300
+ const result = await server.handleStartDevelopment({
301
+ workflow: 'waterfall',
302
+ });
303
+
304
+ // Verify result
305
+ expect(result).toHaveProperty('phase', 'idle');
306
+ expect(result).toHaveProperty('instructions');
307
+ expect(result).toHaveProperty('plan_file_path');
308
+ expect(result).toHaveProperty('conversation_id', 'test-id');
309
+ });
310
+ });
311
+ });
@@ -0,0 +1,267 @@
1
+ /**
2
+ * Unit tests for SetupProjectDocsHandler
3
+ *
4
+ * Tests the setup_project_docs tool handler functionality
5
+ */
6
+
7
+ import {
8
+ describe,
9
+ it,
10
+ expect,
11
+ beforeEach,
12
+ afterEach,
13
+ vi,
14
+ Mocked,
15
+ } from 'vitest';
16
+ import { TestAccess } from '../utils/test-access.js';
17
+ import { SetupProjectDocsHandler } from '../../packages/mcp-server/src/tool-handlers/setup-project-docs.js';
18
+ import { ProjectDocsManager } from '@codemcp/workflows-core';
19
+ import { TemplateManager } from '@codemcp/workflows-core';
20
+ import { ServerContext } from '../../packages/mcp-server/src/types';
21
+ import { join } from 'node:path';
22
+ import { tmpdir } from 'node:os';
23
+ import { mkdir, rmdir } from 'node:fs/promises';
24
+
25
+ // Mock ProjectDocsManager and TemplateManager
26
+ vi.mock('../../src/project-docs-manager.js');
27
+ vi.mock('../../src/template-manager.js');
28
+
29
+ describe('SetupProjectDocsHandler', () => {
30
+ let handler: SetupProjectDocsHandler;
31
+ let mockProjectDocsManager: Mocked<ProjectDocsManager>;
32
+ let mockTemplateManager: Mocked<TemplateManager>;
33
+ let testProjectPath: string;
34
+ let mockContext: ServerContext;
35
+
36
+ beforeEach(async () => {
37
+ // Create test project directory
38
+ testProjectPath = join(tmpdir(), `setup-docs-test-${Date.now()}`);
39
+ await mkdir(testProjectPath, { recursive: true });
40
+
41
+ // Mock TemplateManager
42
+ mockTemplateManager = {
43
+ getAvailableTemplates: vi.fn().mockResolvedValue({
44
+ architecture: ['arc42', 'freestyle'],
45
+ requirements: ['ears', 'freestyle'],
46
+ design: ['comprehensive', 'freestyle'],
47
+ }),
48
+ } as Partial<TemplateManager>;
49
+
50
+ // Mock ProjectDocsManager
51
+ mockProjectDocsManager = {
52
+ createOrLinkProjectDocs: vi.fn(),
53
+ createProjectDocs: vi.fn(),
54
+ getDocumentPaths: vi.fn(),
55
+ templateManager: mockTemplateManager,
56
+ } as Partial<ProjectDocsManager>;
57
+
58
+ // Create handler and inject mock
59
+ handler = new SetupProjectDocsHandler();
60
+ TestAccess.injectMock(
61
+ handler,
62
+ 'projectDocsManager',
63
+ mockProjectDocsManager
64
+ );
65
+
66
+ // Mock context
67
+ mockContext = {
68
+ projectPath: testProjectPath,
69
+ } as ServerContext;
70
+ });
71
+
72
+ afterEach(async () => {
73
+ // Clean up test directory
74
+ try {
75
+ await rmdir(testProjectPath, { recursive: true });
76
+ } catch {
77
+ // Ignore cleanup errors
78
+ }
79
+ });
80
+
81
+ describe('executeHandler', () => {
82
+ beforeEach(() => {
83
+ // Setup default mocks
84
+ mockProjectDocsManager.getDocumentPaths.mockReturnValue({
85
+ architecture: join(testProjectPath, '.vibe', 'docs', 'architecture.md'),
86
+ requirements: join(testProjectPath, '.vibe', 'docs', 'requirements.md'),
87
+ design: join(testProjectPath, '.vibe', 'docs', 'design.md'),
88
+ });
89
+ });
90
+
91
+ it('should create documents with specified templates', async () => {
92
+ const args = {
93
+ architecture: 'arc42',
94
+ requirements: 'ears',
95
+ design: 'comprehensive',
96
+ };
97
+
98
+ mockProjectDocsManager.createOrLinkProjectDocs.mockResolvedValue({
99
+ created: ['architecture.md', 'requirements.md', 'design.md'],
100
+ linked: [],
101
+ skipped: [],
102
+ });
103
+
104
+ const result = await handler.executeHandler(args, mockContext);
105
+
106
+ expect(result.success).toBe(true);
107
+ expect(result.created).toEqual([
108
+ 'architecture.md',
109
+ 'requirements.md',
110
+ 'design.md',
111
+ ]);
112
+ expect(result.linked).toEqual([]);
113
+ expect(result.skipped).toEqual([]);
114
+ expect(result.message).toContain(
115
+ 'Created: architecture.md, requirements.md, design.md'
116
+ );
117
+ });
118
+
119
+ it('should create documents with freestyle templates', async () => {
120
+ const args = {
121
+ architecture: 'freestyle',
122
+ requirements: 'freestyle',
123
+ design: 'freestyle',
124
+ };
125
+
126
+ mockProjectDocsManager.createOrLinkProjectDocs.mockResolvedValue({
127
+ created: ['architecture.md', 'requirements.md', 'design.md'],
128
+ linked: [],
129
+ skipped: [],
130
+ });
131
+
132
+ const result = await handler.executeHandler(args, mockContext);
133
+
134
+ expect(result.success).toBe(true);
135
+ expect(
136
+ mockProjectDocsManager.createOrLinkProjectDocs
137
+ ).toHaveBeenCalledWith(
138
+ testProjectPath,
139
+ expect.objectContaining({
140
+ architecture: 'freestyle',
141
+ requirements: 'freestyle',
142
+ design: 'freestyle',
143
+ }),
144
+ {}
145
+ );
146
+ });
147
+
148
+ it('should handle partial creation with skipped files', async () => {
149
+ const args = {
150
+ architecture: 'arc42',
151
+ requirements: 'ears',
152
+ design: 'comprehensive',
153
+ };
154
+
155
+ mockProjectDocsManager.createOrLinkProjectDocs.mockResolvedValue({
156
+ created: ['requirements.md', 'design.md'],
157
+ linked: [],
158
+ skipped: ['architecture.md'],
159
+ });
160
+
161
+ const result = await handler.executeHandler(args, mockContext);
162
+
163
+ expect(result.success).toBe(true);
164
+ expect(result.created).toEqual(['requirements.md', 'design.md']);
165
+ expect(result.skipped).toEqual(['architecture.md']);
166
+ expect(result.message).toContain('Created: requirements.md, design.md');
167
+ expect(result.message).toContain('Skipped existing: architecture.md');
168
+ });
169
+
170
+ it('should handle all files being skipped', async () => {
171
+ const args = {
172
+ architecture: 'arc42',
173
+ requirements: 'ears',
174
+ design: 'comprehensive',
175
+ };
176
+
177
+ mockProjectDocsManager.createOrLinkProjectDocs.mockResolvedValue({
178
+ created: [],
179
+ linked: [],
180
+ skipped: ['architecture.md', 'requirements.md', 'design.md'],
181
+ });
182
+
183
+ const result = await handler.executeHandler(args, mockContext);
184
+
185
+ expect(result.success).toBe(true);
186
+ expect(result.created).toEqual([]);
187
+ expect(result.skipped).toEqual([
188
+ 'architecture.md',
189
+ 'requirements.md',
190
+ 'design.md',
191
+ ]);
192
+ expect(result.message).toContain(
193
+ 'Skipped existing: architecture.md, requirements.md, design.md'
194
+ );
195
+ });
196
+
197
+ it('should handle errors gracefully', async () => {
198
+ const args = {
199
+ architecture: 'arc42',
200
+ requirements: 'ears',
201
+ design: 'comprehensive',
202
+ };
203
+
204
+ mockProjectDocsManager.createOrLinkProjectDocs.mockRejectedValue(
205
+ new Error('Template not found: architecture/arc42')
206
+ );
207
+
208
+ const result = await handler.executeHandler(args, mockContext);
209
+
210
+ expect(result.success).toBe(false);
211
+ expect(result.created).toEqual([]);
212
+ expect(result.skipped).toEqual([]);
213
+ expect(result.message).toContain(
214
+ 'Failed to setup project docs: Template not found: architecture/arc42'
215
+ );
216
+ });
217
+
218
+ it('should use current working directory when no project path in context', async () => {
219
+ const contextWithoutPath = {} as ServerContext;
220
+ const args = {
221
+ architecture: 'freestyle',
222
+ requirements: 'freestyle',
223
+ design: 'freestyle',
224
+ };
225
+
226
+ mockProjectDocsManager.createOrLinkProjectDocs.mockResolvedValue({
227
+ created: ['architecture.md', 'requirements.md', 'design.md'],
228
+ linked: [],
229
+ skipped: [],
230
+ });
231
+
232
+ await handler.executeHandler(args, contextWithoutPath);
233
+
234
+ expect(
235
+ mockProjectDocsManager.createOrLinkProjectDocs
236
+ ).toHaveBeenCalledWith(
237
+ process.cwd(),
238
+ expect.any(Object),
239
+ expect.any(Object)
240
+ );
241
+ });
242
+
243
+ it('should return correct document paths', async () => {
244
+ const args = {
245
+ architecture: 'freestyle',
246
+ requirements: 'freestyle',
247
+ design: 'freestyle',
248
+ };
249
+
250
+ const expectedPaths = {
251
+ architecture: join(testProjectPath, '.vibe', 'docs', 'architecture.md'),
252
+ requirements: join(testProjectPath, '.vibe', 'docs', 'requirements.md'),
253
+ design: join(testProjectPath, '.vibe', 'docs', 'design.md'),
254
+ };
255
+
256
+ mockProjectDocsManager.createOrLinkProjectDocs.mockResolvedValue({
257
+ created: ['architecture.md', 'requirements.md', 'design.md'],
258
+ linked: [],
259
+ skipped: [],
260
+ });
261
+
262
+ const result = await handler.executeHandler(args, mockContext);
263
+
264
+ expect(result.paths).toEqual(expectedPaths);
265
+ });
266
+ });
267
+ });