@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.
- 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/get-tool-info.d.ts.map +1 -1
- package/dist/tool-handlers/get-tool-info.js +2 -1
- package/dist/tool-handlers/get-tool-info.js.map +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 +7 -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/dist/version-info.d.ts +30 -0
- package/dist/version-info.d.ts.map +1 -0
- package/dist/version-info.js +178 -0
- package/dist/version-info.js.map +1 -0
- 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/get-tool-info.ts +2 -1
- package/src/tool-handlers/proceed-to-phase.ts +140 -0
- package/src/tool-handlers/start-development.ts +14 -3
- package/src/tool-handlers/whats-next.ts +4 -117
- package/src/types.ts +7 -4
- package/src/version-info.ts +213 -0
- 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
|
@@ -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,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
|
+
});
|