@codemcp/workflows 4.7.0 → 4.9.0

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 (61) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/components/beads/beads-instruction-generator.d.ts +48 -0
  3. package/dist/components/beads/beads-instruction-generator.d.ts.map +1 -0
  4. package/dist/components/beads/beads-instruction-generator.js +182 -0
  5. package/dist/components/beads/beads-instruction-generator.js.map +1 -0
  6. package/dist/components/beads/beads-plan-manager.d.ts +66 -0
  7. package/dist/components/beads/beads-plan-manager.d.ts.map +1 -0
  8. package/dist/components/beads/beads-plan-manager.js +288 -0
  9. package/dist/components/beads/beads-plan-manager.js.map +1 -0
  10. package/dist/components/beads/beads-task-backend-client.d.ts +43 -0
  11. package/dist/components/beads/beads-task-backend-client.d.ts.map +1 -0
  12. package/dist/components/beads/beads-task-backend-client.js +178 -0
  13. package/dist/components/beads/beads-task-backend-client.js.map +1 -0
  14. package/dist/components/server-components-factory.d.ts +39 -0
  15. package/dist/components/server-components-factory.d.ts.map +1 -0
  16. package/dist/components/server-components-factory.js +62 -0
  17. package/dist/components/server-components-factory.js.map +1 -0
  18. package/dist/server-config.d.ts.map +1 -1
  19. package/dist/server-config.js +8 -4
  20. package/dist/server-config.js.map +1 -1
  21. package/dist/server-implementation.d.ts +1 -1
  22. package/dist/tool-handlers/get-tool-info.d.ts.map +1 -1
  23. package/dist/tool-handlers/get-tool-info.js +2 -1
  24. package/dist/tool-handlers/get-tool-info.js.map +1 -1
  25. package/dist/tool-handlers/proceed-to-phase.d.ts +5 -0
  26. package/dist/tool-handlers/proceed-to-phase.d.ts.map +1 -1
  27. package/dist/tool-handlers/proceed-to-phase.js +95 -0
  28. package/dist/tool-handlers/proceed-to-phase.js.map +1 -1
  29. package/dist/tool-handlers/start-development.d.ts.map +1 -1
  30. package/dist/tool-handlers/start-development.js +7 -3
  31. package/dist/tool-handlers/start-development.js.map +1 -1
  32. package/dist/tool-handlers/whats-next.d.ts +0 -12
  33. package/dist/tool-handlers/whats-next.d.ts.map +1 -1
  34. package/dist/tool-handlers/whats-next.js +1 -88
  35. package/dist/tool-handlers/whats-next.js.map +1 -1
  36. package/dist/types.d.ts +7 -4
  37. package/dist/types.d.ts.map +1 -1
  38. package/dist/version-info.d.ts +30 -0
  39. package/dist/version-info.d.ts.map +1 -0
  40. package/dist/version-info.js +178 -0
  41. package/dist/version-info.js.map +1 -0
  42. package/package.json +2 -2
  43. package/src/components/beads/beads-instruction-generator.ts +261 -0
  44. package/src/components/beads/beads-plan-manager.ts +358 -0
  45. package/src/components/beads/beads-task-backend-client.ts +232 -0
  46. package/src/components/server-components-factory.ts +86 -0
  47. package/src/server-config.ts +9 -4
  48. package/src/tool-handlers/get-tool-info.ts +2 -1
  49. package/src/tool-handlers/proceed-to-phase.ts +140 -0
  50. package/src/tool-handlers/start-development.ts +14 -3
  51. package/src/tool-handlers/whats-next.ts +4 -117
  52. package/src/types.ts +7 -4
  53. package/src/version-info.ts +213 -0
  54. package/test/e2e/component-substitution.test.ts +208 -0
  55. package/test/unit/beads-instruction-generator.test.ts +847 -0
  56. package/test/unit/beads-phase-task-id-integration.test.ts +557 -0
  57. package/test/unit/server-components-factory.test.ts +279 -0
  58. package/test/unit/setup-project-docs-handler.test.ts +3 -2
  59. package/test/utils/e2e-test-setup.ts +0 -1
  60. package/test/utils/temp-files.ts +12 -0
  61. package/tsconfig.build.tsbuildinfo +1 -1
@@ -7,7 +7,7 @@
7
7
 
8
8
  import { ConversationRequiredToolHandler } from './base-tool-handler.js';
9
9
  import type { ConversationContext } from '@codemcp/workflows-core';
10
- import { TaskBackendManager, BeadsIntegration } from '@codemcp/workflows-core';
10
+ // TaskBackendManager and BeadsIntegration functionality now handled by injected components
11
11
  import { ServerContext } from '../types.js';
12
12
 
13
13
  /**
@@ -181,14 +181,7 @@ export class WhatsNextHandler extends ConversationRequiredToolHandler<
181
181
  finalInstructions += `\n\n**Git Commit Required**: Create a commit for this step using:\n\`\`\`bash\ngit add . && git commit -m "${commitMessage}"\n\`\`\``;
182
182
  }
183
183
 
184
- // Add beads instructions if beads backend is active
185
- const beadsInstructions = await this.generateBeadsInstructions(
186
- conversationContext,
187
- transitionResult.newPhase
188
- );
189
- if (beadsInstructions) {
190
- finalInstructions += '\n\n' + beadsInstructions;
191
- }
184
+ // Note: Beads-specific instructions are now handled by BeadsInstructionGenerator via strategy pattern
192
185
 
193
186
  // Prepare response
194
187
  const response: WhatsNextResult = {
@@ -259,113 +252,7 @@ export class WhatsNextHandler extends ConversationRequiredToolHandler<
259
252
  return true;
260
253
  }
261
254
 
262
- /**
263
- * Generate beads-specific instructions if beads backend is active
264
- */
265
- private async generateBeadsInstructions(
266
- conversationContext: ConversationContext,
267
- currentPhase: string
268
- ): Promise<string | null> {
269
- // Check if beads backend is configured
270
- const taskBackendConfig = TaskBackendManager.detectTaskBackend();
271
- if (
272
- taskBackendConfig.backend !== 'beads' ||
273
- !taskBackendConfig.isAvailable
274
- ) {
275
- return null;
276
- }
277
-
278
- try {
279
- // Read plan file to extract current phase task ID
280
- const phaseTaskId = await this.extractPhaseTaskId(
281
- conversationContext.planFilePath,
282
- currentPhase
283
- );
284
- if (!phaseTaskId) {
285
- this.logger.warn(
286
- 'Could not find beads phase task ID for current phase',
287
- {
288
- phase: currentPhase,
289
- planFilePath: conversationContext.planFilePath,
290
- }
291
- );
292
- return null;
293
- }
294
-
295
- // Generate beads instructions using BeadsIntegration utility
296
- const beadsIntegration = new BeadsIntegration(
297
- conversationContext.projectPath
298
- );
299
- const phaseName = this.capitalizePhase(currentPhase);
300
- return beadsIntegration.generateBeadsInstructions(phaseTaskId, phaseName);
301
- } catch (error) {
302
- this.logger.warn('Failed to generate beads instructions', {
303
- phase: currentPhase,
304
- projectPath: conversationContext.projectPath,
305
- error: error instanceof Error ? error.message : String(error),
306
- });
307
- return null;
308
- }
309
- }
310
-
311
- /**
312
- * Extract beads phase task ID from plan file for the given phase
313
- */
314
- private async extractPhaseTaskId(
315
- planFilePath: string,
316
- phase: string
317
- ): Promise<string | null> {
318
- try {
319
- const { readFile } = await import('node:fs/promises');
320
- const content = await readFile(planFilePath, 'utf-8');
321
-
322
- const phaseName = this.capitalizePhase(phase);
323
- const phaseHeader = `## ${phaseName}`;
324
-
325
- // Look for the phase header followed by beads-phase-id comment
326
- const phaseSection = content.split('\n');
327
- let foundPhaseHeader = false;
255
+ // Beads-specific instruction logic has been moved to BeadsInstructionGenerator strategy
328
256
 
329
- for (const line of phaseSection) {
330
- if (line.trim() === phaseHeader) {
331
- foundPhaseHeader = true;
332
- continue;
333
- }
334
-
335
- if (foundPhaseHeader && line.includes('beads-phase-id:')) {
336
- const match = line.match(/beads-phase-id:\s*([\w\d-]+)/);
337
- if (match) {
338
- return match[1] || null;
339
- }
340
- }
341
-
342
- // Stop looking if we hit the next phase header
343
- if (foundPhaseHeader && line.startsWith('##') && line !== phaseHeader) {
344
- break;
345
- }
346
- }
347
-
348
- return null;
349
- } catch (error) {
350
- this.logger.warn(
351
- 'Failed to read plan file for phase task ID extraction',
352
- {
353
- planFilePath,
354
- phase,
355
- error: error instanceof Error ? error.message : String(error),
356
- }
357
- );
358
- return null;
359
- }
360
- }
361
-
362
- /**
363
- * Capitalize phase name for display
364
- */
365
- private capitalizePhase(phase: string): string {
366
- return phase
367
- .split('_')
368
- .map(word => word.charAt(0).toUpperCase() + word.slice(1))
369
- .join(' ');
370
- }
257
+ // Utility methods moved to strategy implementations where needed
371
258
  }
package/src/types.ts CHANGED
@@ -4,10 +4,11 @@
4
4
 
5
5
  import { ConversationManager } from '@codemcp/workflows-core';
6
6
  import { TransitionEngine } from '@codemcp/workflows-core';
7
- import { PlanManager } from '@codemcp/workflows-core';
8
- import { InstructionGenerator } from '@codemcp/workflows-core';
7
+ import { IPlanManager } from '@codemcp/workflows-core';
8
+ import { IInstructionGenerator } from '@codemcp/workflows-core';
9
9
  import { WorkflowManager } from '@codemcp/workflows-core';
10
10
  import { InteractionLogger } from '@codemcp/workflows-core';
11
+ import type { TaskBackendConfig } from '@codemcp/workflows-core';
11
12
 
12
13
  /**
13
14
  * Server context shared across all handlers
@@ -16,8 +17,8 @@ import { InteractionLogger } from '@codemcp/workflows-core';
16
17
  export interface ServerContext {
17
18
  conversationManager: ConversationManager;
18
19
  transitionEngine: TransitionEngine;
19
- planManager: PlanManager;
20
- instructionGenerator: InstructionGenerator;
20
+ planManager: IPlanManager;
21
+ instructionGenerator: IInstructionGenerator;
21
22
  workflowManager: WorkflowManager;
22
23
  interactionLogger?: InteractionLogger;
23
24
  projectPath: string;
@@ -127,4 +128,6 @@ export interface ServerConfig {
127
128
  databasePath?: string;
128
129
  /** Enable interaction logging */
129
130
  enableLogging?: boolean;
131
+ /** Task backend configuration override (for testing) */
132
+ taskBackend?: TaskBackendConfig;
130
133
  }
@@ -0,0 +1,213 @@
1
+ /**
2
+ * Version Information Utility
3
+ *
4
+ * Provides version information for the MCP server, supporting both
5
+ * build-time injection and runtime determination for local development.
6
+ */
7
+
8
+ import { execSync } from 'node:child_process';
9
+ import { readFileSync } from 'node:fs';
10
+ import { join, dirname } from 'node:path';
11
+ import { fileURLToPath } from 'node:url';
12
+ import { createLogger } from '@codemcp/workflows-core';
13
+
14
+ const logger = createLogger('VersionInfo');
15
+
16
+ /**
17
+ * Structure for version information
18
+ */
19
+ export interface VersionInfo {
20
+ /** The semantic version (e.g., "4.8.0") */
21
+ version: string;
22
+ /** Git commit hash (short form, e.g., "bbb06ba") */
23
+ commit?: string;
24
+ /** Whether working directory has uncommitted changes */
25
+ isDirty?: boolean;
26
+ /** Full git describe output (e.g., "v4.8.0-1-gbbb06ba-dirty") */
27
+ gitDescribe?: string;
28
+ /** Source of version information */
29
+ source: 'package.json' | 'git' | 'build-time' | 'fallback';
30
+ }
31
+
32
+ /**
33
+ * Build-time version information (injected during build process)
34
+ * This will be replaced by the build system with actual values
35
+ */
36
+ // @ts-ignore - This is replaced at build time
37
+ const BUILD_TIME_VERSION: VersionInfo | null = null;
38
+
39
+ /**
40
+ * Gets version information from package.json
41
+ */
42
+ function getVersionFromPackageJson(): VersionInfo | null {
43
+ try {
44
+ // Get the directory of this module
45
+ const currentDir = dirname(fileURLToPath(import.meta.url));
46
+
47
+ // Try to find package.json - first in the mcp-server package, then root
48
+ const packageJsonPaths = [
49
+ join(currentDir, '..', 'package.json'),
50
+ join(currentDir, '..', '..', '..', 'package.json'),
51
+ ];
52
+
53
+ for (const packageJsonPath of packageJsonPaths) {
54
+ try {
55
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
56
+ if (packageJson.version) {
57
+ logger.debug('Found version in package.json', {
58
+ path: packageJsonPath,
59
+ version: packageJson.version,
60
+ });
61
+
62
+ return {
63
+ version: packageJson.version,
64
+ source: 'package.json',
65
+ };
66
+ }
67
+ } catch (error) {
68
+ logger.debug('Could not read package.json', {
69
+ path: packageJsonPath,
70
+ error: error instanceof Error ? error.message : String(error),
71
+ });
72
+ }
73
+ }
74
+
75
+ return null;
76
+ } catch (error) {
77
+ logger.debug('Error getting version from package.json', { error });
78
+ return null;
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Gets version information from git
84
+ */
85
+ function getVersionFromGit(): VersionInfo | null {
86
+ try {
87
+ // Get git describe output
88
+ const gitDescribe = execSync('git describe --tags --always --dirty', {
89
+ encoding: 'utf-8',
90
+ stdio: 'pipe',
91
+ }).trim();
92
+
93
+ logger.debug('Git describe output', { gitDescribe });
94
+
95
+ // Parse git describe output (e.g., "v4.8.0-1-gbbb06ba-dirty")
96
+ const isDirty = gitDescribe.endsWith('-dirty');
97
+ const cleanDescribe = isDirty ? gitDescribe.slice(0, -6) : gitDescribe;
98
+
99
+ // Try to extract version and commit
100
+ const parts = cleanDescribe.split('-');
101
+ let version: string;
102
+ let commit: string | undefined;
103
+
104
+ if (parts.length >= 3 && parts[0]?.startsWith('v')) {
105
+ // Format: v4.8.0-1-gbbb06ba
106
+ version = parts[0].slice(1); // Remove 'v' prefix
107
+ commit = parts[2]?.startsWith('g') ? parts[2].slice(1) : parts[2];
108
+ } else if (parts.length === 1 && parts[0]?.startsWith('v')) {
109
+ // Format: v4.8.0 (exact tag)
110
+ version = parts[0].slice(1);
111
+ } else if (parts.length === 1) {
112
+ // Format: commit hash only
113
+ version = 'unknown';
114
+ commit = parts[0];
115
+ } else {
116
+ // Fallback
117
+ version = 'unknown';
118
+ }
119
+
120
+ return {
121
+ version,
122
+ commit,
123
+ isDirty,
124
+ gitDescribe,
125
+ source: 'git',
126
+ };
127
+ } catch (error) {
128
+ logger.debug('Error getting version from git', {
129
+ error: error instanceof Error ? error.message : String(error),
130
+ });
131
+ return null;
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Gets comprehensive version information, trying multiple sources
137
+ */
138
+ export function getVersionInfo(): VersionInfo {
139
+ logger.debug('Determining version information');
140
+
141
+ // Try build-time version first (will be null in development)
142
+ if (BUILD_TIME_VERSION) {
143
+ logger.info('Using build-time version information', {
144
+ version: BUILD_TIME_VERSION.version,
145
+ source: BUILD_TIME_VERSION.source,
146
+ });
147
+ return BUILD_TIME_VERSION;
148
+ }
149
+
150
+ // Try git version info (best for development)
151
+ const gitVersion = getVersionFromGit();
152
+ if (gitVersion) {
153
+ // If we have git info, try to enhance with package.json version
154
+ const packageVersion = getVersionFromPackageJson();
155
+ if (packageVersion && gitVersion.version === 'unknown') {
156
+ logger.info('Using package.json version with git commit info', {
157
+ version: packageVersion.version,
158
+ commit: gitVersion.commit || 'unknown',
159
+ isDirty: String(gitVersion.isDirty || false),
160
+ });
161
+
162
+ return {
163
+ ...gitVersion,
164
+ version: packageVersion.version,
165
+ source: 'git' as const,
166
+ };
167
+ }
168
+
169
+ logger.info('Using git version information', {
170
+ version: gitVersion.version,
171
+ source: gitVersion.source,
172
+ commit: gitVersion.commit || 'none',
173
+ isDirty: String(gitVersion.isDirty || false),
174
+ });
175
+ return gitVersion;
176
+ }
177
+
178
+ // Fallback to package.json only
179
+ const packageVersion = getVersionFromPackageJson();
180
+ if (packageVersion) {
181
+ logger.info('Using package.json version information', {
182
+ version: packageVersion.version,
183
+ source: packageVersion.source,
184
+ });
185
+ return packageVersion;
186
+ }
187
+
188
+ // Final fallback
189
+ logger.warn('Could not determine version information, using fallback');
190
+ return {
191
+ version: 'unknown',
192
+ source: 'fallback',
193
+ };
194
+ }
195
+
196
+ /**
197
+ * Gets a formatted version string suitable for display
198
+ */
199
+ export function getFormattedVersion(): string {
200
+ const versionInfo = getVersionInfo();
201
+
202
+ let formatted = versionInfo.version;
203
+
204
+ if (versionInfo.commit) {
205
+ formatted += `+${versionInfo.commit}`;
206
+ }
207
+
208
+ if (versionInfo.isDirty) {
209
+ formatted += '.dirty';
210
+ }
211
+
212
+ return formatted;
213
+ }
@@ -0,0 +1,208 @@
1
+ /**
2
+ * Component Substitution E2E Tests
3
+ *
4
+ * Tests that the strategy pattern component substitution works correctly
5
+ * in different task backend configurations.
6
+ */
7
+
8
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
9
+ import { createTempProjectWithDefaultStateMachine } from '../utils/temp-files';
10
+ import {
11
+ DirectServerInterface,
12
+ createSuiteIsolatedE2EScenario,
13
+ assertToolSuccess,
14
+ initializeDevelopment,
15
+ } from '../utils/e2e-test-setup';
16
+
17
+ vi.unmock('fs');
18
+ vi.unmock('fs/promises');
19
+
20
+ describe('Component Substitution', () => {
21
+ let client: DirectServerInterface;
22
+ let cleanup: () => Promise<void>;
23
+
24
+ describe('Markdown Backend Strategy', () => {
25
+ beforeEach(async () => {
26
+ // Ensure markdown backend is detected
27
+ process.env.TASK_BACKEND = 'markdown';
28
+
29
+ const scenario = await createSuiteIsolatedE2EScenario({
30
+ suiteName: 'component-substitution-markdown',
31
+ tempProjectFactory: createTempProjectWithDefaultStateMachine,
32
+ });
33
+ client = scenario.client;
34
+ cleanup = scenario.cleanup;
35
+
36
+ await initializeDevelopment(client);
37
+ });
38
+
39
+ afterEach(async () => {
40
+ delete process.env.TASK_BACKEND;
41
+ if (cleanup) {
42
+ await cleanup();
43
+ }
44
+ });
45
+
46
+ it('should use markdown-based components for plan management', async () => {
47
+ const result = await client.callTool('whats_next', {
48
+ user_input: 'test markdown component substitution',
49
+ });
50
+ const response = assertToolSuccess(result);
51
+
52
+ expect(response.phase).toBeTruthy();
53
+ expect(response.instructions).toBeTruthy();
54
+ expect(response.plan_file_path).toBeTruthy();
55
+
56
+ // Verify plan file operations work with markdown backend
57
+ expect(response.plan_file_path).toContain('.vibe');
58
+ expect(response.plan_file_path).toMatch(/\.md$/);
59
+ });
60
+
61
+ it('should generate markdown-compatible instructions', async () => {
62
+ const result = await client.callTool('whats_next', {
63
+ user_input: 'create feature with markdown backend',
64
+ });
65
+ const response = assertToolSuccess(result);
66
+
67
+ // Instructions should be generated using markdown-based strategy
68
+ expect(response.instructions).toContain('Plan File Guidance');
69
+ expect(response.instructions).toContain('Project Context');
70
+ expect(typeof response.instructions).toBe('string');
71
+ });
72
+
73
+ it('should handle phase transitions with markdown backend', async () => {
74
+ // Initialize conversation
75
+ await client.callTool('whats_next', { user_input: 'start project' });
76
+
77
+ const result = await client.callTool('proceed_to_phase', {
78
+ target_phase: 'design',
79
+ reason: 'requirements complete',
80
+ review_state: 'not-required',
81
+ });
82
+ const response = assertToolSuccess(result);
83
+
84
+ expect(response.phase).toBe('design');
85
+ expect(response.instructions).toBeTruthy();
86
+ });
87
+ });
88
+
89
+ describe('Backend Strategy Configuration', () => {
90
+ // Note: Actual beads fallback testing requires specific environment setup
91
+ // These tests document the expected behavior when backend fallback occurs
92
+
93
+ it.todo('should fallback to default components when beads unavailable');
94
+ it.todo('should generate compatible instructions with fallback strategy');
95
+
96
+ // For now, focus on testing the factory pattern mechanism itself
97
+ // rather than specific backend availability scenarios
98
+ });
99
+
100
+ describe('Component Factory Integration', () => {
101
+ beforeEach(async () => {
102
+ const scenario = await createSuiteIsolatedE2EScenario({
103
+ suiteName: 'component-substitution-factory',
104
+ tempProjectFactory: createTempProjectWithDefaultStateMachine,
105
+ });
106
+ client = scenario.client;
107
+ cleanup = scenario.cleanup;
108
+
109
+ await initializeDevelopment(client);
110
+ });
111
+
112
+ afterEach(async () => {
113
+ if (cleanup) {
114
+ await cleanup();
115
+ }
116
+ });
117
+
118
+ it('should maintain consistent behavior across component substitutions', async () => {
119
+ // Test multiple operations to ensure consistent component behavior
120
+ const first = await client.callTool('whats_next', {
121
+ user_input: 'first operation',
122
+ });
123
+ const firstResponse = assertToolSuccess(first);
124
+
125
+ const second = await client.callTool('proceed_to_phase', {
126
+ target_phase: 'design',
127
+ reason: 'ready to design',
128
+ review_state: 'not-required',
129
+ });
130
+ const secondResponse = assertToolSuccess(second);
131
+
132
+ const third = await client.callTool('whats_next', {
133
+ user_input: 'continue after transition',
134
+ });
135
+ const thirdResponse = assertToolSuccess(third);
136
+
137
+ // All responses should be consistent and functional
138
+ expect(firstResponse.conversation_id).toBe(
139
+ secondResponse.conversation_id
140
+ );
141
+ expect(secondResponse.conversation_id).toBe(
142
+ thirdResponse.conversation_id
143
+ );
144
+ expect(thirdResponse.phase).toBe('design');
145
+ });
146
+
147
+ it('should properly inject dependencies through factory pattern', async () => {
148
+ const result = await client.callTool('whats_next', {
149
+ user_input: 'test dependency injection',
150
+ });
151
+ const response = assertToolSuccess(result);
152
+
153
+ // Verify that components work together properly (dependency injection successful)
154
+ expect(response.instructions).toBeTruthy();
155
+ expect(response.plan_file_path).toBeTruthy();
156
+
157
+ // Components should be working together to produce complete responses
158
+ expect(response.phase).toBeTruthy();
159
+ expect(response.conversation_id).toBeTruthy();
160
+ });
161
+
162
+ it('should handle component errors gracefully', async () => {
163
+ // Test that factory-created components handle edge cases
164
+ const result = await client.callTool('whats_next', {
165
+ user_input: '', // Empty input to test robustness
166
+ });
167
+ const response = assertToolSuccess(result);
168
+
169
+ // Should still work with empty input
170
+ expect(response.phase).toBeTruthy();
171
+ expect(response.instructions).toBeTruthy();
172
+ });
173
+ });
174
+
175
+ describe('Backend Detection Integration', () => {
176
+ beforeEach(async () => {
177
+ const scenario = await createSuiteIsolatedE2EScenario({
178
+ suiteName: 'component-substitution-detection',
179
+ tempProjectFactory: createTempProjectWithDefaultStateMachine,
180
+ });
181
+ client = scenario.client;
182
+ cleanup = scenario.cleanup;
183
+
184
+ await initializeDevelopment(client);
185
+ });
186
+
187
+ afterEach(async () => {
188
+ if (cleanup) {
189
+ await cleanup();
190
+ }
191
+ });
192
+
193
+ it('should detect task backend and create appropriate components', async () => {
194
+ const result = await client.callTool('whats_next', {
195
+ user_input: 'test backend detection',
196
+ });
197
+ const response = assertToolSuccess(result);
198
+
199
+ // Verify the factory correctly detected and created appropriate components
200
+ expect(response.phase).toBeTruthy();
201
+ expect(response.instructions).toBeTruthy();
202
+
203
+ // The response should indicate which components are being used
204
+ // (in practice, this would be markdown components since beads isn't available in tests)
205
+ expect(response.plan_file_path).toMatch(/\.md$/);
206
+ });
207
+ });
208
+ });