@codemcp/workflows 4.6.1 → 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.
- package/.turbo/turbo-build.log +1 -1
- package/dist/components/beads/beads-instruction-generator.d.ts +48 -0
- package/dist/components/beads/beads-instruction-generator.d.ts.map +1 -0
- package/dist/components/beads/beads-instruction-generator.js +182 -0
- package/dist/components/beads/beads-instruction-generator.js.map +1 -0
- package/dist/components/beads/beads-plan-manager.d.ts +66 -0
- package/dist/components/beads/beads-plan-manager.d.ts.map +1 -0
- package/dist/components/beads/beads-plan-manager.js +288 -0
- package/dist/components/beads/beads-plan-manager.js.map +1 -0
- package/dist/components/beads/beads-task-backend-client.d.ts +43 -0
- package/dist/components/beads/beads-task-backend-client.d.ts.map +1 -0
- package/dist/components/beads/beads-task-backend-client.js +178 -0
- package/dist/components/beads/beads-task-backend-client.js.map +1 -0
- package/dist/components/server-components-factory.d.ts +39 -0
- package/dist/components/server-components-factory.d.ts.map +1 -0
- package/dist/components/server-components-factory.js +62 -0
- package/dist/components/server-components-factory.js.map +1 -0
- package/dist/server-config.d.ts.map +1 -1
- package/dist/server-config.js +8 -4
- package/dist/server-config.js.map +1 -1
- package/dist/server-implementation.d.ts +1 -1
- package/dist/tool-handlers/proceed-to-phase.d.ts +5 -0
- package/dist/tool-handlers/proceed-to-phase.d.ts.map +1 -1
- package/dist/tool-handlers/proceed-to-phase.js +95 -0
- package/dist/tool-handlers/proceed-to-phase.js.map +1 -1
- package/dist/tool-handlers/start-development.d.ts.map +1 -1
- package/dist/tool-handlers/start-development.js +9 -3
- package/dist/tool-handlers/start-development.js.map +1 -1
- package/dist/tool-handlers/whats-next.d.ts +0 -12
- package/dist/tool-handlers/whats-next.d.ts.map +1 -1
- package/dist/tool-handlers/whats-next.js +1 -88
- package/dist/tool-handlers/whats-next.js.map +1 -1
- package/dist/types.d.ts +7 -4
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/components/beads/beads-instruction-generator.ts +261 -0
- package/src/components/beads/beads-plan-manager.ts +358 -0
- package/src/components/beads/beads-task-backend-client.ts +232 -0
- package/src/components/server-components-factory.ts +86 -0
- package/src/server-config.ts +9 -4
- package/src/tool-handlers/proceed-to-phase.ts +140 -0
- package/src/tool-handlers/start-development.ts +17 -3
- package/src/tool-handlers/whats-next.ts +4 -117
- package/src/types.ts +7 -4
- package/test/e2e/component-substitution.test.ts +208 -0
- package/test/unit/beads-instruction-generator.test.ts +847 -0
- package/test/unit/beads-phase-task-id-integration.test.ts +557 -0
- package/test/unit/server-components-factory.test.ts +279 -0
- package/test/unit/setup-project-docs-handler.test.ts +3 -2
- package/test/utils/e2e-test-setup.ts +0 -1
- package/test/utils/temp-files.ts +12 -0
- 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 {
|
|
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);
|
|
@@ -667,14 +673,22 @@ ${templateOptionsText}
|
|
|
667
673
|
workflowName
|
|
668
674
|
);
|
|
669
675
|
|
|
676
|
+
// Create sequential dependencies between phases
|
|
677
|
+
await beadsIntegration.createPhaseDependencies(phaseTasks);
|
|
678
|
+
|
|
670
679
|
// Update plan file with phase task IDs
|
|
671
680
|
await this.updatePlanFileWithPhaseTaskIds(planFilePath, phaseTasks);
|
|
672
681
|
|
|
682
|
+
// Create beads state for this conversation
|
|
683
|
+
const beadsStateManager = new BeadsStateManager(projectPath);
|
|
684
|
+
await beadsStateManager.createState(conversationId, epicId, phaseTasks);
|
|
685
|
+
|
|
673
686
|
this.logger.info('Beads integration setup complete', {
|
|
674
687
|
projectPath,
|
|
675
688
|
epicId,
|
|
676
689
|
phaseCount: phaseTasks.length,
|
|
677
690
|
planFilePath,
|
|
691
|
+
conversationId,
|
|
678
692
|
});
|
|
679
693
|
} catch (error) {
|
|
680
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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 {
|
|
8
|
-
import {
|
|
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:
|
|
20
|
-
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
|
+
});
|