@codemcp/workflows-core 3.1.22 → 3.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/package.json +8 -3
  2. package/resources/templates/architecture/arc42/arc42-template-EN.md +1077 -0
  3. package/resources/templates/architecture/arc42/images/01_2_iso-25010-topics-EN.drawio-2023.png +0 -0
  4. package/resources/templates/architecture/arc42/images/01_2_iso-25010-topics-EN.drawio.png +0 -0
  5. package/resources/templates/architecture/arc42/images/05_building_blocks-EN.png +0 -0
  6. package/resources/templates/architecture/arc42/images/08-concepts-EN.drawio.png +0 -0
  7. package/resources/templates/architecture/arc42/images/arc42-logo.png +0 -0
  8. package/resources/templates/architecture/c4.md +224 -0
  9. package/resources/templates/architecture/freestyle.md +53 -0
  10. package/resources/templates/architecture/none.md +17 -0
  11. package/resources/templates/design/comprehensive.md +207 -0
  12. package/resources/templates/design/freestyle.md +37 -0
  13. package/resources/templates/design/none.md +17 -0
  14. package/resources/templates/requirements/ears.md +90 -0
  15. package/resources/templates/requirements/freestyle.md +42 -0
  16. package/resources/templates/requirements/none.md +17 -0
  17. package/resources/workflows/big-bang-conversion.yaml +539 -0
  18. package/resources/workflows/boundary-testing.yaml +334 -0
  19. package/resources/workflows/bugfix.yaml +185 -0
  20. package/resources/workflows/business-analysis.yaml +671 -0
  21. package/resources/workflows/c4-analysis.yaml +485 -0
  22. package/resources/workflows/epcc.yaml +161 -0
  23. package/resources/workflows/greenfield.yaml +189 -0
  24. package/resources/workflows/minor.yaml +127 -0
  25. package/resources/workflows/posts.yaml +207 -0
  26. package/resources/workflows/slides.yaml +256 -0
  27. package/resources/workflows/tdd.yaml +157 -0
  28. package/resources/workflows/waterfall.yaml +195 -0
  29. package/.turbo/turbo-build.log +0 -4
  30. package/src/config-manager.ts +0 -96
  31. package/src/conversation-manager.ts +0 -489
  32. package/src/database.ts +0 -427
  33. package/src/file-detection-manager.ts +0 -302
  34. package/src/git-manager.ts +0 -64
  35. package/src/index.ts +0 -28
  36. package/src/instruction-generator.ts +0 -210
  37. package/src/interaction-logger.ts +0 -109
  38. package/src/logger.ts +0 -353
  39. package/src/path-validation-utils.ts +0 -261
  40. package/src/plan-manager.ts +0 -323
  41. package/src/project-docs-manager.ts +0 -523
  42. package/src/state-machine-loader.ts +0 -365
  43. package/src/state-machine-types.ts +0 -72
  44. package/src/state-machine.ts +0 -370
  45. package/src/system-prompt-generator.ts +0 -122
  46. package/src/template-manager.ts +0 -328
  47. package/src/transition-engine.ts +0 -386
  48. package/src/types.ts +0 -60
  49. package/src/workflow-manager.ts +0 -606
  50. package/test/unit/conversation-manager.test.ts +0 -179
  51. package/test/unit/custom-workflow-loading.test.ts +0 -174
  52. package/test/unit/directory-linking-and-extensions.test.ts +0 -338
  53. package/test/unit/file-linking-integration.test.ts +0 -256
  54. package/test/unit/git-commit-integration.test.ts +0 -91
  55. package/test/unit/git-manager.test.ts +0 -86
  56. package/test/unit/install-workflow.test.ts +0 -138
  57. package/test/unit/instruction-generator.test.ts +0 -247
  58. package/test/unit/list-workflows-filtering.test.ts +0 -68
  59. package/test/unit/none-template-functionality.test.ts +0 -224
  60. package/test/unit/project-docs-manager.test.ts +0 -337
  61. package/test/unit/state-machine-loader.test.ts +0 -234
  62. package/test/unit/template-manager.test.ts +0 -217
  63. package/test/unit/validate-workflow-name.test.ts +0 -150
  64. package/test/unit/workflow-domain-filtering.test.ts +0 -75
  65. package/test/unit/workflow-enum-generation.test.ts +0 -92
  66. package/test/unit/workflow-manager-enhanced-path-resolution.test.ts +0 -369
  67. package/test/unit/workflow-manager-path-resolution.test.ts +0 -150
  68. package/test/unit/workflow-migration.test.ts +0 -155
  69. package/test/unit/workflow-override-by-name.test.ts +0 -116
  70. package/test/unit/workflow-prioritization.test.ts +0 -38
  71. package/test/unit/workflow-validation.test.ts +0 -303
  72. package/test/utils/e2e-test-setup.ts +0 -453
  73. package/test/utils/run-server-in-dir.sh +0 -27
  74. package/test/utils/temp-files.ts +0 -308
  75. package/test/utils/test-access.ts +0 -79
  76. package/test/utils/test-helpers.ts +0 -286
  77. package/test/utils/test-setup.ts +0 -78
  78. package/tsconfig.build.json +0 -21
  79. package/tsconfig.json +0 -8
  80. package/vitest.config.ts +0 -18
@@ -1,217 +0,0 @@
1
- /**
2
- * Unit tests for TemplateManager
3
- *
4
- * Tests template loading, validation, and rendering functionality
5
- */
6
-
7
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
8
- import { TestAccess } from '../utils/test-access.js';
9
- import { TemplateManager } from '@codemcp/workflows-core';
10
- import { mkdir, writeFile, rmdir } from 'node:fs/promises';
11
- import { join } from 'node:path';
12
- import { tmpdir } from 'node:os';
13
-
14
- describe('TemplateManager', () => {
15
- let templateManager: TemplateManager;
16
- let testTemplatesPath: string;
17
-
18
- beforeEach(async () => {
19
- // Create a temporary directory for test templates
20
- testTemplatesPath = join(tmpdir(), `template-test-${Date.now()}`);
21
- await mkdir(testTemplatesPath, { recursive: true });
22
-
23
- // Create test template directories
24
- await mkdir(join(testTemplatesPath, 'architecture'), { recursive: true });
25
- await mkdir(join(testTemplatesPath, 'requirements'), { recursive: true });
26
- await mkdir(join(testTemplatesPath, 'design'), { recursive: true });
27
-
28
- // Create test template files
29
- await writeFile(
30
- join(testTemplatesPath, 'architecture', 'freestyle.md'),
31
- '# Test Architecture Template\n\nThis is a test template.'
32
- );
33
-
34
- await writeFile(
35
- join(testTemplatesPath, 'requirements', 'ears.md'),
36
- '# Test Requirements Template\n\n## REQ-1: Test Requirement'
37
- );
38
-
39
- await writeFile(
40
- join(testTemplatesPath, 'requirements', 'freestyle.md'),
41
- '# Freestyle Requirements Template\n\nFlexible requirements format.'
42
- );
43
-
44
- await writeFile(
45
- join(testTemplatesPath, 'design', 'comprehensive.md'),
46
- '# Test Design Template\n\n## Components\n\nTest components here.'
47
- );
48
-
49
- await writeFile(
50
- join(testTemplatesPath, 'design', 'freestyle.md'),
51
- '# Freestyle Design Template\n\nFlexible design format.'
52
- );
53
-
54
- // Create arc42 directory template with images
55
- const arc42Path = join(testTemplatesPath, 'architecture', 'arc42');
56
- await mkdir(arc42Path, { recursive: true });
57
- await mkdir(join(arc42Path, 'images'), { recursive: true });
58
-
59
- await writeFile(
60
- join(arc42Path, 'arc42-template-EN.md'),
61
- '# Arc42 Template\n\n## Introduction and Goals\n\nTest arc42 content.'
62
- );
63
-
64
- await writeFile(
65
- join(arc42Path, 'images', 'test-image.png'),
66
- Buffer.from('fake-image-data')
67
- );
68
-
69
- // Mock the templatesPath in TemplateManager
70
- templateManager = new TemplateManager();
71
- TestAccess.injectMock(templateManager, 'templatesPath', testTemplatesPath);
72
- });
73
-
74
- afterEach(async () => {
75
- // Clean up test directory
76
- try {
77
- await rmdir(testTemplatesPath, { recursive: true });
78
- } catch {
79
- // Ignore cleanup errors
80
- }
81
- });
82
-
83
- describe('getDefaults', () => {
84
- it('should return correct default template options', async () => {
85
- const defaults = await templateManager.getDefaults();
86
-
87
- expect(defaults).toEqual({
88
- architecture: 'arc42',
89
- requirements: 'ears',
90
- design: 'comprehensive',
91
- });
92
- });
93
- });
94
-
95
- describe('validateOptions', () => {
96
- it('should validate correct template options', async () => {
97
- await expect(
98
- templateManager.validateOptions({
99
- architecture: 'arc42',
100
- requirements: 'ears',
101
- design: 'comprehensive',
102
- })
103
- ).resolves.not.toThrow();
104
- });
105
-
106
- it('should validate freestyle options', async () => {
107
- await expect(
108
- templateManager.validateOptions({
109
- architecture: 'freestyle',
110
- requirements: 'freestyle',
111
- design: 'freestyle',
112
- })
113
- ).resolves.not.toThrow();
114
- });
115
-
116
- it('should throw error for invalid architecture template', async () => {
117
- await expect(
118
- templateManager.validateOptions({
119
- // @ts-ignore - testing invalid input
120
- architecture: 'invalid',
121
- })
122
- ).rejects.toThrow('Invalid architecture template: invalid');
123
- });
124
-
125
- it('should throw error for invalid requirements template', async () => {
126
- await expect(
127
- templateManager.validateOptions({
128
- // @ts-ignore - testing invalid input
129
- requirements: 'invalid',
130
- })
131
- ).rejects.toThrow('Invalid requirements template: invalid');
132
- });
133
-
134
- it('should throw error for invalid design template', async () => {
135
- await expect(
136
- templateManager.validateOptions({
137
- // @ts-ignore - testing invalid input
138
- design: 'invalid',
139
- })
140
- ).rejects.toThrow('Invalid design template: invalid');
141
- });
142
- });
143
-
144
- describe('loadTemplate', () => {
145
- it('should load freestyle architecture template', async () => {
146
- const result = await templateManager.loadTemplate(
147
- 'architecture',
148
- 'freestyle'
149
- );
150
-
151
- expect(result.content).toContain('# Test Architecture Template');
152
- expect(result.content).toContain('This is a test template.');
153
- expect(result.additionalFiles).toBeUndefined();
154
- });
155
-
156
- it('should load ears requirements template', async () => {
157
- const result = await templateManager.loadTemplate('requirements', 'ears');
158
-
159
- expect(result.content).toContain('# Test Requirements Template');
160
- expect(result.content).toContain('## REQ-1: Test Requirement');
161
- expect(result.additionalFiles).toBeUndefined();
162
- });
163
-
164
- it('should load comprehensive design template', async () => {
165
- const result = await templateManager.loadTemplate(
166
- 'design',
167
- 'comprehensive'
168
- );
169
-
170
- expect(result.content).toContain('# Test Design Template');
171
- expect(result.content).toContain('## Components');
172
- expect(result.additionalFiles).toBeUndefined();
173
- });
174
-
175
- it('should load arc42 directory template with images', async () => {
176
- const result = await templateManager.loadTemplate(
177
- 'architecture',
178
- 'arc42'
179
- );
180
-
181
- expect(result.content).toContain('# Arc42 Template');
182
- expect(result.content).toContain('## Introduction and Goals');
183
- expect(result.additionalFiles).toBeDefined();
184
- expect(result.additionalFiles).toHaveLength(1);
185
-
186
- // Safe to access [0] after length check
187
- const firstFile = result.additionalFiles?.[0];
188
- expect(firstFile?.relativePath).toBe('images/test-image.png');
189
- expect(firstFile?.content).toEqual(Buffer.from('fake-image-data'));
190
- });
191
-
192
- it('should throw error for non-existent template', async () => {
193
- await expect(
194
- templateManager.loadTemplate('architecture', 'nonexistent')
195
- ).rejects.toThrow('Template not found: architecture/nonexistent');
196
- });
197
-
198
- it('should throw error for non-existent template type', async () => {
199
- await expect(
200
- // @ts-ignore - testing invalid input
201
- templateManager.loadTemplate('invalid', 'freestyle')
202
- ).rejects.toThrow('Template not found: invalid/freestyle');
203
- });
204
- });
205
-
206
- describe('getAvailableTemplates', () => {
207
- it('should return all available template options', async () => {
208
- const templates = await templateManager.getAvailableTemplates();
209
-
210
- expect(templates).toEqual({
211
- architecture: ['arc42', 'freestyle'],
212
- requirements: ['ears', 'freestyle'],
213
- design: ['comprehensive', 'freestyle'],
214
- });
215
- });
216
- });
217
- });
@@ -1,150 +0,0 @@
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('validateWorkflowName', () => {
8
- let testProjectPath: string;
9
- let manager: WorkflowManager;
10
-
11
- beforeEach(() => {
12
- testProjectPath = fs.mkdtempSync(
13
- path.join(tmpdir(), 'validate-workflow-test-')
14
- );
15
- manager = new WorkflowManager();
16
- });
17
-
18
- afterEach(() => {
19
- fs.rmSync(testProjectPath, { recursive: true, force: true });
20
- });
21
-
22
- it('should validate predefined workflows', () => {
23
- expect(manager.validateWorkflowName('waterfall', testProjectPath)).toBe(
24
- true
25
- );
26
- expect(manager.validateWorkflowName('epcc', testProjectPath)).toBe(true);
27
- expect(manager.validateWorkflowName('bugfix', testProjectPath)).toBe(true);
28
- });
29
-
30
- it('should validate project workflows', () => {
31
- // Create a project workflow using valid format from existing workflow
32
- const workflowsDir = path.join(testProjectPath, '.vibe', 'workflows');
33
- fs.mkdirSync(workflowsDir, { recursive: true });
34
-
35
- // Copy minor workflow and customize it
36
- const minorPath = path.join(
37
- __dirname,
38
- '..',
39
- '..',
40
- 'resources',
41
- 'workflows',
42
- 'minor.yaml'
43
- );
44
- const originalContent = fs.readFileSync(minorPath, 'utf8');
45
- const customContent = originalContent
46
- .replace("name: 'minor'", "name: 'my-project-workflow'")
47
- .replace(/description: .*/, "description: 'Project specific workflow'");
48
-
49
- fs.writeFileSync(path.join(workflowsDir, 'project.yaml'), customContent);
50
-
51
- // Should validate the project workflow
52
- expect(
53
- manager.validateWorkflowName('my-project-workflow', testProjectPath)
54
- ).toBe(true);
55
- });
56
-
57
- it('should reject invalid workflow names', () => {
58
- expect(manager.validateWorkflowName('nonexistent', testProjectPath)).toBe(
59
- false
60
- );
61
- expect(
62
- manager.validateWorkflowName('invalid-workflow', testProjectPath)
63
- ).toBe(false);
64
- });
65
-
66
- it('should validate "custom" workflow if it exists as project workflow', () => {
67
- // Create a project workflow named "custom"
68
- const workflowsDir = path.join(testProjectPath, '.vibe', 'workflows');
69
- fs.mkdirSync(workflowsDir, { recursive: true });
70
-
71
- // Copy minor workflow and name it "custom"
72
- const minorPath = path.join(
73
- __dirname,
74
- '..',
75
- '..',
76
- 'resources',
77
- 'workflows',
78
- 'minor.yaml'
79
- );
80
- const originalContent = fs.readFileSync(minorPath, 'utf8');
81
- const customContent = originalContent
82
- .replace("name: 'minor'", "name: 'custom'")
83
- .replace(/description: .*/, "description: 'Custom workflow'");
84
-
85
- fs.writeFileSync(path.join(workflowsDir, 'custom.yaml'), customContent);
86
-
87
- // Should validate the "custom" workflow as a normal project workflow
88
- expect(manager.validateWorkflowName('custom', testProjectPath)).toBe(true);
89
- });
90
-
91
- it('should validate "custom" workflow if it exists as project workflow', () => {
92
- // Create a project workflow named "custom"
93
- const workflowsDir = path.join(testProjectPath, '.vibe', 'workflows');
94
- fs.mkdirSync(workflowsDir, { recursive: true });
95
-
96
- // Copy minor workflow and name it "custom"
97
- const minorPath = path.join(
98
- __dirname,
99
- '..',
100
- '..',
101
- 'resources',
102
- 'workflows',
103
- 'minor.yaml'
104
- );
105
- const originalContent = fs.readFileSync(minorPath, 'utf8');
106
- const customContent = originalContent
107
- .replace("name: 'minor'", "name: 'custom'")
108
- .replace(/description: .*/, "description: 'Custom workflow'");
109
-
110
- fs.writeFileSync(path.join(workflowsDir, 'custom.yaml'), customContent);
111
-
112
- // Should validate the "custom" workflow as a normal project workflow
113
- expect(manager.validateWorkflowName('custom', testProjectPath)).toBe(true);
114
- });
115
-
116
- it('should validate project workflows after migration', () => {
117
- // Create legacy workflow file using valid format
118
- const vibeDir = path.join(testProjectPath, '.vibe');
119
- fs.mkdirSync(vibeDir, { recursive: true });
120
-
121
- // Copy minor workflow and customize it
122
- const minorPath = path.join(
123
- __dirname,
124
- '..',
125
- '..',
126
- 'resources',
127
- 'workflows',
128
- 'minor.yaml'
129
- );
130
- const originalContent = fs.readFileSync(minorPath, 'utf8');
131
- const legacyContent = originalContent
132
- .replace("name: 'minor'", "name: 'migrated-workflow'")
133
- .replace(/description: .*/, "description: 'Migrated workflow'");
134
-
135
- fs.writeFileSync(path.join(vibeDir, 'workflow.yaml'), legacyContent);
136
-
137
- // Should validate the migrated workflow (migration happens during validation)
138
- expect(
139
- manager.validateWorkflowName('migrated-workflow', testProjectPath)
140
- ).toBe(true);
141
-
142
- // Legacy file should be gone
143
- expect(fs.existsSync(path.join(vibeDir, 'workflow.yaml'))).toBe(false);
144
-
145
- // New file should exist
146
- expect(fs.existsSync(path.join(vibeDir, 'workflows', 'custom.yaml'))).toBe(
147
- true
148
- );
149
- });
150
- });
@@ -1,75 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
- import { WorkflowManager } from '@codemcp/workflows-core';
3
-
4
- describe('Workflow Domain Filtering', () => {
5
- let originalEnv: string | undefined;
6
-
7
- beforeEach(() => {
8
- originalEnv = process.env.VIBE_WORKFLOW_DOMAINS;
9
- });
10
-
11
- afterEach(() => {
12
- if (originalEnv !== undefined) {
13
- process.env.VIBE_WORKFLOW_DOMAINS = originalEnv;
14
- } else {
15
- delete process.env.VIBE_WORKFLOW_DOMAINS;
16
- }
17
- });
18
-
19
- it('should load only code workflows when no domain filter is set', () => {
20
- delete process.env.VIBE_WORKFLOW_DOMAINS;
21
-
22
- const manager = new WorkflowManager();
23
- const workflows = manager.getAvailableWorkflows();
24
-
25
- // Should only include code domain workflows and workflows without domain
26
- const codeWorkflows = workflows.filter(
27
- w => !w.metadata?.domain || w.metadata.domain === 'code'
28
- );
29
- const nonCodeWorkflows = workflows.filter(
30
- w => w.metadata?.domain && w.metadata.domain !== 'code'
31
- );
32
-
33
- expect(codeWorkflows.length).toBeGreaterThan(0);
34
- expect(nonCodeWorkflows.length).toBe(0);
35
- });
36
-
37
- it('should filter workflows by domain when VIBE_WORKFLOW_DOMAINS is set', () => {
38
- process.env.VIBE_WORKFLOW_DOMAINS = 'code';
39
-
40
- const manager = new WorkflowManager();
41
- const workflows = manager.getAvailableWorkflows();
42
-
43
- // Should only include code domain workflows
44
- const codeWorkflows = workflows.filter(w => w.metadata?.domain === 'code');
45
- const nonCodeWorkflows = workflows.filter(
46
- w => w.metadata?.domain && w.metadata.domain !== 'code'
47
- );
48
-
49
- expect(codeWorkflows.length).toBeGreaterThan(0);
50
- expect(nonCodeWorkflows.length).toBe(0);
51
- });
52
-
53
- it('should support multiple domains', () => {
54
- process.env.VIBE_WORKFLOW_DOMAINS = 'code,office';
55
-
56
- const manager = new WorkflowManager();
57
- const workflows = manager.getAvailableWorkflows();
58
-
59
- const allowedWorkflows = workflows.filter(
60
- w => !w.metadata?.domain || ['code', 'office'].includes(w.metadata.domain)
61
- );
62
-
63
- expect(allowedWorkflows.length).toBe(workflows.length);
64
- });
65
-
66
- it('should handle invalid domains gracefully', () => {
67
- process.env.VIBE_WORKFLOW_DOMAINS = 'code,invalid,office';
68
-
69
- const manager = new WorkflowManager();
70
- const workflows = manager.getAvailableWorkflows();
71
-
72
- // Should still work with valid domains
73
- expect(workflows.length).toBeGreaterThan(0);
74
- });
75
- });
@@ -1,92 +0,0 @@
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 Enum Generation', () => {
8
- let testProjectPath: string;
9
- let manager: WorkflowManager;
10
-
11
- beforeEach(() => {
12
- testProjectPath = fs.mkdtempSync(
13
- path.join(tmpdir(), 'workflow-enum-test-')
14
- );
15
- manager = new WorkflowManager();
16
- });
17
-
18
- afterEach(() => {
19
- fs.rmSync(testProjectPath, { recursive: true, force: true });
20
- });
21
-
22
- it('should include predefined workflows in enum', () => {
23
- const workflowNames = manager.getWorkflowNames();
24
-
25
- // Should include common predefined workflows
26
- expect(workflowNames).toContain('waterfall');
27
- expect(workflowNames).toContain('epcc');
28
- expect(workflowNames).toContain('bugfix');
29
- });
30
-
31
- it('should include project workflows in enum after loading', () => {
32
- // Create a project workflow
33
- const workflowsDir = path.join(testProjectPath, '.vibe', 'workflows');
34
- fs.mkdirSync(workflowsDir, { recursive: true });
35
-
36
- // Copy minor workflow and customize it
37
- const minorPath = path.join(
38
- __dirname,
39
- '..',
40
- '..',
41
- 'resources',
42
- 'workflows',
43
- 'minor.yaml'
44
- );
45
- const originalContent = fs.readFileSync(minorPath, 'utf8');
46
- const customContent = originalContent
47
- .replace("name: 'minor'", "name: 'my-custom-workflow'")
48
- .replace(/description: .*/, "description: 'Custom workflow for testing'");
49
-
50
- fs.writeFileSync(path.join(workflowsDir, 'custom.yaml'), customContent);
51
-
52
- // Load project workflows
53
- manager.loadProjectWorkflows(testProjectPath);
54
-
55
- const workflowNames = manager.getWorkflowNames();
56
-
57
- // Should include both predefined and project workflows
58
- expect(workflowNames).toContain('waterfall'); // predefined
59
- expect(workflowNames).toContain('my-custom-workflow'); // project
60
- });
61
-
62
- it('should not duplicate workflow names when project overrides predefined', () => {
63
- // Create a project workflow with same name as predefined
64
- const workflowsDir = path.join(testProjectPath, '.vibe', 'workflows');
65
- fs.mkdirSync(workflowsDir, { recursive: true });
66
-
67
- // Copy minor workflow but keep the same name
68
- const minorPath = path.join(
69
- __dirname,
70
- '..',
71
- '..',
72
- 'resources',
73
- 'workflows',
74
- 'minor.yaml'
75
- );
76
- const originalContent = fs.readFileSync(minorPath, 'utf8');
77
-
78
- fs.writeFileSync(
79
- path.join(workflowsDir, 'minor-override.yaml'),
80
- originalContent
81
- );
82
-
83
- // Load project workflows
84
- manager.loadProjectWorkflows(testProjectPath);
85
-
86
- const workflowNames = manager.getWorkflowNames();
87
-
88
- // Should not have duplicates
89
- const minorCount = workflowNames.filter(name => name === 'minor').length;
90
- expect(minorCount).toBe(1);
91
- });
92
- });