@codemcp/workflows 4.7.0 → 4.8.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 (52) 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/proceed-to-phase.d.ts +5 -0
  23. package/dist/tool-handlers/proceed-to-phase.d.ts.map +1 -1
  24. package/dist/tool-handlers/proceed-to-phase.js +95 -0
  25. package/dist/tool-handlers/proceed-to-phase.js.map +1 -1
  26. package/dist/tool-handlers/start-development.d.ts.map +1 -1
  27. package/dist/tool-handlers/start-development.js +7 -3
  28. package/dist/tool-handlers/start-development.js.map +1 -1
  29. package/dist/tool-handlers/whats-next.d.ts +0 -12
  30. package/dist/tool-handlers/whats-next.d.ts.map +1 -1
  31. package/dist/tool-handlers/whats-next.js +1 -88
  32. package/dist/tool-handlers/whats-next.js.map +1 -1
  33. package/dist/types.d.ts +7 -4
  34. package/dist/types.d.ts.map +1 -1
  35. package/package.json +2 -2
  36. package/src/components/beads/beads-instruction-generator.ts +261 -0
  37. package/src/components/beads/beads-plan-manager.ts +358 -0
  38. package/src/components/beads/beads-task-backend-client.ts +232 -0
  39. package/src/components/server-components-factory.ts +86 -0
  40. package/src/server-config.ts +9 -4
  41. package/src/tool-handlers/proceed-to-phase.ts +140 -0
  42. package/src/tool-handlers/start-development.ts +14 -3
  43. package/src/tool-handlers/whats-next.ts +4 -117
  44. package/src/types.ts +7 -4
  45. package/test/e2e/component-substitution.test.ts +208 -0
  46. package/test/unit/beads-instruction-generator.test.ts +847 -0
  47. package/test/unit/beads-phase-task-id-integration.test.ts +557 -0
  48. package/test/unit/server-components-factory.test.ts +279 -0
  49. package/test/unit/setup-project-docs-handler.test.ts +3 -2
  50. package/test/utils/e2e-test-setup.ts +0 -1
  51. package/test/utils/temp-files.ts +12 -0
  52. package/tsconfig.build.tsbuildinfo +1 -1
@@ -8,6 +8,8 @@
8
8
  import { ConversationRequiredToolHandler } from './base-tool-handler.js';
9
9
  import { validateRequiredArgs } from '../server-helpers.js';
10
10
  import type { ConversationContext } from '@codemcp/workflows-core';
11
+ import { BeadsStateManager } from '@codemcp/workflows-core';
12
+ import { ServerComponentsFactory } from '../components/server-components-factory.js';
11
13
  import { ServerContext } from '../types.js';
12
14
 
13
15
  /**
@@ -78,6 +80,14 @@ export class ProceedToPhaseHandler extends ConversationRequiredToolHandler<
78
80
  context
79
81
  );
80
82
 
83
+ // Validate beads task completion if in beads mode
84
+ await this.validateBeadsTaskCompletion(
85
+ conversationId,
86
+ currentPhase,
87
+ target_phase,
88
+ conversationContext.projectPath
89
+ );
90
+
81
91
  // Ensure state machine is loaded for this project
82
92
  this.ensureStateMachineForProject(context, conversationContext.projectPath);
83
93
 
@@ -279,4 +289,134 @@ export class ProceedToPhaseHandler extends ConversationRequiredToolHandler<
279
289
  }
280
290
  }
281
291
  }
292
+
293
+ /**
294
+ * Validate that all beads tasks in the current phase are completed
295
+ * before proceeding to the next phase. Only applies when beads mode is active.
296
+ */
297
+ private async validateBeadsTaskCompletion(
298
+ conversationId: string,
299
+ currentPhase: string,
300
+ targetPhase: string,
301
+ projectPath: string
302
+ ): Promise<void> {
303
+ try {
304
+ // Use factory to create task backend client (strategy pattern)
305
+ const factory = new ServerComponentsFactory({ projectPath });
306
+ const taskBackendClient = factory.createTaskBackendClient();
307
+
308
+ if (!taskBackendClient) {
309
+ // Not in beads mode or beads not available, skip validation
310
+ this.logger.debug(
311
+ 'Skipping beads task validation - no task backend client available',
312
+ {
313
+ conversationId,
314
+ currentPhase,
315
+ targetPhase,
316
+ }
317
+ );
318
+ return;
319
+ }
320
+
321
+ // Get beads state for this conversation
322
+ const beadsStateManager = new BeadsStateManager(projectPath);
323
+ const currentPhaseTaskId = await beadsStateManager.getPhaseTaskId(
324
+ conversationId,
325
+ currentPhase
326
+ );
327
+
328
+ if (!currentPhaseTaskId) {
329
+ // No beads state found for this conversation - fallback to graceful handling
330
+ this.logger.debug('No beads phase task ID found for current phase', {
331
+ conversationId,
332
+ currentPhase,
333
+ targetPhase,
334
+ projectPath,
335
+ });
336
+ return;
337
+ }
338
+
339
+ this.logger.debug(
340
+ 'Checking for incomplete beads tasks using task backend client',
341
+ {
342
+ conversationId,
343
+ currentPhase,
344
+ currentPhaseTaskId,
345
+ }
346
+ );
347
+
348
+ // Use task backend client to validate task completion (strategy pattern)
349
+ const validationResult =
350
+ await taskBackendClient.validateTasksCompleted(currentPhaseTaskId);
351
+
352
+ if (!validationResult.valid) {
353
+ // Get the incomplete tasks from the validation result
354
+ const incompleteTasks = validationResult.openTasks;
355
+
356
+ // Create detailed error message with incomplete tasks
357
+ const taskDetails = incompleteTasks
358
+ .map(task => ` • ${task.id} - ${task.title || 'Untitled task'}`)
359
+ .join('\n');
360
+
361
+ const errorMessage = `Cannot proceed to ${targetPhase} - ${incompleteTasks.length} incomplete task(s) in current phase "${currentPhase}":
362
+
363
+ ${taskDetails}
364
+
365
+ To proceed, please complete these tasks using:
366
+ bd close <task-id>
367
+
368
+ Or check remaining work with:
369
+ bd list --parent ${currentPhaseTaskId} --status open
370
+
371
+ You can also defer tasks if they're no longer needed:
372
+ bd defer <task-id> --until tomorrow`;
373
+
374
+ this.logger.info(
375
+ 'Blocking phase transition due to incomplete beads tasks',
376
+ {
377
+ conversationId,
378
+ currentPhase,
379
+ targetPhase,
380
+ currentPhaseTaskId,
381
+ incompleteTaskCount: incompleteTasks.length,
382
+ incompleteTaskIds: incompleteTasks.map(t => t.id),
383
+ }
384
+ );
385
+
386
+ throw new Error(errorMessage);
387
+ }
388
+
389
+ this.logger.info(
390
+ 'All beads tasks completed in current phase, allowing transition',
391
+ {
392
+ conversationId,
393
+ currentPhase,
394
+ targetPhase,
395
+ currentPhaseTaskId,
396
+ }
397
+ );
398
+ } catch (error) {
399
+ // Re-throw validation errors (incomplete tasks)
400
+ if (
401
+ error instanceof Error &&
402
+ error.message.includes('Cannot proceed to')
403
+ ) {
404
+ throw error;
405
+ }
406
+
407
+ // Log other errors but allow transition (graceful degradation)
408
+ const errorMessage =
409
+ error instanceof Error ? error.message : String(error);
410
+ this.logger.warn(
411
+ 'Beads task validation failed, allowing transition to proceed',
412
+ {
413
+ error: errorMessage,
414
+ conversationId,
415
+ currentPhase,
416
+ targetPhase,
417
+ projectPath,
418
+ }
419
+ );
420
+ }
421
+ }
282
422
  }
@@ -17,7 +17,11 @@ import { GitCommitConfig } from '@codemcp/workflows-core';
17
17
  import { GitManager } from '@codemcp/workflows-core';
18
18
  import type { YamlStateMachine } from '@codemcp/workflows-core';
19
19
  import { ProjectDocsManager, ProjectDocsInfo } from '@codemcp/workflows-core';
20
- import { TaskBackendManager, BeadsIntegration } from '@codemcp/workflows-core';
20
+ import {
21
+ TaskBackendManager,
22
+ BeadsIntegration,
23
+ BeadsStateManager,
24
+ } from '@codemcp/workflows-core';
21
25
  import { ServerContext } from '../types.js';
22
26
 
23
27
  /**
@@ -215,7 +219,8 @@ export class StartDevelopmentHandler extends BaseToolHandler<
215
219
  projectPath,
216
220
  stateMachine,
217
221
  selectedWorkflow,
218
- conversationContext.planFilePath
222
+ conversationContext.planFilePath,
223
+ conversationContext.conversationId
219
224
  );
220
225
  }
221
226
 
@@ -647,7 +652,8 @@ ${templateOptionsText}
647
652
  projectPath: string,
648
653
  stateMachine: YamlStateMachine,
649
654
  workflowName: string,
650
- planFilePath: string
655
+ planFilePath: string,
656
+ conversationId: string
651
657
  ): Promise<void> {
652
658
  try {
653
659
  const beadsIntegration = new BeadsIntegration(projectPath);
@@ -673,11 +679,16 @@ ${templateOptionsText}
673
679
  // Update plan file with phase task IDs
674
680
  await this.updatePlanFileWithPhaseTaskIds(planFilePath, phaseTasks);
675
681
 
682
+ // Create beads state for this conversation
683
+ const beadsStateManager = new BeadsStateManager(projectPath);
684
+ await beadsStateManager.createState(conversationId, epicId, phaseTasks);
685
+
676
686
  this.logger.info('Beads integration setup complete', {
677
687
  projectPath,
678
688
  epicId,
679
689
  phaseCount: phaseTasks.length,
680
690
  planFilePath,
691
+ conversationId,
681
692
  });
682
693
  } catch (error) {
683
694
  this.logger.error(
@@ -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,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
+ });