@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,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WhatsNext Tool Handler
|
|
3
|
+
*
|
|
4
|
+
* Handles the whats_next tool which analyzes conversation context and
|
|
5
|
+
* determines the next development phase with specific instructions for the LLM.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { ConversationRequiredToolHandler } from './base-tool-handler.js';
|
|
9
|
+
import type { ConversationContext } from '@codemcp/workflows-core';
|
|
10
|
+
import { ServerContext } from '../types.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Arguments for the whats_next tool
|
|
14
|
+
*/
|
|
15
|
+
export interface WhatsNextArgs {
|
|
16
|
+
context?: string;
|
|
17
|
+
user_input?: string;
|
|
18
|
+
conversation_summary?: string;
|
|
19
|
+
recent_messages?: Array<{
|
|
20
|
+
role: 'user' | 'assistant';
|
|
21
|
+
content: string;
|
|
22
|
+
}>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Response from the whats_next tool
|
|
27
|
+
*/
|
|
28
|
+
export interface WhatsNextResult {
|
|
29
|
+
phase: string;
|
|
30
|
+
instructions: string;
|
|
31
|
+
plan_file_path: string;
|
|
32
|
+
is_modeled_transition: boolean;
|
|
33
|
+
conversation_id: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* WhatsNext tool handler implementation
|
|
38
|
+
*/
|
|
39
|
+
export class WhatsNextHandler extends ConversationRequiredToolHandler<
|
|
40
|
+
WhatsNextArgs,
|
|
41
|
+
WhatsNextResult
|
|
42
|
+
> {
|
|
43
|
+
protected async executeWithConversation(
|
|
44
|
+
args: WhatsNextArgs,
|
|
45
|
+
context: ServerContext,
|
|
46
|
+
conversationContext: ConversationContext
|
|
47
|
+
): Promise<WhatsNextResult> {
|
|
48
|
+
const {
|
|
49
|
+
context: requestContext = '',
|
|
50
|
+
user_input = '',
|
|
51
|
+
conversation_summary = '',
|
|
52
|
+
recent_messages = [],
|
|
53
|
+
} = args;
|
|
54
|
+
|
|
55
|
+
const conversationId = conversationContext.conversationId;
|
|
56
|
+
const currentPhase = conversationContext.currentPhase;
|
|
57
|
+
|
|
58
|
+
this.logger.debug('Processing whats_next request', {
|
|
59
|
+
conversationId,
|
|
60
|
+
currentPhase,
|
|
61
|
+
hasContext: !!requestContext,
|
|
62
|
+
hasUserInput: !!user_input,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Ensure state machine is loaded for this project
|
|
66
|
+
this.ensureStateMachineForProject(
|
|
67
|
+
context,
|
|
68
|
+
conversationContext.projectPath,
|
|
69
|
+
conversationContext.workflowName
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
// Ensure plan file exists
|
|
73
|
+
await context.planManager.ensurePlanFile(
|
|
74
|
+
conversationContext.planFilePath,
|
|
75
|
+
conversationContext.projectPath,
|
|
76
|
+
conversationContext.gitBranch
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
// Analyze phase transition
|
|
80
|
+
const transitionResult =
|
|
81
|
+
await context.transitionEngine.analyzePhaseTransition({
|
|
82
|
+
currentPhase,
|
|
83
|
+
projectPath: conversationContext.projectPath,
|
|
84
|
+
userInput: user_input,
|
|
85
|
+
context: requestContext,
|
|
86
|
+
conversationSummary: conversation_summary,
|
|
87
|
+
recentMessages: recent_messages,
|
|
88
|
+
conversationId: conversationContext.conversationId,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Update conversation state if phase changed
|
|
92
|
+
if (transitionResult.newPhase !== currentPhase) {
|
|
93
|
+
const shouldUpdateState = await this.shouldUpdateConversationState(
|
|
94
|
+
currentPhase,
|
|
95
|
+
transitionResult.newPhase,
|
|
96
|
+
conversationContext,
|
|
97
|
+
context
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
if (shouldUpdateState) {
|
|
101
|
+
await context.conversationManager.updateConversationState(
|
|
102
|
+
conversationId,
|
|
103
|
+
{ currentPhase: transitionResult.newPhase }
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// If this was a first-call auto-transition, regenerate the plan file
|
|
108
|
+
if (
|
|
109
|
+
transitionResult.transitionReason.includes(
|
|
110
|
+
'Starting development - defining criteria'
|
|
111
|
+
)
|
|
112
|
+
) {
|
|
113
|
+
this.logger.info(
|
|
114
|
+
'Regenerating plan file after first-call auto-transition',
|
|
115
|
+
{
|
|
116
|
+
from: currentPhase,
|
|
117
|
+
to: transitionResult.newPhase,
|
|
118
|
+
planFilePath: conversationContext.planFilePath,
|
|
119
|
+
}
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
await context.planManager.ensurePlanFile(
|
|
123
|
+
conversationContext.planFilePath,
|
|
124
|
+
conversationContext.projectPath,
|
|
125
|
+
conversationContext.gitBranch
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
this.logger.info('Phase transition completed', {
|
|
130
|
+
from: currentPhase,
|
|
131
|
+
to: transitionResult.newPhase,
|
|
132
|
+
reason: transitionResult.transitionReason,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Check if plan file exists
|
|
137
|
+
const planInfo = await context.planManager.getPlanFileInfo(
|
|
138
|
+
conversationContext.planFilePath
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
// Generate enhanced instructions
|
|
142
|
+
const instructions =
|
|
143
|
+
await context.instructionGenerator.generateInstructions(
|
|
144
|
+
transitionResult.instructions,
|
|
145
|
+
{
|
|
146
|
+
phase: transitionResult.newPhase,
|
|
147
|
+
conversationContext: {
|
|
148
|
+
...conversationContext,
|
|
149
|
+
currentPhase: transitionResult.newPhase,
|
|
150
|
+
},
|
|
151
|
+
transitionReason: transitionResult.transitionReason,
|
|
152
|
+
isModeled: transitionResult.isModeled,
|
|
153
|
+
planFileExists: planInfo.exists,
|
|
154
|
+
}
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
// Add commit instructions if configured
|
|
158
|
+
let finalInstructions = instructions.instructions;
|
|
159
|
+
if (
|
|
160
|
+
conversationContext.gitCommitConfig?.enabled &&
|
|
161
|
+
conversationContext.gitCommitConfig.commitOnStep
|
|
162
|
+
) {
|
|
163
|
+
const commitMessage = requestContext || 'Step completion';
|
|
164
|
+
finalInstructions += `\n\n**Git Commit Required**: Create a commit for this step using:\n\`\`\`bash\ngit add . && git commit -m "${commitMessage}"\n\`\`\``;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Prepare response
|
|
168
|
+
const response: WhatsNextResult = {
|
|
169
|
+
phase: transitionResult.newPhase,
|
|
170
|
+
instructions: finalInstructions,
|
|
171
|
+
plan_file_path: conversationContext.planFilePath,
|
|
172
|
+
is_modeled_transition: transitionResult.isModeled,
|
|
173
|
+
conversation_id: conversationContext.conversationId,
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
// Log interaction
|
|
177
|
+
await this.logInteraction(
|
|
178
|
+
context,
|
|
179
|
+
conversationId,
|
|
180
|
+
'whats_next',
|
|
181
|
+
args,
|
|
182
|
+
response,
|
|
183
|
+
transitionResult.newPhase
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
return response;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Determines whether conversation state should be updated for a phase transition
|
|
191
|
+
*/
|
|
192
|
+
private async shouldUpdateConversationState(
|
|
193
|
+
currentPhase: string,
|
|
194
|
+
newPhase: string,
|
|
195
|
+
conversationContext: ConversationContext,
|
|
196
|
+
context: ServerContext
|
|
197
|
+
): Promise<boolean> {
|
|
198
|
+
if (!conversationContext.requireReviewsBeforePhaseTransition) {
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const stateMachine = context.workflowManager.loadWorkflowForProject(
|
|
203
|
+
conversationContext.projectPath,
|
|
204
|
+
conversationContext.workflowName
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
const currentState = stateMachine.states[currentPhase];
|
|
208
|
+
if (!currentState) {
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const transition = currentState.transitions.find(t => t.to === newPhase);
|
|
213
|
+
if (!transition) {
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const hasReviewPerspectives =
|
|
218
|
+
transition.review_perspectives &&
|
|
219
|
+
transition.review_perspectives.length > 0;
|
|
220
|
+
|
|
221
|
+
if (hasReviewPerspectives) {
|
|
222
|
+
this.logger.debug(
|
|
223
|
+
'Preventing state update - review required for transition',
|
|
224
|
+
{
|
|
225
|
+
from: currentPhase,
|
|
226
|
+
to: newPhase,
|
|
227
|
+
reviewPerspectives: transition.review_perspectives?.length || 0,
|
|
228
|
+
}
|
|
229
|
+
);
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core types and interfaces for the refactored server architecture
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { ConversationManager } from '@codemcp/workflows-core';
|
|
6
|
+
import { TransitionEngine } from '@codemcp/workflows-core';
|
|
7
|
+
import { PlanManager } from '@codemcp/workflows-core';
|
|
8
|
+
import { InstructionGenerator } from '@codemcp/workflows-core';
|
|
9
|
+
import { WorkflowManager } from '@codemcp/workflows-core';
|
|
10
|
+
import { InteractionLogger } from '@codemcp/workflows-core';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Server context shared across all handlers
|
|
14
|
+
* Contains all the core dependencies needed by tool and resource handlers
|
|
15
|
+
*/
|
|
16
|
+
export interface ServerContext {
|
|
17
|
+
conversationManager: ConversationManager;
|
|
18
|
+
transitionEngine: TransitionEngine;
|
|
19
|
+
planManager: PlanManager;
|
|
20
|
+
instructionGenerator: InstructionGenerator;
|
|
21
|
+
workflowManager: WorkflowManager;
|
|
22
|
+
interactionLogger?: InteractionLogger;
|
|
23
|
+
projectPath: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Standard result format for all handler operations
|
|
28
|
+
* Separates business logic results from MCP protocol concerns
|
|
29
|
+
*/
|
|
30
|
+
export interface HandlerResult<T = unknown> {
|
|
31
|
+
success: boolean;
|
|
32
|
+
data?: T;
|
|
33
|
+
error?: string;
|
|
34
|
+
metadata?: Record<string, unknown>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Resource content structure
|
|
39
|
+
*/
|
|
40
|
+
export interface ResourceContent {
|
|
41
|
+
uri: string;
|
|
42
|
+
text: string;
|
|
43
|
+
mimeType: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* MCP Tool Response format (compatible with MCP SDK)
|
|
48
|
+
*/
|
|
49
|
+
export interface McpToolResponse {
|
|
50
|
+
[x: string]: unknown;
|
|
51
|
+
content: Array<{
|
|
52
|
+
type: 'text';
|
|
53
|
+
text: string;
|
|
54
|
+
}>;
|
|
55
|
+
isError?: boolean;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* MCP Resource Response format (compatible with MCP SDK)
|
|
60
|
+
*/
|
|
61
|
+
export interface McpResourceResponse {
|
|
62
|
+
[x: string]: unknown;
|
|
63
|
+
contents: Array<{
|
|
64
|
+
uri: string;
|
|
65
|
+
text: string;
|
|
66
|
+
mimeType: string;
|
|
67
|
+
}>;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Tool handler interface
|
|
72
|
+
* All tool handlers must implement this interface
|
|
73
|
+
*/
|
|
74
|
+
export interface ToolHandler<TArgs = unknown, TResult = unknown> {
|
|
75
|
+
handle(args: TArgs, context: ServerContext): Promise<HandlerResult<TResult>>;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Resource handler interface
|
|
80
|
+
* All resource handlers must implement this interface
|
|
81
|
+
*/
|
|
82
|
+
export interface ResourceHandler {
|
|
83
|
+
handle(
|
|
84
|
+
uri: URL,
|
|
85
|
+
context: ServerContext
|
|
86
|
+
): Promise<HandlerResult<ResourceContent>>;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Response renderer interface
|
|
91
|
+
* Handles translation between domain results and MCP protocol responses
|
|
92
|
+
*/
|
|
93
|
+
export interface ResponseRenderer {
|
|
94
|
+
renderToolResponse<T>(result: HandlerResult<T>): McpToolResponse;
|
|
95
|
+
renderResourceResponse(
|
|
96
|
+
result: HandlerResult<ResourceContent>
|
|
97
|
+
): McpResourceResponse;
|
|
98
|
+
renderError(error: Error | string): McpToolResponse;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Tool registry interface
|
|
103
|
+
* Manages registration and lookup of tool handlers
|
|
104
|
+
*/
|
|
105
|
+
export interface ToolRegistry {
|
|
106
|
+
register<T extends ToolHandler>(name: string, handler: T): void;
|
|
107
|
+
get(name: string): ToolHandler | undefined;
|
|
108
|
+
list(): string[];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Resource registry interface
|
|
113
|
+
* Manages registration and lookup of resource handlers
|
|
114
|
+
*/
|
|
115
|
+
export interface ResourceRegistry {
|
|
116
|
+
register(pattern: string, handler: ResourceHandler): void;
|
|
117
|
+
resolve(uri: string): ResourceHandler | undefined;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Server configuration options
|
|
122
|
+
*/
|
|
123
|
+
export interface ServerConfig {
|
|
124
|
+
/** Project path to operate on (defaults to process.cwd()) */
|
|
125
|
+
projectPath?: string;
|
|
126
|
+
/** Database path override */
|
|
127
|
+
databasePath?: string;
|
|
128
|
+
/** Enable interaction logging */
|
|
129
|
+
enableLogging?: boolean;
|
|
130
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
import { createTempProjectWithDefaultStateMachine } from '../utils/temp-files';
|
|
3
|
+
import {
|
|
4
|
+
DirectServerInterface,
|
|
5
|
+
createSuiteIsolatedE2EScenario,
|
|
6
|
+
assertToolSuccess,
|
|
7
|
+
initializeDevelopment,
|
|
8
|
+
} from '../utils/e2e-test-setup';
|
|
9
|
+
|
|
10
|
+
vi.unmock('fs');
|
|
11
|
+
vi.unmock('fs/promises');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Core Functionality Tests
|
|
15
|
+
*
|
|
16
|
+
* Tests the essential server operations including:
|
|
17
|
+
* - Server initialization and basic tool operations
|
|
18
|
+
* - Resource access (plan and state resources)
|
|
19
|
+
* - Basic conversation management
|
|
20
|
+
* - Error handling and graceful failures
|
|
21
|
+
*/
|
|
22
|
+
describe('Core Functionality', () => {
|
|
23
|
+
let client: DirectServerInterface;
|
|
24
|
+
let cleanup: () => Promise<void>;
|
|
25
|
+
|
|
26
|
+
beforeEach(async () => {
|
|
27
|
+
const scenario = await createSuiteIsolatedE2EScenario({
|
|
28
|
+
suiteName: 'core-functionality',
|
|
29
|
+
tempProjectFactory: createTempProjectWithDefaultStateMachine,
|
|
30
|
+
});
|
|
31
|
+
client = scenario.client;
|
|
32
|
+
cleanup = scenario.cleanup;
|
|
33
|
+
|
|
34
|
+
// Initialize development with default workflow before each test
|
|
35
|
+
await initializeDevelopment(client);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
afterEach(async () => {
|
|
39
|
+
if (cleanup) {
|
|
40
|
+
await cleanup();
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe('Server Initialization', () => {
|
|
45
|
+
it('should initialize server and provide tools', async () => {
|
|
46
|
+
const tools = await client.listTools();
|
|
47
|
+
expect(tools.tools).toBeTruthy();
|
|
48
|
+
expect(tools.tools).toHaveLength(2);
|
|
49
|
+
expect(tools.tools.map(t => t.name)).toContain('whats_next');
|
|
50
|
+
expect(tools.tools.map(t => t.name)).toContain('proceed_to_phase');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should provide resources', async () => {
|
|
54
|
+
const resources = await client.listResources();
|
|
55
|
+
expect(resources.resources).toBeTruthy();
|
|
56
|
+
expect(resources.resources).toHaveLength(3);
|
|
57
|
+
expect(resources.resources.map(r => r.uri)).toContain('plan://current');
|
|
58
|
+
expect(resources.resources.map(r => r.uri)).toContain('state://current');
|
|
59
|
+
expect(resources.resources.map(r => r.uri)).toContain('system-prompt://');
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe('Basic Tool Operations', () => {
|
|
64
|
+
it('should handle whats_next tool calls', async () => {
|
|
65
|
+
const result = await client.callTool('whats_next', {
|
|
66
|
+
user_input: 'implement authentication',
|
|
67
|
+
});
|
|
68
|
+
const response = assertToolSuccess(result);
|
|
69
|
+
|
|
70
|
+
expect(response.phase).toBeTruthy();
|
|
71
|
+
expect(response.instructions).toBeTruthy();
|
|
72
|
+
expect(response.conversation_id).toBeTruthy();
|
|
73
|
+
expect(response.plan_file_path).toBeTruthy();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should handle proceed_to_phase tool calls', async () => {
|
|
77
|
+
// First establish a conversation
|
|
78
|
+
await client.callTool('whats_next', { user_input: 'start project' });
|
|
79
|
+
|
|
80
|
+
const result = await client.callTool('proceed_to_phase', {
|
|
81
|
+
target_phase: 'design',
|
|
82
|
+
reason: 'requirements complete',
|
|
83
|
+
review_state: 'not-required',
|
|
84
|
+
});
|
|
85
|
+
const response = assertToolSuccess(result);
|
|
86
|
+
|
|
87
|
+
expect(response.phase).toBe('design');
|
|
88
|
+
expect(response.instructions).toBeTruthy();
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe('Resource Access', () => {
|
|
93
|
+
it('should provide plan resource as markdown', async () => {
|
|
94
|
+
// Initialize conversation to create plan file
|
|
95
|
+
await client.callTool('whats_next', { user_input: 'test project' });
|
|
96
|
+
|
|
97
|
+
const planResource = await client.readResource('plan://current');
|
|
98
|
+
expect(planResource.contents).toHaveLength(1);
|
|
99
|
+
expect(planResource.contents[0].mimeType).toBe('text/markdown');
|
|
100
|
+
expect(planResource.contents[0].text).toContain('# Development Plan');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should provide state resource as JSON', async () => {
|
|
104
|
+
// Initialize conversation
|
|
105
|
+
await client.callTool('whats_next', { user_input: 'test project' });
|
|
106
|
+
|
|
107
|
+
const stateResource = await client.readResource('state://current');
|
|
108
|
+
expect(stateResource.contents).toHaveLength(1);
|
|
109
|
+
expect(stateResource.contents[0].mimeType).toBe('application/json');
|
|
110
|
+
|
|
111
|
+
const stateData = JSON.parse(stateResource.contents[0].text);
|
|
112
|
+
expect(stateData.conversationId).toBeTruthy();
|
|
113
|
+
expect(stateData.currentPhase).toBeTruthy();
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe('Error Handling', () => {
|
|
118
|
+
it('should handle invalid tool parameters gracefully', async () => {
|
|
119
|
+
const result = await client.callTool('proceed_to_phase', {
|
|
120
|
+
target_phase: 'invalid_phase',
|
|
121
|
+
reason: 'test',
|
|
122
|
+
review_state: 'not-required',
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Should not throw, but may return error or fallback behavior
|
|
126
|
+
expect(result).toBeTruthy();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should handle missing parameters gracefully', async () => {
|
|
130
|
+
const result = await client.callTool('whats_next', {});
|
|
131
|
+
const response = assertToolSuccess(result);
|
|
132
|
+
|
|
133
|
+
// Should still work with empty parameters
|
|
134
|
+
expect(response.phase).toBeTruthy();
|
|
135
|
+
expect(response.instructions).toBeTruthy();
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('should handle database errors gracefully', async () => {
|
|
139
|
+
// This test would require mocking database failures
|
|
140
|
+
// For now, verify basic resilience
|
|
141
|
+
const result = await client.callTool('whats_next', {
|
|
142
|
+
user_input: 'test resilience',
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
expect(assertToolSuccess(result)).toBeTruthy();
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
describe('Basic Conversation Management', () => {
|
|
150
|
+
it('should create new conversations', async () => {
|
|
151
|
+
const result = await client.callTool('whats_next', {
|
|
152
|
+
user_input: 'new feature request',
|
|
153
|
+
});
|
|
154
|
+
const response = assertToolSuccess(result);
|
|
155
|
+
|
|
156
|
+
expect(response.conversation_id).toBeTruthy();
|
|
157
|
+
expect(response.conversation_id).toMatch(/^default-sm-/);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('should maintain conversation state across calls', async () => {
|
|
161
|
+
const first = await client.callTool('whats_next', {
|
|
162
|
+
user_input: 'start project',
|
|
163
|
+
});
|
|
164
|
+
const firstResponse = assertToolSuccess(first);
|
|
165
|
+
|
|
166
|
+
const second = await client.callTool('whats_next', {
|
|
167
|
+
user_input: 'continue project',
|
|
168
|
+
});
|
|
169
|
+
const secondResponse = assertToolSuccess(second);
|
|
170
|
+
|
|
171
|
+
expect(firstResponse.conversation_id).toBe(
|
|
172
|
+
secondResponse.conversation_id
|
|
173
|
+
);
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
});
|