@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.
Files changed (80) hide show
  1. package/package.json +8 -3
  2. package/resources/templates/architecture/arc42/arc42-template-EN.md +1077 -0
  3. package/resources/templates/architecture/arc42/images/01_2_iso-25010-topics-EN.drawio-2023.png +0 -0
  4. package/resources/templates/architecture/arc42/images/01_2_iso-25010-topics-EN.drawio.png +0 -0
  5. package/resources/templates/architecture/arc42/images/05_building_blocks-EN.png +0 -0
  6. package/resources/templates/architecture/arc42/images/08-concepts-EN.drawio.png +0 -0
  7. package/resources/templates/architecture/arc42/images/arc42-logo.png +0 -0
  8. package/resources/templates/architecture/c4.md +224 -0
  9. package/resources/templates/architecture/freestyle.md +53 -0
  10. package/resources/templates/architecture/none.md +17 -0
  11. package/resources/templates/design/comprehensive.md +207 -0
  12. package/resources/templates/design/freestyle.md +37 -0
  13. package/resources/templates/design/none.md +17 -0
  14. package/resources/templates/requirements/ears.md +90 -0
  15. package/resources/templates/requirements/freestyle.md +42 -0
  16. package/resources/templates/requirements/none.md +17 -0
  17. package/resources/workflows/big-bang-conversion.yaml +539 -0
  18. package/resources/workflows/boundary-testing.yaml +334 -0
  19. package/resources/workflows/bugfix.yaml +185 -0
  20. package/resources/workflows/business-analysis.yaml +671 -0
  21. package/resources/workflows/c4-analysis.yaml +485 -0
  22. package/resources/workflows/epcc.yaml +161 -0
  23. package/resources/workflows/greenfield.yaml +189 -0
  24. package/resources/workflows/minor.yaml +127 -0
  25. package/resources/workflows/posts.yaml +207 -0
  26. package/resources/workflows/slides.yaml +256 -0
  27. package/resources/workflows/tdd.yaml +157 -0
  28. package/resources/workflows/waterfall.yaml +195 -0
  29. package/.turbo/turbo-build.log +0 -4
  30. package/src/config-manager.ts +0 -96
  31. package/src/conversation-manager.ts +0 -489
  32. package/src/database.ts +0 -427
  33. package/src/file-detection-manager.ts +0 -302
  34. package/src/git-manager.ts +0 -64
  35. package/src/index.ts +0 -28
  36. package/src/instruction-generator.ts +0 -210
  37. package/src/interaction-logger.ts +0 -109
  38. package/src/logger.ts +0 -353
  39. package/src/path-validation-utils.ts +0 -261
  40. package/src/plan-manager.ts +0 -323
  41. package/src/project-docs-manager.ts +0 -523
  42. package/src/state-machine-loader.ts +0 -365
  43. package/src/state-machine-types.ts +0 -72
  44. package/src/state-machine.ts +0 -370
  45. package/src/system-prompt-generator.ts +0 -122
  46. package/src/template-manager.ts +0 -328
  47. package/src/transition-engine.ts +0 -386
  48. package/src/types.ts +0 -60
  49. package/src/workflow-manager.ts +0 -606
  50. package/test/unit/conversation-manager.test.ts +0 -179
  51. package/test/unit/custom-workflow-loading.test.ts +0 -174
  52. package/test/unit/directory-linking-and-extensions.test.ts +0 -338
  53. package/test/unit/file-linking-integration.test.ts +0 -256
  54. package/test/unit/git-commit-integration.test.ts +0 -91
  55. package/test/unit/git-manager.test.ts +0 -86
  56. package/test/unit/install-workflow.test.ts +0 -138
  57. package/test/unit/instruction-generator.test.ts +0 -247
  58. package/test/unit/list-workflows-filtering.test.ts +0 -68
  59. package/test/unit/none-template-functionality.test.ts +0 -224
  60. package/test/unit/project-docs-manager.test.ts +0 -337
  61. package/test/unit/state-machine-loader.test.ts +0 -234
  62. package/test/unit/template-manager.test.ts +0 -217
  63. package/test/unit/validate-workflow-name.test.ts +0 -150
  64. package/test/unit/workflow-domain-filtering.test.ts +0 -75
  65. package/test/unit/workflow-enum-generation.test.ts +0 -92
  66. package/test/unit/workflow-manager-enhanced-path-resolution.test.ts +0 -369
  67. package/test/unit/workflow-manager-path-resolution.test.ts +0 -150
  68. package/test/unit/workflow-migration.test.ts +0 -155
  69. package/test/unit/workflow-override-by-name.test.ts +0 -116
  70. package/test/unit/workflow-prioritization.test.ts +0 -38
  71. package/test/unit/workflow-validation.test.ts +0 -303
  72. package/test/utils/e2e-test-setup.ts +0 -453
  73. package/test/utils/run-server-in-dir.sh +0 -27
  74. package/test/utils/temp-files.ts +0 -308
  75. package/test/utils/test-access.ts +0 -79
  76. package/test/utils/test-helpers.ts +0 -286
  77. package/test/utils/test-setup.ts +0 -78
  78. package/tsconfig.build.json +0 -21
  79. package/tsconfig.json +0 -8
  80. package/vitest.config.ts +0 -18
@@ -1,328 +0,0 @@
1
- /**
2
- * Template Manager
3
- *
4
- * Handles loading and rendering of project document templates.
5
- * Supports different template formats for architecture, requirements, and design documents.
6
- */
7
-
8
- import { readFile, readdir, stat } from 'node:fs/promises';
9
- import { join, dirname } from 'node:path';
10
- import { fileURLToPath } from 'node:url';
11
- import { createRequire } from 'node:module';
12
- import { createLogger } from './logger.js';
13
-
14
- const logger = createLogger('TemplateManager');
15
-
16
- // Dynamic template types - will be populated from file system
17
- export type ArchitectureTemplate = string;
18
- export type RequirementsTemplate = string;
19
- export type DesignTemplate = string;
20
-
21
- export interface TemplateOptions {
22
- architecture?: ArchitectureTemplate;
23
- requirements?: RequirementsTemplate;
24
- design?: DesignTemplate;
25
- }
26
-
27
- export interface TemplateResult {
28
- content: string;
29
- additionalFiles?: Array<{
30
- relativePath: string;
31
- content: Buffer;
32
- }>;
33
- }
34
-
35
- export class TemplateManager {
36
- private templatesPath: string;
37
-
38
- constructor() {
39
- this.templatesPath = this.resolveTemplatesPath();
40
- }
41
-
42
- /**
43
- * Resolve templates path using similar strategy as WorkflowManager
44
- */
45
- private resolveTemplatesPath(): string {
46
- const strategies: string[] = [];
47
-
48
- // Strategy 1: Local resources directory (symlinked from root)
49
- strategies.push(
50
- join(dirname(fileURLToPath(import.meta.url)), '../resources/templates')
51
- );
52
-
53
- // Strategy 2: From compiled dist directory
54
- const currentFileUrl = import.meta.url;
55
- if (currentFileUrl.startsWith('file://')) {
56
- const currentFilePath = fileURLToPath(currentFileUrl);
57
- // From dist/template-manager.js -> ../resources/templates
58
- strategies.push(join(dirname(currentFilePath), '../resources/templates'));
59
- }
60
-
61
- // Strategy 3: Current working directory (for development)
62
- strategies.push(join(process.cwd(), 'resources/templates'));
63
-
64
- // Strategy 4: From node_modules
65
- strategies.push(
66
- join(
67
- process.cwd(),
68
- 'node_modules/@codemcp/workflows-core/resources/templates'
69
- )
70
- );
71
-
72
- // Strategy 5: From package directory (for development)
73
- try {
74
- const require = createRequire(import.meta.url);
75
- const packagePath = require.resolve(
76
- '@codemcp/workflows-core/package.json'
77
- );
78
- const packageDir = dirname(packagePath);
79
- strategies.push(join(packageDir, 'resources/templates'));
80
- } catch (_error) {
81
- // Ignore if package not found
82
- }
83
-
84
- // Find the first existing path
85
- for (const strategy of strategies) {
86
- try {
87
- // This will throw if path doesn't exist
88
- require('node:fs').accessSync(strategy);
89
- logger.debug('Using templates path', { path: strategy });
90
- return strategy;
91
- } catch (_error) {
92
- // Continue to next strategy
93
- }
94
- }
95
-
96
- // Fallback to first strategy if none found
97
- const fallback = strategies[0];
98
- logger.warn('No templates directory found, using fallback', {
99
- fallback,
100
- strategies,
101
- });
102
- return fallback;
103
- }
104
-
105
- /**
106
- * Get the default template options based on available templates
107
- */
108
- async getDefaults(): Promise<Required<TemplateOptions>> {
109
- const availableTemplates = await this.getAvailableTemplates();
110
-
111
- return {
112
- architecture: this.getPreferredTemplate(availableTemplates.architecture, [
113
- 'arc42',
114
- 'freestyle',
115
- ]),
116
- requirements: this.getPreferredTemplate(availableTemplates.requirements, [
117
- 'ears',
118
- 'freestyle',
119
- ]),
120
- design: this.getPreferredTemplate(availableTemplates.design, [
121
- 'comprehensive',
122
- 'freestyle',
123
- ]),
124
- };
125
- }
126
-
127
- /**
128
- * Get preferred template from available options, with fallback preferences
129
- */
130
- private getPreferredTemplate(
131
- available: string[],
132
- preferences: string[]
133
- ): string {
134
- // Try to find the first preference that's available
135
- for (const preference of preferences) {
136
- if (available.includes(preference)) {
137
- return preference;
138
- }
139
- }
140
-
141
- // If no preferences are available, return the first available template
142
- return available[0] || preferences[0];
143
- }
144
-
145
- /**
146
- * Load and render a template
147
- */
148
- async loadTemplate(
149
- type: 'architecture' | 'requirements' | 'design',
150
- template: string
151
- ): Promise<TemplateResult> {
152
- const templatePath = join(this.templatesPath, type, template);
153
- const templateFilePath = `${templatePath}.md`;
154
-
155
- try {
156
- // First try to check if it's a directory (like arc42)
157
- try {
158
- const stats = await stat(templatePath);
159
- if (stats.isDirectory()) {
160
- return await this.loadDirectoryTemplate(templatePath);
161
- }
162
- } catch (_error) {
163
- // Not a directory, continue to file check
164
- }
165
-
166
- // Try to load as a markdown file
167
- const content = await readFile(templateFilePath, 'utf-8');
168
- return { content };
169
- } catch (error) {
170
- logger.error(
171
- `Failed to load template: ${type}/${template}`,
172
- error as Error
173
- );
174
- throw new Error(
175
- `Template not found: ${type}/${template}. Tried: ${templatePath} (directory) and ${templateFilePath} (file)`
176
- );
177
- }
178
- }
179
-
180
- /**
181
- * Load a directory-based template (like arc42 with images)
182
- */
183
- private async loadDirectoryTemplate(
184
- templatePath: string
185
- ): Promise<TemplateResult> {
186
- const additionalFiles: Array<{ relativePath: string; content: Buffer }> =
187
- [];
188
-
189
- // Find the main markdown file
190
- const files = await readdir(templatePath);
191
- const markdownFile = files.find(file => file.endsWith('.md'));
192
-
193
- if (!markdownFile) {
194
- throw new Error(
195
- `No markdown file found in template directory: ${templatePath}`
196
- );
197
- }
198
-
199
- const content = await readFile(join(templatePath, markdownFile), 'utf-8');
200
-
201
- // Load additional files (like images)
202
- await this.loadAdditionalFiles(templatePath, '', additionalFiles);
203
-
204
- return {
205
- content,
206
- additionalFiles: additionalFiles.filter(
207
- file => !file.relativePath.endsWith('.md')
208
- ),
209
- };
210
- }
211
-
212
- /**
213
- * Recursively load additional files from template directory
214
- */
215
- private async loadAdditionalFiles(
216
- basePath: string,
217
- relativePath: string,
218
- additionalFiles: Array<{ relativePath: string; content: Buffer }>
219
- ): Promise<void> {
220
- const currentPath = join(basePath, relativePath);
221
- const items = await readdir(currentPath);
222
-
223
- for (const item of items) {
224
- const itemPath = join(currentPath, item);
225
- const itemRelativePath = relativePath ? join(relativePath, item) : item;
226
- const stats = await stat(itemPath);
227
-
228
- if (stats.isDirectory()) {
229
- // Recursively process subdirectories
230
- await this.loadAdditionalFiles(
231
- basePath,
232
- itemRelativePath,
233
- additionalFiles
234
- );
235
- } else if (!item.endsWith('.md')) {
236
- // Load non-markdown files as additional files
237
- const content = await readFile(itemPath);
238
- additionalFiles.push({
239
- relativePath: itemRelativePath,
240
- content,
241
- });
242
- }
243
- }
244
- }
245
-
246
- /**
247
- * Validate template options against available templates
248
- */
249
- async validateOptions(options: TemplateOptions): Promise<void> {
250
- const availableTemplates = await this.getAvailableTemplates();
251
-
252
- if (
253
- options.architecture &&
254
- !availableTemplates.architecture.includes(options.architecture)
255
- ) {
256
- throw new Error(
257
- `Invalid architecture template: ${options.architecture}. Valid options: ${availableTemplates.architecture.join(', ')}`
258
- );
259
- }
260
-
261
- if (
262
- options.requirements &&
263
- !availableTemplates.requirements.includes(options.requirements)
264
- ) {
265
- throw new Error(
266
- `Invalid requirements template: ${options.requirements}. Valid options: ${availableTemplates.requirements.join(', ')}`
267
- );
268
- }
269
-
270
- if (options.design && !availableTemplates.design.includes(options.design)) {
271
- throw new Error(
272
- `Invalid design template: ${options.design}. Valid options: ${availableTemplates.design.join(', ')}`
273
- );
274
- }
275
- }
276
-
277
- /**
278
- * Get available templates for each type by scanning the file system
279
- */
280
- async getAvailableTemplates(): Promise<{
281
- architecture: string[];
282
- requirements: string[];
283
- design: string[];
284
- }> {
285
- const result = {
286
- architecture: [] as string[],
287
- requirements: [] as string[],
288
- design: [] as string[],
289
- };
290
-
291
- try {
292
- // Scan each template type directory
293
- for (const [type, templates] of Object.entries(result)) {
294
- const typePath = join(this.templatesPath, type);
295
-
296
- try {
297
- const entries = await readdir(typePath, { withFileTypes: true });
298
-
299
- for (const entry of entries) {
300
- if (entry.isDirectory()) {
301
- // Directory-based template (like arc42)
302
- templates.push(entry.name);
303
- } else if (entry.isFile() && entry.name.endsWith('.md')) {
304
- // File-based template (like freestyle.md)
305
- const templateName = entry.name.replace('.md', '');
306
- templates.push(templateName);
307
- }
308
- }
309
-
310
- // Sort templates for consistent ordering
311
- templates.sort();
312
- } catch (error) {
313
- logger.warn(`Failed to scan templates for type: ${type}`, {
314
- typePath,
315
- error: error instanceof Error ? error.message : String(error),
316
- });
317
- }
318
- }
319
-
320
- logger.debug('Discovered available templates', result);
321
- return result;
322
- } catch (error) {
323
- logger.error('Failed to discover templates', error as Error);
324
- // Return empty arrays if discovery fails
325
- return result;
326
- }
327
- }
328
- }
@@ -1,386 +0,0 @@
1
- /**
2
- * Transition Engine
3
- *
4
- * Manages the development state machine and determines appropriate phase transitions.
5
- * Analyzes conversation context and user input to make intelligent phase decisions.
6
- */
7
-
8
- import { createLogger } from './logger.js';
9
- import { StateMachineLoader } from './state-machine-loader.js';
10
- import { WorkflowManager } from './workflow-manager.js';
11
- import type { ConversationState } from './types.js';
12
-
13
- const logger = createLogger('TransitionEngine');
14
-
15
- export interface TransitionContext {
16
- currentPhase: string;
17
- projectPath: string;
18
- conversationId: string;
19
- userInput?: string;
20
- context?: string;
21
- conversationSummary?: string;
22
- recentMessages?: Array<{ role: string; content: string }>;
23
- }
24
-
25
- export interface TransitionResult {
26
- newPhase: string;
27
- instructions: string;
28
- transitionReason: string;
29
- isModeled: boolean;
30
- }
31
-
32
- export class TransitionEngine {
33
- private stateMachineLoader: StateMachineLoader;
34
- private workflowManager: WorkflowManager;
35
- private conversationManager?: {
36
- hasInteractions: (conversationId: string) => Promise<boolean>;
37
- getConversationState: (
38
- conversationId: string
39
- ) => Promise<ConversationState | null>;
40
- };
41
-
42
- constructor(projectPath: string) {
43
- this.stateMachineLoader = new StateMachineLoader();
44
- this.workflowManager = new WorkflowManager();
45
-
46
- logger.info('TransitionEngine initialized', { projectPath });
47
- }
48
-
49
- /**
50
- * Set the conversation manager (dependency injection)
51
- */
52
- setConversationManager(conversationManager: {
53
- hasInteractions: (conversationId: string) => Promise<boolean>;
54
- getConversationState: (
55
- conversationId: string
56
- ) => Promise<ConversationState | null>;
57
- }) {
58
- this.conversationManager = conversationManager;
59
- }
60
-
61
- /**
62
- * Get the loaded state machine for the current project
63
- */
64
- getStateMachine(projectPath: string, workflowName?: string) {
65
- // Use WorkflowManager to load the appropriate workflow
66
- return this.workflowManager.loadWorkflowForProject(
67
- projectPath,
68
- workflowName
69
- );
70
- }
71
-
72
- /**
73
- * Check if this is the first call from initial state based on database interactions
74
- */
75
- private async isFirstCallFromInitialState(
76
- context: TransitionContext
77
- ): Promise<boolean> {
78
- // Get workflow name from conversation state
79
- const conversationState =
80
- await this.conversationManager?.getConversationState(
81
- context.conversationId
82
- );
83
- const workflowName = conversationState?.workflowName;
84
-
85
- const stateMachine = this.workflowManager.loadWorkflowForProject(
86
- context.projectPath,
87
- workflowName
88
- );
89
- const isInitialState = context.currentPhase === stateMachine.initial_state;
90
-
91
- if (!isInitialState) return false;
92
-
93
- // Check database for any previous interactions in this conversation
94
- if (!this.conversationManager) {
95
- logger.warn('ConversationManager not set, assuming first call');
96
- return true;
97
- }
98
-
99
- const hasInteractions = await this.conversationManager.hasInteractions(
100
- context.conversationId
101
- );
102
-
103
- logger.debug('Checking first call from initial state', {
104
- isInitialState,
105
- hasInteractions,
106
- conversationId: context.conversationId,
107
- currentPhase: context.currentPhase,
108
- });
109
-
110
- return !hasInteractions;
111
- }
112
-
113
- /**
114
- * Generate instructions for defining phase entrance criteria
115
- */
116
- private async generateCriteriaDefinitionInstructions(
117
- projectPath: string,
118
- conversationId: string
119
- ): Promise<string> {
120
- // Get workflow name from conversation state
121
- const conversationState =
122
- await this.conversationManager?.getConversationState(conversationId);
123
- const workflowName = conversationState?.workflowName;
124
-
125
- const stateMachine = this.workflowManager.loadWorkflowForProject(
126
- projectPath,
127
- workflowName
128
- );
129
- const phases = Object.keys(stateMachine.states);
130
-
131
- let instructions = `Welcome to ${stateMachine.name}!
132
-
133
- Before we begin development, let's establish clear entrance criteria for each phase. This will help us make informed decisions about when to transition between phases throughout the development process.
134
-
135
- Please update the plan file with a "Phase Entrance Criteria" section that defines specific, measurable criteria for entering each phase:
136
-
137
- ## Phase Entrance Criteria
138
-
139
- `;
140
-
141
- // Generate criteria template for each phase (except initial state)
142
- for (const phase of phases) {
143
- if (phase === stateMachine.initial_state) continue; // Skip initial state
144
-
145
- const phaseDefinition = stateMachine.states[phase];
146
- const capitalizedPhase = this.capitalizePhase(phase);
147
-
148
- instructions += `### ${capitalizedPhase} Phase
149
- *${phaseDefinition.description}*
150
-
151
- **Enter when:**
152
- - [ ] [Define specific criteria for entering ${phase} phase]
153
- - [ ] [Add measurable conditions that must be met]
154
- - [ ] [Include any deliverables or milestones required]
155
-
156
- `;
157
- }
158
-
159
- instructions += `
160
- Once you've defined these criteria, we can begin development. Throughout the process, consult these criteria when considering phase transitions.
161
-
162
- **Remember**: These criteria should be specific and measurable so we can clearly determine when each phase is ready to begin.`;
163
-
164
- return instructions;
165
- }
166
-
167
- /**
168
- * Get phase-specific instructions for continuing work in current phase
169
- */
170
- private async getContinuePhaseInstructions(
171
- phase: string,
172
- projectPath: string,
173
- conversationId: string
174
- ): Promise<string> {
175
- // Get workflow name from conversation state
176
- const conversationState =
177
- await this.conversationManager?.getConversationState(conversationId);
178
- const workflowName = conversationState?.workflowName;
179
-
180
- const stateMachine = this.workflowManager.loadWorkflowForProject(
181
- projectPath,
182
- workflowName
183
- );
184
-
185
- const stateDefinition = stateMachine.states[phase];
186
- if (!stateDefinition) {
187
- logger.error('Unknown phase', new Error(`Unknown phase: ${phase}`));
188
- throw new Error(`Unknown phase: ${phase}`);
189
- }
190
-
191
- const continueTransition = stateDefinition.transitions.find(
192
- t => t.to === phase
193
- );
194
-
195
- if (continueTransition) {
196
- // Use the transition instructions if available, otherwise use default + additional
197
- if (continueTransition.instructions) {
198
- return continueTransition.instructions;
199
- } else {
200
- let composedInstructions = stateDefinition.default_instructions;
201
- if (continueTransition.additional_instructions) {
202
- composedInstructions = `${composedInstructions}\n\n**Additional Context:**\n${continueTransition.additional_instructions}`;
203
- }
204
- return composedInstructions;
205
- }
206
- }
207
-
208
- // Fall back to default instructions for the phase
209
- return stateDefinition.default_instructions;
210
- }
211
- /**
212
- * Get the first development phase from the state machine
213
- */
214
- private async getFirstDevelopmentPhase(
215
- projectPath: string,
216
- conversationId: string
217
- ): Promise<string> {
218
- // Get workflow name from conversation state
219
- const conversationState =
220
- await this.conversationManager?.getConversationState(conversationId);
221
- const workflowName = conversationState?.workflowName;
222
-
223
- const stateMachine = this.workflowManager.loadWorkflowForProject(
224
- projectPath,
225
- workflowName
226
- );
227
- const initialState = stateMachine.initial_state;
228
-
229
- // The first development phase IS the initial state - we should stay there
230
- // Don't automatically transition to the first transition target
231
- return initialState;
232
- }
233
-
234
- /**
235
- * Analyze context and determine appropriate phase transition
236
- */
237
- async analyzePhaseTransition(
238
- context: TransitionContext
239
- ): Promise<TransitionResult> {
240
- const {
241
- currentPhase,
242
- projectPath,
243
- conversationId,
244
- userInput,
245
- context: additionalContext,
246
- conversationSummary,
247
- } = context;
248
-
249
- // Load the appropriate workflow for this project/conversation
250
-
251
- logger.debug('Analyzing phase transition', {
252
- currentPhase,
253
- projectPath,
254
- hasUserInput: !!userInput,
255
- hasContext: !!additionalContext,
256
- hasSummary: !!conversationSummary,
257
- userInput: userInput
258
- ? userInput.substring(0, 50) + (userInput.length > 50 ? '...' : '')
259
- : undefined,
260
- });
261
-
262
- // Check if this is the first call from initial state - transition to first development phase
263
- if (await this.isFirstCallFromInitialState(context)) {
264
- const firstDevelopmentPhase = await this.getFirstDevelopmentPhase(
265
- projectPath,
266
- conversationId
267
- );
268
-
269
- logger.info(
270
- 'First call from initial state - transitioning to first development phase with criteria',
271
- {
272
- currentPhase,
273
- firstDevelopmentPhase,
274
- projectPath,
275
- }
276
- );
277
-
278
- // Combine criteria definition with first phase instructions
279
- const criteriaInstructions =
280
- await this.generateCriteriaDefinitionInstructions(
281
- projectPath,
282
- conversationId
283
- );
284
- const phaseInstructions = await this.getContinuePhaseInstructions(
285
- firstDevelopmentPhase,
286
- projectPath,
287
- conversationId
288
- );
289
-
290
- return {
291
- newPhase: firstDevelopmentPhase, // Transition to first development phase
292
- instructions: criteriaInstructions + '\n\n---\n\n' + phaseInstructions,
293
- transitionReason:
294
- 'Starting development - defining criteria and beginning first phase',
295
- isModeled: true,
296
- };
297
- }
298
-
299
- // For all other cases, stay in current phase and let LLM decide based on plan file criteria
300
- // The LLM will consult the entrance criteria in the plan file and use proceed_to_phase when ready
301
- const continueInstructions = await this.getContinuePhaseInstructions(
302
- currentPhase,
303
- projectPath,
304
- conversationId
305
- );
306
-
307
- logger.debug(
308
- 'Continuing in current phase - LLM will evaluate transition criteria',
309
- {
310
- currentPhase,
311
- projectPath,
312
- }
313
- );
314
-
315
- return {
316
- newPhase: currentPhase,
317
- instructions: continueInstructions,
318
- transitionReason:
319
- 'Continue current phase - LLM will evaluate transition criteria from plan file',
320
- isModeled: false,
321
- };
322
- }
323
-
324
- /**
325
- * Handle explicit phase transition request
326
- */
327
- handleExplicitTransition(
328
- currentPhase: string,
329
- targetPhase: string,
330
- projectPath: string,
331
- reason?: string,
332
- workflowName?: string
333
- ): TransitionResult {
334
- // Load the appropriate state machine for this project/workflow
335
- const stateMachine = this.getStateMachine(projectPath, workflowName);
336
-
337
- logger.debug('Handling explicit phase transition', {
338
- currentPhase,
339
- targetPhase,
340
- projectPath,
341
- workflowName,
342
- reason,
343
- });
344
-
345
- // Validate that the target phase exists in the state machine
346
- if (!stateMachine.states[targetPhase]) {
347
- const validPhases = Object.keys(stateMachine.states);
348
- const errorMsg = `Invalid target phase: "${targetPhase}". Valid phases are: ${validPhases.join(', ')}`;
349
- logger.error('Invalid target phase', new Error(errorMsg));
350
- throw new Error(errorMsg);
351
- }
352
-
353
- // Get default instructions from the target state
354
- const targetState = stateMachine.states[targetPhase];
355
- const instructions = targetState.default_instructions;
356
- const transitionInfo = {
357
- instructions: instructions,
358
- transitionReason: reason || `Moving to ${targetPhase}`,
359
- isModeled: false, // Direct phase transitions are not modeled
360
- };
361
-
362
- logger.info('Explicit phase transition processed', {
363
- fromPhase: currentPhase,
364
- toPhase: targetPhase,
365
- reason: transitionInfo.transitionReason,
366
- isModeled: transitionInfo.isModeled,
367
- });
368
-
369
- return {
370
- newPhase: targetPhase,
371
- instructions: transitionInfo.instructions,
372
- transitionReason: reason || transitionInfo.transitionReason,
373
- isModeled: transitionInfo.isModeled,
374
- };
375
- }
376
-
377
- /**
378
- * Capitalize phase name for display
379
- */
380
- private capitalizePhase(phase: string): string {
381
- return phase
382
- .split('_')
383
- .map(word => word.charAt(0).toUpperCase() + word.slice(1))
384
- .join(' ');
385
- }
386
- }