@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,96 +0,0 @@
1
- /**
2
- * Configuration Manager
3
- *
4
- * Handles loading and validation of project configuration from .vibe/config.yaml
5
- */
6
-
7
- import fs from 'node:fs';
8
- import path from 'node:path';
9
- import yaml from 'js-yaml';
10
- import { createLogger } from './logger.js';
11
-
12
- const logger = createLogger('ConfigManager');
13
-
14
- export interface ProjectConfig {
15
- enabled_workflows?: string[];
16
- }
17
-
18
- /**
19
- * Manages project configuration loading and validation
20
- */
21
- export class ConfigManager {
22
- private static readonly CONFIG_FILENAME = 'config.yaml';
23
-
24
- /**
25
- * Load project configuration from .vibe/config.yaml
26
- * Returns null if no config file exists (backward compatibility)
27
- * Throws error for invalid configuration
28
- */
29
- public static loadProjectConfig(projectPath: string): ProjectConfig | null {
30
- const configPath = path.join(projectPath, '.vibe', this.CONFIG_FILENAME);
31
-
32
- // No config file = backward compatibility (all workflows available)
33
- if (!fs.existsSync(configPath)) {
34
- logger.debug('No config file found, using defaults', { configPath });
35
- return null;
36
- }
37
-
38
- try {
39
- const configContent = fs.readFileSync(configPath, 'utf-8');
40
- const config = yaml.load(configContent) as ProjectConfig;
41
-
42
- this.validateConfig(config, configPath);
43
-
44
- logger.info('Loaded project configuration', {
45
- configPath,
46
- enabledWorkflows: config.enabled_workflows?.length || 0,
47
- });
48
-
49
- return config;
50
- } catch (error) {
51
- if (error instanceof yaml.YAMLException) {
52
- throw new Error(
53
- `Invalid YAML in config file ${configPath}: ${error.message}`
54
- );
55
- }
56
- throw new Error(`Failed to load config file ${configPath}: ${error}`);
57
- }
58
- }
59
-
60
- /**
61
- * Validate configuration structure and content
62
- */
63
- private static validateConfig(
64
- config: ProjectConfig,
65
- configPath: string
66
- ): void {
67
- if (!config || typeof config !== 'object') {
68
- throw new Error(
69
- `Invalid config file ${configPath}: must be a YAML object`
70
- );
71
- }
72
-
73
- if (config.enabled_workflows !== undefined) {
74
- if (!Array.isArray(config.enabled_workflows)) {
75
- throw new Error(
76
- `Invalid config file ${configPath}: enabled_workflows must be an array`
77
- );
78
- }
79
-
80
- if (config.enabled_workflows.length === 0) {
81
- throw new Error(
82
- `Invalid config file ${configPath}: enabled_workflows cannot be empty`
83
- );
84
- }
85
-
86
- // Validate all entries are strings
87
- for (const workflow of config.enabled_workflows) {
88
- if (typeof workflow !== 'string' || workflow.trim() === '') {
89
- throw new Error(
90
- `Invalid config file ${configPath}: all workflow names must be non-empty strings`
91
- );
92
- }
93
- }
94
- }
95
- }
96
- }
@@ -1,489 +0,0 @@
1
- /**
2
- * Conversation Manager
3
- *
4
- * Handles conversation identification, state persistence, and coordination
5
- * between components. Generates unique conversation identifiers from
6
- * project path + git branch combination.
7
- */
8
-
9
- import { execSync } from 'node:child_process';
10
- import { resolve } from 'node:path';
11
- import { existsSync } from 'node:fs';
12
- import { createLogger } from './logger.js';
13
- import { Database } from './database.js';
14
- import type { ConversationState, ConversationContext } from './types.js';
15
- import { WorkflowManager } from './workflow-manager.js';
16
- import { PlanManager } from './plan-manager.js';
17
-
18
- const logger = createLogger('ConversationManager');
19
-
20
- export class ConversationManager {
21
- private database: Database;
22
- private projectPath: string;
23
- private workflowManager: WorkflowManager;
24
-
25
- constructor(
26
- database: Database,
27
- workflowManager: WorkflowManager,
28
- projectPath: string
29
- ) {
30
- this.database = database;
31
- this.workflowManager = workflowManager;
32
- this.projectPath = projectPath;
33
- }
34
-
35
- /**
36
- * Get conversation state by ID
37
- */
38
- async getConversationState(
39
- conversationId: string
40
- ): Promise<ConversationState | null> {
41
- return await this.database.getConversationState(conversationId);
42
- }
43
-
44
- /**
45
- * Get the current conversation context
46
- *
47
- * Detects the current project path and git branch, then retrieves an existing
48
- * conversation state for this context. Does NOT create a new conversation.
49
- *
50
- * @throws Error if no conversation exists for this context
51
- */
52
- async getConversationContext(): Promise<ConversationContext> {
53
- const projectPath = this.getProjectPath();
54
- const gitBranch = this.getGitBranch(projectPath);
55
-
56
- logger.debug('Getting conversation context', { projectPath, gitBranch });
57
-
58
- // Generate a unique conversation ID based on project path and git branch
59
- const conversationId = this.generateConversationId(projectPath, gitBranch);
60
-
61
- // Try to find existing conversation state
62
- const state = await this.database.getConversationState(conversationId);
63
-
64
- // If no existing state, throw an error - conversation must be created with start_development first
65
- if (!state) {
66
- logger.warn('No conversation found for context', {
67
- projectPath,
68
- gitBranch,
69
- conversationId,
70
- });
71
- throw new Error(
72
- 'No development conversation exists for this project. Use the start_development tool first to initialize development with a workflow.'
73
- );
74
- }
75
-
76
- // Return the conversation context
77
- return {
78
- conversationId: state.conversationId,
79
- projectPath: state.projectPath,
80
- gitBranch: state.gitBranch,
81
- currentPhase: state.currentPhase,
82
- planFilePath: state.planFilePath,
83
- workflowName: state.workflowName,
84
- };
85
- }
86
-
87
- /**
88
- * Create a new conversation context
89
- *
90
- * This should only be called by the start_development tool to explicitly
91
- * create a new conversation with a selected workflow.
92
- *
93
- * @param workflowName - The workflow to use for this conversation
94
- * @returns The newly created conversation context
95
- */
96
- async createConversationContext(
97
- workflowName: string
98
- ): Promise<ConversationContext> {
99
- const projectPath = this.getProjectPath();
100
- const gitBranch = this.getGitBranch(projectPath);
101
-
102
- logger.debug('Creating conversation context', {
103
- projectPath,
104
- gitBranch,
105
- workflowName,
106
- });
107
-
108
- // Generate a unique conversation ID based on project path and git branch
109
- const conversationId = this.generateConversationId(projectPath, gitBranch);
110
-
111
- // Check if a conversation already exists
112
- const existingState =
113
- await this.database.getConversationState(conversationId);
114
-
115
- if (existingState) {
116
- logger.debug('Conversation already exists, returning existing context', {
117
- conversationId,
118
- });
119
- return {
120
- conversationId: existingState.conversationId,
121
- projectPath: existingState.projectPath,
122
- gitBranch: existingState.gitBranch,
123
- currentPhase: existingState.currentPhase,
124
- planFilePath: existingState.planFilePath,
125
- workflowName: existingState.workflowName,
126
- };
127
- }
128
-
129
- // Create a new conversation state
130
- const state = await this.createNewConversationState(
131
- conversationId,
132
- projectPath,
133
- gitBranch,
134
- workflowName
135
- );
136
-
137
- // Return the conversation context
138
- return {
139
- conversationId: state.conversationId,
140
- projectPath: state.projectPath,
141
- gitBranch: state.gitBranch,
142
- currentPhase: state.currentPhase,
143
- planFilePath: state.planFilePath,
144
- workflowName: state.workflowName,
145
- };
146
- }
147
-
148
- /**
149
- * Update the conversation state
150
- *
151
- * @param conversationId - ID of the conversation to update
152
- * @param updates - Partial state updates to apply
153
- */
154
- async updateConversationState(
155
- conversationId: string,
156
- updates: Partial<
157
- Pick<
158
- ConversationState,
159
- | 'currentPhase'
160
- | 'planFilePath'
161
- | 'workflowName'
162
- | 'gitCommitConfig'
163
- | 'requireReviewsBeforePhaseTransition'
164
- >
165
- >
166
- ): Promise<void> {
167
- logger.debug('Updating conversation state', { conversationId, updates });
168
-
169
- // Get current state
170
- const currentState =
171
- await this.database.getConversationState(conversationId);
172
-
173
- if (!currentState) {
174
- throw new Error(`Conversation state not found for ID: ${conversationId}`);
175
- }
176
-
177
- // Apply updates
178
- const updatedState: ConversationState = {
179
- ...currentState,
180
- ...updates,
181
- updatedAt: new Date().toISOString(),
182
- };
183
-
184
- // Save updated state
185
- await this.database.saveConversationState(updatedState);
186
-
187
- logger.info('Conversation state updated', {
188
- conversationId,
189
- currentPhase: updatedState.currentPhase,
190
- });
191
- }
192
-
193
- /**
194
- * Create a new conversation state
195
- *
196
- * @param conversationId - ID for the new conversation
197
- * @param projectPath - Path to the project
198
- * @param gitBranch - Git branch name
199
- */
200
- private async createNewConversationState(
201
- conversationId: string,
202
- projectPath: string,
203
- gitBranch: string,
204
- workflowName: string = 'waterfall'
205
- ): Promise<ConversationState> {
206
- logger.info('Creating new conversation state', {
207
- conversationId,
208
- projectPath,
209
- gitBranch,
210
- });
211
-
212
- const timestamp = new Date().toISOString();
213
-
214
- // Generate a plan file path based on the branch name
215
- const planFileName =
216
- gitBranch === 'main' || gitBranch === 'master'
217
- ? 'development-plan.md'
218
- : `development-plan-${gitBranch}.md`;
219
-
220
- const planFilePath = resolve(projectPath, '.vibe', planFileName);
221
-
222
- // Get initial state from the appropriate workflow
223
- const stateMachine = this.workflowManager.loadWorkflowForProject(
224
- projectPath,
225
- workflowName
226
- );
227
- const initialPhase = stateMachine.initial_state;
228
-
229
- // Create new state
230
- const newState: ConversationState = {
231
- conversationId,
232
- projectPath,
233
- gitBranch,
234
- currentPhase: initialPhase,
235
- planFilePath,
236
- workflowName,
237
- requireReviewsBeforePhaseTransition: false, // Default to false for new conversations
238
- createdAt: timestamp,
239
- updatedAt: timestamp,
240
- };
241
-
242
- // Save to database
243
- await this.database.saveConversationState(newState);
244
-
245
- logger.info('New conversation state created', {
246
- conversationId,
247
- planFilePath,
248
- initialPhase,
249
- });
250
-
251
- return newState;
252
- }
253
-
254
- /**
255
- * Generate a unique conversation ID based on project path and git branch
256
- *
257
- * @param projectPath - Path to the project
258
- * @param gitBranch - Git branch name
259
- */
260
- private generateConversationId(
261
- projectPath: string,
262
- gitBranch: string
263
- ): string {
264
- // Extract project name from path
265
- const projectName = projectPath.split('/').pop() || 'unknown-project';
266
-
267
- // Clean branch name for use in ID
268
- const cleanBranch = gitBranch
269
- .replace(/[^a-zA-Z0-9-]/g, '-')
270
- .replace(/-+/g, '-')
271
- .replace(/^-|-$/g, '');
272
-
273
- // For tests, use a deterministic ID
274
- if (process.env.NODE_ENV === 'test') {
275
- return `${projectName}-${cleanBranch}-p423k1`;
276
- }
277
-
278
- // Generate a deterministic ID based on project path and branch
279
- // This ensures the same project/branch combination always gets the same conversation ID
280
- let hash = 0;
281
- const str = `${projectPath}:${gitBranch}`;
282
- for (let i = 0; i < str.length; i++) {
283
- const char = str.charCodeAt(i);
284
- hash = (hash << 5) - hash + char;
285
- hash = hash & hash; // Convert to 32-bit integer
286
- }
287
- const hashStr = Math.abs(hash).toString(36).substring(0, 6);
288
-
289
- return `${projectName}-${cleanBranch}-${hashStr}`;
290
- }
291
-
292
- /**
293
- * Get the current project path
294
- */
295
- private getProjectPath(): string {
296
- return this.projectPath;
297
- }
298
-
299
- /**
300
- * Get the current git branch for a project
301
- *
302
- * @param projectPath - Path to the project
303
- */
304
- private getGitBranch(projectPath: string): string {
305
- try {
306
- // Check if this is a git repository
307
- if (!existsSync(`${projectPath}/.git`)) {
308
- logger.debug('Not a git repository, using "default" as branch name', {
309
- projectPath,
310
- });
311
- return 'default';
312
- }
313
-
314
- // Get current branch name
315
- const branch = execSync('git rev-parse --abbrev-ref HEAD', {
316
- cwd: projectPath,
317
- encoding: 'utf-8',
318
- stdio: ['ignore', 'pipe', 'ignore'], // Suppress stderr to avoid "fatal: not a git repository" warnings
319
- }).trim();
320
-
321
- logger.debug('Detected git branch', { projectPath, branch });
322
-
323
- return branch;
324
- } catch (_error) {
325
- logger.debug('Failed to get git branch, using "default" as branch name', {
326
- projectPath,
327
- });
328
- return 'default';
329
- }
330
- }
331
-
332
- /**
333
- * Check if a conversation has any previous interactions
334
- * Used to determine if this is the first interaction in a conversation
335
- */
336
- async hasInteractions(conversationId: string): Promise<boolean> {
337
- try {
338
- // Get all interactions for this conversation
339
- const interactions =
340
- await this.database.getInteractionsByConversationId(conversationId);
341
- const count = interactions.length;
342
-
343
- logger.debug('Checked interaction count for conversation', {
344
- conversationId,
345
- count,
346
- });
347
-
348
- return count > 0;
349
- } catch (error) {
350
- logger.error('Failed to check interaction count', error as Error, {
351
- conversationId,
352
- });
353
- // If we can't check, assume this is the first interaction to be safe
354
- return false;
355
- }
356
- }
357
-
358
- /**
359
- * Reset conversation data (hybrid approach)
360
- */
361
- async resetConversation(
362
- confirm: boolean,
363
- reason?: string
364
- ): Promise<{
365
- success: boolean;
366
- resetItems: string[];
367
- conversationId: string;
368
- message: string;
369
- }> {
370
- logger.info('Starting conversation reset', { confirm, reason });
371
-
372
- // Validate reset request
373
- this.validateResetRequest(confirm);
374
-
375
- const context = await this.getConversationContext();
376
- const resetItems: string[] = [];
377
-
378
- try {
379
- // Step 1: Soft delete interaction logs
380
- await this.database.softDeleteInteractionLogs(context.conversationId);
381
- resetItems.push('interaction_logs');
382
- logger.debug('Interaction logs soft deleted');
383
-
384
- // Step 2: Hard delete conversation state
385
- await this.database.deleteConversationState(context.conversationId);
386
- resetItems.push('conversation_state');
387
- logger.debug('Conversation state hard deleted');
388
-
389
- // Step 3: Hard delete plan file
390
- const planManager = new PlanManager();
391
- await planManager.deletePlanFile(context.planFilePath);
392
- resetItems.push('plan_file');
393
- logger.debug('Plan file deleted');
394
-
395
- // Verify cleanup
396
- await this.verifyResetCleanup(
397
- context.conversationId,
398
- context.planFilePath
399
- );
400
-
401
- const message = `Successfully reset conversation ${context.conversationId}. Reset items: ${resetItems.join(', ')}${reason ? `. Reason: ${reason}` : ''}`;
402
-
403
- logger.info('Conversation reset completed successfully', {
404
- conversationId: context.conversationId,
405
- resetItems,
406
- reason,
407
- });
408
-
409
- return {
410
- success: true,
411
- resetItems,
412
- conversationId: context.conversationId,
413
- message,
414
- };
415
- } catch (error) {
416
- logger.error('Failed to reset conversation', error as Error, {
417
- conversationId: context.conversationId,
418
- resetItems,
419
- reason,
420
- });
421
-
422
- throw new Error(
423
- `Reset failed: ${error instanceof Error ? error.message : 'Unknown error'}`
424
- );
425
- }
426
- }
427
-
428
- /**
429
- * Validate reset request parameters
430
- */
431
- private validateResetRequest(confirm: boolean): void {
432
- if (!confirm) {
433
- throw new Error(
434
- 'Reset operation requires explicit confirmation. Set confirm parameter to true.'
435
- );
436
- }
437
- }
438
-
439
- /**
440
- * Verify that reset cleanup was successful
441
- */
442
- private async verifyResetCleanup(
443
- conversationId: string,
444
- planFilePath: string
445
- ): Promise<void> {
446
- logger.debug('Verifying reset cleanup', { conversationId, planFilePath });
447
-
448
- try {
449
- // Check that conversation state is deleted
450
- const state = await this.database.getConversationState(conversationId);
451
- if (state) {
452
- throw new Error('Conversation state was not properly deleted');
453
- }
454
-
455
- // Check that plan file is deleted
456
- const planManager = new PlanManager();
457
- const isDeleted = await planManager.ensurePlanFileDeleted(planFilePath);
458
- if (!isDeleted) {
459
- throw new Error('Plan file was not properly deleted');
460
- }
461
-
462
- logger.debug('Reset cleanup verification successful');
463
- } catch (error) {
464
- logger.error('Reset cleanup verification failed', error as Error);
465
- throw error;
466
- }
467
- }
468
-
469
- /**
470
- * Clean up conversation data (used internally)
471
- */
472
- async cleanupConversationData(conversationId: string): Promise<void> {
473
- logger.debug('Cleaning up conversation data', { conversationId });
474
-
475
- try {
476
- // This method can be used for additional cleanup if needed
477
- // Currently, the main cleanup is handled by resetConversation
478
- await this.database.softDeleteInteractionLogs(conversationId);
479
- await this.database.deleteConversationState(conversationId);
480
-
481
- logger.debug('Conversation data cleanup completed', { conversationId });
482
- } catch (error) {
483
- logger.error('Failed to cleanup conversation data', error as Error, {
484
- conversationId,
485
- });
486
- throw error;
487
- }
488
- }
489
- }