@codemcp/workflows-core 3.1.16

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 (114) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/LICENSE +674 -0
  3. package/dist/config-manager.d.ts +24 -0
  4. package/dist/config-manager.js +68 -0
  5. package/dist/config-manager.js.map +1 -0
  6. package/dist/conversation-manager.d.ts +97 -0
  7. package/dist/conversation-manager.js +367 -0
  8. package/dist/conversation-manager.js.map +1 -0
  9. package/dist/database.d.ts +73 -0
  10. package/dist/database.js +500 -0
  11. package/dist/database.js.map +1 -0
  12. package/dist/file-detection-manager.d.ts +53 -0
  13. package/dist/file-detection-manager.js +221 -0
  14. package/dist/file-detection-manager.js.map +1 -0
  15. package/dist/git-manager.d.ts +14 -0
  16. package/dist/git-manager.js +59 -0
  17. package/dist/git-manager.js.map +1 -0
  18. package/dist/index.d.ts +19 -0
  19. package/dist/index.js +25 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/instruction-generator.d.ts +69 -0
  22. package/dist/instruction-generator.js +133 -0
  23. package/dist/instruction-generator.js.map +1 -0
  24. package/dist/interaction-logger.d.ts +37 -0
  25. package/dist/interaction-logger.js +87 -0
  26. package/dist/interaction-logger.js.map +1 -0
  27. package/dist/logger.d.ts +64 -0
  28. package/dist/logger.js +283 -0
  29. package/dist/logger.js.map +1 -0
  30. package/dist/path-validation-utils.d.ts +51 -0
  31. package/dist/path-validation-utils.js +202 -0
  32. package/dist/path-validation-utils.js.map +1 -0
  33. package/dist/plan-manager.d.ts +65 -0
  34. package/dist/plan-manager.js +256 -0
  35. package/dist/plan-manager.js.map +1 -0
  36. package/dist/project-docs-manager.d.ts +119 -0
  37. package/dist/project-docs-manager.js +357 -0
  38. package/dist/project-docs-manager.js.map +1 -0
  39. package/dist/state-machine-loader.d.ts +60 -0
  40. package/dist/state-machine-loader.js +235 -0
  41. package/dist/state-machine-loader.js.map +1 -0
  42. package/dist/state-machine-types.d.ts +58 -0
  43. package/dist/state-machine-types.js +7 -0
  44. package/dist/state-machine-types.js.map +1 -0
  45. package/dist/state-machine.d.ts +52 -0
  46. package/dist/state-machine.js +256 -0
  47. package/dist/state-machine.js.map +1 -0
  48. package/dist/system-prompt-generator.d.ts +17 -0
  49. package/dist/system-prompt-generator.js +113 -0
  50. package/dist/system-prompt-generator.js.map +1 -0
  51. package/dist/template-manager.d.ts +61 -0
  52. package/dist/template-manager.js +229 -0
  53. package/dist/template-manager.js.map +1 -0
  54. package/dist/transition-engine.d.ts +70 -0
  55. package/dist/transition-engine.js +240 -0
  56. package/dist/transition-engine.js.map +1 -0
  57. package/dist/types.d.ts +56 -0
  58. package/dist/types.js +5 -0
  59. package/dist/types.js.map +1 -0
  60. package/dist/workflow-manager.d.ts +89 -0
  61. package/dist/workflow-manager.js +466 -0
  62. package/dist/workflow-manager.js.map +1 -0
  63. package/package.json +27 -0
  64. package/src/config-manager.ts +96 -0
  65. package/src/conversation-manager.ts +492 -0
  66. package/src/database.ts +685 -0
  67. package/src/file-detection-manager.ts +302 -0
  68. package/src/git-manager.ts +64 -0
  69. package/src/index.ts +28 -0
  70. package/src/instruction-generator.ts +210 -0
  71. package/src/interaction-logger.ts +109 -0
  72. package/src/logger.ts +353 -0
  73. package/src/path-validation-utils.ts +261 -0
  74. package/src/plan-manager.ts +323 -0
  75. package/src/project-docs-manager.ts +522 -0
  76. package/src/state-machine-loader.ts +308 -0
  77. package/src/state-machine-types.ts +72 -0
  78. package/src/state-machine.ts +370 -0
  79. package/src/system-prompt-generator.ts +122 -0
  80. package/src/template-manager.ts +321 -0
  81. package/src/transition-engine.ts +386 -0
  82. package/src/types.ts +60 -0
  83. package/src/workflow-manager.ts +601 -0
  84. package/test/unit/conversation-manager.test.ts +179 -0
  85. package/test/unit/custom-workflow-loading.test.ts +174 -0
  86. package/test/unit/directory-linking-and-extensions.test.ts +338 -0
  87. package/test/unit/file-linking-integration.test.ts +256 -0
  88. package/test/unit/git-commit-integration.test.ts +91 -0
  89. package/test/unit/git-manager.test.ts +86 -0
  90. package/test/unit/install-workflow.test.ts +138 -0
  91. package/test/unit/instruction-generator.test.ts +247 -0
  92. package/test/unit/list-workflows-filtering.test.ts +68 -0
  93. package/test/unit/none-template-functionality.test.ts +224 -0
  94. package/test/unit/project-docs-manager.test.ts +337 -0
  95. package/test/unit/state-machine-loader.test.ts +234 -0
  96. package/test/unit/template-manager.test.ts +217 -0
  97. package/test/unit/validate-workflow-name.test.ts +150 -0
  98. package/test/unit/workflow-domain-filtering.test.ts +75 -0
  99. package/test/unit/workflow-enum-generation.test.ts +92 -0
  100. package/test/unit/workflow-manager-enhanced-path-resolution.test.ts +369 -0
  101. package/test/unit/workflow-manager-path-resolution.test.ts +150 -0
  102. package/test/unit/workflow-migration.test.ts +155 -0
  103. package/test/unit/workflow-override-by-name.test.ts +116 -0
  104. package/test/unit/workflow-prioritization.test.ts +38 -0
  105. package/test/unit/workflow-validation.test.ts +303 -0
  106. package/test/utils/e2e-test-setup.ts +453 -0
  107. package/test/utils/run-server-in-dir.sh +27 -0
  108. package/test/utils/temp-files.ts +308 -0
  109. package/test/utils/test-access.ts +79 -0
  110. package/test/utils/test-helpers.ts +286 -0
  111. package/test/utils/test-setup.ts +78 -0
  112. package/tsconfig.build.json +21 -0
  113. package/tsconfig.json +8 -0
  114. package/vitest.config.ts +18 -0
package/src/types.ts ADDED
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Common types used across the application
3
+ */
4
+
5
+ /**
6
+ * Interface for interaction log entries
7
+ */
8
+ export interface InteractionLog {
9
+ id?: number;
10
+ conversationId: string;
11
+ toolName: string;
12
+ inputParams: string;
13
+ responseData: string;
14
+ currentPhase: string;
15
+ timestamp: string;
16
+ isReset?: boolean;
17
+ resetAt?: string;
18
+ }
19
+
20
+ /**
21
+ * Interface for conversation state
22
+ */
23
+ /**
24
+ * Git commit configuration options
25
+ */
26
+ export interface GitCommitConfig {
27
+ enabled: boolean;
28
+ commitOnStep: boolean; // Commit after each step (before whats_next)
29
+ commitOnPhase: boolean; // Commit after each phase (before phase transition)
30
+ commitOnComplete: boolean; // Final commit at development end with rebase+squash
31
+ initialMessage: string; // Initial user message for commit context
32
+ startCommitHash?: string; // Hash of commit when development started (for squashing)
33
+ }
34
+
35
+ export interface ConversationState {
36
+ conversationId: string;
37
+ projectPath: string;
38
+ gitBranch: string;
39
+ currentPhase: string;
40
+ planFilePath: string;
41
+ workflowName: string;
42
+ gitCommitConfig?: GitCommitConfig;
43
+ requireReviewsBeforePhaseTransition: boolean;
44
+ createdAt: string;
45
+ updatedAt: string;
46
+ }
47
+
48
+ /**
49
+ * Interface for conversation context
50
+ */
51
+ export interface ConversationContext {
52
+ conversationId: string;
53
+ projectPath: string;
54
+ gitBranch: string;
55
+ currentPhase: string;
56
+ planFilePath: string;
57
+ workflowName: string;
58
+ gitCommitConfig?: GitCommitConfig;
59
+ requireReviewsBeforePhaseTransition?: boolean;
60
+ }
@@ -0,0 +1,601 @@
1
+ /**
2
+ * Workflow Manager
3
+ *
4
+ * Manages multiple predefined workflows and provides workflow discovery and selection
5
+ */
6
+
7
+ import fs from 'node:fs';
8
+ import path from 'node:path';
9
+ import { createRequire } from 'node:module';
10
+ import { fileURLToPath } from 'node:url';
11
+ import { createLogger } from './logger.js';
12
+ import { StateMachineLoader } from './state-machine-loader.js';
13
+ import { YamlStateMachine } from './state-machine-types.js';
14
+ import { ConfigManager } from './config-manager.js';
15
+
16
+ const logger = createLogger('WorkflowManager');
17
+
18
+ export interface WorkflowInfo {
19
+ name: string;
20
+ displayName: string;
21
+ description: string;
22
+ initialState: string;
23
+ phases: string[];
24
+ // Enhanced metadata for better discoverability
25
+ metadata?: {
26
+ domain?: string;
27
+ complexity?: 'low' | 'medium' | 'high';
28
+ bestFor?: string[];
29
+ useCases?: string[];
30
+ examples?: string[];
31
+ };
32
+ }
33
+
34
+ /**
35
+ * Manages predefined workflows and provides workflow discovery
36
+ */
37
+ export class WorkflowManager {
38
+ private predefinedWorkflows: Map<string, YamlStateMachine> = new Map();
39
+ private projectWorkflows: Map<string, YamlStateMachine> = new Map();
40
+ private workflowInfos: Map<string, WorkflowInfo> = new Map();
41
+ private stateMachineLoader: StateMachineLoader;
42
+ private lastProjectPath: string | null = null; // Track last loaded project path
43
+ private enabledDomains: Set<string>;
44
+
45
+ constructor() {
46
+ this.stateMachineLoader = new StateMachineLoader();
47
+ this.enabledDomains = this.parseEnabledDomains();
48
+ this.loadPredefinedWorkflows();
49
+ }
50
+
51
+ /**
52
+ * Parse enabled domains from environment variable
53
+ */
54
+ private parseEnabledDomains(): Set<string> {
55
+ const domainsEnv = process.env.VIBE_WORKFLOW_DOMAINS;
56
+ if (!domainsEnv) {
57
+ return new Set(['code']);
58
+ }
59
+
60
+ return new Set(
61
+ domainsEnv
62
+ .split(',')
63
+ .map(d => d.trim())
64
+ .filter(d => d)
65
+ );
66
+ }
67
+
68
+ /**
69
+ * Load project-specific workflows from .vibe/workflows/
70
+ */
71
+ public loadProjectWorkflows(projectPath: string): void {
72
+ // Clear project workflows cache if project path changed
73
+ if (this.lastProjectPath !== projectPath) {
74
+ this.projectWorkflows.clear();
75
+ this.lastProjectPath = projectPath;
76
+ }
77
+
78
+ // First, migrate any legacy workflow files
79
+ this.migrateLegacyWorkflow(projectPath);
80
+
81
+ const workflowsDir = path.join(projectPath, '.vibe', 'workflows');
82
+
83
+ if (!fs.existsSync(workflowsDir)) {
84
+ return;
85
+ }
86
+
87
+ try {
88
+ const files = fs.readdirSync(workflowsDir);
89
+ const yamlFiles = files.filter(
90
+ file => file.endsWith('.yaml') || file.endsWith('.yml')
91
+ );
92
+
93
+ for (const file of yamlFiles) {
94
+ try {
95
+ const filePath = path.join(workflowsDir, file);
96
+ const workflow = this.stateMachineLoader.loadFromFile(filePath);
97
+ const workflowName = workflow.name; // Use name from YAML, not filename
98
+
99
+ // Project workflows are always loaded (no domain filtering)
100
+ this.projectWorkflows.set(workflowName, workflow);
101
+
102
+ const workflowInfo: WorkflowInfo = {
103
+ name: workflowName,
104
+ displayName: workflow.name,
105
+ description: workflow.description,
106
+ initialState: workflow.initial_state,
107
+ phases: Object.keys(workflow.states),
108
+ metadata: workflow.metadata,
109
+ };
110
+
111
+ this.workflowInfos.set(workflowName, workflowInfo);
112
+
113
+ logger.info('Loaded project workflow', {
114
+ name: workflowName,
115
+ domain: workflow.metadata?.domain,
116
+ });
117
+ } catch (error) {
118
+ logger.error('Failed to load project workflow', error as Error, {
119
+ file,
120
+ });
121
+ }
122
+ }
123
+ } catch (error) {
124
+ logger.error(
125
+ 'Failed to scan project workflows directory',
126
+ error as Error,
127
+ { workflowsDir }
128
+ );
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Migrate legacy workflow.yaml to new workflows directory
134
+ */
135
+ private migrateLegacyWorkflow(projectPath: string): void {
136
+ const legacyPaths = [
137
+ path.join(projectPath, '.vibe', 'workflow.yaml'),
138
+ path.join(projectPath, '.vibe', 'workflow.yml'),
139
+ ];
140
+
141
+ const workflowsDir = path.join(projectPath, '.vibe', 'workflows');
142
+ const targetPath = path.join(workflowsDir, 'custom.yaml');
143
+
144
+ for (const legacyPath of legacyPaths) {
145
+ if (fs.existsSync(legacyPath) && !fs.existsSync(targetPath)) {
146
+ try {
147
+ // Create workflows directory if it doesn't exist
148
+ if (!fs.existsSync(workflowsDir)) {
149
+ fs.mkdirSync(workflowsDir, { recursive: true });
150
+ }
151
+
152
+ // Copy the file to new location
153
+ fs.copyFileSync(legacyPath, targetPath);
154
+
155
+ // Remove the old file
156
+ fs.unlinkSync(legacyPath);
157
+
158
+ logger.info('Migrated legacy workflow to new location', {
159
+ from: legacyPath,
160
+ to: targetPath,
161
+ });
162
+ break;
163
+ } catch (_error) {
164
+ logger.error('Failed to migrate legacy workflow');
165
+ }
166
+ }
167
+ }
168
+ }
169
+ /**
170
+ * Get all available workflows regardless of domain filtering
171
+ */
172
+ public getAllAvailableWorkflows(): WorkflowInfo[] {
173
+ // Create a temporary manager with all domains enabled
174
+ const originalEnv = process.env.VIBE_WORKFLOW_DOMAINS;
175
+ process.env.VIBE_WORKFLOW_DOMAINS = 'code,architecture,office';
176
+
177
+ try {
178
+ const tempManager = new WorkflowManager();
179
+ return tempManager.getAvailableWorkflows();
180
+ } finally {
181
+ if (originalEnv !== undefined) {
182
+ process.env.VIBE_WORKFLOW_DOMAINS = originalEnv;
183
+ } else {
184
+ delete process.env.VIBE_WORKFLOW_DOMAINS;
185
+ }
186
+ }
187
+ }
188
+
189
+ public getAvailableWorkflows(): WorkflowInfo[] {
190
+ return Array.from(this.workflowInfos.values());
191
+ }
192
+
193
+ /**
194
+ * Get available workflows for a specific project
195
+ * Applies configuration filtering and loads project-specific workflows
196
+ */
197
+ public getAvailableWorkflowsForProject(projectPath: string): WorkflowInfo[] {
198
+ // Load project workflows first
199
+ this.loadProjectWorkflows(projectPath);
200
+
201
+ const allWorkflows = this.getAvailableWorkflows();
202
+
203
+ // Load project configuration
204
+ const config = ConfigManager.loadProjectConfig(projectPath);
205
+
206
+ // Apply configuration filtering if enabled_workflows is specified
207
+ let filteredWorkflows = allWorkflows;
208
+ if (config?.enabled_workflows) {
209
+ // Validate that all configured workflows exist
210
+ for (const workflowName of config.enabled_workflows) {
211
+ if (
212
+ workflowName !== 'custom' &&
213
+ !this.isPredefinedWorkflow(workflowName)
214
+ ) {
215
+ throw new Error(
216
+ `Invalid workflow '${workflowName}' in configuration. Available workflows: ${this.getWorkflowNames().join(', ')}, custom`
217
+ );
218
+ }
219
+ }
220
+
221
+ // Filter to only enabled workflows
222
+ filteredWorkflows = allWorkflows.filter(
223
+ w => config.enabled_workflows?.includes(w.name) ?? false
224
+ );
225
+ }
226
+
227
+ // Handle custom workflow (only if custom is in enabled list or no config)
228
+ const customEnabled =
229
+ !config?.enabled_workflows || config.enabled_workflows.includes('custom');
230
+ if (customEnabled) {
231
+ const hasCustomWorkflow = this.validateWorkflowName(
232
+ 'custom',
233
+ projectPath
234
+ );
235
+ if (hasCustomWorkflow) {
236
+ // Add custom workflow to the list if it exists and is enabled
237
+ const customWorkflowInfo: WorkflowInfo = {
238
+ name: 'custom',
239
+ displayName: 'Custom Workflow',
240
+ description: 'Project-specific custom workflow',
241
+ initialState: 'unknown', // Will be determined when loaded
242
+ phases: [], // Will be determined when loaded
243
+ };
244
+ filteredWorkflows.push(customWorkflowInfo);
245
+ }
246
+ }
247
+
248
+ return filteredWorkflows;
249
+ }
250
+
251
+ /**
252
+ * Get workflow information by name
253
+ */
254
+ public getWorkflowInfo(name: string): WorkflowInfo | undefined {
255
+ return this.workflowInfos.get(name);
256
+ }
257
+
258
+ /**
259
+ * Get a specific workflow by name (checks both predefined and project workflows)
260
+ */
261
+ public getWorkflow(name: string): YamlStateMachine | undefined {
262
+ return (
263
+ this.projectWorkflows.get(name) || this.predefinedWorkflows.get(name)
264
+ );
265
+ }
266
+
267
+ /**
268
+ * Check if a workflow name is a predefined workflow
269
+ */
270
+ public isPredefinedWorkflow(name: string): boolean {
271
+ return this.predefinedWorkflows.has(name);
272
+ }
273
+
274
+ /**
275
+ * Get workflow names as enum values for tool schema
276
+ * Includes both predefined and project workflows
277
+ */
278
+ public getWorkflowNames(): string[] {
279
+ const predefinedNames = Array.from(this.predefinedWorkflows.keys());
280
+ const projectNames = Array.from(this.projectWorkflows.keys());
281
+
282
+ // Combine and deduplicate (project workflows override predefined ones)
283
+ const allNames = [...predefinedNames];
284
+ for (const projectName of projectNames) {
285
+ if (!allNames.includes(projectName)) {
286
+ allNames.push(projectName);
287
+ }
288
+ }
289
+
290
+ return allNames;
291
+ }
292
+
293
+ /**
294
+ * Load a workflow (predefined or custom) for a project
295
+ * FIXED: Now respects the workflow parameter correctly
296
+ */
297
+ public loadWorkflowForProject(
298
+ projectPath: string,
299
+ workflowName?: string
300
+ ): YamlStateMachine {
301
+ // Load project workflows first
302
+ this.loadProjectWorkflows(projectPath);
303
+
304
+ // If no workflow specified, use first available workflow
305
+ if (!workflowName) {
306
+ const availableWorkflows =
307
+ this.getAvailableWorkflowsForProject(projectPath);
308
+ if (availableWorkflows.length === 0) {
309
+ throw new Error(
310
+ 'No workflows available. Please install a workflow or adjust VIBE_WORKFLOW_DOMAINS environment variable.'
311
+ );
312
+ }
313
+ workflowName = availableWorkflows[0].name;
314
+ }
315
+
316
+ // If it's a predefined workflow, return it
317
+ if (this.isPredefinedWorkflow(workflowName)) {
318
+ const workflow = this.getWorkflow(workflowName);
319
+ if (workflow) {
320
+ logger.info('Loading predefined workflow', { workflowName });
321
+ return workflow;
322
+ }
323
+ }
324
+
325
+ // Check project workflows
326
+ if (this.projectWorkflows.has(workflowName)) {
327
+ const workflow = this.projectWorkflows.get(workflowName);
328
+ if (workflow) {
329
+ logger.info('Loading project workflow', { workflowName });
330
+ return workflow;
331
+ }
332
+ }
333
+
334
+ throw new Error(`Unknown workflow: ${workflowName}`);
335
+ }
336
+
337
+ /**
338
+ * Find the workflows directory using multiple strategies
339
+ * This handles both development and npm package deployment scenarios
340
+ */
341
+ private findWorkflowsDirectory(): string | null {
342
+ const currentFileUrl = import.meta.url;
343
+ const currentFilePath = fileURLToPath(currentFileUrl);
344
+ const strategies: string[] = [];
345
+
346
+ // Strategy 1: Relative to current file (development and direct npm scenarios)
347
+ // From packages/core/dist/workflow-manager.js -> ../../../../resources/workflows
348
+ strategies.push(
349
+ path.resolve(
350
+ path.dirname(currentFilePath),
351
+ '../../../../resources/workflows'
352
+ )
353
+ );
354
+
355
+ // Strategy 2: Find package root by looking for package.json with our package name
356
+ let currentDir = path.dirname(currentFilePath);
357
+ for (let i = 0; i < 10; i++) {
358
+ // Limit search depth
359
+ const packageJsonPath = path.join(currentDir, 'package.json');
360
+ if (fs.existsSync(packageJsonPath)) {
361
+ try {
362
+ const packageJson = JSON.parse(
363
+ fs.readFileSync(packageJsonPath, 'utf-8')
364
+ );
365
+ if (packageJson.name === 'responsible-vibe-mcp') {
366
+ strategies.push(path.join(currentDir, 'resources/workflows'));
367
+ break;
368
+ }
369
+ } catch (_error) {
370
+ // Ignore JSON parse errors and continue searching
371
+ }
372
+ }
373
+ const parentDir = path.dirname(currentDir);
374
+ if (parentDir === currentDir) break; // Reached filesystem root
375
+ currentDir = parentDir;
376
+ }
377
+
378
+ // Strategy 3: Common npm installation paths
379
+ // Local node_modules (when used as dependency)
380
+ strategies.push(
381
+ path.join(
382
+ process.cwd(),
383
+ 'node_modules/responsible-vibe-mcp/resources/workflows'
384
+ )
385
+ );
386
+
387
+ // Global npm installation (when installed globally)
388
+ if (process.env.NODE_PATH) {
389
+ strategies.push(
390
+ path.join(
391
+ process.env.NODE_PATH,
392
+ 'responsible-vibe-mcp/resources/workflows'
393
+ )
394
+ );
395
+ }
396
+
397
+ // Strategy 4: npx cache locations (for npx responsible-vibe-mcp@latest)
398
+ // npx typically caches packages in ~/.npm/_npx or similar locations
399
+ const homeDir = process.env.HOME || process.env.USERPROFILE;
400
+ if (homeDir) {
401
+ // Common npx cache locations
402
+ const npxCachePaths = [
403
+ path.join(homeDir, '.npm/_npx'),
404
+ path.join(homeDir, '.npm/_cacache'),
405
+ path.join(homeDir, 'AppData/Local/npm-cache/_npx'), // Windows
406
+ path.join(homeDir, 'Library/Caches/npm/_npx'), // macOS
407
+ ];
408
+
409
+ for (const cachePath of npxCachePaths) {
410
+ if (fs.existsSync(cachePath)) {
411
+ try {
412
+ // Look for responsible-vibe-mcp in cache subdirectories
413
+ const cacheEntries = fs.readdirSync(cachePath);
414
+ for (const entry of cacheEntries) {
415
+ const entryPath = path.join(cachePath, entry);
416
+ if (fs.statSync(entryPath).isDirectory()) {
417
+ // Look for our package in this cache entry
418
+ const possiblePaths = [
419
+ path.join(
420
+ entryPath,
421
+ 'node_modules/responsible-vibe-mcp/resources/workflows'
422
+ ),
423
+ path.join(
424
+ entryPath,
425
+ 'responsible-vibe-mcp/resources/workflows'
426
+ ),
427
+ ];
428
+ strategies.push(...possiblePaths);
429
+ }
430
+ }
431
+ } catch (_error) {
432
+ // Ignore errors reading cache directories
433
+ }
434
+ }
435
+ }
436
+ }
437
+
438
+ // Strategy 5: Look in the directory where the current executable is located
439
+ // This handles cases where npx runs the package from a temporary location
440
+ const executableDir = path.dirname(process.argv[1] || '');
441
+ if (executableDir) {
442
+ strategies.push(path.join(executableDir, '../resources/workflows'));
443
+ strategies.push(path.join(executableDir, 'resources/workflows'));
444
+ }
445
+
446
+ // Strategy 6: Use require.resolve to find the package location
447
+ try {
448
+ // Try to resolve the package.json of our own package
449
+ const require = createRequire(import.meta.url);
450
+ const packagePath = require.resolve('responsible-vibe-mcp/package.json');
451
+ const packageDir = path.dirname(packagePath);
452
+ strategies.push(path.join(packageDir, 'resources/workflows'));
453
+ } catch (_error) {
454
+ // require.resolve might fail in some environments, that's okay
455
+ }
456
+
457
+ // Remove duplicates and invalid paths
458
+ const uniqueStrategies = [...new Set(strategies)].filter(
459
+ p => p.trim() !== '/resources/workflows'
460
+ );
461
+
462
+ // Test each strategy
463
+ for (const workflowsDir of uniqueStrategies) {
464
+ logger.debug('Trying workflows directory', { workflowsDir });
465
+ if (fs.existsSync(workflowsDir)) {
466
+ // Verify it contains workflow files
467
+ try {
468
+ const files = fs.readdirSync(workflowsDir);
469
+ const yamlFiles = files.filter(
470
+ file => file.endsWith('.yaml') || file.endsWith('.yml')
471
+ );
472
+ if (yamlFiles.length > 0) {
473
+ logger.info('Found workflows directory', {
474
+ workflowsDir,
475
+ yamlFiles: yamlFiles.length,
476
+ });
477
+ return workflowsDir;
478
+ }
479
+ } catch (error) {
480
+ // Directory exists but can't read it, continue to next strategy
481
+ logger.debug('Cannot read workflows directory', {
482
+ workflowsDir,
483
+ error,
484
+ });
485
+ }
486
+ }
487
+ }
488
+
489
+ logger.error(
490
+ 'Could not find workflows directory',
491
+ new Error('Workflows directory not found'),
492
+ {
493
+ strategiesCount: uniqueStrategies.length,
494
+ currentFilePath,
495
+ strategies: uniqueStrategies,
496
+ }
497
+ );
498
+ return null;
499
+ }
500
+
501
+ /**
502
+ * Load all predefined workflows from resources/workflows directory
503
+ */
504
+ private loadPredefinedWorkflows(): void {
505
+ try {
506
+ const workflowsDir = this.findWorkflowsDirectory();
507
+
508
+ if (!workflowsDir || !fs.existsSync(workflowsDir)) {
509
+ logger.warn('Workflows directory not found', { workflowsDir });
510
+ return;
511
+ }
512
+
513
+ // Read all YAML files in the workflows directory
514
+ const files = fs.readdirSync(workflowsDir);
515
+ const yamlFiles = files.filter(
516
+ file => file.endsWith('.yaml') || file.endsWith('.yml')
517
+ );
518
+
519
+ logger.info('Loading predefined workflows', {
520
+ workflowsDir,
521
+ yamlFiles: yamlFiles.length,
522
+ });
523
+
524
+ for (const file of yamlFiles) {
525
+ try {
526
+ const filePath = path.join(workflowsDir, file);
527
+ const workflow = this.stateMachineLoader.loadFromFile(filePath);
528
+ const workflowName = path.basename(file, path.extname(file));
529
+
530
+ // Apply domain filtering
531
+ if (this.enabledDomains.size > 0 && workflow.metadata?.domain) {
532
+ if (!this.enabledDomains.has(workflow.metadata.domain)) {
533
+ logger.debug('Skipping workflow due to domain filter', {
534
+ name: workflowName,
535
+ domain: workflow.metadata.domain,
536
+ enabledDomains: Array.from(this.enabledDomains),
537
+ });
538
+ continue;
539
+ }
540
+ }
541
+
542
+ this.predefinedWorkflows.set(workflowName, workflow);
543
+
544
+ const workflowInfo: WorkflowInfo = {
545
+ name: workflowName,
546
+ displayName: workflow.name,
547
+ description: workflow.description,
548
+ initialState: workflow.initial_state,
549
+ phases: Object.keys(workflow.states),
550
+ metadata: workflow.metadata,
551
+ };
552
+
553
+ this.workflowInfos.set(workflowName, workflowInfo);
554
+
555
+ logger.info('Loaded predefined workflow', {
556
+ name: workflowName,
557
+ domain: workflow.metadata?.domain,
558
+ phases: workflowInfo.phases.length,
559
+ });
560
+ } catch (error) {
561
+ logger.error('Failed to load workflow file', error as Error, {
562
+ file,
563
+ });
564
+ }
565
+ }
566
+
567
+ logger.info('Predefined workflows loaded', {
568
+ count: this.predefinedWorkflows.size,
569
+ workflows: Array.from(this.predefinedWorkflows.keys()),
570
+ });
571
+ } catch (error) {
572
+ logger.error('Failed to load predefined workflows', error as Error);
573
+ }
574
+ }
575
+
576
+ /**
577
+ * Validate a workflow name
578
+ */
579
+ public validateWorkflowName(
580
+ workflowName: string,
581
+ projectPath: string
582
+ ): boolean {
583
+ // Check if it's a predefined workflow
584
+ if (this.isPredefinedWorkflow(workflowName)) {
585
+ return true;
586
+ }
587
+
588
+ // Check if it's a project workflow (load project workflows first)
589
+ this.loadProjectWorkflows(projectPath);
590
+ if (this.projectWorkflows.has(workflowName)) {
591
+ return true;
592
+ }
593
+
594
+ // Also check workflow infos in case workflow failed to load but info was created
595
+ if (this.workflowInfos.has(workflowName)) {
596
+ return true;
597
+ }
598
+
599
+ return false;
600
+ }
601
+ }