@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.
- package/.turbo/turbo-build.log +4 -0
- package/.vibe/conversation-state.sqlite +0 -0
- package/LICENSE +674 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +74 -0
- package/dist/index.js.map +1 -0
- package/dist/notification-service.d.ts +14 -0
- package/dist/notification-service.d.ts.map +1 -0
- package/dist/notification-service.js +18 -0
- package/dist/notification-service.js.map +1 -0
- package/dist/resource-handlers/conversation-state.d.ts +15 -0
- package/dist/resource-handlers/conversation-state.d.ts.map +1 -0
- package/dist/resource-handlers/conversation-state.js +40 -0
- package/dist/resource-handlers/conversation-state.js.map +1 -0
- package/dist/resource-handlers/development-plan.d.ts +14 -0
- package/dist/resource-handlers/development-plan.d.ts.map +1 -0
- package/dist/resource-handlers/development-plan.js +31 -0
- package/dist/resource-handlers/development-plan.js.map +1 -0
- package/dist/resource-handlers/index.d.ts +24 -0
- package/dist/resource-handlers/index.d.ts.map +1 -0
- package/dist/resource-handlers/index.js +62 -0
- package/dist/resource-handlers/index.js.map +1 -0
- package/dist/resource-handlers/system-prompt.d.ts +15 -0
- package/dist/resource-handlers/system-prompt.d.ts.map +1 -0
- package/dist/resource-handlers/system-prompt.js +40 -0
- package/dist/resource-handlers/system-prompt.js.map +1 -0
- package/dist/resource-handlers/workflow-resource.d.ts +15 -0
- package/dist/resource-handlers/workflow-resource.d.ts.map +1 -0
- package/dist/resource-handlers/workflow-resource.js +85 -0
- package/dist/resource-handlers/workflow-resource.js.map +1 -0
- package/dist/response-renderer.d.ts +30 -0
- package/dist/response-renderer.d.ts.map +1 -0
- package/dist/response-renderer.js +94 -0
- package/dist/response-renderer.js.map +1 -0
- package/dist/server-config.d.ts +34 -0
- package/dist/server-config.d.ts.map +1 -0
- package/dist/server-config.js +486 -0
- package/dist/server-config.js.map +1 -0
- package/dist/server-helpers.d.ts +62 -0
- package/dist/server-helpers.d.ts.map +1 -0
- package/dist/server-helpers.js +156 -0
- package/dist/server-helpers.js.map +1 -0
- package/dist/server-implementation.d.ts +74 -0
- package/dist/server-implementation.d.ts.map +1 -0
- package/dist/server-implementation.js +201 -0
- package/dist/server-implementation.js.map +1 -0
- package/dist/server.d.ts +6 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +5 -0
- package/dist/server.js.map +1 -0
- package/dist/tool-handlers/base-tool-handler.d.ts +50 -0
- package/dist/tool-handlers/base-tool-handler.d.ts.map +1 -0
- package/dist/tool-handlers/base-tool-handler.js +74 -0
- package/dist/tool-handlers/base-tool-handler.js.map +1 -0
- package/dist/tool-handlers/conduct-review.d.ts +49 -0
- package/dist/tool-handlers/conduct-review.d.ts.map +1 -0
- package/dist/tool-handlers/conduct-review.js +105 -0
- package/dist/tool-handlers/conduct-review.js.map +1 -0
- package/dist/tool-handlers/get-tool-info.d.ts +76 -0
- package/dist/tool-handlers/get-tool-info.d.ts.map +1 -0
- package/dist/tool-handlers/get-tool-info.js +168 -0
- package/dist/tool-handlers/get-tool-info.js.map +1 -0
- package/dist/tool-handlers/index.d.ts +42 -0
- package/dist/tool-handlers/index.d.ts.map +1 -0
- package/dist/tool-handlers/index.js +74 -0
- package/dist/tool-handlers/index.js.map +1 -0
- package/dist/tool-handlers/install-workflow.d.ts +48 -0
- package/dist/tool-handlers/install-workflow.d.ts.map +1 -0
- package/dist/tool-handlers/install-workflow.js +131 -0
- package/dist/tool-handlers/install-workflow.js.map +1 -0
- package/dist/tool-handlers/list-workflows.d.ts +47 -0
- package/dist/tool-handlers/list-workflows.d.ts.map +1 -0
- package/dist/tool-handlers/list-workflows.js +58 -0
- package/dist/tool-handlers/list-workflows.js.map +1 -0
- package/dist/tool-handlers/no-idea.d.ts +41 -0
- package/dist/tool-handlers/no-idea.d.ts.map +1 -0
- package/dist/tool-handlers/no-idea.js +29 -0
- package/dist/tool-handlers/no-idea.js.map +1 -0
- package/dist/tool-handlers/proceed-to-phase.d.ts +39 -0
- package/dist/tool-handlers/proceed-to-phase.d.ts.map +1 -0
- package/dist/tool-handlers/proceed-to-phase.js +109 -0
- package/dist/tool-handlers/proceed-to-phase.js.map +1 -0
- package/dist/tool-handlers/reset-development.d.ts +31 -0
- package/dist/tool-handlers/reset-development.d.ts.map +1 -0
- package/dist/tool-handlers/reset-development.js +48 -0
- package/dist/tool-handlers/reset-development.js.map +1 -0
- package/dist/tool-handlers/resume-workflow.d.ts +88 -0
- package/dist/tool-handlers/resume-workflow.d.ts.map +1 -0
- package/dist/tool-handlers/resume-workflow.js +213 -0
- package/dist/tool-handlers/resume-workflow.js.map +1 -0
- package/dist/tool-handlers/setup-project-docs.d.ts +36 -0
- package/dist/tool-handlers/setup-project-docs.d.ts.map +1 -0
- package/dist/tool-handlers/setup-project-docs.js +136 -0
- package/dist/tool-handlers/setup-project-docs.js.map +1 -0
- package/dist/tool-handlers/start-development.d.ts +82 -0
- package/dist/tool-handlers/start-development.d.ts.map +1 -0
- package/dist/tool-handlers/start-development.js +448 -0
- package/dist/tool-handlers/start-development.js.map +1 -0
- package/dist/tool-handlers/whats-next.d.ts +42 -0
- package/dist/tool-handlers/whats-next.d.ts.map +1 -0
- package/dist/tool-handlers/whats-next.js +118 -0
- package/dist/tool-handlers/whats-next.js.map +1 -0
- package/dist/types.d.ts +114 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/package.json +29 -0
- package/src/index.ts +93 -0
- package/src/notification-service.ts +23 -0
- package/src/resource-handlers/conversation-state.ts +55 -0
- package/src/resource-handlers/development-plan.ts +48 -0
- package/src/resource-handlers/index.ts +73 -0
- package/src/resource-handlers/system-prompt.ts +55 -0
- package/src/resource-handlers/workflow-resource.ts +126 -0
- package/src/response-renderer.ts +116 -0
- package/src/server-config.ts +744 -0
- package/src/server-helpers.ts +225 -0
- package/src/server-implementation.ts +277 -0
- package/src/server.ts +9 -0
- package/src/tool-handlers/base-tool-handler.ts +141 -0
- package/src/tool-handlers/conduct-review.ts +191 -0
- package/src/tool-handlers/get-tool-info.ts +274 -0
- package/src/tool-handlers/index.ts +117 -0
- package/src/tool-handlers/install-workflow.ts +185 -0
- package/src/tool-handlers/list-workflows.ts +94 -0
- package/src/tool-handlers/no-idea.ts +47 -0
- package/src/tool-handlers/proceed-to-phase.ts +205 -0
- package/src/tool-handlers/reset-development.ts +90 -0
- package/src/tool-handlers/resume-workflow.ts +380 -0
- package/src/tool-handlers/setup-project-docs.ts +226 -0
- package/src/tool-handlers/start-development.ts +685 -0
- package/src/tool-handlers/whats-next.ts +235 -0
- package/src/types.ts +130 -0
- package/test/e2e/core-functionality.test.ts +176 -0
- package/test/e2e/mcp-contract.test.ts +540 -0
- package/test/e2e/plan-management.test.ts +331 -0
- package/test/e2e/state-management.test.ts +392 -0
- package/test/e2e/workflow-integration.test.ts +506 -0
- package/test/unit/commit-behaviour-interface.test.ts +244 -0
- package/test/unit/conduct-review.test.ts +151 -0
- package/test/unit/reset-functionality.test.ts +72 -0
- package/test/unit/resume-workflow.test.ts +192 -0
- package/test/unit/server-tools.test.ts +311 -0
- package/test/unit/setup-project-docs-handler.test.ts +267 -0
- package/test/unit/start-development-artifact-detection.test.ts +387 -0
- package/test/unit/start-development-gitignore.test.ts +178 -0
- package/test/unit/system-prompt-resource.test.ts +101 -0
- package/test/unit/tool-handlers/no-idea.test.ts +40 -0
- package/test/utils/e2e-test-setup.ts +453 -0
- package/test/utils/run-server-in-dir.sh +27 -0
- package/test/utils/temp-files.ts +308 -0
- package/test/utils/test-access.ts +79 -0
- package/test/utils/test-helpers.ts +286 -0
- package/test/utils/test-setup.ts +78 -0
- package/tsconfig.build.json +9 -0
- package/tsconfig.build.tsbuildinfo +1 -0
- package/tsconfig.json +12 -0
- 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
|
+
});
|