@codemcp/workflows-core 3.1.21 → 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 +9 -5
- 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
@@ -1,337 +0,0 @@
|
|
1
|
-
/**
|
2
|
-
* Unit tests for ProjectDocsManager
|
3
|
-
*
|
4
|
-
* Tests project documentation management functionality
|
5
|
-
*/
|
6
|
-
|
7
|
-
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
8
|
-
import { TestAccess } from '../utils/test-access.js';
|
9
|
-
import { ProjectDocsManager } from '@codemcp/workflows-core';
|
10
|
-
import { TemplateManager } from '@codemcp/workflows-core';
|
11
|
-
import { mkdir, writeFile, rmdir, readFile } from 'node:fs/promises';
|
12
|
-
import { join } from 'node:path';
|
13
|
-
import { tmpdir } from 'node:os';
|
14
|
-
|
15
|
-
// Mock TemplateManager
|
16
|
-
vi.mock('../../src/template-manager.js');
|
17
|
-
|
18
|
-
describe('ProjectDocsManager', () => {
|
19
|
-
let projectDocsManager: ProjectDocsManager;
|
20
|
-
let testProjectPath: string;
|
21
|
-
let mockTemplateManager: vi.Mocked<TemplateManager>;
|
22
|
-
|
23
|
-
beforeEach(async () => {
|
24
|
-
// Create a temporary directory for test project
|
25
|
-
testProjectPath = join(tmpdir(), `project-test-${Date.now()}`);
|
26
|
-
await mkdir(testProjectPath, { recursive: true });
|
27
|
-
|
28
|
-
// Mock TemplateManager
|
29
|
-
mockTemplateManager = {
|
30
|
-
getDefaults: vi.fn().mockReturnValue({
|
31
|
-
architecture: 'arc42',
|
32
|
-
requirements: 'ears',
|
33
|
-
design: 'comprehensive',
|
34
|
-
}),
|
35
|
-
validateOptions: vi.fn(),
|
36
|
-
loadTemplate: vi.fn(),
|
37
|
-
getAvailableTemplates: vi.fn(),
|
38
|
-
} as Partial<TemplateManager>;
|
39
|
-
|
40
|
-
projectDocsManager = new ProjectDocsManager();
|
41
|
-
TestAccess.injectMock(
|
42
|
-
projectDocsManager,
|
43
|
-
'templateManager',
|
44
|
-
mockTemplateManager
|
45
|
-
);
|
46
|
-
});
|
47
|
-
|
48
|
-
afterEach(async () => {
|
49
|
-
// Clean up test directory
|
50
|
-
try {
|
51
|
-
await rmdir(testProjectPath, { recursive: true });
|
52
|
-
} catch {
|
53
|
-
// Ignore cleanup errors
|
54
|
-
}
|
55
|
-
});
|
56
|
-
|
57
|
-
describe('getDocsPath', () => {
|
58
|
-
it('should return correct docs path', () => {
|
59
|
-
const docsPath = projectDocsManager.getDocsPath(testProjectPath);
|
60
|
-
expect(docsPath).toBe(join(testProjectPath, '.vibe', 'docs'));
|
61
|
-
});
|
62
|
-
});
|
63
|
-
|
64
|
-
describe('getDocumentPaths', () => {
|
65
|
-
it('should return correct document paths', () => {
|
66
|
-
const paths = projectDocsManager.getDocumentPaths(testProjectPath);
|
67
|
-
|
68
|
-
expect(paths.architecture).toBe(
|
69
|
-
join(testProjectPath, '.vibe', 'docs', 'architecture.md')
|
70
|
-
);
|
71
|
-
expect(paths.requirements).toBe(
|
72
|
-
join(testProjectPath, '.vibe', 'docs', 'requirements.md')
|
73
|
-
);
|
74
|
-
expect(paths.design).toBe(
|
75
|
-
join(testProjectPath, '.vibe', 'docs', 'design.md')
|
76
|
-
);
|
77
|
-
});
|
78
|
-
});
|
79
|
-
|
80
|
-
describe('getProjectDocsInfo', () => {
|
81
|
-
it('should return info when no documents exist', async () => {
|
82
|
-
const info = await projectDocsManager.getProjectDocsInfo(testProjectPath);
|
83
|
-
|
84
|
-
expect(info.architecture.exists).toBe(false);
|
85
|
-
expect(info.requirements.exists).toBe(false);
|
86
|
-
expect(info.design.exists).toBe(false);
|
87
|
-
expect(info.architecture.path).toBe(
|
88
|
-
join(testProjectPath, '.vibe', 'docs', 'architecture.md')
|
89
|
-
);
|
90
|
-
});
|
91
|
-
|
92
|
-
it('should return info when documents exist', async () => {
|
93
|
-
// Create docs directory and files
|
94
|
-
const docsPath = join(testProjectPath, '.vibe', 'docs');
|
95
|
-
await mkdir(docsPath, { recursive: true });
|
96
|
-
await writeFile(join(docsPath, 'architecture.md'), '# Architecture');
|
97
|
-
await writeFile(join(docsPath, 'requirements.md'), '# Requirements');
|
98
|
-
|
99
|
-
const info = await projectDocsManager.getProjectDocsInfo(testProjectPath);
|
100
|
-
|
101
|
-
expect(info.architecture.exists).toBe(true);
|
102
|
-
expect(info.requirements.exists).toBe(true);
|
103
|
-
expect(info.design.exists).toBe(false);
|
104
|
-
});
|
105
|
-
});
|
106
|
-
|
107
|
-
describe('createProjectDocs', () => {
|
108
|
-
beforeEach(() => {
|
109
|
-
// Setup template manager mocks
|
110
|
-
mockTemplateManager.loadTemplate.mockImplementation(
|
111
|
-
async (type, template) => {
|
112
|
-
return {
|
113
|
-
content: `# Test ${type} template (${template})\n\nTest content for ${type}.`,
|
114
|
-
};
|
115
|
-
}
|
116
|
-
);
|
117
|
-
});
|
118
|
-
|
119
|
-
it('should create all documents with default templates', async () => {
|
120
|
-
const result =
|
121
|
-
await projectDocsManager.createProjectDocs(testProjectPath);
|
122
|
-
|
123
|
-
expect(result.created).toEqual([
|
124
|
-
'architecture.md',
|
125
|
-
'requirements.md',
|
126
|
-
'design.md',
|
127
|
-
]);
|
128
|
-
expect(result.skipped).toEqual([]);
|
129
|
-
|
130
|
-
// Verify template manager was called with defaults
|
131
|
-
expect(mockTemplateManager.loadTemplate).toHaveBeenCalledWith(
|
132
|
-
'architecture',
|
133
|
-
'arc42'
|
134
|
-
);
|
135
|
-
expect(mockTemplateManager.loadTemplate).toHaveBeenCalledWith(
|
136
|
-
'requirements',
|
137
|
-
'ears'
|
138
|
-
);
|
139
|
-
expect(mockTemplateManager.loadTemplate).toHaveBeenCalledWith(
|
140
|
-
'design',
|
141
|
-
'comprehensive'
|
142
|
-
);
|
143
|
-
|
144
|
-
// Verify files were created
|
145
|
-
const docsPath = join(testProjectPath, '.vibe', 'docs');
|
146
|
-
const archContent = await readFile(
|
147
|
-
join(docsPath, 'architecture.md'),
|
148
|
-
'utf-8'
|
149
|
-
);
|
150
|
-
expect(archContent).toContain('Test architecture template (arc42)');
|
151
|
-
});
|
152
|
-
|
153
|
-
it('should create documents with custom templates', async () => {
|
154
|
-
const options = {
|
155
|
-
architecture: 'freestyle' as const,
|
156
|
-
requirements: 'freestyle' as const,
|
157
|
-
design: 'freestyle' as const,
|
158
|
-
};
|
159
|
-
|
160
|
-
const result = await projectDocsManager.createProjectDocs(
|
161
|
-
testProjectPath,
|
162
|
-
options
|
163
|
-
);
|
164
|
-
|
165
|
-
expect(result.created).toEqual([
|
166
|
-
'architecture.md',
|
167
|
-
'requirements.md',
|
168
|
-
'design.md',
|
169
|
-
]);
|
170
|
-
expect(result.skipped).toEqual([]);
|
171
|
-
|
172
|
-
// Verify template manager was called with custom options
|
173
|
-
expect(mockTemplateManager.loadTemplate).toHaveBeenCalledWith(
|
174
|
-
'architecture',
|
175
|
-
'freestyle'
|
176
|
-
);
|
177
|
-
expect(mockTemplateManager.loadTemplate).toHaveBeenCalledWith(
|
178
|
-
'requirements',
|
179
|
-
'freestyle'
|
180
|
-
);
|
181
|
-
expect(mockTemplateManager.loadTemplate).toHaveBeenCalledWith(
|
182
|
-
'design',
|
183
|
-
'freestyle'
|
184
|
-
);
|
185
|
-
});
|
186
|
-
|
187
|
-
it('should skip existing documents', async () => {
|
188
|
-
// Create docs directory and one existing file
|
189
|
-
const docsPath = join(testProjectPath, '.vibe', 'docs');
|
190
|
-
await mkdir(docsPath, { recursive: true });
|
191
|
-
await writeFile(
|
192
|
-
join(docsPath, 'architecture.md'),
|
193
|
-
'# Existing Architecture'
|
194
|
-
);
|
195
|
-
|
196
|
-
const result =
|
197
|
-
await projectDocsManager.createProjectDocs(testProjectPath);
|
198
|
-
|
199
|
-
expect(result.created).toEqual(['requirements.md', 'design.md']);
|
200
|
-
expect(result.skipped).toEqual(['architecture.md']);
|
201
|
-
|
202
|
-
// Verify existing file wasn't overwritten
|
203
|
-
const archContent = await readFile(
|
204
|
-
join(docsPath, 'architecture.md'),
|
205
|
-
'utf-8'
|
206
|
-
);
|
207
|
-
expect(archContent).toBe('# Existing Architecture');
|
208
|
-
});
|
209
|
-
|
210
|
-
it('should handle template with additional files', async () => {
|
211
|
-
mockTemplateManager.loadTemplate.mockImplementation(
|
212
|
-
async (type, template) => {
|
213
|
-
if (type === 'architecture' && template === 'arc42') {
|
214
|
-
return {
|
215
|
-
content: '# Arc42 Template\n\nSee images/diagram.png',
|
216
|
-
additionalFiles: [
|
217
|
-
{
|
218
|
-
relativePath: 'images/diagram.png',
|
219
|
-
content: Buffer.from('fake-image-data'),
|
220
|
-
},
|
221
|
-
],
|
222
|
-
};
|
223
|
-
}
|
224
|
-
return {
|
225
|
-
content: `# Test ${type} template (${template})`,
|
226
|
-
};
|
227
|
-
}
|
228
|
-
);
|
229
|
-
|
230
|
-
const result =
|
231
|
-
await projectDocsManager.createProjectDocs(testProjectPath);
|
232
|
-
|
233
|
-
expect(result.created).toEqual([
|
234
|
-
'architecture.md',
|
235
|
-
'requirements.md',
|
236
|
-
'design.md',
|
237
|
-
]);
|
238
|
-
|
239
|
-
// Verify additional file was created
|
240
|
-
const imagePath = join(
|
241
|
-
testProjectPath,
|
242
|
-
'.vibe',
|
243
|
-
'docs',
|
244
|
-
'images',
|
245
|
-
'diagram.png'
|
246
|
-
);
|
247
|
-
const imageContent = await readFile(imagePath);
|
248
|
-
expect(imageContent).toEqual(Buffer.from('fake-image-data'));
|
249
|
-
});
|
250
|
-
|
251
|
-
it('should handle template loading errors', async () => {
|
252
|
-
mockTemplateManager.loadTemplate.mockRejectedValue(
|
253
|
-
new Error('Template not found')
|
254
|
-
);
|
255
|
-
|
256
|
-
// The method should throw when template loading fails
|
257
|
-
await expect(
|
258
|
-
projectDocsManager.createProjectDocs(testProjectPath)
|
259
|
-
).rejects.toThrow('Template not found');
|
260
|
-
});
|
261
|
-
});
|
262
|
-
|
263
|
-
describe('getVariableSubstitutions', () => {
|
264
|
-
it('should return correct variable substitutions', () => {
|
265
|
-
const substitutions =
|
266
|
-
projectDocsManager.getVariableSubstitutions(testProjectPath);
|
267
|
-
|
268
|
-
expect(substitutions).toEqual({
|
269
|
-
$ARCHITECTURE_DOC: join(
|
270
|
-
testProjectPath,
|
271
|
-
'.vibe',
|
272
|
-
'docs',
|
273
|
-
'architecture.md'
|
274
|
-
),
|
275
|
-
$REQUIREMENTS_DOC: join(
|
276
|
-
testProjectPath,
|
277
|
-
'.vibe',
|
278
|
-
'docs',
|
279
|
-
'requirements.md'
|
280
|
-
),
|
281
|
-
$DESIGN_DOC: join(testProjectPath, '.vibe', 'docs', 'design.md'),
|
282
|
-
});
|
283
|
-
});
|
284
|
-
});
|
285
|
-
|
286
|
-
describe('readDocument', () => {
|
287
|
-
it('should return path to existing document', async () => {
|
288
|
-
// Create docs directory and file
|
289
|
-
const docsPath = join(testProjectPath, '.vibe', 'docs');
|
290
|
-
await mkdir(docsPath, { recursive: true });
|
291
|
-
await writeFile(join(docsPath, 'architecture.md'), '# Test Architecture');
|
292
|
-
|
293
|
-
const path = await projectDocsManager.readDocument(
|
294
|
-
testProjectPath,
|
295
|
-
'architecture'
|
296
|
-
);
|
297
|
-
expect(path).toContain('architecture.md');
|
298
|
-
expect(path).toContain('.vibe/docs');
|
299
|
-
});
|
300
|
-
|
301
|
-
it('should throw error for non-existent document', async () => {
|
302
|
-
await expect(
|
303
|
-
projectDocsManager.readDocument(testProjectPath, 'architecture')
|
304
|
-
).rejects.toThrow('architecture document not found');
|
305
|
-
});
|
306
|
-
});
|
307
|
-
|
308
|
-
describe('allDocumentsExist', () => {
|
309
|
-
it('should return false when no documents exist', async () => {
|
310
|
-
const result =
|
311
|
-
await projectDocsManager.allDocumentsExist(testProjectPath);
|
312
|
-
expect(result).toBe(false);
|
313
|
-
});
|
314
|
-
|
315
|
-
it('should return false when some documents exist', async () => {
|
316
|
-
const docsPath = join(testProjectPath, '.vibe', 'docs');
|
317
|
-
await mkdir(docsPath, { recursive: true });
|
318
|
-
await writeFile(join(docsPath, 'architecture.md'), '# Architecture');
|
319
|
-
|
320
|
-
const result =
|
321
|
-
await projectDocsManager.allDocumentsExist(testProjectPath);
|
322
|
-
expect(result).toBe(false);
|
323
|
-
});
|
324
|
-
|
325
|
-
it('should return true when all documents exist', async () => {
|
326
|
-
const docsPath = join(testProjectPath, '.vibe', 'docs');
|
327
|
-
await mkdir(docsPath, { recursive: true });
|
328
|
-
await writeFile(join(docsPath, 'architecture.md'), '# Architecture');
|
329
|
-
await writeFile(join(docsPath, 'requirements.md'), '# Requirements');
|
330
|
-
await writeFile(join(docsPath, 'design.md'), '# Design');
|
331
|
-
|
332
|
-
const result =
|
333
|
-
await projectDocsManager.allDocumentsExist(testProjectPath);
|
334
|
-
expect(result).toBe(true);
|
335
|
-
});
|
336
|
-
});
|
337
|
-
});
|
@@ -1,234 +0,0 @@
|
|
1
|
-
/**
|
2
|
-
* Unit tests for StateMachineLoader
|
3
|
-
*/
|
4
|
-
|
5
|
-
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
6
|
-
import { StateMachineLoader } from '@codemcp/workflows-core';
|
7
|
-
|
8
|
-
// Mock global URL constructor
|
9
|
-
global.URL = vi.fn().mockImplementation(() => ({
|
10
|
-
pathname: '/mock/src/state-machine-loader.ts',
|
11
|
-
})) as typeof URL;
|
12
|
-
|
13
|
-
// Mock import.meta.url
|
14
|
-
vi.stubGlobal('import.meta', {
|
15
|
-
url: 'file:///mock/src/state-machine-loader.ts',
|
16
|
-
});
|
17
|
-
|
18
|
-
// Create a mock state machine object
|
19
|
-
const mockStateMachine = {
|
20
|
-
name: 'Test State Machine',
|
21
|
-
description: 'Test state machine for unit tests',
|
22
|
-
initial_state: 'idle',
|
23
|
-
states: {
|
24
|
-
idle: {
|
25
|
-
description: 'Idle state',
|
26
|
-
default_instructions:
|
27
|
-
'Starting idle state. Wait for user input and analyze requests.',
|
28
|
-
transitions: [
|
29
|
-
{
|
30
|
-
trigger: 'new_feature_request',
|
31
|
-
to: 'requirements',
|
32
|
-
instructions: 'Start requirements analysis',
|
33
|
-
transition_reason: 'New feature request detected',
|
34
|
-
},
|
35
|
-
],
|
36
|
-
},
|
37
|
-
requirements: {
|
38
|
-
description: 'Requirements state',
|
39
|
-
default_instructions:
|
40
|
-
'Starting requirements analysis. Gather and document user requirements.',
|
41
|
-
transitions: [],
|
42
|
-
},
|
43
|
-
},
|
44
|
-
};
|
45
|
-
|
46
|
-
// Mock modules
|
47
|
-
vi.mock('fs', () => {
|
48
|
-
return {
|
49
|
-
default: {
|
50
|
-
existsSync: vi.fn(),
|
51
|
-
readFileSync: vi.fn(),
|
52
|
-
},
|
53
|
-
existsSync: vi.fn(),
|
54
|
-
readFileSync: vi.fn(),
|
55
|
-
};
|
56
|
-
});
|
57
|
-
|
58
|
-
vi.mock('path', () => {
|
59
|
-
return {
|
60
|
-
default: {
|
61
|
-
resolve: vi.fn(p => p),
|
62
|
-
join: vi.fn((...paths) => paths.join('/')),
|
63
|
-
dirname: vi.fn(p => {
|
64
|
-
if (!p) return '';
|
65
|
-
const lastSlash = p.lastIndexOf('/');
|
66
|
-
return lastSlash >= 0 ? p.substring(0, lastSlash) : p;
|
67
|
-
}),
|
68
|
-
},
|
69
|
-
resolve: vi.fn(p => p),
|
70
|
-
join: vi.fn((...paths) => paths.join('/')),
|
71
|
-
dirname: vi.fn(p => {
|
72
|
-
if (!p) return '';
|
73
|
-
const lastSlash = p.lastIndexOf('/');
|
74
|
-
return lastSlash >= 0 ? p.substring(0, lastSlash) : p;
|
75
|
-
}),
|
76
|
-
};
|
77
|
-
});
|
78
|
-
|
79
|
-
// Properly mock js-yaml with a default export
|
80
|
-
vi.mock('js-yaml', () => {
|
81
|
-
return {
|
82
|
-
default: {
|
83
|
-
load: vi.fn(() => mockStateMachine),
|
84
|
-
dump: vi.fn(() => 'mocked yaml content'),
|
85
|
-
},
|
86
|
-
load: vi.fn(() => mockStateMachine),
|
87
|
-
dump: vi.fn(() => 'mocked yaml content'),
|
88
|
-
};
|
89
|
-
});
|
90
|
-
|
91
|
-
// Silence logger
|
92
|
-
vi.mock('../../src/logger.ts', () => ({
|
93
|
-
createLogger: () => ({
|
94
|
-
debug: vi.fn(),
|
95
|
-
info: vi.fn(),
|
96
|
-
warn: vi.fn(),
|
97
|
-
error: vi.fn(),
|
98
|
-
child: () => ({
|
99
|
-
debug: vi.fn(),
|
100
|
-
info: vi.fn(),
|
101
|
-
warn: vi.fn(),
|
102
|
-
error: vi.fn(),
|
103
|
-
}),
|
104
|
-
}),
|
105
|
-
}));
|
106
|
-
|
107
|
-
// Import mocked modules
|
108
|
-
import fs from 'node:fs';
|
109
|
-
|
110
|
-
describe('StateMachineLoader', () => {
|
111
|
-
let stateMachineLoader: StateMachineLoader;
|
112
|
-
|
113
|
-
beforeEach(() => {
|
114
|
-
// Reset mocks
|
115
|
-
vi.resetAllMocks();
|
116
|
-
|
117
|
-
// Create a new instance for each test
|
118
|
-
stateMachineLoader = new StateMachineLoader();
|
119
|
-
});
|
120
|
-
|
121
|
-
afterEach(() => {
|
122
|
-
vi.resetAllMocks();
|
123
|
-
});
|
124
|
-
|
125
|
-
describe('loadStateMachine', () => {
|
126
|
-
it('should load custom state machine file if it exists', () => {
|
127
|
-
// Mock fs.existsSync to return true for custom file
|
128
|
-
vi.mocked(fs.existsSync).mockImplementation(p => {
|
129
|
-
return String(p) === 'project/.vibe/workflow.yaml';
|
130
|
-
});
|
131
|
-
|
132
|
-
// Mock fs.readFileSync to return valid YAML content
|
133
|
-
vi.mocked(fs.readFileSync).mockReturnValue('valid yaml content');
|
134
|
-
|
135
|
-
const result = stateMachineLoader.loadStateMachine('project');
|
136
|
-
|
137
|
-
expect(fs.existsSync).toHaveBeenCalledWith('project/.vibe/workflow.yaml');
|
138
|
-
expect(fs.readFileSync).toHaveBeenCalledWith(
|
139
|
-
'project/.vibe/workflow.yaml',
|
140
|
-
'utf8'
|
141
|
-
);
|
142
|
-
expect(result).toBeDefined();
|
143
|
-
expect(result.name).toBe('Test State Machine');
|
144
|
-
});
|
145
|
-
|
146
|
-
it('should load default state machine file if custom file does not exist', () => {
|
147
|
-
// Mock fs.existsSync to return false for all paths
|
148
|
-
vi.mocked(fs.existsSync).mockReturnValue(false);
|
149
|
-
|
150
|
-
// Mock fs.readFileSync to return valid YAML content
|
151
|
-
vi.mocked(fs.readFileSync).mockReturnValue('valid yaml content');
|
152
|
-
|
153
|
-
const result = stateMachineLoader.loadStateMachine('project');
|
154
|
-
|
155
|
-
expect(fs.existsSync).toHaveBeenCalledWith('project/.vibe/workflow.yaml');
|
156
|
-
expect(fs.existsSync).toHaveBeenCalledWith('project/.vibe/workflow.yml');
|
157
|
-
|
158
|
-
// The path might be absolute or relative depending on the environment
|
159
|
-
expect(fs.readFileSync).toHaveBeenCalled();
|
160
|
-
expect(vi.mocked(fs.readFileSync).mock.calls[0][0]).toMatch(
|
161
|
-
/.*resources\/workflows\/waterfall\.yaml$/
|
162
|
-
);
|
163
|
-
expect(vi.mocked(fs.readFileSync).mock.calls[0][1]).toBe('utf8');
|
164
|
-
expect(result).toBeDefined();
|
165
|
-
expect(result.name).toBe('Test State Machine');
|
166
|
-
});
|
167
|
-
});
|
168
|
-
|
169
|
-
describe('loadFromFile', () => {
|
170
|
-
it('should load and validate state machine from file', () => {
|
171
|
-
// Mock fs.readFileSync to return valid YAML content
|
172
|
-
vi.mocked(fs.readFileSync).mockReturnValue('valid yaml content');
|
173
|
-
|
174
|
-
const result = stateMachineLoader.loadFromFile('test-file.yaml');
|
175
|
-
|
176
|
-
expect(fs.readFileSync).toHaveBeenCalledWith('test-file.yaml', 'utf8');
|
177
|
-
expect(result).toBeDefined();
|
178
|
-
expect(result.name).toBe('Test State Machine');
|
179
|
-
});
|
180
|
-
|
181
|
-
it('should throw error if file cannot be loaded', () => {
|
182
|
-
// Mock fs.readFileSync to throw error
|
183
|
-
vi.mocked(fs.readFileSync).mockImplementation(() => {
|
184
|
-
throw new Error('File not found');
|
185
|
-
});
|
186
|
-
|
187
|
-
expect(() =>
|
188
|
-
stateMachineLoader.loadFromFile('invalid-file.yaml')
|
189
|
-
).toThrow('Failed to load state machine: File not found');
|
190
|
-
});
|
191
|
-
});
|
192
|
-
|
193
|
-
describe('getTransitionInstructions', () => {
|
194
|
-
beforeEach(() => {
|
195
|
-
// Load state machine
|
196
|
-
vi.mocked(fs.readFileSync).mockReturnValue('valid yaml content');
|
197
|
-
stateMachineLoader.loadFromFile('test-file.yaml');
|
198
|
-
});
|
199
|
-
|
200
|
-
it('should return transition instructions for modeled transition', () => {
|
201
|
-
const result = stateMachineLoader.getTransitionInstructions(
|
202
|
-
'idle',
|
203
|
-
'requirements',
|
204
|
-
'new_feature_request'
|
205
|
-
);
|
206
|
-
|
207
|
-
expect(result).toEqual({
|
208
|
-
instructions: 'Start requirements analysis',
|
209
|
-
transitionReason: 'New feature request detected',
|
210
|
-
isModeled: true,
|
211
|
-
});
|
212
|
-
});
|
213
|
-
|
214
|
-
it('should return default instructions when no modeled transition exists', () => {
|
215
|
-
const result = stateMachineLoader.getTransitionInstructions(
|
216
|
-
'requirements',
|
217
|
-
'idle'
|
218
|
-
);
|
219
|
-
|
220
|
-
expect(result).toEqual({
|
221
|
-
instructions:
|
222
|
-
'Starting idle state. Wait for user input and analyze requests.',
|
223
|
-
transitionReason: 'Direct transition to idle phase',
|
224
|
-
isModeled: false,
|
225
|
-
});
|
226
|
-
});
|
227
|
-
|
228
|
-
it('should throw error if no transition found', () => {
|
229
|
-
expect(() =>
|
230
|
-
stateMachineLoader.getTransitionInstructions('requirements', 'unknown')
|
231
|
-
).toThrow('Target state "unknown" not found');
|
232
|
-
});
|
233
|
-
});
|
234
|
-
});
|