@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.
- package/.turbo/turbo-build.log +4 -0
- package/LICENSE +674 -0
- package/dist/config-manager.d.ts +24 -0
- package/dist/config-manager.js +68 -0
- package/dist/config-manager.js.map +1 -0
- package/dist/conversation-manager.d.ts +97 -0
- package/dist/conversation-manager.js +367 -0
- package/dist/conversation-manager.js.map +1 -0
- package/dist/database.d.ts +73 -0
- package/dist/database.js +500 -0
- package/dist/database.js.map +1 -0
- package/dist/file-detection-manager.d.ts +53 -0
- package/dist/file-detection-manager.js +221 -0
- package/dist/file-detection-manager.js.map +1 -0
- package/dist/git-manager.d.ts +14 -0
- package/dist/git-manager.js +59 -0
- package/dist/git-manager.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/instruction-generator.d.ts +69 -0
- package/dist/instruction-generator.js +133 -0
- package/dist/instruction-generator.js.map +1 -0
- package/dist/interaction-logger.d.ts +37 -0
- package/dist/interaction-logger.js +87 -0
- package/dist/interaction-logger.js.map +1 -0
- package/dist/logger.d.ts +64 -0
- package/dist/logger.js +283 -0
- package/dist/logger.js.map +1 -0
- package/dist/path-validation-utils.d.ts +51 -0
- package/dist/path-validation-utils.js +202 -0
- package/dist/path-validation-utils.js.map +1 -0
- package/dist/plan-manager.d.ts +65 -0
- package/dist/plan-manager.js +256 -0
- package/dist/plan-manager.js.map +1 -0
- package/dist/project-docs-manager.d.ts +119 -0
- package/dist/project-docs-manager.js +357 -0
- package/dist/project-docs-manager.js.map +1 -0
- package/dist/state-machine-loader.d.ts +60 -0
- package/dist/state-machine-loader.js +235 -0
- package/dist/state-machine-loader.js.map +1 -0
- package/dist/state-machine-types.d.ts +58 -0
- package/dist/state-machine-types.js +7 -0
- package/dist/state-machine-types.js.map +1 -0
- package/dist/state-machine.d.ts +52 -0
- package/dist/state-machine.js +256 -0
- package/dist/state-machine.js.map +1 -0
- package/dist/system-prompt-generator.d.ts +17 -0
- package/dist/system-prompt-generator.js +113 -0
- package/dist/system-prompt-generator.js.map +1 -0
- package/dist/template-manager.d.ts +61 -0
- package/dist/template-manager.js +229 -0
- package/dist/template-manager.js.map +1 -0
- package/dist/transition-engine.d.ts +70 -0
- package/dist/transition-engine.js +240 -0
- package/dist/transition-engine.js.map +1 -0
- package/dist/types.d.ts +56 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/dist/workflow-manager.d.ts +89 -0
- package/dist/workflow-manager.js +466 -0
- package/dist/workflow-manager.js.map +1 -0
- package/package.json +27 -0
- package/src/config-manager.ts +96 -0
- package/src/conversation-manager.ts +492 -0
- package/src/database.ts +685 -0
- package/src/file-detection-manager.ts +302 -0
- package/src/git-manager.ts +64 -0
- package/src/index.ts +28 -0
- package/src/instruction-generator.ts +210 -0
- package/src/interaction-logger.ts +109 -0
- package/src/logger.ts +353 -0
- package/src/path-validation-utils.ts +261 -0
- package/src/plan-manager.ts +323 -0
- package/src/project-docs-manager.ts +522 -0
- package/src/state-machine-loader.ts +308 -0
- package/src/state-machine-types.ts +72 -0
- package/src/state-machine.ts +370 -0
- package/src/system-prompt-generator.ts +122 -0
- package/src/template-manager.ts +321 -0
- package/src/transition-engine.ts +386 -0
- package/src/types.ts +60 -0
- package/src/workflow-manager.ts +601 -0
- package/test/unit/conversation-manager.test.ts +179 -0
- package/test/unit/custom-workflow-loading.test.ts +174 -0
- package/test/unit/directory-linking-and-extensions.test.ts +338 -0
- package/test/unit/file-linking-integration.test.ts +256 -0
- package/test/unit/git-commit-integration.test.ts +91 -0
- package/test/unit/git-manager.test.ts +86 -0
- package/test/unit/install-workflow.test.ts +138 -0
- package/test/unit/instruction-generator.test.ts +247 -0
- package/test/unit/list-workflows-filtering.test.ts +68 -0
- package/test/unit/none-template-functionality.test.ts +224 -0
- package/test/unit/project-docs-manager.test.ts +337 -0
- package/test/unit/state-machine-loader.test.ts +234 -0
- package/test/unit/template-manager.test.ts +217 -0
- package/test/unit/validate-workflow-name.test.ts +150 -0
- package/test/unit/workflow-domain-filtering.test.ts +75 -0
- package/test/unit/workflow-enum-generation.test.ts +92 -0
- package/test/unit/workflow-manager-enhanced-path-resolution.test.ts +369 -0
- package/test/unit/workflow-manager-path-resolution.test.ts +150 -0
- package/test/unit/workflow-migration.test.ts +155 -0
- package/test/unit/workflow-override-by-name.test.ts +116 -0
- package/test/unit/workflow-prioritization.test.ts +38 -0
- package/test/unit/workflow-validation.test.ts +303 -0
- package/test/utils/e2e-test-setup.ts +453 -0
- package/test/utils/run-server-in-dir.sh +27 -0
- package/test/utils/temp-files.ts +308 -0
- package/test/utils/test-access.ts +79 -0
- package/test/utils/test-helpers.ts +286 -0
- package/test/utils/test-setup.ts +78 -0
- package/tsconfig.build.json +21 -0
- package/tsconfig.json +8 -0
- package/vitest.config.ts +18 -0
@@ -0,0 +1,116 @@
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
2
|
+
import { WorkflowManager } from '@codemcp/workflows-core';
|
3
|
+
import fs from 'node:fs';
|
4
|
+
import path from 'node:path';
|
5
|
+
import { tmpdir } from 'node:os';
|
6
|
+
|
7
|
+
describe('Workflow Override by Name', () => {
|
8
|
+
let testProjectPath: string;
|
9
|
+
let originalEnv: string | undefined;
|
10
|
+
|
11
|
+
beforeEach(() => {
|
12
|
+
originalEnv = process.env.VIBE_WORKFLOW_DOMAINS;
|
13
|
+
testProjectPath = fs.mkdtempSync(
|
14
|
+
path.join(tmpdir(), 'workflow-override-test-')
|
15
|
+
);
|
16
|
+
});
|
17
|
+
|
18
|
+
afterEach(() => {
|
19
|
+
if (originalEnv !== undefined) {
|
20
|
+
process.env.VIBE_WORKFLOW_DOMAINS = originalEnv;
|
21
|
+
} else {
|
22
|
+
delete process.env.VIBE_WORKFLOW_DOMAINS;
|
23
|
+
}
|
24
|
+
|
25
|
+
fs.rmSync(testProjectPath, { recursive: true, force: true });
|
26
|
+
});
|
27
|
+
|
28
|
+
it('should override predefined workflow by YAML name, not filename', () => {
|
29
|
+
process.env.VIBE_WORKFLOW_DOMAINS = 'code';
|
30
|
+
|
31
|
+
const workflowsDir = path.join(testProjectPath, '.vibe', 'workflows');
|
32
|
+
fs.mkdirSync(workflowsDir, { recursive: true });
|
33
|
+
|
34
|
+
// Copy existing waterfall workflow but change description
|
35
|
+
const waterfallPath = path.join(
|
36
|
+
__dirname,
|
37
|
+
'..',
|
38
|
+
'..',
|
39
|
+
'resources',
|
40
|
+
'workflows',
|
41
|
+
'waterfall.yaml'
|
42
|
+
);
|
43
|
+
const originalContent = fs.readFileSync(waterfallPath, 'utf8');
|
44
|
+
const customContent = originalContent.replace(
|
45
|
+
/description: .*/,
|
46
|
+
"description: 'Custom company waterfall'"
|
47
|
+
);
|
48
|
+
|
49
|
+
fs.writeFileSync(
|
50
|
+
path.join(workflowsDir, 'company-waterfall.yaml'),
|
51
|
+
customContent
|
52
|
+
);
|
53
|
+
|
54
|
+
const manager = new WorkflowManager();
|
55
|
+
|
56
|
+
// Load project workflows to trigger override
|
57
|
+
const workflows = manager.getAvailableWorkflowsForProject(testProjectPath);
|
58
|
+
|
59
|
+
// Should override predefined waterfall workflow
|
60
|
+
const workflow = manager.getWorkflow('waterfall');
|
61
|
+
expect(workflow?.name).toBe('waterfall');
|
62
|
+
expect(workflow?.description).toBe('Custom company waterfall');
|
63
|
+
|
64
|
+
// Should be available in workflow list
|
65
|
+
const waterfallWorkflow = workflows.find(w => w.name === 'waterfall');
|
66
|
+
expect(waterfallWorkflow?.description).toBe('Custom company waterfall');
|
67
|
+
});
|
68
|
+
|
69
|
+
it('should use YAML name as workflow key, not filename', () => {
|
70
|
+
process.env.VIBE_WORKFLOW_DOMAINS = 'code';
|
71
|
+
|
72
|
+
const workflowsDir = path.join(testProjectPath, '.vibe', 'workflows');
|
73
|
+
fs.mkdirSync(workflowsDir, { recursive: true });
|
74
|
+
|
75
|
+
// Copy existing waterfall workflow with different filename
|
76
|
+
const waterfallPath = path.join(
|
77
|
+
__dirname,
|
78
|
+
'..',
|
79
|
+
'..',
|
80
|
+
'resources',
|
81
|
+
'workflows',
|
82
|
+
'waterfall.yaml'
|
83
|
+
);
|
84
|
+
const originalContent = fs.readFileSync(waterfallPath, 'utf8');
|
85
|
+
const customContent = originalContent.replace(
|
86
|
+
/description: .*/,
|
87
|
+
"description: 'Different filename, same name'"
|
88
|
+
);
|
89
|
+
|
90
|
+
// Use completely different filename but keep YAML name as 'waterfall'
|
91
|
+
fs.writeFileSync(
|
92
|
+
path.join(workflowsDir, 'totally-different-filename.yaml'),
|
93
|
+
customContent
|
94
|
+
);
|
95
|
+
|
96
|
+
const manager = new WorkflowManager();
|
97
|
+
|
98
|
+
// Load project workflows
|
99
|
+
const workflows = manager.getAvailableWorkflowsForProject(testProjectPath);
|
100
|
+
|
101
|
+
// Should still be accessible as 'waterfall' (YAML name), not 'totally-different-filename'
|
102
|
+
const workflow = manager.getWorkflow('waterfall');
|
103
|
+
expect(workflow?.name).toBe('waterfall');
|
104
|
+
expect(workflow?.description).toBe('Different filename, same name');
|
105
|
+
|
106
|
+
// Should not be accessible by filename
|
107
|
+
const byFilename = manager.getWorkflow('totally-different-filename');
|
108
|
+
expect(byFilename).toBeUndefined();
|
109
|
+
|
110
|
+
// Should be in workflow list with correct name
|
111
|
+
const waterfallWorkflow = workflows.find(w => w.name === 'waterfall');
|
112
|
+
expect(waterfallWorkflow?.description).toBe(
|
113
|
+
'Different filename, same name'
|
114
|
+
);
|
115
|
+
});
|
116
|
+
});
|
@@ -0,0 +1,38 @@
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
2
|
+
import { WorkflowManager } from '@codemcp/workflows-core';
|
3
|
+
|
4
|
+
describe('Workflow Prioritization', () => {
|
5
|
+
it('should prioritize project workflows over predefined ones in getWorkflow method', () => {
|
6
|
+
const manager = new WorkflowManager();
|
7
|
+
|
8
|
+
// Get original waterfall workflow
|
9
|
+
const originalWaterfall = manager.getWorkflow('waterfall');
|
10
|
+
expect(originalWaterfall?.name).toBe('waterfall');
|
11
|
+
|
12
|
+
// Simulate adding a project workflow with same name
|
13
|
+
// Access private property for testing (this simulates successful loading)
|
14
|
+
const projectWorkflows = (manager as unknown).projectWorkflows;
|
15
|
+
const workflowInfos = (manager as unknown).workflowInfos;
|
16
|
+
|
17
|
+
const customWorkflow = {
|
18
|
+
name: 'custom-waterfall',
|
19
|
+
description: 'Custom override',
|
20
|
+
initial_state: 'custom-start',
|
21
|
+
states: { 'custom-start': { description: 'test' } },
|
22
|
+
};
|
23
|
+
|
24
|
+
projectWorkflows.set('waterfall', customWorkflow);
|
25
|
+
workflowInfos.set('waterfall', {
|
26
|
+
name: 'waterfall',
|
27
|
+
displayName: 'custom-waterfall',
|
28
|
+
description: 'Custom override',
|
29
|
+
initialState: 'custom-start',
|
30
|
+
phases: ['custom-start'],
|
31
|
+
});
|
32
|
+
|
33
|
+
// Now getWorkflow should return the project workflow
|
34
|
+
const prioritizedWorkflow = manager.getWorkflow('waterfall');
|
35
|
+
expect(prioritizedWorkflow?.name).toBe('custom-waterfall');
|
36
|
+
expect(prioritizedWorkflow?.description).toBe('Custom override');
|
37
|
+
});
|
38
|
+
});
|
@@ -0,0 +1,303 @@
|
|
1
|
+
/**
|
2
|
+
* Workflow Validation Tests
|
3
|
+
*
|
4
|
+
* Comprehensive tests to ensure all workflow files are valid and meet formal criteria:
|
5
|
+
* - All workflows can be loaded without errors
|
6
|
+
* - Every state is reachable through transition chains
|
7
|
+
* - Workflow structure integrity (initial state exists, phases defined, etc.)
|
8
|
+
* - No orphaned states or unreachable phases
|
9
|
+
*/
|
10
|
+
|
11
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
12
|
+
import { WorkflowInfo, WorkflowManager } from '@codemcp/workflows-core';
|
13
|
+
import { readdirSync } from 'node:fs';
|
14
|
+
import { join } from 'node:path';
|
15
|
+
import { YamlStateMachine, YamlState } from '@codemcp/workflows-core';
|
16
|
+
|
17
|
+
describe('Workflow Validation', () => {
|
18
|
+
const workflowsDir = join(__dirname, '..', '..', 'resources', 'workflows');
|
19
|
+
const workflowFiles = readdirSync(workflowsDir).filter(
|
20
|
+
file => file.endsWith('.yaml') || file.endsWith('.yml')
|
21
|
+
);
|
22
|
+
|
23
|
+
describe('Workflow Loading', () => {
|
24
|
+
it('should load all workflow files without errors', () => {
|
25
|
+
const workflowManager = new WorkflowManager();
|
26
|
+
|
27
|
+
// Get all available workflows
|
28
|
+
const workflows = workflowManager.getAvailableWorkflows();
|
29
|
+
|
30
|
+
// Should have loaded workflows (at least the core ones)
|
31
|
+
expect(workflows.length).toBeGreaterThan(0);
|
32
|
+
|
33
|
+
// Check that we have the expected core workflows
|
34
|
+
const expectedCoreWorkflows = ['bugfix', 'waterfall', 'epcc', 'minor'];
|
35
|
+
const workflowNames = workflows.map(w => w.name);
|
36
|
+
for (const workflow of expectedCoreWorkflows) {
|
37
|
+
expect(workflowNames).toContain(workflow);
|
38
|
+
}
|
39
|
+
});
|
40
|
+
|
41
|
+
it('should load ALL workflow files from resources directory', () => {
|
42
|
+
// Temporarily disable domain filtering for this test
|
43
|
+
const originalEnv = process.env.VIBE_WORKFLOW_DOMAINS;
|
44
|
+
process.env.VIBE_WORKFLOW_DOMAINS = 'code,architecture,office';
|
45
|
+
|
46
|
+
try {
|
47
|
+
// Create manager after setting env var to include all domains
|
48
|
+
const workflowManager = new WorkflowManager();
|
49
|
+
const loadedWorkflows = workflowManager.getAvailableWorkflows();
|
50
|
+
const loadedWorkflowNames = loadedWorkflows.map(w => w.name);
|
51
|
+
|
52
|
+
// Count expected workflow files
|
53
|
+
const expectedWorkflowCount = workflowFiles.length;
|
54
|
+
|
55
|
+
// Should load exactly the same number of workflows as files
|
56
|
+
expect(loadedWorkflows.length).toBe(expectedWorkflowCount);
|
57
|
+
|
58
|
+
// Each workflow file should correspond to a loaded workflow
|
59
|
+
for (const file of workflowFiles) {
|
60
|
+
const workflowName = file.replace(/\.(yaml|yml)$/, '');
|
61
|
+
expect(loadedWorkflowNames).toContain(workflowName);
|
62
|
+
}
|
63
|
+
} finally {
|
64
|
+
if (originalEnv !== undefined) {
|
65
|
+
process.env.VIBE_WORKFLOW_DOMAINS = originalEnv;
|
66
|
+
} else {
|
67
|
+
delete process.env.VIBE_WORKFLOW_DOMAINS;
|
68
|
+
}
|
69
|
+
}
|
70
|
+
});
|
71
|
+
|
72
|
+
it('should have valid workflow files in resources directory', () => {
|
73
|
+
expect(workflowFiles.length).toBeGreaterThan(0);
|
74
|
+
|
75
|
+
// Check that all files have valid extensions
|
76
|
+
for (const file of workflowFiles) {
|
77
|
+
expect(file.endsWith('.yaml') || file.endsWith('.yml')).toBe(true);
|
78
|
+
}
|
79
|
+
});
|
80
|
+
});
|
81
|
+
|
82
|
+
describe('Workflow Structure Validation', () => {
|
83
|
+
let workflowManager: WorkflowManager;
|
84
|
+
let workflows: WorkflowInfo[];
|
85
|
+
|
86
|
+
beforeEach(() => {
|
87
|
+
workflowManager = new WorkflowManager();
|
88
|
+
workflows = workflowManager.getAvailableWorkflows();
|
89
|
+
});
|
90
|
+
|
91
|
+
it('should have required fields for each workflow', () => {
|
92
|
+
for (const workflow of workflows) {
|
93
|
+
expect(workflow).toBeDefined();
|
94
|
+
expect(workflow.name).toBeDefined();
|
95
|
+
expect(typeof workflow.name).toBe('string');
|
96
|
+
expect(workflow.displayName).toBeDefined();
|
97
|
+
expect(typeof workflow.displayName).toBe('string');
|
98
|
+
expect(workflow.phases).toBeDefined();
|
99
|
+
expect(Array.isArray(workflow.phases)).toBe(true);
|
100
|
+
expect(workflow.phases.length).toBeGreaterThan(0);
|
101
|
+
}
|
102
|
+
});
|
103
|
+
|
104
|
+
it('should have valid state machines for each workflow', () => {
|
105
|
+
for (const workflow of workflows) {
|
106
|
+
// Get the state machine for this workflow
|
107
|
+
const stateMachine = workflowManager.getWorkflow(workflow.name);
|
108
|
+
|
109
|
+
expect(stateMachine).toBeDefined();
|
110
|
+
expect(stateMachine!.name).toBe(workflow.name);
|
111
|
+
expect(stateMachine!.description).toBeDefined();
|
112
|
+
expect(stateMachine!.initial_state).toBeDefined();
|
113
|
+
expect(stateMachine!.states).toBeDefined();
|
114
|
+
expect(typeof stateMachine!.states).toBe('object');
|
115
|
+
}
|
116
|
+
});
|
117
|
+
|
118
|
+
it('should have initial state defined in states', () => {
|
119
|
+
for (const workflow of workflows) {
|
120
|
+
const stateMachine = workflowManager.getWorkflow(workflow.name);
|
121
|
+
|
122
|
+
expect(stateMachine!.states[stateMachine!.initial_state]).toBeDefined();
|
123
|
+
}
|
124
|
+
});
|
125
|
+
|
126
|
+
it('should have all phases represented as states', () => {
|
127
|
+
for (const workflow of workflows) {
|
128
|
+
const stateMachine = workflowManager.getWorkflow(workflow.name);
|
129
|
+
|
130
|
+
// Every phase should exist as a state
|
131
|
+
for (const phase of workflow.phases) {
|
132
|
+
expect(stateMachine!.states[phase]).toBeDefined();
|
133
|
+
}
|
134
|
+
}
|
135
|
+
});
|
136
|
+
});
|
137
|
+
|
138
|
+
describe('State Reachability Analysis', () => {
|
139
|
+
let workflowManager: WorkflowManager;
|
140
|
+
let workflows: WorkflowInfo[];
|
141
|
+
|
142
|
+
beforeEach(() => {
|
143
|
+
workflowManager = new WorkflowManager();
|
144
|
+
workflows = workflowManager.getAvailableWorkflows();
|
145
|
+
});
|
146
|
+
|
147
|
+
/**
|
148
|
+
* Build a graph of state transitions and check reachability
|
149
|
+
*/
|
150
|
+
function analyzeStateReachability(stateMachine: YamlStateMachine) {
|
151
|
+
const states = Object.keys(stateMachine.states);
|
152
|
+
const reachableStates = new Set<string>();
|
153
|
+
const visited = new Set<string>();
|
154
|
+
|
155
|
+
// Start from initial state
|
156
|
+
const queue = [stateMachine.initial_state];
|
157
|
+
reachableStates.add(stateMachine.initial_state);
|
158
|
+
|
159
|
+
while (queue.length > 0) {
|
160
|
+
const currentState = queue.shift()!;
|
161
|
+
|
162
|
+
if (visited.has(currentState)) continue;
|
163
|
+
visited.add(currentState);
|
164
|
+
|
165
|
+
const stateConfig = stateMachine.states[currentState];
|
166
|
+
if (stateConfig.transitions) {
|
167
|
+
for (const transition of stateConfig.transitions) {
|
168
|
+
if (transition.to && !reachableStates.has(transition.to)) {
|
169
|
+
reachableStates.add(transition.to);
|
170
|
+
queue.push(transition.to);
|
171
|
+
}
|
172
|
+
}
|
173
|
+
}
|
174
|
+
}
|
175
|
+
|
176
|
+
return {
|
177
|
+
allStates: states,
|
178
|
+
reachableStates: Array.from(reachableStates),
|
179
|
+
unreachableStates: states.filter(state => !reachableStates.has(state)),
|
180
|
+
};
|
181
|
+
}
|
182
|
+
|
183
|
+
it('should have all states reachable from initial state', () => {
|
184
|
+
for (const workflow of workflows) {
|
185
|
+
const stateMachine = workflowManager.getWorkflow(workflow.name);
|
186
|
+
const analysis = analyzeStateReachability(stateMachine!);
|
187
|
+
|
188
|
+
expect(analysis.unreachableStates).toEqual([]);
|
189
|
+
|
190
|
+
// All states should be reachable
|
191
|
+
expect(analysis.reachableStates.length).toBe(analysis.allStates.length);
|
192
|
+
}
|
193
|
+
});
|
194
|
+
|
195
|
+
it('should have valid transition targets', () => {
|
196
|
+
for (const workflow of workflows) {
|
197
|
+
const stateMachine = workflowManager.getWorkflow(workflow.name);
|
198
|
+
|
199
|
+
for (const [_stateName, stateConfig] of Object.entries(
|
200
|
+
stateMachine!.states
|
201
|
+
) as [string, YamlState][]) {
|
202
|
+
if (stateConfig.transitions) {
|
203
|
+
for (const transition of stateConfig.transitions) {
|
204
|
+
if (transition.to) {
|
205
|
+
// Target state must exist
|
206
|
+
expect(stateMachine!.states[transition.to]).toBeDefined();
|
207
|
+
}
|
208
|
+
}
|
209
|
+
}
|
210
|
+
}
|
211
|
+
}
|
212
|
+
});
|
213
|
+
|
214
|
+
it('should have meaningful transition triggers', () => {
|
215
|
+
for (const workflow of workflows) {
|
216
|
+
const stateMachine = workflowManager.getWorkflow(workflow.name);
|
217
|
+
|
218
|
+
for (const [_stateName, stateConfig] of Object.entries(
|
219
|
+
stateMachine!.states
|
220
|
+
) as [string, YamlState][]) {
|
221
|
+
if (stateConfig.transitions) {
|
222
|
+
for (const transition of stateConfig.transitions) {
|
223
|
+
// Each transition should have a trigger
|
224
|
+
expect(transition.trigger).toBeDefined();
|
225
|
+
expect(typeof transition.trigger).toBe('string');
|
226
|
+
expect(transition.trigger.length).toBeGreaterThan(0);
|
227
|
+
|
228
|
+
// Trigger should be meaningful (not just whitespace)
|
229
|
+
expect(transition.trigger.trim()).toBe(transition.trigger);
|
230
|
+
expect(transition.trigger.trim().length).toBeGreaterThan(0);
|
231
|
+
}
|
232
|
+
}
|
233
|
+
}
|
234
|
+
}
|
235
|
+
});
|
236
|
+
});
|
237
|
+
|
238
|
+
describe('Workflow Content Quality', () => {
|
239
|
+
let workflowManager: WorkflowManager;
|
240
|
+
let workflows: WorkflowInfo[];
|
241
|
+
|
242
|
+
beforeEach(() => {
|
243
|
+
workflowManager = new WorkflowManager();
|
244
|
+
workflows = workflowManager.getAvailableWorkflows();
|
245
|
+
});
|
246
|
+
|
247
|
+
it('should have meaningful descriptions for all states', () => {
|
248
|
+
for (const workflow of workflows) {
|
249
|
+
const stateMachine = workflowManager.getWorkflow(workflow.name);
|
250
|
+
|
251
|
+
for (const [_stateName, stateConfig] of Object.entries(
|
252
|
+
stateMachine!.states
|
253
|
+
) as [string, YamlState][]) {
|
254
|
+
expect(stateConfig.description).toBeDefined();
|
255
|
+
expect(typeof stateConfig.description).toBe('string');
|
256
|
+
expect(stateConfig.description.length).toBeGreaterThan(10); // Meaningful description
|
257
|
+
}
|
258
|
+
}
|
259
|
+
});
|
260
|
+
|
261
|
+
it('should have default instructions for all states', () => {
|
262
|
+
for (const workflow of workflows) {
|
263
|
+
const stateMachine = workflowManager.getWorkflow(workflow.name);
|
264
|
+
|
265
|
+
for (const [_stateName, stateConfig] of Object.entries(
|
266
|
+
stateMachine!.states
|
267
|
+
) as [string, YamlState][]) {
|
268
|
+
expect(stateConfig.default_instructions).toBeDefined();
|
269
|
+
expect(typeof stateConfig.default_instructions).toBe('string');
|
270
|
+
expect(stateConfig.default_instructions.length).toBeGreaterThan(20); // Substantial instructions
|
271
|
+
}
|
272
|
+
}
|
273
|
+
});
|
274
|
+
|
275
|
+
it('should have workflow metadata', () => {
|
276
|
+
for (const workflow of workflows) {
|
277
|
+
const stateMachine = workflowManager.getWorkflow(workflow.name);
|
278
|
+
|
279
|
+
// Should have basic metadata
|
280
|
+
expect(stateMachine!.name).toBeDefined();
|
281
|
+
expect(stateMachine!.description).toBeDefined();
|
282
|
+
expect(typeof stateMachine!.description).toBe('string');
|
283
|
+
expect(stateMachine!.description.length).toBeGreaterThan(10);
|
284
|
+
}
|
285
|
+
});
|
286
|
+
});
|
287
|
+
|
288
|
+
describe('Workflow Integration', () => {
|
289
|
+
it('should be able to create workflow instances', () => {
|
290
|
+
const workflowManager = new WorkflowManager();
|
291
|
+
const workflows = workflowManager.getAvailableWorkflows();
|
292
|
+
|
293
|
+
// Should be able to get workflow info for each workflow
|
294
|
+
for (const workflow of workflows) {
|
295
|
+
const workflowInfo = workflowManager.getWorkflowInfo(workflow.name);
|
296
|
+
expect(workflowInfo).toBeDefined();
|
297
|
+
expect(workflowInfo!.name).toBe(workflow.name);
|
298
|
+
expect(workflowInfo!.phases).toBeDefined();
|
299
|
+
expect(Array.isArray(workflowInfo!.phases)).toBe(true);
|
300
|
+
}
|
301
|
+
});
|
302
|
+
});
|
303
|
+
});
|