@codemcp/workflows 5.0.1 → 5.1.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/SKILL.md +23 -0
- package/package.json +6 -2
- package/.prettierignore +0 -2
- package/.turbo/turbo-build.log +0 -4
- package/.vibe/conversation-state.sqlite +0 -0
- package/src/components/beads/beads-instruction-generator.ts +0 -230
- package/src/components/beads/beads-plan-manager.ts +0 -333
- package/src/components/beads/beads-task-backend-client.ts +0 -229
- package/src/index.ts +0 -93
- package/src/notification-service.ts +0 -23
- package/src/plugin-system/beads-plugin.ts +0 -649
- package/src/plugin-system/commit-plugin.ts +0 -252
- package/src/plugin-system/index.ts +0 -20
- package/src/plugin-system/plugin-interfaces.ts +0 -153
- package/src/plugin-system/plugin-registry.ts +0 -190
- package/src/resource-handlers/conversation-state.ts +0 -55
- package/src/resource-handlers/development-plan.ts +0 -48
- package/src/resource-handlers/index.ts +0 -73
- package/src/resource-handlers/system-prompt.ts +0 -55
- package/src/resource-handlers/workflow-resource.ts +0 -132
- package/src/response-renderer.ts +0 -116
- package/src/server-config.ts +0 -760
- package/src/server-helpers.ts +0 -245
- package/src/server-implementation.ts +0 -277
- package/src/server.ts +0 -9
- package/src/tool-handlers/base-tool-handler.ts +0 -151
- package/src/tool-handlers/conduct-review.ts +0 -190
- package/src/tool-handlers/get-tool-info.ts +0 -273
- package/src/tool-handlers/index.ts +0 -115
- package/src/tool-handlers/list-workflows.ts +0 -78
- package/src/tool-handlers/no-idea.ts +0 -47
- package/src/tool-handlers/proceed-to-phase.ts +0 -296
- package/src/tool-handlers/reset-development.ts +0 -90
- package/src/tool-handlers/resume-workflow.ts +0 -378
- package/src/tool-handlers/setup-project-docs.ts +0 -232
- package/src/tool-handlers/start-development.ts +0 -746
- package/src/tool-handlers/whats-next.ts +0 -246
- package/src/types.ts +0 -135
- package/src/version-info.ts +0 -213
- package/test/e2e/beads-plugin-integration.test.ts +0 -1623
- package/test/e2e/commit-plugin-integration.test.ts +0 -222
- package/test/e2e/core-functionality.test.ts +0 -167
- package/test/e2e/git-branch-detection.test.ts +0 -351
- package/test/e2e/mcp-contract.test.ts +0 -509
- package/test/e2e/plan-management.test.ts +0 -334
- package/test/e2e/plugin-system-integration.test.ts +0 -1410
- package/test/e2e/state-management.test.ts +0 -387
- package/test/e2e/workflow-integration.test.ts +0 -498
- package/test/unit/beads-instruction-generator.test.ts +0 -979
- package/test/unit/beads-phase-task-id-integration.test.ts +0 -535
- package/test/unit/beads-plugin-behavioral.test.ts +0 -545
- package/test/unit/beads-plugin.test.ts +0 -117
- package/test/unit/commit-plugin.test.ts +0 -196
- package/test/unit/conduct-review.test.ts +0 -151
- package/test/unit/conversation-not-found-error.test.ts +0 -120
- package/test/unit/plugin-error-handling.test.ts +0 -240
- package/test/unit/proceed-to-phase-plugin-integration.test.ts +0 -150
- package/test/unit/reset-functionality.test.ts +0 -72
- package/test/unit/resume-workflow.test.ts +0 -193
- package/test/unit/server-config-plugin-registry.test.ts +0 -99
- package/test/unit/server-tools.test.ts +0 -310
- package/test/unit/setup-project-docs-handler.test.ts +0 -268
- package/test/unit/start-development-artifact-detection.test.ts +0 -387
- package/test/unit/start-development-gitignore.test.ts +0 -178
- package/test/unit/start-development-goal-extraction.test.ts +0 -226
- package/test/unit/system-prompt-resource.test.ts +0 -102
- package/test/unit/tool-handlers/no-idea.test.ts +0 -40
- package/test/utils/e2e-test-setup.ts +0 -451
- package/test/utils/run-server-in-dir.sh +0 -27
- package/test/utils/temp-files.ts +0 -320
- package/test/utils/test-access.ts +0 -79
- package/test/utils/test-helpers.ts +0 -288
- package/test/utils/test-setup.ts +0 -77
- package/tsconfig.build.json +0 -10
- package/tsconfig.build.tsbuildinfo +0 -1
- package/tsconfig.json +0 -12
- 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
|
-
});
|