@codemcp/workflows 4.10.0 → 4.10.2
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 +1 -1
- package/dist/components/beads/beads-instruction-generator.d.ts +3 -4
- package/dist/components/beads/beads-instruction-generator.d.ts.map +1 -1
- package/dist/components/beads/beads-instruction-generator.js +12 -7
- package/dist/components/beads/beads-instruction-generator.js.map +1 -1
- package/dist/components/beads/beads-task-backend-client.d.ts.map +1 -1
- package/dist/components/beads/beads-task-backend-client.js +1 -4
- package/dist/components/beads/beads-task-backend-client.js.map +1 -1
- package/dist/plugin-system/beads-plugin.d.ts +70 -0
- package/dist/plugin-system/beads-plugin.d.ts.map +1 -0
- package/dist/plugin-system/beads-plugin.js +459 -0
- package/dist/plugin-system/beads-plugin.js.map +1 -0
- package/dist/plugin-system/index.d.ts +9 -0
- package/dist/plugin-system/index.d.ts.map +1 -0
- package/dist/plugin-system/index.js +9 -0
- package/dist/plugin-system/index.js.map +1 -0
- package/dist/plugin-system/plugin-interfaces.d.ts +99 -0
- package/dist/plugin-system/plugin-interfaces.d.ts.map +1 -0
- package/dist/plugin-system/plugin-interfaces.js +9 -0
- package/dist/plugin-system/plugin-interfaces.js.map +1 -0
- package/dist/plugin-system/plugin-registry.d.ts +44 -0
- package/dist/plugin-system/plugin-registry.d.ts.map +1 -0
- package/dist/plugin-system/plugin-registry.js +132 -0
- package/dist/plugin-system/plugin-registry.js.map +1 -0
- package/dist/server-config.d.ts.map +1 -1
- package/dist/server-config.js +28 -8
- package/dist/server-config.js.map +1 -1
- package/dist/tool-handlers/conduct-review.d.ts.map +1 -1
- package/dist/tool-handlers/conduct-review.js +1 -2
- package/dist/tool-handlers/conduct-review.js.map +1 -1
- package/dist/tool-handlers/proceed-to-phase.d.ts +0 -5
- package/dist/tool-handlers/proceed-to-phase.d.ts.map +1 -1
- package/dist/tool-handlers/proceed-to-phase.js +15 -93
- package/dist/tool-handlers/proceed-to-phase.js.map +1 -1
- package/dist/tool-handlers/start-development.d.ts +0 -13
- package/dist/tool-handlers/start-development.d.ts.map +1 -1
- package/dist/tool-handlers/start-development.js +29 -124
- package/dist/tool-handlers/start-development.js.map +1 -1
- package/dist/tool-handlers/whats-next.d.ts.map +1 -1
- package/dist/tool-handlers/whats-next.js +1 -0
- package/dist/tool-handlers/whats-next.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/components/beads/beads-instruction-generator.ts +12 -12
- package/src/components/beads/beads-task-backend-client.ts +1 -4
- package/src/plugin-system/beads-plugin.ts +641 -0
- package/src/plugin-system/index.ts +20 -0
- package/src/plugin-system/plugin-interfaces.ts +154 -0
- package/src/plugin-system/plugin-registry.ts +190 -0
- package/src/server-config.ts +30 -8
- package/src/tool-handlers/conduct-review.ts +1 -2
- package/src/tool-handlers/proceed-to-phase.ts +19 -135
- package/src/tool-handlers/start-development.ts +35 -205
- package/src/tool-handlers/whats-next.ts +1 -0
- package/src/types.ts +2 -0
- package/test/e2e/beads-plugin-integration.test.ts +1609 -0
- package/test/e2e/plugin-system-integration.test.ts +1729 -0
- package/test/unit/beads-plugin-behavioral.test.ts +512 -0
- package/test/unit/beads-plugin.test.ts +94 -0
- package/test/unit/plugin-error-handling.test.ts +240 -0
- package/test/unit/proceed-to-phase-plugin-integration.test.ts +150 -0
- package/test/unit/server-config-plugin-registry.test.ts +81 -0
- package/test/unit/start-development-goal-extraction.test.ts +22 -16
- package/test/utils/test-helpers.ts +3 -1
- package/tsconfig.build.tsbuildinfo +1 -1
- package/dist/components/server-components-factory.d.ts +0 -39
- package/dist/components/server-components-factory.d.ts.map +0 -1
- package/dist/components/server-components-factory.js +0 -62
- package/dist/components/server-components-factory.js.map +0 -1
- package/src/components/server-components-factory.ts +0 -86
- package/test/e2e/component-substitution.test.ts +0 -208
- package/test/unit/beads-integration-filename.test.ts +0 -93
- package/test/unit/server-components-factory.test.ts +0 -279
|
@@ -0,0 +1,240 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test plugin hook integration in proceed-to-phase
|
|
3
|
+
* Focus on testing that plugin hooks are called correctly
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
7
|
+
import { ProceedToPhaseHandler } from '../../src/tool-handlers/proceed-to-phase.js';
|
|
8
|
+
import { PluginRegistry } from '../../src/plugin-system/plugin-registry.js';
|
|
9
|
+
import type { ServerContext } from '../../src/types.js';
|
|
10
|
+
import type { PluginHookContext } from '../../src/plugin-system/plugin-interfaces.js';
|
|
11
|
+
|
|
12
|
+
// Mock dependencies
|
|
13
|
+
vi.mock('@codemcp/workflows-core', () => ({
|
|
14
|
+
createLogger: vi.fn(() => ({
|
|
15
|
+
debug: vi.fn(),
|
|
16
|
+
info: vi.fn(),
|
|
17
|
+
warn: vi.fn(),
|
|
18
|
+
error: vi.fn(),
|
|
19
|
+
})),
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
describe('ProceedToPhase Plugin Integration', () => {
|
|
23
|
+
let handler: ProceedToPhaseHandler;
|
|
24
|
+
let mockPluginRegistry: PluginRegistry;
|
|
25
|
+
let mockContext: ServerContext;
|
|
26
|
+
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
mockPluginRegistry = new PluginRegistry();
|
|
29
|
+
|
|
30
|
+
mockContext = {
|
|
31
|
+
conversationManager: {
|
|
32
|
+
getConversationContext: vi.fn().mockResolvedValue({
|
|
33
|
+
conversationId: 'test-conversation',
|
|
34
|
+
planFilePath: '/test/plan.md',
|
|
35
|
+
currentPhase: 'plan',
|
|
36
|
+
workflowName: 'epcc',
|
|
37
|
+
projectPath: '/test/project',
|
|
38
|
+
gitBranch: 'main',
|
|
39
|
+
}),
|
|
40
|
+
updateConversationState: vi.fn().mockResolvedValue(undefined),
|
|
41
|
+
},
|
|
42
|
+
transitionEngine: {
|
|
43
|
+
handleExplicitTransition: vi.fn().mockReturnValue({
|
|
44
|
+
newPhase: 'code',
|
|
45
|
+
transitionReason: 'Test transition',
|
|
46
|
+
isModeled: true,
|
|
47
|
+
instructions: 'Test transition instructions',
|
|
48
|
+
}),
|
|
49
|
+
},
|
|
50
|
+
planManager: {
|
|
51
|
+
getPlanFileInfo: vi.fn().mockResolvedValue({ exists: true }),
|
|
52
|
+
},
|
|
53
|
+
instructionGenerator: {
|
|
54
|
+
generateInstructions: vi.fn().mockResolvedValue({
|
|
55
|
+
instructions: 'Test instructions',
|
|
56
|
+
}),
|
|
57
|
+
},
|
|
58
|
+
workflowManager: {
|
|
59
|
+
loadWorkflowForProject: vi.fn().mockReturnValue({
|
|
60
|
+
name: 'epcc',
|
|
61
|
+
states: { plan: {}, code: {} },
|
|
62
|
+
}),
|
|
63
|
+
},
|
|
64
|
+
interactionLogger: {
|
|
65
|
+
logInteraction: vi.fn().mockResolvedValue(undefined),
|
|
66
|
+
},
|
|
67
|
+
projectPath: '/test/project',
|
|
68
|
+
pluginRegistry: mockPluginRegistry,
|
|
69
|
+
} as unknown as ServerContext;
|
|
70
|
+
|
|
71
|
+
handler = new ProceedToPhaseHandler();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should call beforePhaseTransition plugin hook during phase transition', async () => {
|
|
75
|
+
const hookSpy = vi.fn().mockResolvedValue(undefined);
|
|
76
|
+
|
|
77
|
+
// Register a mock plugin with beforePhaseTransition hook
|
|
78
|
+
const mockPlugin = {
|
|
79
|
+
getName: () => 'TestPlugin',
|
|
80
|
+
getSequence: () => 100,
|
|
81
|
+
isEnabled: () => true,
|
|
82
|
+
getHooks: () => ({
|
|
83
|
+
beforePhaseTransition: hookSpy,
|
|
84
|
+
}),
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
mockPluginRegistry.registerPlugin(mockPlugin);
|
|
88
|
+
|
|
89
|
+
// Execute the proceed_to_phase handler
|
|
90
|
+
await handler.handle(
|
|
91
|
+
{
|
|
92
|
+
target_phase: 'code',
|
|
93
|
+
reason: 'Testing plugin integration',
|
|
94
|
+
review_state: 'not-required',
|
|
95
|
+
},
|
|
96
|
+
mockContext
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
// Verify the hook was called with correct parameters
|
|
100
|
+
expect(hookSpy).toHaveBeenCalledOnce();
|
|
101
|
+
|
|
102
|
+
const [pluginContext, currentPhase, targetPhase] = hookSpy.mock.calls[0];
|
|
103
|
+
|
|
104
|
+
// Verify plugin context structure
|
|
105
|
+
expect(pluginContext).toMatchObject<Partial<PluginHookContext>>({
|
|
106
|
+
conversationId: 'test-conversation',
|
|
107
|
+
planFilePath: '/test/plan.md',
|
|
108
|
+
currentPhase: 'plan',
|
|
109
|
+
workflow: 'epcc',
|
|
110
|
+
projectPath: '/test/project',
|
|
111
|
+
gitBranch: 'main',
|
|
112
|
+
targetPhase: 'code',
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Verify phase parameters
|
|
116
|
+
expect(currentPhase).toBe('plan');
|
|
117
|
+
expect(targetPhase).toBe('code');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should handle plugin hook errors by returning error result', async () => {
|
|
121
|
+
const hookError = new Error('Plugin validation failed');
|
|
122
|
+
const hookSpy = vi.fn().mockRejectedValue(hookError);
|
|
123
|
+
|
|
124
|
+
// Register a mock plugin that throws an error
|
|
125
|
+
const mockPlugin = {
|
|
126
|
+
getName: () => 'TestPlugin',
|
|
127
|
+
getSequence: () => 100,
|
|
128
|
+
isEnabled: () => true,
|
|
129
|
+
getHooks: () => ({
|
|
130
|
+
beforePhaseTransition: hookSpy,
|
|
131
|
+
}),
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
mockPluginRegistry.registerPlugin(mockPlugin);
|
|
135
|
+
|
|
136
|
+
// Execute the handler and expect it to return error result
|
|
137
|
+
const result = await handler.handle(
|
|
138
|
+
{
|
|
139
|
+
target_phase: 'code',
|
|
140
|
+
reason: 'Testing plugin error handling',
|
|
141
|
+
review_state: 'not-required',
|
|
142
|
+
},
|
|
143
|
+
mockContext
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
expect(result.success).toBe(false);
|
|
147
|
+
expect(result.error).toContain('Plugin validation failed');
|
|
148
|
+
expect(hookSpy).toHaveBeenCalledOnce();
|
|
149
|
+
});
|
|
150
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test plugin registration in server-config
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
|
|
6
|
+
import { initializeServerComponents } from '../../src/server-config.js';
|
|
7
|
+
import { mkdtemp, rm } from 'node:fs/promises';
|
|
8
|
+
import { tmpdir } from 'node:os';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
|
|
11
|
+
describe('Server Config Plugin Registration', () => {
|
|
12
|
+
let tempDir: string;
|
|
13
|
+
|
|
14
|
+
beforeEach(async () => {
|
|
15
|
+
vi.clearAllMocks();
|
|
16
|
+
tempDir = await mkdtemp(join(tmpdir(), 'server-config-test-'));
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
afterEach(async () => {
|
|
20
|
+
vi.clearAllMocks();
|
|
21
|
+
try {
|
|
22
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
23
|
+
} catch {
|
|
24
|
+
// Ignore cleanup errors
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should register BeadsPlugin when TASK_BACKEND is beads', async () => {
|
|
29
|
+
vi.stubEnv('TASK_BACKEND', 'beads');
|
|
30
|
+
|
|
31
|
+
const components = await initializeServerComponents({
|
|
32
|
+
projectPath: tempDir,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
expect(components.context.pluginRegistry).toBeDefined();
|
|
36
|
+
|
|
37
|
+
// Check that BeadsPlugin was registered
|
|
38
|
+
const pluginNames = components.context.pluginRegistry.getPluginNames();
|
|
39
|
+
expect(pluginNames).toContain('BeadsPlugin');
|
|
40
|
+
|
|
41
|
+
// Check that it's enabled
|
|
42
|
+
const enabledPlugins =
|
|
43
|
+
components.context.pluginRegistry.getEnabledPlugins();
|
|
44
|
+
expect(enabledPlugins).toHaveLength(1);
|
|
45
|
+
expect(enabledPlugins[0].getName()).toBe('BeadsPlugin');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should not register BeadsPlugin when TASK_BACKEND is not beads', async () => {
|
|
49
|
+
vi.stubEnv('TASK_BACKEND', 'none');
|
|
50
|
+
|
|
51
|
+
const components = await initializeServerComponents({
|
|
52
|
+
projectPath: tempDir,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
expect(components.context.pluginRegistry).toBeDefined();
|
|
56
|
+
|
|
57
|
+
// Check that no plugins are registered
|
|
58
|
+
const pluginNames = components.context.pluginRegistry.getPluginNames();
|
|
59
|
+
expect(pluginNames).toHaveLength(0);
|
|
60
|
+
|
|
61
|
+
// Check that no plugins are enabled
|
|
62
|
+
const enabledPlugins =
|
|
63
|
+
components.context.pluginRegistry.getEnabledPlugins();
|
|
64
|
+
expect(enabledPlugins).toHaveLength(0);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should initialize empty plugin registry by default', async () => {
|
|
68
|
+
// Don't set TASK_BACKEND environment variable
|
|
69
|
+
vi.unstubAllEnvs();
|
|
70
|
+
|
|
71
|
+
const components = await initializeServerComponents({
|
|
72
|
+
projectPath: tempDir,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
expect(components.context.pluginRegistry).toBeDefined();
|
|
76
|
+
expect(components.context.pluginRegistry.getPluginNames()).toHaveLength(0);
|
|
77
|
+
expect(components.context.pluginRegistry.getEnabledPlugins()).toHaveLength(
|
|
78
|
+
0
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
@@ -1,19 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Unit tests for
|
|
2
|
+
* Unit tests for BeadsPlugin Goal extraction functionality
|
|
3
3
|
*
|
|
4
4
|
* Tests the extractGoalFromPlan method that extracts meaningful goal content
|
|
5
5
|
* from development plan files for use in beads integration
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { describe, it, expect, beforeEach } from 'vitest';
|
|
8
|
+
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
|
|
9
9
|
import { TestAccess } from '../utils/test-access.js';
|
|
10
|
-
import {
|
|
10
|
+
import { BeadsPlugin } from '../../src/plugin-system/beads-plugin.js';
|
|
11
11
|
|
|
12
|
-
describe('
|
|
13
|
-
let
|
|
12
|
+
describe('BeadsPlugin - Goal Extraction', () => {
|
|
13
|
+
let plugin: BeadsPlugin;
|
|
14
14
|
|
|
15
15
|
beforeEach(() => {
|
|
16
|
-
|
|
16
|
+
// Mock environment variable for plugin enablement
|
|
17
|
+
vi.stubEnv('TASK_BACKEND', 'beads');
|
|
18
|
+
plugin = new BeadsPlugin({ projectPath: '/test/project' });
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
vi.unstubAllEnvs();
|
|
17
23
|
});
|
|
18
24
|
|
|
19
25
|
describe('extractGoalFromPlan', () => {
|
|
@@ -29,7 +35,7 @@ Build a user authentication system with JWT tokens and password reset functional
|
|
|
29
35
|
`;
|
|
30
36
|
|
|
31
37
|
const result = TestAccess.callMethod(
|
|
32
|
-
|
|
38
|
+
plugin,
|
|
33
39
|
'extractGoalFromPlan',
|
|
34
40
|
planContent
|
|
35
41
|
);
|
|
@@ -51,7 +57,7 @@ Build a user authentication system with JWT tokens and password reset functional
|
|
|
51
57
|
`;
|
|
52
58
|
|
|
53
59
|
const result = TestAccess.callMethod(
|
|
54
|
-
|
|
60
|
+
plugin,
|
|
55
61
|
'extractGoalFromPlan',
|
|
56
62
|
planContent
|
|
57
63
|
);
|
|
@@ -71,7 +77,7 @@ To be defined during exploration
|
|
|
71
77
|
`;
|
|
72
78
|
|
|
73
79
|
const result = TestAccess.callMethod(
|
|
74
|
-
|
|
80
|
+
plugin,
|
|
75
81
|
'extractGoalFromPlan',
|
|
76
82
|
planContent
|
|
77
83
|
);
|
|
@@ -91,7 +97,7 @@ Fix bug
|
|
|
91
97
|
`;
|
|
92
98
|
|
|
93
99
|
const result = TestAccess.callMethod(
|
|
94
|
-
|
|
100
|
+
plugin,
|
|
95
101
|
'extractGoalFromPlan',
|
|
96
102
|
planContent
|
|
97
103
|
);
|
|
@@ -117,7 +123,7 @@ The system should support different log levels and output formats.
|
|
|
117
123
|
`;
|
|
118
124
|
|
|
119
125
|
const result = TestAccess.callMethod(
|
|
120
|
-
|
|
126
|
+
plugin,
|
|
121
127
|
'extractGoalFromPlan',
|
|
122
128
|
planContent
|
|
123
129
|
);
|
|
@@ -141,7 +147,7 @@ The system should support different log levels and output formats.`);
|
|
|
141
147
|
`;
|
|
142
148
|
|
|
143
149
|
const result = TestAccess.callMethod(
|
|
144
|
-
|
|
150
|
+
plugin,
|
|
145
151
|
'extractGoalFromPlan',
|
|
146
152
|
planContent
|
|
147
153
|
);
|
|
@@ -151,15 +157,15 @@ The system should support different log levels and output formats.`);
|
|
|
151
157
|
|
|
152
158
|
it('should return undefined for empty or null input', () => {
|
|
153
159
|
expect(
|
|
154
|
-
TestAccess.callMethod(
|
|
160
|
+
TestAccess.callMethod(plugin, 'extractGoalFromPlan', '')
|
|
155
161
|
).toBeUndefined();
|
|
156
162
|
|
|
157
163
|
expect(
|
|
158
|
-
TestAccess.callMethod(
|
|
164
|
+
TestAccess.callMethod(plugin, 'extractGoalFromPlan', null)
|
|
159
165
|
).toBeUndefined();
|
|
160
166
|
|
|
161
167
|
expect(
|
|
162
|
-
TestAccess.callMethod(
|
|
168
|
+
TestAccess.callMethod(plugin, 'extractGoalFromPlan', undefined)
|
|
163
169
|
).toBeUndefined();
|
|
164
170
|
});
|
|
165
171
|
|
|
@@ -175,7 +181,7 @@ Build a user authentication system with secure login and registration.
|
|
|
175
181
|
`;
|
|
176
182
|
|
|
177
183
|
const result = TestAccess.callMethod(
|
|
178
|
-
|
|
184
|
+
plugin,
|
|
179
185
|
'extractGoalFromPlan',
|
|
180
186
|
planContent
|
|
181
187
|
);
|
|
@@ -11,10 +11,11 @@ import { vi, expect } from 'vitest';
|
|
|
11
11
|
import {
|
|
12
12
|
ResponsibleVibeMCPServer,
|
|
13
13
|
createResponsibleVibeMCPServer,
|
|
14
|
-
StartDevelopmentResult,
|
|
15
14
|
} from '../../src/server.js';
|
|
16
15
|
import type { ServerContext } from '../../src/types';
|
|
16
|
+
import type { StartDevelopmentResult } from '../../src/tool-handlers/start-development.js';
|
|
17
17
|
import { TempProject } from './temp-files.js';
|
|
18
|
+
import { PluginRegistry } from '../../src/plugin-system/plugin-registry.js';
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* Mock project documents content
|
|
@@ -114,6 +115,7 @@ export class MockContextFactory {
|
|
|
114
115
|
) {
|
|
115
116
|
return {
|
|
116
117
|
projectPath,
|
|
118
|
+
pluginRegistry: new PluginRegistry(),
|
|
117
119
|
workflowManager: {
|
|
118
120
|
validateWorkflowName: vi.fn().mockReturnValue(true),
|
|
119
121
|
getWorkflowNames: vi
|