@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,387 @@
1
+ /**
2
+ * Unit tests for StartDevelopmentHandler dynamic artifact detection
3
+ *
4
+ * Tests the enhanced start_development functionality that dynamically analyzes workflows
5
+ * to detect which document variables are referenced and validates only those documents
6
+ */
7
+
8
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
9
+ import { TestAccess } from '../utils/test-access.js';
10
+ import { StartDevelopmentHandler } from '../../packages/mcp-server/src/tool-handlers/start-development.js';
11
+ import type { YamlStateMachine } from './../../src/state-machine-types';
12
+ import { join } from 'node:path';
13
+ import {
14
+ MockContextFactory,
15
+ TEST_WORKFLOWS,
16
+ TestAssertions,
17
+ } from '../utils/test-helpers.js';
18
+
19
+ // Mock ProjectDocsManager
20
+ vi.mock('../../src/project-docs-manager.js');
21
+
22
+ // Mock other dependencies
23
+ vi.mock('../../src/git-manager.js', () => ({
24
+ GitManager: {
25
+ isGitRepository: vi.fn().mockReturnValue(true),
26
+ getCurrentCommitHash: vi.fn().mockReturnValue('abc123'),
27
+ },
28
+ }));
29
+
30
+ describe('StartDevelopmentHandler - Dynamic Artifact Detection', () => {
31
+ let handler: StartDevelopmentHandler;
32
+ let mockProjectDocsManager: ReturnType<
33
+ typeof MockContextFactory.createProjectDocsManagerMock
34
+ >;
35
+ let testProjectPath: string;
36
+ let mockContext: ReturnType<typeof MockContextFactory.createBasicContext>;
37
+
38
+ beforeEach(() => {
39
+ testProjectPath = '/test/project';
40
+
41
+ // Create mock project docs manager
42
+ mockProjectDocsManager =
43
+ MockContextFactory.createProjectDocsManagerMock(testProjectPath);
44
+
45
+ // Create handler and inject mock
46
+ handler = new StartDevelopmentHandler();
47
+ TestAccess.injectMock(
48
+ handler,
49
+ 'projectDocsManager',
50
+ mockProjectDocsManager
51
+ );
52
+
53
+ // Create basic mock context
54
+ mockContext = MockContextFactory.createBasicContext(testProjectPath);
55
+
56
+ // Mock file system operations
57
+ vi.mock('fs', () => ({
58
+ readFileSync: vi.fn().mockReturnValue('main'),
59
+ writeFileSync: vi.fn(),
60
+ existsSync: vi.fn().mockReturnValue(false),
61
+ }));
62
+ });
63
+
64
+ describe('dynamic workflow analysis', () => {
65
+ it('should proceed normally when workflow contains no document variables', async () => {
66
+ mockContext.workflowManager.loadWorkflowForProject.mockReturnValue(
67
+ TEST_WORKFLOWS.simple
68
+ );
69
+
70
+ const result = await handler.executeHandler(
71
+ { workflow: 'simple-workflow' },
72
+ mockContext
73
+ );
74
+
75
+ expect(mockProjectDocsManager.getProjectDocsInfo).not.toHaveBeenCalled();
76
+ TestAssertions.expectNormalPhase(result, 'requirements');
77
+ });
78
+
79
+ it('should detect and validate only referenced document variables', async () => {
80
+ mockContext.workflowManager.loadWorkflowForProject.mockReturnValue(
81
+ TEST_WORKFLOWS.requiredArchDoc
82
+ );
83
+
84
+ // Update mock context to match the workflow's initial state
85
+ mockContext.conversationManager.createConversationContext = vi
86
+ .fn()
87
+ .mockResolvedValue({
88
+ conversationId: 'test-conversation',
89
+ currentPhase: 'design',
90
+ planFilePath: join(testProjectPath, '.vibe', 'development-plan.md'),
91
+ });
92
+
93
+ mockProjectDocsManager.getProjectDocsInfo.mockResolvedValue({
94
+ architecture: {
95
+ exists: false,
96
+ path: join(testProjectPath, '.vibe', 'docs', 'architecture.md'),
97
+ },
98
+ requirements: {
99
+ exists: true,
100
+ path: join(testProjectPath, '.vibe', 'docs', 'requirements.md'),
101
+ },
102
+ design: {
103
+ exists: true,
104
+ path: join(testProjectPath, '.vibe', 'docs', 'design.md'),
105
+ },
106
+ });
107
+
108
+ const result = await handler.executeHandler(
109
+ { workflow: 'arch-focused' },
110
+ mockContext
111
+ );
112
+
113
+ TestAssertions.expectArtifactSetupPhase(result);
114
+ expect(result.instructions).toContain(
115
+ '**Referenced Variables:** `$ARCHITECTURE_DOC`'
116
+ );
117
+ expect(result.instructions).toContain('architecture.md');
118
+ expect(result.instructions).toContain('✅ requirements.md');
119
+ expect(result.instructions).toContain('✅ design.md');
120
+ });
121
+
122
+ it('should detect multiple document variables in workflow', async () => {
123
+ mockContext.workflowManager.loadWorkflowForProject.mockReturnValue(
124
+ TEST_WORKFLOWS.requiredMultipleDocs
125
+ );
126
+
127
+ // Update mock context to match the workflow's initial state
128
+ mockContext.conversationManager.createConversationContext = vi
129
+ .fn()
130
+ .mockResolvedValue({
131
+ conversationId: 'test-conversation',
132
+ currentPhase: 'implementation',
133
+ planFilePath: join(testProjectPath, '.vibe', 'development-plan.md'),
134
+ });
135
+
136
+ mockProjectDocsManager.getProjectDocsInfo.mockResolvedValue({
137
+ architecture: {
138
+ exists: false,
139
+ path: join(testProjectPath, '.vibe', 'docs', 'architecture.md'),
140
+ },
141
+ requirements: {
142
+ exists: false,
143
+ path: join(testProjectPath, '.vibe', 'docs', 'requirements.md'),
144
+ },
145
+ design: {
146
+ exists: false,
147
+ path: join(testProjectPath, '.vibe', 'docs', 'design.md'),
148
+ },
149
+ });
150
+
151
+ const result = await handler.executeHandler(
152
+ { workflow: 'multi-doc' },
153
+ mockContext
154
+ );
155
+
156
+ TestAssertions.expectArtifactSetupPhase(result);
157
+ expect(result.instructions).toContain(
158
+ '**Referenced Variables:** `$ARCHITECTURE_DOC`, `$REQUIREMENTS_DOC`, `$DESIGN_DOC`'
159
+ );
160
+ expect(result.instructions).toContain('architecture.md');
161
+ expect(result.instructions).toContain('requirements.md');
162
+ expect(result.instructions).toContain('design.md');
163
+ });
164
+
165
+ it('should proceed normally when all referenced documents exist', async () => {
166
+ mockContext.workflowManager.loadWorkflowForProject.mockReturnValue(
167
+ TEST_WORKFLOWS.requiredMultipleDocs
168
+ );
169
+
170
+ // Mock conversation context to match initial state
171
+ mockContext.conversationManager.createConversationContext.mockResolvedValue(
172
+ {
173
+ conversationId: 'test-conversation',
174
+ currentPhase: 'implementation',
175
+ projectPath: testProjectPath,
176
+ planFilePath: join(testProjectPath, '.vibe', 'plan.md'),
177
+ gitBranch: 'feature-branch',
178
+ }
179
+ );
180
+
181
+ // Mock transition engine to return the correct phase
182
+ mockContext.transitionEngine.handleExplicitTransition.mockResolvedValue({
183
+ newPhase: 'implementation',
184
+ });
185
+
186
+ mockProjectDocsManager.getProjectDocsInfo.mockResolvedValue({
187
+ architecture: {
188
+ exists: true,
189
+ path: join(testProjectPath, '.vibe', 'docs', 'architecture.md'),
190
+ },
191
+ requirements: {
192
+ exists: true,
193
+ path: join(testProjectPath, '.vibe', 'docs', 'requirements.md'),
194
+ },
195
+ design: {
196
+ exists: true,
197
+ path: join(testProjectPath, '.vibe', 'docs', 'design.md'),
198
+ },
199
+ });
200
+
201
+ const result = await handler.executeHandler(
202
+ { workflow: 'complete-docs' },
203
+ mockContext
204
+ );
205
+
206
+ expect(mockProjectDocsManager.getProjectDocsInfo).toHaveBeenCalledWith(
207
+ testProjectPath
208
+ );
209
+ TestAssertions.expectNormalPhase(result, 'implementation');
210
+ });
211
+
212
+ it('should handle partial document availability correctly', async () => {
213
+ // Mock workflow that references REQUIREMENTS_DOC and DESIGN_DOC
214
+ const partialWorkflow = {
215
+ name: 'req-design-focused',
216
+ description: 'Workflow focusing on requirements and design docs',
217
+ initial_state: 'development',
218
+ metadata: {
219
+ requiresDocumentation: true,
220
+ },
221
+ states: {
222
+ development: {
223
+ default_instructions:
224
+ 'Implement features based on $REQUIREMENTS_DOC and follow $DESIGN_DOC patterns.',
225
+ },
226
+ },
227
+ } as Partial<YamlStateMachine>;
228
+
229
+ mockContext.workflowManager.loadWorkflowForProject.mockReturnValue(
230
+ partialWorkflow
231
+ );
232
+
233
+ // Update mock context to match the workflow's initial state
234
+ mockContext.conversationManager.createConversationContext = vi
235
+ .fn()
236
+ .mockResolvedValue({
237
+ conversationId: 'test-conversation',
238
+ currentPhase: 'development',
239
+ planFilePath: join(testProjectPath, '.vibe', 'development-plan.md'),
240
+ });
241
+
242
+ mockProjectDocsManager.getProjectDocsInfo.mockResolvedValue({
243
+ architecture: {
244
+ exists: true,
245
+ path: join(testProjectPath, '.vibe', 'docs', 'architecture.md'),
246
+ },
247
+ requirements: {
248
+ exists: true,
249
+ path: join(testProjectPath, '.vibe', 'docs', 'requirements.md'),
250
+ },
251
+ design: {
252
+ exists: false,
253
+ path: join(testProjectPath, '.vibe', 'docs', 'design.md'),
254
+ },
255
+ });
256
+
257
+ const result = await handler.executeHandler(
258
+ { workflow: 'req-design-focused' },
259
+ mockContext
260
+ );
261
+
262
+ TestAssertions.expectArtifactSetupPhase(result);
263
+ expect(result.instructions).toContain(
264
+ '**Referenced Variables:** `$REQUIREMENTS_DOC`, `$DESIGN_DOC`'
265
+ );
266
+ expect(result.instructions).toContain('design.md'); // Only missing doc
267
+ expect(result.instructions).toContain('✅ architecture.md'); // Existing doc
268
+ expect(result.instructions).toContain('✅ requirements.md'); // Existing doc
269
+ });
270
+
271
+ it('should handle workflow loading errors gracefully', async () => {
272
+ // Mock workflow loading failure - this should happen during artifact check, not during normal workflow loading
273
+ let callCount = 0;
274
+ mockContext.workflowManager.loadWorkflowForProject.mockImplementation(
275
+ () => {
276
+ callCount++;
277
+ if (callCount === 1) {
278
+ // First call during artifact check - throw error
279
+ throw new Error('Workflow not found');
280
+ } else {
281
+ // Second call during normal flow - return valid workflow
282
+ return TEST_WORKFLOWS.simple;
283
+ }
284
+ }
285
+ );
286
+
287
+ const result = await handler.executeHandler(
288
+ { workflow: 'invalid-workflow' },
289
+ mockContext
290
+ );
291
+
292
+ // Should proceed without artifact check when workflow analysis fails
293
+ TestAssertions.expectNormalPhase(result, 'requirements');
294
+ });
295
+
296
+ it('should include detected variables in setup guidance', async () => {
297
+ mockContext.workflowManager.loadWorkflowForProject.mockReturnValue(
298
+ TEST_WORKFLOWS.requiredArchDoc
299
+ );
300
+
301
+ // Update mock context to match the workflow's initial state
302
+ mockContext.conversationManager.createConversationContext = vi
303
+ .fn()
304
+ .mockResolvedValue({
305
+ conversationId: 'test-conversation',
306
+ currentPhase: 'design',
307
+ planFilePath: join(testProjectPath, '.vibe', 'development-plan.md'),
308
+ });
309
+
310
+ mockProjectDocsManager.getProjectDocsInfo.mockResolvedValue({
311
+ architecture: {
312
+ exists: false,
313
+ path: join(testProjectPath, '.vibe', 'docs', 'architecture.md'),
314
+ },
315
+ requirements: {
316
+ exists: true,
317
+ path: join(testProjectPath, '.vibe', 'docs', 'requirements.md'),
318
+ },
319
+ design: {
320
+ exists: true,
321
+ path: join(testProjectPath, '.vibe', 'docs', 'design.md'),
322
+ },
323
+ });
324
+
325
+ const result = await handler.executeHandler(
326
+ { workflow: 'arch-only' },
327
+ mockContext
328
+ );
329
+
330
+ expect(result.instructions).toContain(
331
+ '**Referenced Variables:** `$ARCHITECTURE_DOC`'
332
+ );
333
+ expect(result.instructions).toContain(
334
+ 'detected variables: `$ARCHITECTURE_DOC`'
335
+ );
336
+ });
337
+
338
+ it('should proceed normally for optional workflows with missing documents', async () => {
339
+ // Use a workflow that has document variables but NO requiresDocumentation flag
340
+ mockContext.workflowManager.loadWorkflowForProject.mockReturnValue(
341
+ TEST_WORKFLOWS.withArchDoc
342
+ );
343
+
344
+ // Mock conversation context to match initial state
345
+ mockContext.conversationManager.createConversationContext.mockResolvedValue(
346
+ {
347
+ conversationId: 'test-conversation',
348
+ currentPhase: 'design',
349
+ projectPath: testProjectPath,
350
+ planFilePath: join(testProjectPath, '.vibe', 'plan.md'),
351
+ gitBranch: 'feature-branch',
352
+ }
353
+ );
354
+
355
+ // Mock transition engine to return the correct phase
356
+ mockContext.transitionEngine.handleExplicitTransition.mockResolvedValue({
357
+ newPhase: 'design',
358
+ });
359
+
360
+ // Mock all documents as missing
361
+ mockProjectDocsManager.getProjectDocsInfo.mockResolvedValue({
362
+ architecture: {
363
+ exists: false,
364
+ path: join(testProjectPath, '.vibe', 'docs', 'architecture.md'),
365
+ },
366
+ requirements: {
367
+ exists: false,
368
+ path: join(testProjectPath, '.vibe', 'docs', 'requirements.md'),
369
+ },
370
+ design: {
371
+ exists: false,
372
+ path: join(testProjectPath, '.vibe', 'docs', 'design.md'),
373
+ },
374
+ });
375
+
376
+ const result = await handler.executeHandler(
377
+ { workflow: 'optional-arch-workflow' },
378
+ mockContext
379
+ );
380
+
381
+ // Should proceed to normal phase instead of artifact-setup
382
+ // because requiresDocumentation is not set (defaults to false)
383
+ TestAssertions.expectNormalPhase(result, 'design');
384
+ expect(result.instructions).not.toContain('Referenced Variables');
385
+ });
386
+ });
387
+ });
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Unit tests for StartDevelopment .gitignore management
3
+ *
4
+ * Tests the automatic .vibe/.gitignore creation functionality
5
+ */
6
+
7
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
8
+ import {
9
+ existsSync,
10
+ readFileSync,
11
+ writeFileSync,
12
+ mkdirSync,
13
+ rmSync,
14
+ } from 'node:fs';
15
+ import { resolve } from 'node:path';
16
+ import { TestAccess } from '../utils/test-access.js';
17
+ import { tmpdir } from 'node:os';
18
+ import { StartDevelopmentHandler } from '../../packages/mcp-server/src/tool-handlers/start-development.js';
19
+
20
+ describe('StartDevelopment .gitignore management', () => {
21
+ let tempDir: string;
22
+ let handler: StartDevelopmentHandler;
23
+ let mockLogger: {
24
+ debug: ReturnType<typeof vi.fn>;
25
+ info: ReturnType<typeof vi.fn>;
26
+ warn: ReturnType<typeof vi.fn>;
27
+ error: ReturnType<typeof vi.fn>;
28
+ };
29
+
30
+ beforeEach(() => {
31
+ // Create temporary directory for testing
32
+ tempDir = resolve(tmpdir(), `vibe-test-${Date.now()}`);
33
+ mkdirSync(tempDir, { recursive: true });
34
+
35
+ // Mock logger
36
+ mockLogger = {
37
+ debug: vi.fn(),
38
+ info: vi.fn(),
39
+ warn: vi.fn(),
40
+ error: vi.fn(),
41
+ };
42
+
43
+ // Create handler instance with mocked logger
44
+ handler = new StartDevelopmentHandler();
45
+ (handler as unknown as { logger: unknown }).logger = mockLogger;
46
+ });
47
+
48
+ afterEach(() => {
49
+ // Clean up temporary directory
50
+ if (existsSync(tempDir)) {
51
+ rmSync(tempDir, { recursive: true, force: true });
52
+ }
53
+ });
54
+
55
+ describe('ensureGitignoreEntry', () => {
56
+ it('should skip non-git repositories', () => {
57
+ // Call the private method cleanly
58
+ TestAccess.callMethod(handler, 'ensureGitignoreEntry', tempDir);
59
+
60
+ expect(mockLogger.debug).toHaveBeenCalledWith(
61
+ 'Not a git repository, skipping .gitignore management',
62
+ { projectPath: tempDir }
63
+ );
64
+ });
65
+
66
+ it('should create .vibe/.gitignore when none exists in git repo', () => {
67
+ // Create .git directory to simulate git repo
68
+ mkdirSync(resolve(tempDir, '.git'));
69
+
70
+ // Call the method
71
+ TestAccess.callMethod(handler, 'ensureGitignoreEntry', tempDir);
72
+
73
+ // Check that .vibe/.gitignore was created with correct content
74
+ const vibeGitignorePath = resolve(tempDir, '.vibe', '.gitignore');
75
+ expect(existsSync(vibeGitignorePath)).toBe(true);
76
+
77
+ const content = readFileSync(vibeGitignorePath, 'utf-8');
78
+ expect(content).toContain('*.sqlite');
79
+ expect(content).toContain('conversation-state.sqlite');
80
+
81
+ expect(mockLogger.info).toHaveBeenCalledWith(
82
+ 'Created .vibe/.gitignore to exclude SQLite files',
83
+ { projectPath: tempDir, gitignorePath: vibeGitignorePath }
84
+ );
85
+ });
86
+
87
+ it('should create .vibe directory if it does not exist', () => {
88
+ // Create .git directory to simulate git repo
89
+ mkdirSync(resolve(tempDir, '.git'));
90
+
91
+ // Ensure .vibe directory doesn't exist
92
+ const vibeDir = resolve(tempDir, '.vibe');
93
+ expect(existsSync(vibeDir)).toBe(false);
94
+
95
+ // Call the method
96
+ TestAccess.callMethod(handler, 'ensureGitignoreEntry', tempDir);
97
+
98
+ // Check that .vibe directory was created
99
+ expect(existsSync(vibeDir)).toBe(true);
100
+
101
+ // Check that .vibe/.gitignore was created
102
+ const vibeGitignorePath = resolve(vibeDir, '.gitignore');
103
+ expect(existsSync(vibeGitignorePath)).toBe(true);
104
+ });
105
+
106
+ it('should skip when .vibe/.gitignore already exists with SQLite exclusions', () => {
107
+ // Create .git directory
108
+ mkdirSync(resolve(tempDir, '.git'));
109
+
110
+ // Create .vibe directory and .gitignore with existing SQLite exclusions
111
+ const vibeDir = resolve(tempDir, '.vibe');
112
+ mkdirSync(vibeDir, { recursive: true });
113
+ const vibeGitignorePath = resolve(vibeDir, '.gitignore');
114
+ const existingContent = '*.sqlite\nconversation-state.sqlite*\n';
115
+ writeFileSync(vibeGitignorePath, existingContent);
116
+
117
+ // Call the method
118
+ TestAccess.callMethod(handler, 'ensureGitignoreEntry', tempDir);
119
+
120
+ // Check that content wasn't changed
121
+ const content = readFileSync(vibeGitignorePath, 'utf-8');
122
+ expect(content).toBe(existingContent);
123
+
124
+ expect(mockLogger.debug).toHaveBeenCalledWith(
125
+ '.vibe/.gitignore already exists with SQLite exclusions',
126
+ { gitignorePath: vibeGitignorePath }
127
+ );
128
+ });
129
+
130
+ it('should recreate .vibe/.gitignore if existing one is incomplete', () => {
131
+ // Create .git directory
132
+ mkdirSync(resolve(tempDir, '.git'));
133
+
134
+ // Create .vibe directory and incomplete .gitignore
135
+ const vibeDir = resolve(tempDir, '.vibe');
136
+ mkdirSync(vibeDir, { recursive: true });
137
+ const vibeGitignorePath = resolve(vibeDir, '.gitignore');
138
+ const incompleteContent = 'some-other-file\n';
139
+ writeFileSync(vibeGitignorePath, incompleteContent);
140
+
141
+ // Call the method
142
+ TestAccess.callMethod(handler, 'ensureGitignoreEntry', tempDir);
143
+
144
+ // Check that content was updated to include SQLite exclusions
145
+ const content = readFileSync(vibeGitignorePath, 'utf-8');
146
+ expect(content).toContain('*.sqlite');
147
+ expect(content).toContain('conversation-state.sqlite');
148
+
149
+ expect(mockLogger.info).toHaveBeenCalledWith(
150
+ 'Created .vibe/.gitignore to exclude SQLite files',
151
+ { projectPath: tempDir, gitignorePath: vibeGitignorePath }
152
+ );
153
+ });
154
+
155
+ it('should not affect parent directory .gitignore', () => {
156
+ // Create .git directory
157
+ mkdirSync(resolve(tempDir, '.git'));
158
+
159
+ // Create parent .gitignore with existing content
160
+ const parentGitignorePath = resolve(tempDir, '.gitignore');
161
+ const parentContent = 'node_modules/\n*.log\n';
162
+ writeFileSync(parentGitignorePath, parentContent);
163
+
164
+ // Call the method
165
+ TestAccess.callMethod(handler, 'ensureGitignoreEntry', tempDir);
166
+
167
+ // Check that parent .gitignore was not modified
168
+ const parentContentAfter = readFileSync(parentGitignorePath, 'utf-8');
169
+ expect(parentContentAfter).toBe(parentContent);
170
+
171
+ // Check that .vibe/.gitignore was created
172
+ const vibeGitignorePath = resolve(tempDir, '.vibe', '.gitignore');
173
+ expect(existsSync(vibeGitignorePath)).toBe(true);
174
+ const vibeContent = readFileSync(vibeGitignorePath, 'utf-8');
175
+ expect(vibeContent).toContain('*.sqlite');
176
+ });
177
+ });
178
+ });
@@ -0,0 +1,101 @@
1
+ /**
2
+ * System Prompt Resource Tests
3
+ *
4
+ * Tests for the system-prompt resource handler to ensure it properly
5
+ * exposes the system prompt through the MCP protocol.
6
+ */
7
+
8
+ import { describe, it, expect } from 'vitest';
9
+ import { SystemPromptResourceHandler } from '../../src/resource-handlers/system-prompt.js';
10
+ import type { ServerContext } from '../../src/types.js';
11
+
12
+ describe('System Prompt Resource', () => {
13
+ it('should expose system prompt as MCP resource', async () => {
14
+ const handler = new SystemPromptResourceHandler();
15
+
16
+ // Call the handler directly
17
+ const result = await handler.handle(
18
+ new URL('system-prompt://'),
19
+ {} as ServerContext
20
+ );
21
+
22
+ // Verify the safeExecute wrapper structure
23
+ expect(result).toBeDefined();
24
+ expect(result.success).toBe(true);
25
+ expect(result.data).toBeDefined();
26
+
27
+ const data = result.data;
28
+ expect(data.uri).toBe('system-prompt://');
29
+ expect(data.mimeType).toBe('text/plain');
30
+ expect(data.text).toBeDefined();
31
+ expect(typeof data.text).toBe('string');
32
+
33
+ // Verify content contains expected system prompt elements
34
+ expect(data.text).toContain(
35
+ 'You are an AI assistant that helps users develop software features'
36
+ );
37
+ expect(data.text).toContain('responsible-vibe-mcp');
38
+ expect(data.text).toContain('whats_next()');
39
+ expect(data.text).toContain('proceed_to_phase({');
40
+ expect(data.text).toContain('Core Workflow');
41
+
42
+ // Verify it's a substantial prompt (not empty or truncated)
43
+ expect(data.text.length).toBeGreaterThan(1000);
44
+ });
45
+
46
+ it('should be workflow-independent and consistent', async () => {
47
+ const handler = new SystemPromptResourceHandler();
48
+
49
+ // Get system prompt multiple times
50
+ const result1 = await handler.handle(
51
+ new URL('system-prompt://'),
52
+ {} as ServerContext
53
+ );
54
+ const result2 = await handler.handle(
55
+ new URL('system-prompt://'),
56
+ {} as ServerContext
57
+ );
58
+ const result3 = await handler.handle(
59
+ new URL('system-prompt://'),
60
+ {} as ServerContext
61
+ );
62
+
63
+ // All should be successful
64
+ expect(result1.success).toBe(true);
65
+ expect(result2.success).toBe(true);
66
+ expect(result3.success).toBe(true);
67
+
68
+ // All should be identical
69
+ expect(result1.data.text).toBe(result2.data.text);
70
+ expect(result2.data.text).toBe(result3.data.text);
71
+
72
+ // Verify the prompt contains standard elements
73
+ expect(result1.data.text).toContain('You are an AI assistant');
74
+ expect(result1.data.text).toContain('whats_next()');
75
+ expect(result1.data.text).toContain('Development Workflow');
76
+ expect(result1.data.text).toContain('start_development()');
77
+ });
78
+
79
+ it('should use default waterfall workflow for system prompt', async () => {
80
+ const handler = new SystemPromptResourceHandler();
81
+
82
+ const result = await handler.handle(
83
+ new URL('system-prompt://'),
84
+ {} as ServerContext
85
+ );
86
+
87
+ expect(result.success).toBe(true);
88
+
89
+ // The system prompt should be generated using the default workflow
90
+ // and should contain workflow-agnostic instructions
91
+ expect(result.data.text).toContain(
92
+ 'The responsible-vibe-mcp server will guide you through development phases'
93
+ );
94
+ expect(result.data.text).toContain(
95
+ 'available phases and their descriptions will be provided'
96
+ );
97
+ expect(result.data.text).toContain(
98
+ 'tool responses from start_development() and resume_workflow()'
99
+ );
100
+ });
101
+ });