@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
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Beads Task Backend Client
|
|
3
|
+
*
|
|
4
|
+
* Implementation of ITaskBackendClient for beads task management system.
|
|
5
|
+
* Handles CLI operations and task validation for beads backend.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
type ITaskBackendClient,
|
|
10
|
+
type BackendTask,
|
|
11
|
+
type TaskValidationResult,
|
|
12
|
+
} from '@codemcp/workflows-core';
|
|
13
|
+
import { execSync } from 'node:child_process';
|
|
14
|
+
import { createLogger } from '@codemcp/workflows-core';
|
|
15
|
+
|
|
16
|
+
const logger = createLogger('BeadsTaskBackendClient');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Beads-specific implementation of task backend client
|
|
20
|
+
*/
|
|
21
|
+
export class BeadsTaskBackendClient implements ITaskBackendClient {
|
|
22
|
+
private projectPath: string;
|
|
23
|
+
|
|
24
|
+
constructor(projectPath: string) {
|
|
25
|
+
this.projectPath = projectPath;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Execute a beads command safely
|
|
30
|
+
*/
|
|
31
|
+
private async executeBeadsCommand(
|
|
32
|
+
args: string[]
|
|
33
|
+
): Promise<{ success: boolean; stdout?: string; stderr?: string }> {
|
|
34
|
+
try {
|
|
35
|
+
const command = `bd ${args.join(' ')}`;
|
|
36
|
+
logger.debug('Executing beads command', {
|
|
37
|
+
command,
|
|
38
|
+
projectPath: this.projectPath,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const stdout = execSync(`bd ${args.join(' ')}`, {
|
|
42
|
+
cwd: this.projectPath,
|
|
43
|
+
encoding: 'utf-8',
|
|
44
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
return { success: true, stdout };
|
|
48
|
+
} catch (error: unknown) {
|
|
49
|
+
const execError = error as {
|
|
50
|
+
stderr?: string;
|
|
51
|
+
stdout?: string;
|
|
52
|
+
status?: number;
|
|
53
|
+
};
|
|
54
|
+
logger.warn('Beads command failed', {
|
|
55
|
+
args,
|
|
56
|
+
error: error instanceof Error ? error.message : String(error),
|
|
57
|
+
stderr: execError.stderr,
|
|
58
|
+
stdout: execError.stdout,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
success: false,
|
|
63
|
+
stderr:
|
|
64
|
+
execError.stderr ||
|
|
65
|
+
(error instanceof Error ? error.message : String(error)),
|
|
66
|
+
stdout: execError.stdout,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Check if beads backend is available
|
|
73
|
+
*/
|
|
74
|
+
async isAvailable(): Promise<boolean> {
|
|
75
|
+
try {
|
|
76
|
+
const result = await this.executeBeadsCommand(['--version']);
|
|
77
|
+
return result.success;
|
|
78
|
+
} catch (_error) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get all open tasks for a given parent task
|
|
85
|
+
*/
|
|
86
|
+
async getOpenTasks(parentTaskId: string): Promise<BackendTask[]> {
|
|
87
|
+
try {
|
|
88
|
+
const result = await this.executeBeadsCommand([
|
|
89
|
+
'list',
|
|
90
|
+
'--parent',
|
|
91
|
+
parentTaskId,
|
|
92
|
+
'--status',
|
|
93
|
+
'open',
|
|
94
|
+
]);
|
|
95
|
+
|
|
96
|
+
if (!result.success || !result.stdout) {
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Parse text output from beads CLI (since JSON format may not be available)
|
|
101
|
+
// This is a simplified parser - would need to be adjusted based on actual beads output format
|
|
102
|
+
const lines = result.stdout.trim().split('\n');
|
|
103
|
+
const tasks: BackendTask[] = [];
|
|
104
|
+
|
|
105
|
+
for (const line of lines) {
|
|
106
|
+
if (
|
|
107
|
+
line.trim() &&
|
|
108
|
+
!line.startsWith('○') &&
|
|
109
|
+
!line.startsWith('●') &&
|
|
110
|
+
!line.includes('Tip:')
|
|
111
|
+
) {
|
|
112
|
+
// Extract task ID and title from beads output format
|
|
113
|
+
// This is based on observed beads CLI output format
|
|
114
|
+
const match = line.match(/^○?\s*([^\s]+)\s+.*?\s+-\s+(.+)$/);
|
|
115
|
+
if (match) {
|
|
116
|
+
tasks.push({
|
|
117
|
+
id: match[1] || '',
|
|
118
|
+
title: match[2] || '',
|
|
119
|
+
status: 'open',
|
|
120
|
+
priority: 2,
|
|
121
|
+
parent: parentTaskId,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return tasks;
|
|
128
|
+
} catch (_error) {
|
|
129
|
+
return [];
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Validate that all tasks under a parent are completed
|
|
135
|
+
*/
|
|
136
|
+
async validateTasksCompleted(
|
|
137
|
+
parentTaskId: string
|
|
138
|
+
): Promise<TaskValidationResult> {
|
|
139
|
+
const openTasks = await this.getOpenTasks(parentTaskId);
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
valid: openTasks.length === 0,
|
|
143
|
+
openTasks,
|
|
144
|
+
message:
|
|
145
|
+
openTasks.length > 0
|
|
146
|
+
? `Found ${openTasks.length} incomplete task(s). All tasks must be completed before proceeding to the next phase.`
|
|
147
|
+
: 'All tasks completed.',
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Create a new task under a parent
|
|
153
|
+
*/
|
|
154
|
+
async createTask(
|
|
155
|
+
title: string,
|
|
156
|
+
parentTaskId: string,
|
|
157
|
+
priority = 2
|
|
158
|
+
): Promise<string> {
|
|
159
|
+
const result = await this.executeBeadsCommand([
|
|
160
|
+
'create',
|
|
161
|
+
`"${title}"`,
|
|
162
|
+
'--parent',
|
|
163
|
+
parentTaskId,
|
|
164
|
+
'-p',
|
|
165
|
+
priority.toString(),
|
|
166
|
+
]);
|
|
167
|
+
|
|
168
|
+
if (!result.success) {
|
|
169
|
+
throw new Error(
|
|
170
|
+
`Failed to create task: ${result.stderr || 'Unknown error'}`
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Extract task ID from beads output
|
|
175
|
+
// Based on beads CLI output format: "✓ Created issue: task-id"
|
|
176
|
+
const match =
|
|
177
|
+
result.stdout?.match(/✓ Created issue: ([\w\d.-]+)/) ||
|
|
178
|
+
result.stdout?.match(/Created issue: ([\w\d.-]+)/) ||
|
|
179
|
+
result.stdout?.match(/Created (bd-[\w\d.]+)/);
|
|
180
|
+
|
|
181
|
+
if (!match) {
|
|
182
|
+
throw new Error(
|
|
183
|
+
`Failed to extract task ID from beads output: ${result.stdout || 'No output'}`
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return match[1] || '';
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Update task status
|
|
192
|
+
*/
|
|
193
|
+
async updateTaskStatus(
|
|
194
|
+
taskId: string,
|
|
195
|
+
status: 'open' | 'in_progress' | 'completed' | 'cancelled'
|
|
196
|
+
): Promise<void> {
|
|
197
|
+
const beadsStatus = this.mapStatusToBeads(status);
|
|
198
|
+
|
|
199
|
+
const result = await this.executeBeadsCommand([
|
|
200
|
+
'update',
|
|
201
|
+
taskId,
|
|
202
|
+
'--status',
|
|
203
|
+
beadsStatus,
|
|
204
|
+
]);
|
|
205
|
+
|
|
206
|
+
if (!result.success) {
|
|
207
|
+
throw new Error(
|
|
208
|
+
`Failed to update task status: ${result.stderr || 'Unknown error'}`
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Map our status enum to beads CLI status values
|
|
215
|
+
*/
|
|
216
|
+
private mapStatusToBeads(
|
|
217
|
+
status: 'open' | 'in_progress' | 'completed' | 'cancelled'
|
|
218
|
+
): string {
|
|
219
|
+
switch (status) {
|
|
220
|
+
case 'open':
|
|
221
|
+
return 'open';
|
|
222
|
+
case 'in_progress':
|
|
223
|
+
return 'in_progress';
|
|
224
|
+
case 'completed':
|
|
225
|
+
return 'completed';
|
|
226
|
+
case 'cancelled':
|
|
227
|
+
return 'cancelled';
|
|
228
|
+
default:
|
|
229
|
+
return 'open';
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server Components Factory
|
|
3
|
+
*
|
|
4
|
+
* Factory for creating server components based on configuration.
|
|
5
|
+
* Implements strategy pattern to enable component substitution based on task backend.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
TaskBackendManager,
|
|
10
|
+
PlanManager,
|
|
11
|
+
InstructionGenerator,
|
|
12
|
+
type TaskBackendConfig,
|
|
13
|
+
} from '@codemcp/workflows-core';
|
|
14
|
+
|
|
15
|
+
// Beads implementations
|
|
16
|
+
import { BeadsPlanManager } from './beads/beads-plan-manager.js';
|
|
17
|
+
import { BeadsInstructionGenerator } from './beads/beads-instruction-generator.js';
|
|
18
|
+
import { BeadsTaskBackendClient } from './beads/beads-task-backend-client.js';
|
|
19
|
+
|
|
20
|
+
export interface ComponentFactoryOptions {
|
|
21
|
+
taskBackend?: TaskBackendConfig;
|
|
22
|
+
projectPath?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Factory class for creating server components with appropriate strategy implementations
|
|
27
|
+
*/
|
|
28
|
+
export class ServerComponentsFactory {
|
|
29
|
+
private taskBackend: TaskBackendConfig;
|
|
30
|
+
private projectPath?: string;
|
|
31
|
+
|
|
32
|
+
constructor(options: ComponentFactoryOptions = {}) {
|
|
33
|
+
this.taskBackend =
|
|
34
|
+
options.taskBackend || TaskBackendManager.detectTaskBackend();
|
|
35
|
+
this.projectPath = options.projectPath;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Create the appropriate plan manager implementation
|
|
40
|
+
*/
|
|
41
|
+
createPlanManager(): PlanManager | BeadsPlanManager {
|
|
42
|
+
if (this.taskBackend.backend === 'beads' && this.taskBackend.isAvailable) {
|
|
43
|
+
return new BeadsPlanManager();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Default markdown-based plan manager
|
|
47
|
+
return new PlanManager();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Create the appropriate instruction generator implementation
|
|
52
|
+
*/
|
|
53
|
+
createInstructionGenerator():
|
|
54
|
+
| InstructionGenerator
|
|
55
|
+
| BeadsInstructionGenerator {
|
|
56
|
+
if (this.taskBackend.backend === 'beads' && this.taskBackend.isAvailable) {
|
|
57
|
+
return new BeadsInstructionGenerator();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Default markdown-based instruction generator
|
|
61
|
+
return new InstructionGenerator(new PlanManager());
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Create the appropriate task backend client implementation
|
|
66
|
+
*/
|
|
67
|
+
createTaskBackendClient(): BeadsTaskBackendClient | null {
|
|
68
|
+
if (
|
|
69
|
+
this.taskBackend.backend === 'beads' &&
|
|
70
|
+
this.taskBackend.isAvailable &&
|
|
71
|
+
this.projectPath
|
|
72
|
+
) {
|
|
73
|
+
return new BeadsTaskBackendClient(this.projectPath);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// No task backend client for markdown mode
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Get the current task backend configuration
|
|
82
|
+
*/
|
|
83
|
+
getTaskBackend(): TaskBackendConfig {
|
|
84
|
+
return this.taskBackend;
|
|
85
|
+
}
|
|
86
|
+
}
|
package/src/server-config.ts
CHANGED
|
@@ -15,8 +15,6 @@ import { FileStorage } from '@codemcp/workflows-core';
|
|
|
15
15
|
import type { IPersistence } from '@codemcp/workflows-core';
|
|
16
16
|
import { ConversationManager } from '@codemcp/workflows-core';
|
|
17
17
|
import { TransitionEngine } from '@codemcp/workflows-core';
|
|
18
|
-
import { InstructionGenerator } from '@codemcp/workflows-core';
|
|
19
|
-
import { PlanManager } from '@codemcp/workflows-core';
|
|
20
18
|
import { InteractionLogger } from '@codemcp/workflows-core';
|
|
21
19
|
import { WorkflowManager } from '@codemcp/workflows-core';
|
|
22
20
|
import { GitManager } from '@codemcp/workflows-core';
|
|
@@ -36,6 +34,7 @@ import {
|
|
|
36
34
|
generateWorkflowDescription,
|
|
37
35
|
} from './server-helpers.js';
|
|
38
36
|
import { notificationService } from './notification-service.js';
|
|
37
|
+
import { ServerComponentsFactory } from './components/server-components-factory.js';
|
|
39
38
|
|
|
40
39
|
const logger = createLogger('ServerConfig');
|
|
41
40
|
|
|
@@ -113,8 +112,14 @@ export async function initializeServerComponents(
|
|
|
113
112
|
);
|
|
114
113
|
const transitionEngine = new TransitionEngine(projectPath);
|
|
115
114
|
transitionEngine.setConversationManager(conversationManager);
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
|
|
116
|
+
// Use factory pattern for strategy-based component creation
|
|
117
|
+
const componentsFactory = new ServerComponentsFactory({
|
|
118
|
+
projectPath,
|
|
119
|
+
taskBackend: config.taskBackend, // Pass through task backend config if provided
|
|
120
|
+
});
|
|
121
|
+
const planManager = componentsFactory.createPlanManager();
|
|
122
|
+
const instructionGenerator = componentsFactory.createInstructionGenerator();
|
|
118
123
|
|
|
119
124
|
// Always create interaction logger as it's critical for transition engine logic
|
|
120
125
|
// (determining first call from initial state)
|
|
@@ -9,6 +9,7 @@ import { z } from 'zod';
|
|
|
9
9
|
import { BaseToolHandler } from './base-tool-handler.js';
|
|
10
10
|
import { createLogger } from '@codemcp/workflows-core';
|
|
11
11
|
import { ServerContext } from '../types.js';
|
|
12
|
+
import { getFormattedVersion } from '../version-info.js';
|
|
12
13
|
|
|
13
14
|
const logger = createLogger('GetToolInfoHandler');
|
|
14
15
|
|
|
@@ -207,7 +208,7 @@ export class GetToolInfoHandler extends BaseToolHandler<
|
|
|
207
208
|
// Build the complete response
|
|
208
209
|
const response: GetToolInfoResponse = {
|
|
209
210
|
tool_name: 'Responsible Vibe MCP - Development Workflow Management',
|
|
210
|
-
version:
|
|
211
|
+
version: getFormattedVersion(),
|
|
211
212
|
purpose:
|
|
212
213
|
'Structured development workflows with guided phase transitions and conversation state management',
|
|
213
214
|
description:
|
|
@@ -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);
|
|
@@ -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(
|