@codemcp/workflows 3.1.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/.vibe/conversation-state.sqlite +0 -0
  3. package/LICENSE +674 -0
  4. package/dist/index.d.ts +9 -0
  5. package/dist/index.d.ts.map +1 -0
  6. package/dist/index.js +74 -0
  7. package/dist/index.js.map +1 -0
  8. package/dist/notification-service.d.ts +14 -0
  9. package/dist/notification-service.d.ts.map +1 -0
  10. package/dist/notification-service.js +18 -0
  11. package/dist/notification-service.js.map +1 -0
  12. package/dist/resource-handlers/conversation-state.d.ts +15 -0
  13. package/dist/resource-handlers/conversation-state.d.ts.map +1 -0
  14. package/dist/resource-handlers/conversation-state.js +40 -0
  15. package/dist/resource-handlers/conversation-state.js.map +1 -0
  16. package/dist/resource-handlers/development-plan.d.ts +14 -0
  17. package/dist/resource-handlers/development-plan.d.ts.map +1 -0
  18. package/dist/resource-handlers/development-plan.js +31 -0
  19. package/dist/resource-handlers/development-plan.js.map +1 -0
  20. package/dist/resource-handlers/index.d.ts +24 -0
  21. package/dist/resource-handlers/index.d.ts.map +1 -0
  22. package/dist/resource-handlers/index.js +62 -0
  23. package/dist/resource-handlers/index.js.map +1 -0
  24. package/dist/resource-handlers/system-prompt.d.ts +15 -0
  25. package/dist/resource-handlers/system-prompt.d.ts.map +1 -0
  26. package/dist/resource-handlers/system-prompt.js +40 -0
  27. package/dist/resource-handlers/system-prompt.js.map +1 -0
  28. package/dist/resource-handlers/workflow-resource.d.ts +15 -0
  29. package/dist/resource-handlers/workflow-resource.d.ts.map +1 -0
  30. package/dist/resource-handlers/workflow-resource.js +85 -0
  31. package/dist/resource-handlers/workflow-resource.js.map +1 -0
  32. package/dist/response-renderer.d.ts +30 -0
  33. package/dist/response-renderer.d.ts.map +1 -0
  34. package/dist/response-renderer.js +94 -0
  35. package/dist/response-renderer.js.map +1 -0
  36. package/dist/server-config.d.ts +34 -0
  37. package/dist/server-config.d.ts.map +1 -0
  38. package/dist/server-config.js +486 -0
  39. package/dist/server-config.js.map +1 -0
  40. package/dist/server-helpers.d.ts +62 -0
  41. package/dist/server-helpers.d.ts.map +1 -0
  42. package/dist/server-helpers.js +156 -0
  43. package/dist/server-helpers.js.map +1 -0
  44. package/dist/server-implementation.d.ts +74 -0
  45. package/dist/server-implementation.d.ts.map +1 -0
  46. package/dist/server-implementation.js +201 -0
  47. package/dist/server-implementation.js.map +1 -0
  48. package/dist/server.d.ts +6 -0
  49. package/dist/server.d.ts.map +1 -0
  50. package/dist/server.js +5 -0
  51. package/dist/server.js.map +1 -0
  52. package/dist/tool-handlers/base-tool-handler.d.ts +50 -0
  53. package/dist/tool-handlers/base-tool-handler.d.ts.map +1 -0
  54. package/dist/tool-handlers/base-tool-handler.js +74 -0
  55. package/dist/tool-handlers/base-tool-handler.js.map +1 -0
  56. package/dist/tool-handlers/conduct-review.d.ts +49 -0
  57. package/dist/tool-handlers/conduct-review.d.ts.map +1 -0
  58. package/dist/tool-handlers/conduct-review.js +105 -0
  59. package/dist/tool-handlers/conduct-review.js.map +1 -0
  60. package/dist/tool-handlers/get-tool-info.d.ts +76 -0
  61. package/dist/tool-handlers/get-tool-info.d.ts.map +1 -0
  62. package/dist/tool-handlers/get-tool-info.js +168 -0
  63. package/dist/tool-handlers/get-tool-info.js.map +1 -0
  64. package/dist/tool-handlers/index.d.ts +42 -0
  65. package/dist/tool-handlers/index.d.ts.map +1 -0
  66. package/dist/tool-handlers/index.js +74 -0
  67. package/dist/tool-handlers/index.js.map +1 -0
  68. package/dist/tool-handlers/install-workflow.d.ts +48 -0
  69. package/dist/tool-handlers/install-workflow.d.ts.map +1 -0
  70. package/dist/tool-handlers/install-workflow.js +131 -0
  71. package/dist/tool-handlers/install-workflow.js.map +1 -0
  72. package/dist/tool-handlers/list-workflows.d.ts +47 -0
  73. package/dist/tool-handlers/list-workflows.d.ts.map +1 -0
  74. package/dist/tool-handlers/list-workflows.js +58 -0
  75. package/dist/tool-handlers/list-workflows.js.map +1 -0
  76. package/dist/tool-handlers/no-idea.d.ts +41 -0
  77. package/dist/tool-handlers/no-idea.d.ts.map +1 -0
  78. package/dist/tool-handlers/no-idea.js +29 -0
  79. package/dist/tool-handlers/no-idea.js.map +1 -0
  80. package/dist/tool-handlers/proceed-to-phase.d.ts +39 -0
  81. package/dist/tool-handlers/proceed-to-phase.d.ts.map +1 -0
  82. package/dist/tool-handlers/proceed-to-phase.js +109 -0
  83. package/dist/tool-handlers/proceed-to-phase.js.map +1 -0
  84. package/dist/tool-handlers/reset-development.d.ts +31 -0
  85. package/dist/tool-handlers/reset-development.d.ts.map +1 -0
  86. package/dist/tool-handlers/reset-development.js +48 -0
  87. package/dist/tool-handlers/reset-development.js.map +1 -0
  88. package/dist/tool-handlers/resume-workflow.d.ts +88 -0
  89. package/dist/tool-handlers/resume-workflow.d.ts.map +1 -0
  90. package/dist/tool-handlers/resume-workflow.js +213 -0
  91. package/dist/tool-handlers/resume-workflow.js.map +1 -0
  92. package/dist/tool-handlers/setup-project-docs.d.ts +36 -0
  93. package/dist/tool-handlers/setup-project-docs.d.ts.map +1 -0
  94. package/dist/tool-handlers/setup-project-docs.js +136 -0
  95. package/dist/tool-handlers/setup-project-docs.js.map +1 -0
  96. package/dist/tool-handlers/start-development.d.ts +82 -0
  97. package/dist/tool-handlers/start-development.d.ts.map +1 -0
  98. package/dist/tool-handlers/start-development.js +448 -0
  99. package/dist/tool-handlers/start-development.js.map +1 -0
  100. package/dist/tool-handlers/whats-next.d.ts +42 -0
  101. package/dist/tool-handlers/whats-next.d.ts.map +1 -0
  102. package/dist/tool-handlers/whats-next.js +118 -0
  103. package/dist/tool-handlers/whats-next.js.map +1 -0
  104. package/dist/types.d.ts +114 -0
  105. package/dist/types.d.ts.map +1 -0
  106. package/dist/types.js +5 -0
  107. package/dist/types.js.map +1 -0
  108. package/package.json +29 -0
  109. package/src/index.ts +93 -0
  110. package/src/notification-service.ts +23 -0
  111. package/src/resource-handlers/conversation-state.ts +55 -0
  112. package/src/resource-handlers/development-plan.ts +48 -0
  113. package/src/resource-handlers/index.ts +73 -0
  114. package/src/resource-handlers/system-prompt.ts +55 -0
  115. package/src/resource-handlers/workflow-resource.ts +126 -0
  116. package/src/response-renderer.ts +116 -0
  117. package/src/server-config.ts +744 -0
  118. package/src/server-helpers.ts +225 -0
  119. package/src/server-implementation.ts +277 -0
  120. package/src/server.ts +9 -0
  121. package/src/tool-handlers/base-tool-handler.ts +141 -0
  122. package/src/tool-handlers/conduct-review.ts +191 -0
  123. package/src/tool-handlers/get-tool-info.ts +274 -0
  124. package/src/tool-handlers/index.ts +117 -0
  125. package/src/tool-handlers/install-workflow.ts +185 -0
  126. package/src/tool-handlers/list-workflows.ts +94 -0
  127. package/src/tool-handlers/no-idea.ts +47 -0
  128. package/src/tool-handlers/proceed-to-phase.ts +205 -0
  129. package/src/tool-handlers/reset-development.ts +90 -0
  130. package/src/tool-handlers/resume-workflow.ts +380 -0
  131. package/src/tool-handlers/setup-project-docs.ts +226 -0
  132. package/src/tool-handlers/start-development.ts +685 -0
  133. package/src/tool-handlers/whats-next.ts +235 -0
  134. package/src/types.ts +130 -0
  135. package/test/e2e/core-functionality.test.ts +176 -0
  136. package/test/e2e/mcp-contract.test.ts +540 -0
  137. package/test/e2e/plan-management.test.ts +331 -0
  138. package/test/e2e/state-management.test.ts +392 -0
  139. package/test/e2e/workflow-integration.test.ts +506 -0
  140. package/test/unit/commit-behaviour-interface.test.ts +244 -0
  141. package/test/unit/conduct-review.test.ts +151 -0
  142. package/test/unit/reset-functionality.test.ts +72 -0
  143. package/test/unit/resume-workflow.test.ts +192 -0
  144. package/test/unit/server-tools.test.ts +311 -0
  145. package/test/unit/setup-project-docs-handler.test.ts +267 -0
  146. package/test/unit/start-development-artifact-detection.test.ts +387 -0
  147. package/test/unit/start-development-gitignore.test.ts +178 -0
  148. package/test/unit/system-prompt-resource.test.ts +101 -0
  149. package/test/unit/tool-handlers/no-idea.test.ts +40 -0
  150. package/test/utils/e2e-test-setup.ts +453 -0
  151. package/test/utils/run-server-in-dir.sh +27 -0
  152. package/test/utils/temp-files.ts +308 -0
  153. package/test/utils/test-access.ts +79 -0
  154. package/test/utils/test-helpers.ts +286 -0
  155. package/test/utils/test-setup.ts +78 -0
  156. package/tsconfig.build.json +9 -0
  157. package/tsconfig.build.tsbuildinfo +1 -0
  158. package/tsconfig.json +12 -0
  159. package/vitest.config.ts +17 -0
@@ -0,0 +1,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
+ });