@codemcp/workflows-core 3.1.22 → 3.2.1
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/package.json +8 -3
- package/resources/templates/architecture/arc42/arc42-template-EN.md +1077 -0
- package/resources/templates/architecture/arc42/images/01_2_iso-25010-topics-EN.drawio-2023.png +0 -0
- package/resources/templates/architecture/arc42/images/01_2_iso-25010-topics-EN.drawio.png +0 -0
- package/resources/templates/architecture/arc42/images/05_building_blocks-EN.png +0 -0
- package/resources/templates/architecture/arc42/images/08-concepts-EN.drawio.png +0 -0
- package/resources/templates/architecture/arc42/images/arc42-logo.png +0 -0
- package/resources/templates/architecture/c4.md +224 -0
- package/resources/templates/architecture/freestyle.md +53 -0
- package/resources/templates/architecture/none.md +17 -0
- package/resources/templates/design/comprehensive.md +207 -0
- package/resources/templates/design/freestyle.md +37 -0
- package/resources/templates/design/none.md +17 -0
- package/resources/templates/requirements/ears.md +90 -0
- package/resources/templates/requirements/freestyle.md +42 -0
- package/resources/templates/requirements/none.md +17 -0
- package/resources/workflows/big-bang-conversion.yaml +539 -0
- package/resources/workflows/boundary-testing.yaml +334 -0
- package/resources/workflows/bugfix.yaml +185 -0
- package/resources/workflows/business-analysis.yaml +671 -0
- package/resources/workflows/c4-analysis.yaml +485 -0
- package/resources/workflows/epcc.yaml +161 -0
- package/resources/workflows/greenfield.yaml +189 -0
- package/resources/workflows/minor.yaml +127 -0
- package/resources/workflows/posts.yaml +207 -0
- package/resources/workflows/slides.yaml +256 -0
- package/resources/workflows/tdd.yaml +157 -0
- package/resources/workflows/waterfall.yaml +195 -0
- package/.turbo/turbo-build.log +0 -4
- package/src/config-manager.ts +0 -96
- package/src/conversation-manager.ts +0 -489
- package/src/database.ts +0 -427
- package/src/file-detection-manager.ts +0 -302
- package/src/git-manager.ts +0 -64
- package/src/index.ts +0 -28
- package/src/instruction-generator.ts +0 -210
- package/src/interaction-logger.ts +0 -109
- package/src/logger.ts +0 -353
- package/src/path-validation-utils.ts +0 -261
- package/src/plan-manager.ts +0 -323
- package/src/project-docs-manager.ts +0 -523
- package/src/state-machine-loader.ts +0 -365
- package/src/state-machine-types.ts +0 -72
- package/src/state-machine.ts +0 -370
- package/src/system-prompt-generator.ts +0 -122
- package/src/template-manager.ts +0 -328
- package/src/transition-engine.ts +0 -386
- package/src/types.ts +0 -60
- package/src/workflow-manager.ts +0 -606
- package/test/unit/conversation-manager.test.ts +0 -179
- package/test/unit/custom-workflow-loading.test.ts +0 -174
- package/test/unit/directory-linking-and-extensions.test.ts +0 -338
- package/test/unit/file-linking-integration.test.ts +0 -256
- package/test/unit/git-commit-integration.test.ts +0 -91
- package/test/unit/git-manager.test.ts +0 -86
- package/test/unit/install-workflow.test.ts +0 -138
- package/test/unit/instruction-generator.test.ts +0 -247
- package/test/unit/list-workflows-filtering.test.ts +0 -68
- package/test/unit/none-template-functionality.test.ts +0 -224
- package/test/unit/project-docs-manager.test.ts +0 -337
- package/test/unit/state-machine-loader.test.ts +0 -234
- package/test/unit/template-manager.test.ts +0 -217
- package/test/unit/validate-workflow-name.test.ts +0 -150
- package/test/unit/workflow-domain-filtering.test.ts +0 -75
- package/test/unit/workflow-enum-generation.test.ts +0 -92
- package/test/unit/workflow-manager-enhanced-path-resolution.test.ts +0 -369
- package/test/unit/workflow-manager-path-resolution.test.ts +0 -150
- package/test/unit/workflow-migration.test.ts +0 -155
- package/test/unit/workflow-override-by-name.test.ts +0 -116
- package/test/unit/workflow-prioritization.test.ts +0 -38
- package/test/unit/workflow-validation.test.ts +0 -303
- package/test/utils/e2e-test-setup.ts +0 -453
- package/test/utils/run-server-in-dir.sh +0 -27
- package/test/utils/temp-files.ts +0 -308
- package/test/utils/test-access.ts +0 -79
- package/test/utils/test-helpers.ts +0 -286
- package/test/utils/test-setup.ts +0 -78
- package/tsconfig.build.json +0 -21
- package/tsconfig.json +0 -8
- package/vitest.config.ts +0 -18
@@ -1,365 +0,0 @@
|
|
1
|
-
/**
|
2
|
-
* State Machine Loader
|
3
|
-
*
|
4
|
-
* Loads and validates YAML-based state machine definitions
|
5
|
-
*/
|
6
|
-
|
7
|
-
import fs from 'node:fs';
|
8
|
-
import yaml from 'js-yaml';
|
9
|
-
import path from 'node:path';
|
10
|
-
import { fileURLToPath } from 'node:url';
|
11
|
-
import { createRequire } from 'node:module';
|
12
|
-
import { createLogger } from './logger.js';
|
13
|
-
import { YamlStateMachine, YamlTransition } from './state-machine-types.js';
|
14
|
-
|
15
|
-
const logger = createLogger('StateMachineLoader');
|
16
|
-
|
17
|
-
/**
|
18
|
-
* Loads and manages YAML-based state machine definitions
|
19
|
-
*/
|
20
|
-
export class StateMachineLoader {
|
21
|
-
private stateMachine: YamlStateMachine | null = null;
|
22
|
-
private validPhases: Set<string> = new Set();
|
23
|
-
|
24
|
-
/**
|
25
|
-
* Get all valid phases from the loaded state machine
|
26
|
-
*/
|
27
|
-
public getValidPhases(): string[] {
|
28
|
-
if (!this.stateMachine) {
|
29
|
-
throw new Error('State machine not loaded');
|
30
|
-
}
|
31
|
-
return Array.from(this.validPhases);
|
32
|
-
}
|
33
|
-
|
34
|
-
/**
|
35
|
-
* Get the initial state from the loaded state machine
|
36
|
-
*/
|
37
|
-
public getInitialState(): string {
|
38
|
-
if (!this.stateMachine) {
|
39
|
-
throw new Error('State machine not loaded');
|
40
|
-
}
|
41
|
-
return this.stateMachine.initial_state;
|
42
|
-
}
|
43
|
-
|
44
|
-
/**
|
45
|
-
* Load state machine from YAML file
|
46
|
-
*
|
47
|
-
* Checks for custom state machine file in project directory first,
|
48
|
-
* then falls back to waterfall workflow as default
|
49
|
-
*/
|
50
|
-
public loadStateMachine(projectPath: string): YamlStateMachine {
|
51
|
-
// Check for custom state machine file in project directory
|
52
|
-
const customFilePaths = [
|
53
|
-
path.join(projectPath, '.vibe', 'workflow.yaml'),
|
54
|
-
path.join(projectPath, '.vibe', 'workflow.yml'),
|
55
|
-
];
|
56
|
-
|
57
|
-
// Try to load custom state machine file
|
58
|
-
for (const filePath of customFilePaths) {
|
59
|
-
if (fs.existsSync(filePath)) {
|
60
|
-
logger.info('Loading custom state machine file', { filePath });
|
61
|
-
try {
|
62
|
-
return this.loadFromFile(filePath);
|
63
|
-
} catch (error) {
|
64
|
-
logger.warn(
|
65
|
-
'Failed to load custom state machine, falling back to default',
|
66
|
-
{
|
67
|
-
filePath,
|
68
|
-
error: (error as Error).message,
|
69
|
-
}
|
70
|
-
);
|
71
|
-
// Continue to try next file or fall back to default
|
72
|
-
}
|
73
|
-
}
|
74
|
-
}
|
75
|
-
|
76
|
-
// Fall back to waterfall workflow as default
|
77
|
-
const defaultFilePath = this.resolveWorkflowPath('waterfall.yaml');
|
78
|
-
|
79
|
-
logger.info('Loading default state machine file', { defaultFilePath });
|
80
|
-
return this.loadFromFile(defaultFilePath);
|
81
|
-
}
|
82
|
-
|
83
|
-
/**
|
84
|
-
* Resolve workflow path using similar strategy as TemplateManager
|
85
|
-
*/
|
86
|
-
private resolveWorkflowPath(filename: string): string {
|
87
|
-
const strategies: string[] = [];
|
88
|
-
|
89
|
-
// Strategy 1: Local resources directory (symlinked from root)
|
90
|
-
strategies.push(
|
91
|
-
path.join(
|
92
|
-
path.dirname(fileURLToPath(import.meta.url)),
|
93
|
-
'../resources/workflows',
|
94
|
-
filename
|
95
|
-
)
|
96
|
-
);
|
97
|
-
|
98
|
-
// Strategy 2: From compiled dist directory
|
99
|
-
const currentFileUrl = import.meta.url;
|
100
|
-
if (currentFileUrl.startsWith('file://')) {
|
101
|
-
const currentFilePath = fileURLToPath(currentFileUrl);
|
102
|
-
// From dist/state-machine-loader.js -> ../resources/workflows
|
103
|
-
strategies.push(
|
104
|
-
path.join(
|
105
|
-
path.dirname(currentFilePath),
|
106
|
-
'../resources/workflows',
|
107
|
-
filename
|
108
|
-
)
|
109
|
-
);
|
110
|
-
}
|
111
|
-
|
112
|
-
// Strategy 3: Current working directory (for development)
|
113
|
-
strategies.push(path.join(process.cwd(), 'resources/workflows', filename));
|
114
|
-
|
115
|
-
// Strategy 4: From node_modules
|
116
|
-
strategies.push(
|
117
|
-
path.join(
|
118
|
-
process.cwd(),
|
119
|
-
'node_modules/@codemcp/workflows-core/resources/workflows',
|
120
|
-
filename
|
121
|
-
)
|
122
|
-
);
|
123
|
-
|
124
|
-
// Strategy 5: From package directory (for development)
|
125
|
-
try {
|
126
|
-
const require = createRequire(import.meta.url);
|
127
|
-
const packagePath = require.resolve(
|
128
|
-
'@codemcp/workflows-core/package.json'
|
129
|
-
);
|
130
|
-
const packageDir = path.dirname(packagePath);
|
131
|
-
strategies.push(path.join(packageDir, 'resources/workflows', filename));
|
132
|
-
} catch (_error) {
|
133
|
-
// Ignore if package not found
|
134
|
-
}
|
135
|
-
|
136
|
-
// Find the first existing path
|
137
|
-
for (const strategy of strategies) {
|
138
|
-
try {
|
139
|
-
// This will throw if path doesn't exist
|
140
|
-
fs.accessSync(strategy);
|
141
|
-
logger.debug('Using workflow path', { path: strategy });
|
142
|
-
return strategy;
|
143
|
-
} catch (_error) {
|
144
|
-
// Continue to next strategy
|
145
|
-
}
|
146
|
-
}
|
147
|
-
|
148
|
-
// Fallback to first strategy if none found
|
149
|
-
const fallback = strategies[0];
|
150
|
-
logger.warn('No workflow path found, using fallback', { path: fallback });
|
151
|
-
return fallback;
|
152
|
-
}
|
153
|
-
|
154
|
-
public loadFromFile(filePath: string): YamlStateMachine {
|
155
|
-
try {
|
156
|
-
const yamlContent = fs.readFileSync(path.resolve(filePath), 'utf8');
|
157
|
-
const stateMachine = yaml.load(yamlContent) as YamlStateMachine;
|
158
|
-
|
159
|
-
// Validate the state machine
|
160
|
-
this.validateStateMachine(stateMachine);
|
161
|
-
|
162
|
-
// Store valid phases for later validation
|
163
|
-
this.validPhases = new Set(Object.keys(stateMachine.states));
|
164
|
-
|
165
|
-
this.stateMachine = stateMachine;
|
166
|
-
logger.info('State machine loaded successfully', {
|
167
|
-
name: stateMachine.name,
|
168
|
-
stateCount: Object.keys(stateMachine.states).length,
|
169
|
-
phases: Array.from(this.validPhases),
|
170
|
-
});
|
171
|
-
|
172
|
-
return stateMachine;
|
173
|
-
} catch (error) {
|
174
|
-
logger.error('Failed to load state machine', error as Error);
|
175
|
-
throw new Error(
|
176
|
-
`Failed to load state machine: ${(error as Error).message}`
|
177
|
-
);
|
178
|
-
}
|
179
|
-
}
|
180
|
-
|
181
|
-
/**
|
182
|
-
* Validate the state machine structure and references
|
183
|
-
*/
|
184
|
-
private validateStateMachine(stateMachine: YamlStateMachine): void {
|
185
|
-
// Check required properties
|
186
|
-
if (
|
187
|
-
!stateMachine.name ||
|
188
|
-
!stateMachine.description ||
|
189
|
-
!stateMachine.initial_state ||
|
190
|
-
!stateMachine.states
|
191
|
-
) {
|
192
|
-
throw new Error('State machine is missing required properties');
|
193
|
-
}
|
194
|
-
|
195
|
-
// Get all state names
|
196
|
-
const stateNames = Object.keys(stateMachine.states);
|
197
|
-
|
198
|
-
// Check initial state is valid
|
199
|
-
if (!stateNames.includes(stateMachine.initial_state)) {
|
200
|
-
throw new Error(
|
201
|
-
`Initial state "${stateMachine.initial_state}" is not defined in states`
|
202
|
-
);
|
203
|
-
}
|
204
|
-
|
205
|
-
// Validate states and transitions
|
206
|
-
for (const [stateName, state] of Object.entries(stateMachine.states)) {
|
207
|
-
// Check required state properties
|
208
|
-
if (!state.description || !state.default_instructions) {
|
209
|
-
throw new Error(
|
210
|
-
`State "${stateName}" is missing required properties (description or default_instructions)`
|
211
|
-
);
|
212
|
-
}
|
213
|
-
|
214
|
-
if (!state.transitions || !Array.isArray(state.transitions)) {
|
215
|
-
throw new Error(
|
216
|
-
`State "${stateName}" has invalid transitions property`
|
217
|
-
);
|
218
|
-
}
|
219
|
-
|
220
|
-
for (const transition of state.transitions) {
|
221
|
-
if (!stateNames.includes(transition.to)) {
|
222
|
-
throw new Error(
|
223
|
-
`State "${stateName}" has transition to unknown state "${transition.to}"`
|
224
|
-
);
|
225
|
-
}
|
226
|
-
|
227
|
-
if (!transition.transition_reason) {
|
228
|
-
throw new Error(
|
229
|
-
`Transition from "${stateName}" to "${transition.to}" is missing transition_reason`
|
230
|
-
);
|
231
|
-
}
|
232
|
-
}
|
233
|
-
}
|
234
|
-
|
235
|
-
logger.debug('State machine validation successful');
|
236
|
-
}
|
237
|
-
|
238
|
-
/**
|
239
|
-
* Get transition instructions for a specific state change
|
240
|
-
*/
|
241
|
-
public getTransitionInstructions(
|
242
|
-
fromState: string,
|
243
|
-
toState: string,
|
244
|
-
trigger?: string
|
245
|
-
): { instructions: string; transitionReason: string; isModeled: boolean } {
|
246
|
-
if (!this.stateMachine) {
|
247
|
-
throw new Error('State machine not loaded');
|
248
|
-
}
|
249
|
-
|
250
|
-
// Get target state definition
|
251
|
-
const targetState = this.stateMachine.states[toState];
|
252
|
-
if (!targetState) {
|
253
|
-
throw new Error(`Target state "${toState}" not found`);
|
254
|
-
}
|
255
|
-
|
256
|
-
// Look for a modeled transition first
|
257
|
-
const fromStateDefinition = this.stateMachine.states[fromState];
|
258
|
-
if (fromStateDefinition) {
|
259
|
-
const transition = fromStateDefinition.transitions.find(
|
260
|
-
t => t.to === toState && (!trigger || t.trigger === trigger)
|
261
|
-
);
|
262
|
-
|
263
|
-
if (transition) {
|
264
|
-
// For modeled transitions, compose instructions
|
265
|
-
let composedInstructions = targetState.default_instructions;
|
266
|
-
|
267
|
-
// If transition has specific instructions, use those instead of default
|
268
|
-
if (transition.instructions) {
|
269
|
-
composedInstructions = transition.instructions;
|
270
|
-
}
|
271
|
-
|
272
|
-
// If transition has additional instructions, combine them
|
273
|
-
if (transition.additional_instructions) {
|
274
|
-
composedInstructions = `${composedInstructions}\n\n**Additional Context:**\n${transition.additional_instructions}`;
|
275
|
-
}
|
276
|
-
|
277
|
-
return {
|
278
|
-
instructions: composedInstructions,
|
279
|
-
transitionReason: transition.transition_reason,
|
280
|
-
isModeled: true,
|
281
|
-
};
|
282
|
-
}
|
283
|
-
}
|
284
|
-
|
285
|
-
// Fall back to target state's default instructions for unmodeled transitions
|
286
|
-
return {
|
287
|
-
instructions: targetState.default_instructions,
|
288
|
-
transitionReason: `Direct transition to ${toState} phase`,
|
289
|
-
isModeled: false,
|
290
|
-
};
|
291
|
-
}
|
292
|
-
|
293
|
-
/**
|
294
|
-
* Get all possible transitions from a given state
|
295
|
-
*/
|
296
|
-
public getPossibleTransitions(fromState: string): YamlTransition[] {
|
297
|
-
if (!this.stateMachine) {
|
298
|
-
throw new Error('State machine not loaded');
|
299
|
-
}
|
300
|
-
|
301
|
-
const stateDefinition = this.stateMachine.states[fromState];
|
302
|
-
return stateDefinition ? stateDefinition.transitions : [];
|
303
|
-
}
|
304
|
-
|
305
|
-
/**
|
306
|
-
* Check if a transition is modeled (shown in state diagram)
|
307
|
-
*/
|
308
|
-
public isModeledTransition(fromState: string, toState: string): boolean {
|
309
|
-
if (!this.stateMachine) {
|
310
|
-
throw new Error('State machine not loaded');
|
311
|
-
}
|
312
|
-
|
313
|
-
const stateDefinition = this.stateMachine.states[fromState];
|
314
|
-
if (!stateDefinition) return false;
|
315
|
-
|
316
|
-
return stateDefinition.transitions.some(t => t.to === toState);
|
317
|
-
}
|
318
|
-
|
319
|
-
/**
|
320
|
-
* Check if a phase is valid in the current state machine
|
321
|
-
*/
|
322
|
-
public isValidPhase(phase: string): boolean {
|
323
|
-
return this.validPhases.has(phase);
|
324
|
-
}
|
325
|
-
|
326
|
-
/**
|
327
|
-
* Get phase-specific instructions for continuing work in current phase
|
328
|
-
*/
|
329
|
-
public getContinuePhaseInstructions(phase: string): string {
|
330
|
-
if (!this.stateMachine) {
|
331
|
-
throw new Error('State machine not loaded');
|
332
|
-
}
|
333
|
-
|
334
|
-
const stateDefinition = this.stateMachine.states[phase];
|
335
|
-
if (!stateDefinition) {
|
336
|
-
logger.error('Unknown phase', new Error(`Unknown phase: ${phase}`));
|
337
|
-
throw new Error(`Unknown phase: ${phase}`);
|
338
|
-
}
|
339
|
-
|
340
|
-
// Look for a self-transition (continue in same phase)
|
341
|
-
const continueTransition = stateDefinition.transitions.find(
|
342
|
-
t => t.to === phase
|
343
|
-
);
|
344
|
-
|
345
|
-
if (continueTransition) {
|
346
|
-
// Compose instructions for continue transition
|
347
|
-
let composedInstructions = stateDefinition.default_instructions;
|
348
|
-
|
349
|
-
// If transition has specific instructions, use those instead of default
|
350
|
-
if (continueTransition.instructions) {
|
351
|
-
composedInstructions = continueTransition.instructions;
|
352
|
-
}
|
353
|
-
|
354
|
-
// If transition has additional instructions, combine them
|
355
|
-
if (continueTransition.additional_instructions) {
|
356
|
-
composedInstructions = `${composedInstructions}\n\n**Additional Context:**\n${continueTransition.additional_instructions}`;
|
357
|
-
}
|
358
|
-
|
359
|
-
return composedInstructions;
|
360
|
-
}
|
361
|
-
|
362
|
-
// Fall back to default instructions for the phase
|
363
|
-
return stateDefinition.default_instructions;
|
364
|
-
}
|
365
|
-
}
|
@@ -1,72 +0,0 @@
|
|
1
|
-
/**
|
2
|
-
* State Machine Types
|
3
|
-
*
|
4
|
-
* Type definitions for YAML-based state machine
|
5
|
-
*/
|
6
|
-
|
7
|
-
/**
|
8
|
-
* Transition between states
|
9
|
-
*/
|
10
|
-
export interface YamlTransition {
|
11
|
-
/** Event that triggers this transition */
|
12
|
-
trigger: string;
|
13
|
-
|
14
|
-
/** Target state after transition */
|
15
|
-
to: string;
|
16
|
-
|
17
|
-
/** Instructions to provide when this transition occurs (optional - uses target state default if not provided) */
|
18
|
-
instructions?: string;
|
19
|
-
|
20
|
-
/** Additional instructions to combine with target state's default instructions (optional) */
|
21
|
-
additional_instructions?: string;
|
22
|
-
|
23
|
-
/** Reason for this transition */
|
24
|
-
transition_reason: string;
|
25
|
-
|
26
|
-
/** Optional review perspectives for this transition */
|
27
|
-
review_perspectives?: Array<{
|
28
|
-
perspective: string;
|
29
|
-
prompt: string;
|
30
|
-
}>;
|
31
|
-
}
|
32
|
-
|
33
|
-
/**
|
34
|
-
* State definition
|
35
|
-
*/
|
36
|
-
export interface YamlState {
|
37
|
-
/** Description of this state */
|
38
|
-
description: string;
|
39
|
-
|
40
|
-
/** Default instructions when entering this state */
|
41
|
-
default_instructions: string;
|
42
|
-
|
43
|
-
/** Transitions from this state */
|
44
|
-
transitions: YamlTransition[];
|
45
|
-
}
|
46
|
-
|
47
|
-
/**
|
48
|
-
* Complete state machine definition
|
49
|
-
*/
|
50
|
-
export interface YamlStateMachine {
|
51
|
-
/** Name of the state machine */
|
52
|
-
name: string;
|
53
|
-
|
54
|
-
/** Description of the state machine's purpose */
|
55
|
-
description: string;
|
56
|
-
|
57
|
-
/** The starting state of the machine */
|
58
|
-
initial_state: string;
|
59
|
-
|
60
|
-
/** Map of states in the state machine */
|
61
|
-
states: Record<string, YamlState>;
|
62
|
-
|
63
|
-
/** Optional metadata for enhanced discoverability */
|
64
|
-
metadata?: {
|
65
|
-
complexity?: 'low' | 'medium' | 'high';
|
66
|
-
domain: string;
|
67
|
-
bestFor?: string[];
|
68
|
-
useCases?: string[];
|
69
|
-
examples?: string[];
|
70
|
-
requiresDocumentation?: boolean;
|
71
|
-
};
|
72
|
-
}
|