@codemcp/workflows-core 3.1.22 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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
package/src/types.ts
DELETED
@@ -1,60 +0,0 @@
|
|
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
|
-
}
|
package/src/workflow-manager.ts
DELETED
@@ -1,606 +0,0 @@
|
|
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: Local resources directory (symlinked from root)
|
347
|
-
strategies.push(
|
348
|
-
path.join(path.dirname(currentFilePath), '../resources/workflows')
|
349
|
-
);
|
350
|
-
|
351
|
-
// Strategy 2: Relative to current file (development and direct npm scenarios)
|
352
|
-
// From packages/core/dist/workflow-manager.js -> ../../../../resources/workflows
|
353
|
-
strategies.push(
|
354
|
-
path.resolve(
|
355
|
-
path.dirname(currentFilePath),
|
356
|
-
'../../../../resources/workflows'
|
357
|
-
)
|
358
|
-
);
|
359
|
-
|
360
|
-
// Strategy 3: Find package root by looking for package.json with our package name
|
361
|
-
let currentDir = path.dirname(currentFilePath);
|
362
|
-
for (let i = 0; i < 10; i++) {
|
363
|
-
// Limit search depth
|
364
|
-
const packageJsonPath = path.join(currentDir, 'package.json');
|
365
|
-
if (fs.existsSync(packageJsonPath)) {
|
366
|
-
try {
|
367
|
-
const packageJson = JSON.parse(
|
368
|
-
fs.readFileSync(packageJsonPath, 'utf-8')
|
369
|
-
);
|
370
|
-
if (packageJson.name === '@codemcp/workflows-core') {
|
371
|
-
strategies.push(path.join(currentDir, 'resources/workflows'));
|
372
|
-
break;
|
373
|
-
}
|
374
|
-
} catch (_error) {
|
375
|
-
// Ignore JSON parse errors and continue searching
|
376
|
-
}
|
377
|
-
}
|
378
|
-
const parentDir = path.dirname(currentDir);
|
379
|
-
if (parentDir === currentDir) break; // Reached filesystem root
|
380
|
-
currentDir = parentDir;
|
381
|
-
}
|
382
|
-
|
383
|
-
// Strategy 3: Common npm installation paths
|
384
|
-
// Local node_modules (when used as dependency)
|
385
|
-
strategies.push(
|
386
|
-
path.join(
|
387
|
-
process.cwd(),
|
388
|
-
'node_modules/@codemcp/workflows-core/resources/workflows'
|
389
|
-
)
|
390
|
-
);
|
391
|
-
|
392
|
-
// Global npm installation (when installed globally)
|
393
|
-
if (process.env.NODE_PATH) {
|
394
|
-
strategies.push(
|
395
|
-
path.join(
|
396
|
-
process.env.NODE_PATH,
|
397
|
-
'@codemcp/workflows-core/resources/workflows'
|
398
|
-
)
|
399
|
-
);
|
400
|
-
}
|
401
|
-
|
402
|
-
// Strategy 4: npx cache locations (for npx responsible-vibe-mcp@latest)
|
403
|
-
// npx typically caches packages in ~/.npm/_npx or similar locations
|
404
|
-
const homeDir = process.env.HOME || process.env.USERPROFILE;
|
405
|
-
if (homeDir) {
|
406
|
-
// Common npx cache locations
|
407
|
-
const npxCachePaths = [
|
408
|
-
path.join(homeDir, '.npm/_npx'),
|
409
|
-
path.join(homeDir, '.npm/_cacache'),
|
410
|
-
path.join(homeDir, 'AppData/Local/npm-cache/_npx'), // Windows
|
411
|
-
path.join(homeDir, 'Library/Caches/npm/_npx'), // macOS
|
412
|
-
];
|
413
|
-
|
414
|
-
for (const cachePath of npxCachePaths) {
|
415
|
-
if (fs.existsSync(cachePath)) {
|
416
|
-
try {
|
417
|
-
// Look for responsible-vibe-mcp in cache subdirectories
|
418
|
-
const cacheEntries = fs.readdirSync(cachePath);
|
419
|
-
for (const entry of cacheEntries) {
|
420
|
-
const entryPath = path.join(cachePath, entry);
|
421
|
-
if (fs.statSync(entryPath).isDirectory()) {
|
422
|
-
// Look for our package in this cache entry
|
423
|
-
const possiblePaths = [
|
424
|
-
path.join(
|
425
|
-
entryPath,
|
426
|
-
'node_modules/@codemcp/workflows-core/resources/workflows'
|
427
|
-
),
|
428
|
-
path.join(
|
429
|
-
entryPath,
|
430
|
-
'@codemcp/workflows-core/resources/workflows'
|
431
|
-
),
|
432
|
-
];
|
433
|
-
strategies.push(...possiblePaths);
|
434
|
-
}
|
435
|
-
}
|
436
|
-
} catch (_error) {
|
437
|
-
// Ignore errors reading cache directories
|
438
|
-
}
|
439
|
-
}
|
440
|
-
}
|
441
|
-
}
|
442
|
-
|
443
|
-
// Strategy 5: Look in the directory where the current executable is located
|
444
|
-
// This handles cases where npx runs the package from a temporary location
|
445
|
-
const executableDir = path.dirname(process.argv[1] || '');
|
446
|
-
if (executableDir) {
|
447
|
-
strategies.push(path.join(executableDir, '../resources/workflows'));
|
448
|
-
strategies.push(path.join(executableDir, 'resources/workflows'));
|
449
|
-
}
|
450
|
-
|
451
|
-
// Strategy 6: Use require.resolve to find the package location
|
452
|
-
try {
|
453
|
-
// Try to resolve the package.json of our own package
|
454
|
-
const require = createRequire(import.meta.url);
|
455
|
-
const packagePath = require.resolve('responsible-vibe-mcp/package.json');
|
456
|
-
const packageDir = path.dirname(packagePath);
|
457
|
-
strategies.push(path.join(packageDir, 'resources/workflows'));
|
458
|
-
} catch (_error) {
|
459
|
-
// require.resolve might fail in some environments, that's okay
|
460
|
-
}
|
461
|
-
|
462
|
-
// Remove duplicates and invalid paths
|
463
|
-
const uniqueStrategies = [...new Set(strategies)].filter(
|
464
|
-
p => p.trim() !== '/resources/workflows'
|
465
|
-
);
|
466
|
-
|
467
|
-
// Test each strategy
|
468
|
-
for (const workflowsDir of uniqueStrategies) {
|
469
|
-
logger.debug('Trying workflows directory', { workflowsDir });
|
470
|
-
if (fs.existsSync(workflowsDir)) {
|
471
|
-
// Verify it contains workflow files
|
472
|
-
try {
|
473
|
-
const files = fs.readdirSync(workflowsDir);
|
474
|
-
const yamlFiles = files.filter(
|
475
|
-
file => file.endsWith('.yaml') || file.endsWith('.yml')
|
476
|
-
);
|
477
|
-
if (yamlFiles.length > 0) {
|
478
|
-
logger.info('Found workflows directory', {
|
479
|
-
workflowsDir,
|
480
|
-
yamlFiles: yamlFiles.length,
|
481
|
-
});
|
482
|
-
return workflowsDir;
|
483
|
-
}
|
484
|
-
} catch (error) {
|
485
|
-
// Directory exists but can't read it, continue to next strategy
|
486
|
-
logger.debug('Cannot read workflows directory', {
|
487
|
-
workflowsDir,
|
488
|
-
error,
|
489
|
-
});
|
490
|
-
}
|
491
|
-
}
|
492
|
-
}
|
493
|
-
|
494
|
-
logger.error(
|
495
|
-
'Could not find workflows directory',
|
496
|
-
new Error('Workflows directory not found'),
|
497
|
-
{
|
498
|
-
strategiesCount: uniqueStrategies.length,
|
499
|
-
currentFilePath,
|
500
|
-
strategies: uniqueStrategies,
|
501
|
-
}
|
502
|
-
);
|
503
|
-
return null;
|
504
|
-
}
|
505
|
-
|
506
|
-
/**
|
507
|
-
* Load all predefined workflows from resources/workflows directory
|
508
|
-
*/
|
509
|
-
private loadPredefinedWorkflows(): void {
|
510
|
-
try {
|
511
|
-
const workflowsDir = this.findWorkflowsDirectory();
|
512
|
-
|
513
|
-
if (!workflowsDir || !fs.existsSync(workflowsDir)) {
|
514
|
-
logger.warn('Workflows directory not found', { workflowsDir });
|
515
|
-
return;
|
516
|
-
}
|
517
|
-
|
518
|
-
// Read all YAML files in the workflows directory
|
519
|
-
const files = fs.readdirSync(workflowsDir);
|
520
|
-
const yamlFiles = files.filter(
|
521
|
-
file => file.endsWith('.yaml') || file.endsWith('.yml')
|
522
|
-
);
|
523
|
-
|
524
|
-
logger.info('Loading predefined workflows', {
|
525
|
-
workflowsDir,
|
526
|
-
yamlFiles: yamlFiles.length,
|
527
|
-
});
|
528
|
-
|
529
|
-
for (const file of yamlFiles) {
|
530
|
-
try {
|
531
|
-
const filePath = path.join(workflowsDir, file);
|
532
|
-
const workflow = this.stateMachineLoader.loadFromFile(filePath);
|
533
|
-
const workflowName = path.basename(file, path.extname(file));
|
534
|
-
|
535
|
-
// Apply domain filtering
|
536
|
-
if (this.enabledDomains.size > 0 && workflow.metadata?.domain) {
|
537
|
-
if (!this.enabledDomains.has(workflow.metadata.domain)) {
|
538
|
-
logger.debug('Skipping workflow due to domain filter', {
|
539
|
-
name: workflowName,
|
540
|
-
domain: workflow.metadata.domain,
|
541
|
-
enabledDomains: Array.from(this.enabledDomains),
|
542
|
-
});
|
543
|
-
continue;
|
544
|
-
}
|
545
|
-
}
|
546
|
-
|
547
|
-
this.predefinedWorkflows.set(workflowName, workflow);
|
548
|
-
|
549
|
-
const workflowInfo: WorkflowInfo = {
|
550
|
-
name: workflowName,
|
551
|
-
displayName: workflow.name,
|
552
|
-
description: workflow.description,
|
553
|
-
initialState: workflow.initial_state,
|
554
|
-
phases: Object.keys(workflow.states),
|
555
|
-
metadata: workflow.metadata,
|
556
|
-
};
|
557
|
-
|
558
|
-
this.workflowInfos.set(workflowName, workflowInfo);
|
559
|
-
|
560
|
-
logger.info('Loaded predefined workflow', {
|
561
|
-
name: workflowName,
|
562
|
-
domain: workflow.metadata?.domain,
|
563
|
-
phases: workflowInfo.phases.length,
|
564
|
-
});
|
565
|
-
} catch (error) {
|
566
|
-
logger.error('Failed to load workflow file', error as Error, {
|
567
|
-
file,
|
568
|
-
});
|
569
|
-
}
|
570
|
-
}
|
571
|
-
|
572
|
-
logger.info('Predefined workflows loaded', {
|
573
|
-
count: this.predefinedWorkflows.size,
|
574
|
-
workflows: Array.from(this.predefinedWorkflows.keys()),
|
575
|
-
});
|
576
|
-
} catch (error) {
|
577
|
-
logger.error('Failed to load predefined workflows', error as Error);
|
578
|
-
}
|
579
|
-
}
|
580
|
-
|
581
|
-
/**
|
582
|
-
* Validate a workflow name
|
583
|
-
*/
|
584
|
-
public validateWorkflowName(
|
585
|
-
workflowName: string,
|
586
|
-
projectPath: string
|
587
|
-
): boolean {
|
588
|
-
// Check if it's a predefined workflow
|
589
|
-
if (this.isPredefinedWorkflow(workflowName)) {
|
590
|
-
return true;
|
591
|
-
}
|
592
|
-
|
593
|
-
// Check if it's a project workflow (load project workflows first)
|
594
|
-
this.loadProjectWorkflows(projectPath);
|
595
|
-
if (this.projectWorkflows.has(workflowName)) {
|
596
|
-
return true;
|
597
|
-
}
|
598
|
-
|
599
|
-
// Also check workflow infos in case workflow failed to load but info was created
|
600
|
-
if (this.workflowInfos.has(workflowName)) {
|
601
|
-
return true;
|
602
|
-
}
|
603
|
-
|
604
|
-
return false;
|
605
|
-
}
|
606
|
-
}
|