@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,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
|
+
});
|