@codemcp/workflows 5.0.1 → 5.1.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 (77) hide show
  1. package/package.json +6 -2
  2. package/skill/SKILL.md +23 -0
  3. package/.prettierignore +0 -2
  4. package/.turbo/turbo-build.log +0 -4
  5. package/.vibe/conversation-state.sqlite +0 -0
  6. package/src/components/beads/beads-instruction-generator.ts +0 -230
  7. package/src/components/beads/beads-plan-manager.ts +0 -333
  8. package/src/components/beads/beads-task-backend-client.ts +0 -229
  9. package/src/index.ts +0 -93
  10. package/src/notification-service.ts +0 -23
  11. package/src/plugin-system/beads-plugin.ts +0 -649
  12. package/src/plugin-system/commit-plugin.ts +0 -252
  13. package/src/plugin-system/index.ts +0 -20
  14. package/src/plugin-system/plugin-interfaces.ts +0 -153
  15. package/src/plugin-system/plugin-registry.ts +0 -190
  16. package/src/resource-handlers/conversation-state.ts +0 -55
  17. package/src/resource-handlers/development-plan.ts +0 -48
  18. package/src/resource-handlers/index.ts +0 -73
  19. package/src/resource-handlers/system-prompt.ts +0 -55
  20. package/src/resource-handlers/workflow-resource.ts +0 -132
  21. package/src/response-renderer.ts +0 -116
  22. package/src/server-config.ts +0 -760
  23. package/src/server-helpers.ts +0 -245
  24. package/src/server-implementation.ts +0 -277
  25. package/src/server.ts +0 -9
  26. package/src/tool-handlers/base-tool-handler.ts +0 -151
  27. package/src/tool-handlers/conduct-review.ts +0 -190
  28. package/src/tool-handlers/get-tool-info.ts +0 -273
  29. package/src/tool-handlers/index.ts +0 -115
  30. package/src/tool-handlers/list-workflows.ts +0 -78
  31. package/src/tool-handlers/no-idea.ts +0 -47
  32. package/src/tool-handlers/proceed-to-phase.ts +0 -296
  33. package/src/tool-handlers/reset-development.ts +0 -90
  34. package/src/tool-handlers/resume-workflow.ts +0 -378
  35. package/src/tool-handlers/setup-project-docs.ts +0 -232
  36. package/src/tool-handlers/start-development.ts +0 -746
  37. package/src/tool-handlers/whats-next.ts +0 -246
  38. package/src/types.ts +0 -135
  39. package/src/version-info.ts +0 -213
  40. package/test/e2e/beads-plugin-integration.test.ts +0 -1623
  41. package/test/e2e/commit-plugin-integration.test.ts +0 -222
  42. package/test/e2e/core-functionality.test.ts +0 -167
  43. package/test/e2e/git-branch-detection.test.ts +0 -351
  44. package/test/e2e/mcp-contract.test.ts +0 -509
  45. package/test/e2e/plan-management.test.ts +0 -334
  46. package/test/e2e/plugin-system-integration.test.ts +0 -1410
  47. package/test/e2e/state-management.test.ts +0 -387
  48. package/test/e2e/workflow-integration.test.ts +0 -498
  49. package/test/unit/beads-instruction-generator.test.ts +0 -979
  50. package/test/unit/beads-phase-task-id-integration.test.ts +0 -535
  51. package/test/unit/beads-plugin-behavioral.test.ts +0 -545
  52. package/test/unit/beads-plugin.test.ts +0 -117
  53. package/test/unit/commit-plugin.test.ts +0 -196
  54. package/test/unit/conduct-review.test.ts +0 -151
  55. package/test/unit/conversation-not-found-error.test.ts +0 -120
  56. package/test/unit/plugin-error-handling.test.ts +0 -240
  57. package/test/unit/proceed-to-phase-plugin-integration.test.ts +0 -150
  58. package/test/unit/reset-functionality.test.ts +0 -72
  59. package/test/unit/resume-workflow.test.ts +0 -193
  60. package/test/unit/server-config-plugin-registry.test.ts +0 -99
  61. package/test/unit/server-tools.test.ts +0 -310
  62. package/test/unit/setup-project-docs-handler.test.ts +0 -268
  63. package/test/unit/start-development-artifact-detection.test.ts +0 -387
  64. package/test/unit/start-development-gitignore.test.ts +0 -178
  65. package/test/unit/start-development-goal-extraction.test.ts +0 -226
  66. package/test/unit/system-prompt-resource.test.ts +0 -102
  67. package/test/unit/tool-handlers/no-idea.test.ts +0 -40
  68. package/test/utils/e2e-test-setup.ts +0 -451
  69. package/test/utils/run-server-in-dir.sh +0 -27
  70. package/test/utils/temp-files.ts +0 -320
  71. package/test/utils/test-access.ts +0 -79
  72. package/test/utils/test-helpers.ts +0 -288
  73. package/test/utils/test-setup.ts +0 -77
  74. package/tsconfig.build.json +0 -10
  75. package/tsconfig.build.tsbuildinfo +0 -1
  76. package/tsconfig.json +0 -12
  77. package/vitest.config.ts +0 -19
@@ -1,196 +0,0 @@
1
- /**
2
- * Test CommitPlugin activation and lifecycle hooks
3
- */
4
-
5
- import { describe, it, expect, beforeEach, vi } from 'vitest';
6
- import { CommitPlugin } from '../../src/plugin-system/commit-plugin.js';
7
- import type { PluginHookContext } from '../../src/plugin-system/plugin-interfaces.js';
8
-
9
- // Mock GitManager
10
- vi.mock('@codemcp/workflows-core', async () => {
11
- const actual = await vi.importActual('@codemcp/workflows-core');
12
- return {
13
- ...actual,
14
- GitManager: {
15
- isGitRepository: vi.fn(),
16
- hasUncommittedChanges: vi.fn(),
17
- createCommit: vi.fn(),
18
- },
19
- };
20
- });
21
-
22
- describe('CommitPlugin', () => {
23
- let plugin: CommitPlugin;
24
- const projectPath = '/test/project';
25
-
26
- beforeEach(() => {
27
- vi.clearAllMocks();
28
- // Clear environment variables
29
- delete process.env.COMMIT_BEHAVIOR;
30
- delete process.env.COMMIT_MESSAGE_TEMPLATE;
31
- });
32
-
33
- describe('Plugin Interface', () => {
34
- it('should have correct name and sequence', () => {
35
- plugin = new CommitPlugin({ projectPath });
36
-
37
- expect(plugin.getName()).toBe('CommitPlugin');
38
- expect(plugin.getSequence()).toBe(50); // Before BeadsPlugin (100)
39
- });
40
-
41
- it('should be enabled when COMMIT_BEHAVIOR is set', () => {
42
- process.env.COMMIT_BEHAVIOR = 'step';
43
- plugin = new CommitPlugin({ projectPath });
44
-
45
- expect(plugin.isEnabled()).toBe(true);
46
- });
47
-
48
- it('should be disabled when COMMIT_BEHAVIOR is not set', () => {
49
- plugin = new CommitPlugin({ projectPath });
50
-
51
- expect(plugin.isEnabled()).toBe(false);
52
- });
53
-
54
- it('should be disabled when COMMIT_BEHAVIOR is invalid', () => {
55
- process.env.COMMIT_BEHAVIOR = 'invalid';
56
- plugin = new CommitPlugin({ projectPath });
57
-
58
- expect(plugin.isEnabled()).toBe(false);
59
- });
60
- });
61
-
62
- describe('Lifecycle Hooks', () => {
63
- beforeEach(() => {
64
- process.env.COMMIT_BEHAVIOR = 'step';
65
- plugin = new CommitPlugin({ projectPath });
66
- });
67
-
68
- it('should provide afterStartDevelopment hook', () => {
69
- const hooks = plugin.getHooks();
70
-
71
- expect(hooks.afterStartDevelopment).toBeDefined();
72
- expect(typeof hooks.afterStartDevelopment).toBe('function');
73
- });
74
-
75
- it('should provide beforePhaseTransition hook', () => {
76
- const hooks = plugin.getHooks();
77
-
78
- expect(hooks.beforePhaseTransition).toBeDefined();
79
- expect(typeof hooks.beforePhaseTransition).toBe('function');
80
- });
81
-
82
- it('should provide afterPlanFileCreated hook', () => {
83
- const hooks = plugin.getHooks();
84
-
85
- expect(hooks.afterPlanFileCreated).toBeDefined();
86
- expect(typeof hooks.afterPlanFileCreated).toBe('function');
87
- });
88
- });
89
-
90
- describe('Step Commit Behavior', () => {
91
- beforeEach(() => {
92
- process.env.COMMIT_BEHAVIOR = 'step';
93
- plugin = new CommitPlugin({ projectPath });
94
- });
95
-
96
- it('should create WIP commit on whats_next calls', async () => {
97
- const { GitManager } = await import('@codemcp/workflows-core');
98
- vi.mocked(GitManager.isGitRepository).mockReturnValue(true);
99
- vi.mocked(GitManager.hasUncommittedChanges).mockReturnValue(true);
100
- vi.mocked(GitManager.createCommit).mockReturnValue(true);
101
-
102
- const context: PluginHookContext = {
103
- conversationId: 'test-conv',
104
- planFilePath: '/test/plan.md',
105
- currentPhase: 'explore',
106
- workflow: 'epcc',
107
- projectPath,
108
- gitBranch: 'feature/test',
109
- };
110
-
111
- const hooks = plugin.getHooks();
112
- await hooks.afterStartDevelopment?.(
113
- context,
114
- { workflow: 'epcc' },
115
- {
116
- conversationId: 'test-conv',
117
- planFilePath: '/test/plan.md',
118
- phase: 'explore',
119
- workflow: 'epcc',
120
- }
121
- );
122
-
123
- // Should store initial commit hash for later squashing
124
- expect(GitManager.isGitRepository).toHaveBeenCalledWith(projectPath);
125
- });
126
- });
127
-
128
- describe('Phase Commit Behavior', () => {
129
- beforeEach(() => {
130
- process.env.COMMIT_BEHAVIOR = 'phase';
131
- plugin = new CommitPlugin({ projectPath });
132
- });
133
-
134
- it('should create WIP commit before phase transitions', async () => {
135
- const { GitManager } = await import('@codemcp/workflows-core');
136
- vi.mocked(GitManager.isGitRepository).mockReturnValue(true);
137
- vi.mocked(GitManager.hasUncommittedChanges).mockReturnValue(true);
138
- vi.mocked(GitManager.createCommit).mockReturnValue(true);
139
-
140
- const context: PluginHookContext = {
141
- conversationId: 'test-conv',
142
- planFilePath: '/test/plan.md',
143
- currentPhase: 'explore',
144
- workflow: 'epcc',
145
- projectPath,
146
- gitBranch: 'feature/test',
147
- targetPhase: 'plan',
148
- };
149
-
150
- const hooks = plugin.getHooks();
151
- await hooks.beforePhaseTransition?.(context, 'explore', 'plan');
152
-
153
- expect(GitManager.hasUncommittedChanges).toHaveBeenCalledWith(
154
- projectPath
155
- );
156
- expect(GitManager.createCommit).toHaveBeenCalledWith(
157
- 'WIP: transition to plan',
158
- projectPath
159
- );
160
- });
161
- });
162
-
163
- describe('End Commit Behavior', () => {
164
- beforeEach(() => {
165
- process.env.COMMIT_BEHAVIOR = 'end';
166
- plugin = new CommitPlugin({ projectPath });
167
- });
168
-
169
- it('should add final commit task to plan file', async () => {
170
- const context: PluginHookContext = {
171
- conversationId: 'test-conv',
172
- planFilePath: '/test/plan.md',
173
- currentPhase: 'explore',
174
- workflow: 'epcc',
175
- projectPath,
176
- gitBranch: 'feature/test',
177
- };
178
-
179
- const planContent = `## Commit
180
- ### Tasks
181
- - [ ] Review implementation
182
- ### Completed
183
- *None yet*`;
184
-
185
- const hooks = plugin.getHooks();
186
- const result = await hooks.afterPlanFileCreated?.(
187
- context,
188
- '/test/plan.md',
189
- planContent
190
- );
191
-
192
- expect(result).toContain('Create a conventional commit');
193
- expect(result).toContain('summarize the intentions and key decisions');
194
- });
195
- });
196
- });
@@ -1,151 +0,0 @@
1
- /**
2
- * Unit tests for ConductReviewHandler
3
- * Tests the expected behavior of conducting reviews before phase transitions
4
- */
5
-
6
- import { describe, it, expect, beforeEach, vi } from 'vitest';
7
- import { ConductReviewHandler } from '../../src/tool-handlers/conduct-review.js';
8
- import type { ServerContext } from '../../src/types';
9
- import type { ConversationContext } from '../../src/types.js';
10
-
11
- // Mock logger
12
- vi.mock('../../src/logger', () => ({
13
- createLogger: () => ({
14
- debug: vi.fn(),
15
- info: vi.fn(),
16
- warn: vi.fn(),
17
- error: vi.fn(),
18
- }),
19
- }));
20
-
21
- describe('ConductReviewHandler', () => {
22
- let handler: ConductReviewHandler;
23
- let mockContext: ServerContext;
24
-
25
- beforeEach(() => {
26
- handler = new ConductReviewHandler();
27
-
28
- mockContext = {
29
- workflowManager: {
30
- loadWorkflowForProject: vi.fn(),
31
- },
32
- transitionEngine: {
33
- getStateMachine: vi.fn(),
34
- },
35
- planManager: {
36
- setStateMachine: vi.fn(),
37
- },
38
- instructionGenerator: {
39
- setStateMachine: vi.fn(),
40
- },
41
- projectPath: '/test/project',
42
- } as unknown as ServerContext;
43
- });
44
-
45
- it('should conduct review for ideation to architecture transition in greenfield workflow', async () => {
46
- // Set up greenfield workflow
47
- const mockWorkflow = {
48
- states: {
49
- ideation: {
50
- transitions: [
51
- {
52
- trigger: 'ideation_complete',
53
- to: 'architecture',
54
- review_perspectives: [
55
- {
56
- perspective: 'business_analyst',
57
- prompt:
58
- 'Review the Product Requirements Document for completeness, clarity, and business value.',
59
- },
60
- {
61
- perspective: 'ux_expert',
62
- prompt:
63
- 'Evaluate user experience requirements and usability considerations.',
64
- },
65
- ],
66
- },
67
- ],
68
- },
69
- },
70
- };
71
-
72
- mockContext.workflowManager.loadWorkflowForProject.mockReturnValue(
73
- mockWorkflow
74
- );
75
-
76
- const conversationContext: ConversationContext = {
77
- conversationId: 'test-conversation',
78
- projectPath: '/test/project',
79
- gitBranch: 'main',
80
- currentPhase: 'ideation',
81
- planFilePath: '/test/project/.vibe/plan.md',
82
- workflowName: 'greenfield',
83
- requireReviewsBeforePhaseTransition: true,
84
- };
85
-
86
- const result = await handler.executeWithConversation(
87
- { target_phase: 'architecture' },
88
- mockContext,
89
- conversationContext
90
- );
91
-
92
- expect(result.instructions).toContain('ideation');
93
- expect(result.instructions).toContain('architecture');
94
- expect(result.perspectives).toHaveLength(2);
95
- expect(result.perspectives[0].name).toBe('business_analyst');
96
- expect(result.perspectives[1].name).toBe('ux_expert');
97
- });
98
-
99
- it('should handle the reported bug scenario: ideation_complete transition with reviews', async () => {
100
- const mockWorkflow = {
101
- states: {
102
- ideation: {
103
- transitions: [
104
- {
105
- trigger: 'ideation_complete',
106
- to: 'architecture',
107
- review_perspectives: [
108
- {
109
- perspective: 'business_analyst',
110
- prompt:
111
- 'Review the Product Requirements Document for completeness, clarity, and business value.',
112
- },
113
- {
114
- perspective: 'ux_expert',
115
- prompt:
116
- 'Evaluate user experience requirements and usability considerations.',
117
- },
118
- ],
119
- },
120
- ],
121
- },
122
- },
123
- };
124
-
125
- mockContext.workflowManager.loadWorkflowForProject.mockReturnValue(
126
- mockWorkflow
127
- );
128
-
129
- const conversationContext: ConversationContext = {
130
- conversationId: 'test-conversation',
131
- projectPath: '/test/project',
132
- gitBranch: 'main',
133
- currentPhase: 'ideation',
134
- planFilePath: '/test/project/.vibe/plan.md',
135
- workflowName: 'greenfield',
136
- requireReviewsBeforePhaseTransition: true,
137
- };
138
-
139
- const result = await handler.executeWithConversation(
140
- { target_phase: 'architecture' },
141
- mockContext,
142
- conversationContext
143
- );
144
-
145
- expect(result.instructions).toContain('ideation');
146
- expect(result.instructions).toContain('architecture');
147
- expect(result.perspectives).toHaveLength(2);
148
- expect(result.perspectives[0].name).toBe('business_analyst');
149
- expect(result.perspectives[1].name).toBe('ux_expert');
150
- });
151
- });
@@ -1,120 +0,0 @@
1
- /**
2
- * Unit tests for CONVERSATION_NOT_FOUND error handling
3
- *
4
- * Tests that tools requiring a conversation provide helpful error messages
5
- * when no conversation exists, guiding users to call start_development.
6
- */
7
-
8
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
9
- import { ResponsibleVibeMCPServer } from '../../src/server-implementation.js';
10
- import { tmpdir } from 'node:os';
11
- import { join } from 'node:path';
12
- import { mkdtemp, rm } from 'node:fs/promises';
13
- import { ServerTestHelper } from '../utils/test-helpers.js';
14
-
15
- describe('CONVERSATION_NOT_FOUND error handling', () => {
16
- let server: ResponsibleVibeMCPServer;
17
- let tempDir: string;
18
-
19
- beforeEach(async () => {
20
- // Create temporary directory for testing
21
- tempDir = await mkdtemp(join(tmpdir(), 'conversation-error-test-'));
22
- server = await ServerTestHelper.createServer(tempDir);
23
- // Note: We deliberately do NOT call start_development to test the error case
24
- });
25
-
26
- afterEach(async () => {
27
- await ServerTestHelper.cleanupServer(server);
28
- await rm(tempDir, { recursive: true, force: true });
29
- });
30
-
31
- describe('whats_next tool', () => {
32
- it('should provide helpful error message when no conversation exists', async () => {
33
- try {
34
- await server.handleWhatsNext({});
35
- expect.fail('Should have thrown an error');
36
- } catch (error: unknown) {
37
- // Verify the error message is helpful
38
- const err = error as Error;
39
- expect(err.message).toContain(
40
- 'No development conversation has been started'
41
- );
42
- expect(err.message).toContain('start_development');
43
- }
44
- });
45
- });
46
-
47
- describe('proceed_to_phase tool', () => {
48
- it('should provide helpful error message when no conversation exists', async () => {
49
- try {
50
- await server.handleProceedToPhase({
51
- target_phase: 'implementation',
52
- review_state: 'not-required',
53
- });
54
- expect.fail('Should have thrown an error');
55
- } catch (error: unknown) {
56
- // Verify the error message is helpful
57
- const err = error as Error;
58
- expect(err.message).toContain(
59
- 'No development conversation has been started'
60
- );
61
- expect(err.message).toContain('start_development');
62
- }
63
- });
64
- });
65
-
66
- describe('resume_workflow tool', () => {
67
- it('should provide helpful error message when no conversation exists', async () => {
68
- try {
69
- await server.handleResumeWorkflow({});
70
- expect.fail('Should have thrown an error');
71
- } catch (error: unknown) {
72
- // Verify the error message is helpful
73
- const err = error as Error;
74
- expect(err.message).toContain(
75
- 'No development conversation has been started'
76
- );
77
- expect(err.message).toContain('start_development');
78
- }
79
- });
80
- });
81
-
82
- describe('error message content', () => {
83
- it('should not suggest specific workflows', async () => {
84
- try {
85
- await server.handleWhatsNext({});
86
- expect.fail('Should have thrown an error');
87
- } catch (error: unknown) {
88
- // Verify that error does NOT contain specific workflow names in the suggestion
89
- // (the suggestion should be generic like "Use the start_development tool")
90
- const err = error as Error;
91
- const message = err.message.toLowerCase();
92
-
93
- // Should contain generic guidance
94
- expect(message).toContain('start_development');
95
-
96
- // Should NOT contain workflow-specific suggestions like:
97
- // "start_development({ workflow: 'waterfall' })"
98
- const hasSpecificWorkflowSuggestion =
99
- message.includes('workflow:') ||
100
- message.includes('waterfall') ||
101
- message.includes('epcc') ||
102
- message.includes('bugfix');
103
-
104
- expect(hasSpecificWorkflowSuggestion).toBe(false);
105
- }
106
- });
107
- });
108
-
109
- describe('error with no available workflows', () => {
110
- it('should provide guidance about workflow configuration when no workflows available', async () => {
111
- // This test would require mocking the workflow manager to return no workflows
112
- // For now, we'll just verify the helper function behavior
113
- // The actual scenario is covered by the server-helpers.ts implementation
114
-
115
- // Test is implicit: if no workflows are available, the error should mention
116
- // VIBE_WORKFLOW_DOMAINS environment variable (see server-helpers.ts line 113)
117
- expect(true).toBe(true);
118
- });
119
- });
120
- });
@@ -1,240 +0,0 @@
1
- /**
2
- * Tests for Plugin Error Handling and Graceful Degradation
3
- *
4
- * Verifies that the plugin system handles errors gracefully:
5
- * - Plugin failures don't crash the core application
6
- * - Non-critical plugin errors allow graceful degradation
7
- * - Validation errors (beforePhaseTransition) are always re-thrown
8
- */
9
-
10
- import { describe, it, expect, vi } from 'vitest';
11
- import { PluginRegistry } from '../../src/plugin-system/plugin-registry.js';
12
- import type { IPlugin } from '../../src/plugin-system/plugin-interfaces.js';
13
-
14
- const createMockContext = () => ({
15
- conversationId: 'test',
16
- planFilePath: '/test/plan.md',
17
- projectPath: '/test',
18
- currentPhase: 'explore',
19
- workflow: 'epcc',
20
- gitBranch: 'main',
21
- });
22
-
23
- describe('Plugin Error Handling and Graceful Degradation', () => {
24
- describe('Non-critical hook error handling', () => {
25
- it('should continue execution when afterStartDevelopment hook fails', async () => {
26
- const registry = new PluginRegistry();
27
-
28
- // Register first plugin that throws
29
- const failingPlugin: IPlugin = {
30
- getName: () => 'FailingPlugin',
31
- getSequence: () => 1,
32
- isEnabled: () => true,
33
- getHooks: () => ({
34
- afterStartDevelopment: vi
35
- .fn()
36
- .mockRejectedValue(new Error('Beads backend unavailable')),
37
- }),
38
- };
39
-
40
- // Register second plugin that succeeds
41
- const successHookSpy = vi.fn().mockResolvedValue(undefined);
42
- const successPlugin: IPlugin = {
43
- getName: () => 'SuccessPlugin',
44
- getSequence: () => 2,
45
- isEnabled: () => true,
46
- getHooks: () => ({
47
- afterStartDevelopment: successHookSpy,
48
- }),
49
- };
50
-
51
- registry.registerPlugin(failingPlugin);
52
- registry.registerPlugin(successPlugin);
53
-
54
- // Execute hook - should NOT throw despite first plugin failure
55
- const result = await registry.executeHook(
56
- 'afterStartDevelopment',
57
- createMockContext(),
58
- { workflow: 'epcc', commit_behaviour: 'end' },
59
- {
60
- conversationId: 'test',
61
- planFilePath: '/test/plan.md',
62
- phase: 'explore',
63
- workflow: 'epcc',
64
- }
65
- );
66
-
67
- // Should reach here without throwing
68
- expect(result).toBeUndefined();
69
- expect(successHookSpy).toHaveBeenCalled();
70
- });
71
-
72
- it('should continue execution when afterPlanFileCreated hook fails', async () => {
73
- const registry = new PluginRegistry();
74
-
75
- const failingPlugin: IPlugin = {
76
- getName: () => 'FailingPlugin',
77
- getSequence: () => 1,
78
- isEnabled: () => true,
79
- getHooks: () => ({
80
- afterPlanFileCreated: vi
81
- .fn()
82
- .mockRejectedValue(new Error('Plan file update failed')),
83
- }),
84
- };
85
-
86
- registry.registerPlugin(failingPlugin);
87
-
88
- // Should not throw despite plugin error
89
- const result = await registry.executeHook(
90
- 'afterPlanFileCreated',
91
- createMockContext(),
92
- '/test/plan.md',
93
- 'initial content'
94
- );
95
-
96
- expect(result).toBeUndefined();
97
- });
98
- });
99
-
100
- describe('Validation hook error handling', () => {
101
- it('should re-throw validation errors from beforePhaseTransition', async () => {
102
- const registry = new PluginRegistry();
103
-
104
- const validationPlugin: IPlugin = {
105
- getName: () => 'ValidationPlugin',
106
- getSequence: () => 1,
107
- isEnabled: () => true,
108
- getHooks: () => ({
109
- beforePhaseTransition: vi
110
- .fn()
111
- .mockRejectedValue(
112
- new Error('Cannot proceed to code - incomplete tasks')
113
- ),
114
- }),
115
- };
116
-
117
- registry.registerPlugin(validationPlugin);
118
-
119
- // Should re-throw validation errors
120
- await expect(
121
- registry.executeHook(
122
- 'beforePhaseTransition',
123
- createMockContext(),
124
- 'plan',
125
- 'code'
126
- )
127
- ).rejects.toThrow('Cannot proceed to code - incomplete tasks');
128
- });
129
-
130
- it('should re-throw any beforePhaseTransition hook errors', async () => {
131
- const registry = new PluginRegistry();
132
-
133
- const validationPlugin: IPlugin = {
134
- getName: () => 'ValidationPlugin',
135
- getSequence: () => 1,
136
- isEnabled: () => true,
137
- getHooks: () => ({
138
- beforePhaseTransition: vi
139
- .fn()
140
- .mockRejectedValue(new Error('Validation failed for any reason')),
141
- }),
142
- };
143
-
144
- registry.registerPlugin(validationPlugin);
145
-
146
- // Should re-throw validation errors (not just specific messages)
147
- await expect(
148
- registry.executeHook(
149
- 'beforePhaseTransition',
150
- createMockContext(),
151
- 'plan',
152
- 'code'
153
- )
154
- ).rejects.toThrow('Validation failed for any reason');
155
- });
156
- });
157
-
158
- describe('Multiple plugin execution', () => {
159
- it('should execute all plugins even if some fail on non-critical hooks', async () => {
160
- const registry = new PluginRegistry();
161
-
162
- const plugin1Spy = vi
163
- .fn()
164
- .mockRejectedValue(new Error('Plugin 1 failed'));
165
- const plugin2Spy = vi.fn().mockResolvedValue(undefined);
166
- const plugin3Spy = vi.fn().mockResolvedValue(undefined);
167
-
168
- registry.registerPlugin({
169
- getName: () => 'Plugin1',
170
- getSequence: () => 1,
171
- isEnabled: () => true,
172
- getHooks: () => ({ afterStartDevelopment: plugin1Spy }),
173
- });
174
-
175
- registry.registerPlugin({
176
- getName: () => 'Plugin2',
177
- getSequence: () => 2,
178
- isEnabled: () => true,
179
- getHooks: () => ({ afterStartDevelopment: plugin2Spy }),
180
- });
181
-
182
- registry.registerPlugin({
183
- getName: () => 'Plugin3',
184
- getSequence: () => 3,
185
- isEnabled: () => true,
186
- getHooks: () => ({ afterStartDevelopment: plugin3Spy }),
187
- });
188
-
189
- // Execute should not throw
190
- const _result = await registry.executeHook(
191
- 'afterStartDevelopment',
192
- createMockContext(),
193
- { workflow: 'epcc', commit_behaviour: 'end' },
194
- {
195
- conversationId: 'test',
196
- planFilePath: '/test/plan.md',
197
- phase: 'explore',
198
- workflow: 'epcc',
199
- }
200
- );
201
-
202
- // All plugins should be called
203
- expect(plugin1Spy).toHaveBeenCalled();
204
- expect(plugin2Spy).toHaveBeenCalled();
205
- expect(plugin3Spy).toHaveBeenCalled();
206
- });
207
- });
208
-
209
- describe('Disabled plugin handling', () => {
210
- it('should not execute hooks from disabled plugins', async () => {
211
- const registry = new PluginRegistry();
212
-
213
- const hookSpy = vi.fn();
214
-
215
- const disabledPlugin: IPlugin = {
216
- getName: () => 'DisabledPlugin',
217
- getSequence: () => 1,
218
- isEnabled: () => false, // Disabled
219
- getHooks: () => ({ afterStartDevelopment: hookSpy }),
220
- };
221
-
222
- registry.registerPlugin(disabledPlugin);
223
-
224
- await registry.executeHook(
225
- 'afterStartDevelopment',
226
- createMockContext(),
227
- { workflow: 'epcc', commit_behaviour: 'end' },
228
- {
229
- conversationId: 'test',
230
- planFilePath: '/test/plan.md',
231
- phase: 'explore',
232
- workflow: 'epcc',
233
- }
234
- );
235
-
236
- // Disabled plugin's hook should not be called
237
- expect(hookSpy).not.toHaveBeenCalled();
238
- });
239
- });
240
- });