@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.
- package/package.json +6 -2
- package/skill/SKILL.md +23 -0
- 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,252 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CommitPlugin Implementation
|
|
3
|
-
*
|
|
4
|
-
* Plugin that handles automatic git commits based on COMMIT_BEHAVIOR environment variable.
|
|
5
|
-
* Supports step, phase, and end commit modes with configurable message templates.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type {
|
|
9
|
-
IPlugin,
|
|
10
|
-
PluginHooks,
|
|
11
|
-
PluginHookContext,
|
|
12
|
-
StartDevelopmentArgs,
|
|
13
|
-
StartDevelopmentResult,
|
|
14
|
-
} from './plugin-interfaces.js';
|
|
15
|
-
import { GitManager, createLogger } from '@codemcp/workflows-core';
|
|
16
|
-
|
|
17
|
-
const logger = createLogger('CommitPlugin');
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* CommitPlugin class implementing the IPlugin interface
|
|
21
|
-
*
|
|
22
|
-
* Activation: Only when process.env.COMMIT_BEHAVIOR is set to valid value
|
|
23
|
-
* Priority: Sequence 50 (before BeadsPlugin at 100)
|
|
24
|
-
*/
|
|
25
|
-
export class CommitPlugin implements IPlugin {
|
|
26
|
-
private projectPath: string;
|
|
27
|
-
private initialCommitHash?: string;
|
|
28
|
-
|
|
29
|
-
constructor(options: { projectPath: string }) {
|
|
30
|
-
this.projectPath = options.projectPath;
|
|
31
|
-
logger.debug('CommitPlugin initialized', { projectPath: this.projectPath });
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
getName(): string {
|
|
35
|
-
return 'CommitPlugin';
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
getSequence(): number {
|
|
39
|
-
return 50; // Before BeadsPlugin (100)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
isEnabled(): boolean {
|
|
43
|
-
const behavior = process.env.COMMIT_BEHAVIOR;
|
|
44
|
-
const enabled =
|
|
45
|
-
behavior && ['step', 'phase', 'end', 'none'].includes(behavior);
|
|
46
|
-
logger.debug('CommitPlugin enablement check', {
|
|
47
|
-
COMMIT_BEHAVIOR: behavior,
|
|
48
|
-
enabled: !!enabled,
|
|
49
|
-
});
|
|
50
|
-
return !!enabled;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
getHooks(): PluginHooks {
|
|
54
|
-
return {
|
|
55
|
-
afterStartDevelopment: this.handleAfterStartDevelopment.bind(this),
|
|
56
|
-
beforePhaseTransition: this.handleBeforePhaseTransition.bind(this),
|
|
57
|
-
afterPlanFileCreated: this.handleAfterPlanFileCreated.bind(this),
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Handle afterStartDevelopment hook
|
|
63
|
-
* Store initial commit hash for potential squashing later
|
|
64
|
-
*/
|
|
65
|
-
private async handleAfterStartDevelopment(
|
|
66
|
-
context: PluginHookContext,
|
|
67
|
-
_args: StartDevelopmentArgs,
|
|
68
|
-
_result: StartDevelopmentResult
|
|
69
|
-
): Promise<void> {
|
|
70
|
-
logger.info('CommitPlugin: Setting up commit behavior', {
|
|
71
|
-
conversationId: context.conversationId,
|
|
72
|
-
behavior: process.env.COMMIT_BEHAVIOR,
|
|
73
|
-
projectPath: context.projectPath,
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
try {
|
|
77
|
-
if (GitManager.isGitRepository(context.projectPath)) {
|
|
78
|
-
this.initialCommitHash =
|
|
79
|
-
GitManager.getCurrentCommitHash(context.projectPath) || undefined;
|
|
80
|
-
logger.debug('CommitPlugin: Stored initial commit hash', {
|
|
81
|
-
conversationId: context.conversationId,
|
|
82
|
-
initialCommitHash: this.initialCommitHash,
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
} catch (error) {
|
|
86
|
-
logger.warn('CommitPlugin: Failed to get initial commit hash', {
|
|
87
|
-
error: error instanceof Error ? error.message : String(error),
|
|
88
|
-
conversationId: context.conversationId,
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Handle beforePhaseTransition hook
|
|
95
|
-
* Create WIP commits for phase and step modes
|
|
96
|
-
*/
|
|
97
|
-
private async handleBeforePhaseTransition(
|
|
98
|
-
context: PluginHookContext,
|
|
99
|
-
currentPhase: string,
|
|
100
|
-
targetPhase: string
|
|
101
|
-
): Promise<void> {
|
|
102
|
-
const behavior = process.env.COMMIT_BEHAVIOR;
|
|
103
|
-
|
|
104
|
-
if (behavior !== 'phase' && behavior !== 'step') {
|
|
105
|
-
return; // Only commit on phase transitions for these modes
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
logger.info('CommitPlugin: Creating WIP commit before phase transition', {
|
|
109
|
-
conversationId: context.conversationId,
|
|
110
|
-
currentPhase,
|
|
111
|
-
targetPhase,
|
|
112
|
-
behavior,
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
try {
|
|
116
|
-
if (!GitManager.isGitRepository(context.projectPath)) {
|
|
117
|
-
logger.debug('CommitPlugin: Not a git repository, skipping commit');
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (!GitManager.hasUncommittedChanges(context.projectPath)) {
|
|
122
|
-
logger.debug('CommitPlugin: No uncommitted changes, skipping commit');
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const message = `WIP: transition to ${targetPhase}`;
|
|
127
|
-
const success = GitManager.createCommit(message, context.projectPath);
|
|
128
|
-
|
|
129
|
-
if (success) {
|
|
130
|
-
logger.info('CommitPlugin: Created WIP commit successfully', {
|
|
131
|
-
conversationId: context.conversationId,
|
|
132
|
-
message,
|
|
133
|
-
});
|
|
134
|
-
} else {
|
|
135
|
-
logger.warn('CommitPlugin: Failed to create WIP commit', {
|
|
136
|
-
conversationId: context.conversationId,
|
|
137
|
-
message,
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
} catch (error) {
|
|
141
|
-
logger.warn('CommitPlugin: Error during phase transition commit', {
|
|
142
|
-
error: error instanceof Error ? error.message : String(error),
|
|
143
|
-
conversationId: context.conversationId,
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Handle afterPlanFileCreated hook
|
|
150
|
-
* Add final commit task for end mode or step/phase modes with squashing
|
|
151
|
-
*/
|
|
152
|
-
private async handleAfterPlanFileCreated(
|
|
153
|
-
context: PluginHookContext,
|
|
154
|
-
planFilePath: string,
|
|
155
|
-
content: string
|
|
156
|
-
): Promise<string> {
|
|
157
|
-
const behavior = process.env.COMMIT_BEHAVIOR;
|
|
158
|
-
|
|
159
|
-
if (!behavior || behavior === 'none') {
|
|
160
|
-
return content; // No commit behavior
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
logger.debug('CommitPlugin: Adding final commit task to plan file', {
|
|
164
|
-
conversationId: context.conversationId,
|
|
165
|
-
behavior,
|
|
166
|
-
planFilePath,
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
try {
|
|
170
|
-
// Find the final phase (usually "Commit" or last phase)
|
|
171
|
-
const lines = content.split('\n');
|
|
172
|
-
let finalPhaseIndex = -1;
|
|
173
|
-
|
|
174
|
-
// Look for "## Commit" section first
|
|
175
|
-
for (let i = 0; i < lines.length; i++) {
|
|
176
|
-
if (lines[i]?.trim() === '## Commit') {
|
|
177
|
-
finalPhaseIndex = i;
|
|
178
|
-
break;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// If no Commit section, find the last ## section
|
|
183
|
-
if (finalPhaseIndex === -1) {
|
|
184
|
-
for (let i = lines.length - 1; i >= 0; i--) {
|
|
185
|
-
const line = lines[i];
|
|
186
|
-
if (
|
|
187
|
-
line?.startsWith('## ') &&
|
|
188
|
-
!line.includes('Notes') &&
|
|
189
|
-
!line.includes('Key Decisions')
|
|
190
|
-
) {
|
|
191
|
-
finalPhaseIndex = i;
|
|
192
|
-
break;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
if (finalPhaseIndex === -1) {
|
|
198
|
-
logger.warn(
|
|
199
|
-
'CommitPlugin: Could not find final phase to add commit task'
|
|
200
|
-
);
|
|
201
|
-
return content;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// Generate commit task based on behavior
|
|
205
|
-
let commitTask: string;
|
|
206
|
-
const defaultMessage =
|
|
207
|
-
process.env.COMMIT_MESSAGE_TEMPLATE ||
|
|
208
|
-
'Create a conventional commit. In the message, first summarize the intentions and key decisions from the development plan. Then, add a brief summary of the key changes and their side effects and dependencies';
|
|
209
|
-
|
|
210
|
-
if (behavior === 'end') {
|
|
211
|
-
// End mode: simple final commit
|
|
212
|
-
commitTask = `- [ ] ${defaultMessage}`;
|
|
213
|
-
} else {
|
|
214
|
-
// Step/phase mode: squash WIP commits with instructions
|
|
215
|
-
const squashInstructions = `Squash WIP commits: \`git reset --soft <first commit of this branch>. Then, ${defaultMessage}`;
|
|
216
|
-
commitTask = `- [ ] ${squashInstructions}`;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// Find the Tasks section in the final phase and add the commit task
|
|
220
|
-
let tasksIndex = -1;
|
|
221
|
-
for (let i = finalPhaseIndex; i < lines.length; i++) {
|
|
222
|
-
if (lines[i]?.trim() === '### Tasks') {
|
|
223
|
-
tasksIndex = i;
|
|
224
|
-
break;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
if (tasksIndex !== -1) {
|
|
229
|
-
// Insert after ### Tasks line
|
|
230
|
-
lines.splice(tasksIndex + 1, 0, commitTask);
|
|
231
|
-
} else {
|
|
232
|
-
// Add Tasks section if it doesn't exist
|
|
233
|
-
lines.splice(finalPhaseIndex + 1, 0, '', '### Tasks', commitTask);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
const updatedContent = lines.join('\n');
|
|
237
|
-
logger.info('CommitPlugin: Added final commit task to plan file', {
|
|
238
|
-
conversationId: context.conversationId,
|
|
239
|
-
behavior,
|
|
240
|
-
commitTask,
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
return updatedContent;
|
|
244
|
-
} catch (error) {
|
|
245
|
-
logger.warn('CommitPlugin: Failed to add commit task to plan file', {
|
|
246
|
-
error: error instanceof Error ? error.message : String(error),
|
|
247
|
-
conversationId: context.conversationId,
|
|
248
|
-
});
|
|
249
|
-
return content; // Return original content on error
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Plugin system exports
|
|
3
|
-
*
|
|
4
|
-
* This module provides the core plugin system for extending responsible-vibe-mcp
|
|
5
|
-
* functionality without if-statements in the core application.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
// Core plugin interfaces
|
|
9
|
-
export type {
|
|
10
|
-
IPlugin,
|
|
11
|
-
IPluginRegistry,
|
|
12
|
-
PluginHooks,
|
|
13
|
-
PluginHookContext,
|
|
14
|
-
StartDevelopmentArgs,
|
|
15
|
-
StartDevelopmentResult,
|
|
16
|
-
GeneratedInstructions,
|
|
17
|
-
} from './plugin-interfaces.js';
|
|
18
|
-
|
|
19
|
-
// Plugin registry implementation
|
|
20
|
-
export { PluginRegistry } from './plugin-registry.js';
|
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Plugin system interfaces for extending the responsible-vibe-mcp server
|
|
3
|
-
*
|
|
4
|
-
* Core Principle: Plugins receive only read-only context data and cannot
|
|
5
|
-
* directly manipulate core server components. They extend behavior through
|
|
6
|
-
* semantic lifecycle hooks only.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import type { YamlState } from '@codemcp/workflows-core';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Plugin interface - all plugins must implement this
|
|
13
|
-
*/
|
|
14
|
-
export interface IPlugin {
|
|
15
|
-
/** Unique plugin name */
|
|
16
|
-
getName(): string;
|
|
17
|
-
|
|
18
|
-
/** Execution sequence (lower numbers execute first) */
|
|
19
|
-
getSequence(): number;
|
|
20
|
-
|
|
21
|
-
/** Whether plugin is enabled (typically based on environment) */
|
|
22
|
-
isEnabled(): boolean;
|
|
23
|
-
|
|
24
|
-
/** Lifecycle hooks this plugin provides */
|
|
25
|
-
getHooks(): PluginHooks;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Lifecycle hooks that plugins can implement
|
|
30
|
-
* All hooks receive standardized PluginHookContext as first parameter
|
|
31
|
-
*/
|
|
32
|
-
export interface PluginHooks {
|
|
33
|
-
/** Called before development workflow starts */
|
|
34
|
-
beforeStartDevelopment?: (
|
|
35
|
-
context: PluginHookContext,
|
|
36
|
-
args: StartDevelopmentArgs
|
|
37
|
-
) => Promise<void>;
|
|
38
|
-
|
|
39
|
-
/** Called after development workflow has started */
|
|
40
|
-
afterStartDevelopment?: (
|
|
41
|
-
context: PluginHookContext,
|
|
42
|
-
args: StartDevelopmentArgs,
|
|
43
|
-
result: StartDevelopmentResult
|
|
44
|
-
) => Promise<void>;
|
|
45
|
-
|
|
46
|
-
/** Called after plan file is created - can modify content */
|
|
47
|
-
afterPlanFileCreated?: (
|
|
48
|
-
context: PluginHookContext,
|
|
49
|
-
planFilePath: string,
|
|
50
|
-
content: string
|
|
51
|
-
) => Promise<string>;
|
|
52
|
-
|
|
53
|
-
/** Called before phase transition (can block by throwing) */
|
|
54
|
-
beforePhaseTransition?: (
|
|
55
|
-
context: PluginHookContext,
|
|
56
|
-
currentPhase: string,
|
|
57
|
-
targetPhase: string
|
|
58
|
-
) => Promise<void>;
|
|
59
|
-
|
|
60
|
-
/** Called after instructions are generated - can modify them */
|
|
61
|
-
afterInstructionsGenerated?: (
|
|
62
|
-
context: PluginHookContext,
|
|
63
|
-
instructions: GeneratedInstructions
|
|
64
|
-
) => Promise<GeneratedInstructions>;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Standardized context provided to all plugin hooks
|
|
69
|
-
* Contains ONLY read-only data - no server components
|
|
70
|
-
*/
|
|
71
|
-
export interface PluginHookContext {
|
|
72
|
-
/** Current conversation ID */
|
|
73
|
-
conversationId: string;
|
|
74
|
-
|
|
75
|
-
/** Path to the plan file */
|
|
76
|
-
planFilePath: string;
|
|
77
|
-
|
|
78
|
-
/** Current development phase */
|
|
79
|
-
currentPhase: string;
|
|
80
|
-
|
|
81
|
-
/** Active workflow name */
|
|
82
|
-
workflow: string;
|
|
83
|
-
|
|
84
|
-
/** Project directory path */
|
|
85
|
-
projectPath: string;
|
|
86
|
-
|
|
87
|
-
/** Git branch name */
|
|
88
|
-
gitBranch: string;
|
|
89
|
-
|
|
90
|
-
/** Target phase (only available in phase transitions) */
|
|
91
|
-
targetPhase?: string;
|
|
92
|
-
|
|
93
|
-
/** Workflow state machine definition (read-only) - available in afterStartDevelopment */
|
|
94
|
-
stateMachine?: {
|
|
95
|
-
readonly name: string;
|
|
96
|
-
readonly description: string;
|
|
97
|
-
readonly initial_state: string;
|
|
98
|
-
readonly states: Record<string, YamlState>;
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
// EXPLICITLY EXCLUDED: No access to core server components like:
|
|
102
|
-
// - conversationManager (could manipulate conversations)
|
|
103
|
-
// - transitionEngine (could force transitions)
|
|
104
|
-
// - planManager (could bypass hook system)
|
|
105
|
-
// - instructionGenerator (could generate instructions outside flow)
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Plugin registry interface for managing and executing plugins
|
|
110
|
-
*/
|
|
111
|
-
export interface IPluginRegistry {
|
|
112
|
-
/** Register a plugin */
|
|
113
|
-
registerPlugin(plugin: IPlugin): void;
|
|
114
|
-
|
|
115
|
-
/** Get all enabled plugins sorted by sequence */
|
|
116
|
-
getEnabledPlugins(): IPlugin[];
|
|
117
|
-
|
|
118
|
-
/** Execute a specific hook on all plugins that implement it */
|
|
119
|
-
executeHook<T extends keyof PluginHooks>(
|
|
120
|
-
hookName: T,
|
|
121
|
-
...args: Parameters<NonNullable<PluginHooks[T]>>
|
|
122
|
-
): Promise<unknown>;
|
|
123
|
-
|
|
124
|
-
/** Check if any plugin has a specific hook */
|
|
125
|
-
hasHook(hookName: keyof PluginHooks): boolean;
|
|
126
|
-
|
|
127
|
-
/** Get names of all registered plugins */
|
|
128
|
-
getPluginNames(): string[];
|
|
129
|
-
|
|
130
|
-
/** Clear all plugins (mainly for testing) */
|
|
131
|
-
clear(): void;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Supporting interfaces for hook parameters
|
|
135
|
-
|
|
136
|
-
export interface StartDevelopmentArgs {
|
|
137
|
-
workflow: string;
|
|
138
|
-
require_reviews?: boolean;
|
|
139
|
-
project_path?: string;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
export interface StartDevelopmentResult {
|
|
143
|
-
conversationId: string;
|
|
144
|
-
planFilePath: string;
|
|
145
|
-
phase: string;
|
|
146
|
-
workflow: string;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
export interface GeneratedInstructions {
|
|
150
|
-
instructions: string;
|
|
151
|
-
planFilePath: string;
|
|
152
|
-
phase: string;
|
|
153
|
-
}
|
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Core PluginRegistry implementation for managing plugins and executing lifecycle hooks
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import type {
|
|
6
|
-
IPlugin,
|
|
7
|
-
IPluginRegistry,
|
|
8
|
-
PluginHooks,
|
|
9
|
-
} from './plugin-interfaces.js';
|
|
10
|
-
|
|
11
|
-
export class PluginRegistry implements IPluginRegistry {
|
|
12
|
-
private plugins: Map<string, IPlugin> = new Map();
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Register a plugin if it's enabled
|
|
16
|
-
*/
|
|
17
|
-
registerPlugin(plugin: IPlugin): void {
|
|
18
|
-
if (!plugin.isEnabled()) {
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const name = plugin.getName();
|
|
23
|
-
if (this.plugins.has(name)) {
|
|
24
|
-
throw new Error(`Plugin with name '${name}' is already registered`);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
this.plugins.set(name, plugin);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Get all enabled plugins sorted by execution sequence
|
|
32
|
-
*/
|
|
33
|
-
getEnabledPlugins(): IPlugin[] {
|
|
34
|
-
return Array.from(this.plugins.values())
|
|
35
|
-
.filter(plugin => plugin.isEnabled())
|
|
36
|
-
.sort((a, b) => a.getSequence() - b.getSequence());
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Execute a specific hook on all plugins that implement it
|
|
41
|
-
* Plugins are executed in sequence order
|
|
42
|
-
*
|
|
43
|
-
* Error Handling Strategy:
|
|
44
|
-
* - Validation hooks (beforePhaseTransition): Always re-throw to block invalid transitions
|
|
45
|
-
* - Critical startup hooks: Re-throw to fail fast and show critical errors
|
|
46
|
-
* - Non-critical hooks: Log error and continue execution to enable graceful degradation
|
|
47
|
-
* - Multiple plugins: If one plugin fails on non-critical hook, continue with next plugin
|
|
48
|
-
*/
|
|
49
|
-
async executeHook<T extends keyof PluginHooks>(
|
|
50
|
-
hookName: T,
|
|
51
|
-
...args: Parameters<NonNullable<PluginHooks[T]>>
|
|
52
|
-
): Promise<unknown> {
|
|
53
|
-
const enabledPlugins = this.getEnabledPlugins();
|
|
54
|
-
let result: unknown = undefined;
|
|
55
|
-
|
|
56
|
-
for (const plugin of enabledPlugins) {
|
|
57
|
-
const hooks = plugin.getHooks();
|
|
58
|
-
const hook = hooks[hookName];
|
|
59
|
-
|
|
60
|
-
if (hook) {
|
|
61
|
-
try {
|
|
62
|
-
// Type-safe hook execution using dispatch pattern
|
|
63
|
-
result = await this.executeTypedHook(hookName, hook, args, result);
|
|
64
|
-
} catch (error) {
|
|
65
|
-
const pluginName = plugin.getName();
|
|
66
|
-
const errorMessage =
|
|
67
|
-
error instanceof Error ? error.message : String(error);
|
|
68
|
-
|
|
69
|
-
// Validation hooks (beforePhaseTransition) should ALWAYS re-throw
|
|
70
|
-
// These are intentional blocking errors, not graceful degradation
|
|
71
|
-
if (hookName === 'beforePhaseTransition') {
|
|
72
|
-
console.error(
|
|
73
|
-
`Plugin '${pluginName}' validation failed for hook '${hookName}':`,
|
|
74
|
-
errorMessage
|
|
75
|
-
);
|
|
76
|
-
throw error;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// For non-critical hooks, log the error but continue execution
|
|
80
|
-
// This enables graceful degradation: the app continues even if a plugin hook fails
|
|
81
|
-
console.warn(
|
|
82
|
-
`Plugin '${pluginName}' hook '${hookName}' failed with non-critical error:`,
|
|
83
|
-
errorMessage
|
|
84
|
-
);
|
|
85
|
-
console.warn(
|
|
86
|
-
`Continuing with remaining plugins for hook '${hookName}' (graceful degradation enabled)`
|
|
87
|
-
);
|
|
88
|
-
|
|
89
|
-
// Continue to next plugin for non-critical errors
|
|
90
|
-
// This allows multiple plugins to execute even if one fails
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return result;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Type-safe hook execution dispatcher
|
|
100
|
-
* Handles the differences in hook signatures without type coercion
|
|
101
|
-
*/
|
|
102
|
-
private async executeTypedHook<T extends keyof PluginHooks>(
|
|
103
|
-
hookName: T,
|
|
104
|
-
hook: NonNullable<PluginHooks[T]>,
|
|
105
|
-
args: Parameters<NonNullable<PluginHooks[T]>>,
|
|
106
|
-
previousResult: unknown
|
|
107
|
-
): Promise<unknown> {
|
|
108
|
-
if (hookName === 'afterPlanFileCreated') {
|
|
109
|
-
// Content-chaining hook: replaces the content parameter with previous result
|
|
110
|
-
const typedHook = hook as NonNullable<
|
|
111
|
-
PluginHooks['afterPlanFileCreated']
|
|
112
|
-
>;
|
|
113
|
-
const [context, planFilePath, content] = args as Parameters<
|
|
114
|
-
typeof typedHook
|
|
115
|
-
>;
|
|
116
|
-
const contentToUse = ((previousResult as string | undefined) ??
|
|
117
|
-
content) as string;
|
|
118
|
-
return typedHook(context, planFilePath, contentToUse);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (hookName === 'afterInstructionsGenerated') {
|
|
122
|
-
// Content-chaining hook: replaces the instructions parameter with previous result
|
|
123
|
-
const typedHook = hook as NonNullable<
|
|
124
|
-
PluginHooks['afterInstructionsGenerated']
|
|
125
|
-
>;
|
|
126
|
-
const [context, instructions] = args as Parameters<typeof typedHook>;
|
|
127
|
-
const instructionsToUse = (
|
|
128
|
-
previousResult !== undefined
|
|
129
|
-
? (previousResult as Parameters<typeof typedHook>[1])
|
|
130
|
-
: instructions
|
|
131
|
-
) as Parameters<typeof typedHook>[1];
|
|
132
|
-
return typedHook(context, instructionsToUse);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
if (hookName === 'beforeStartDevelopment') {
|
|
136
|
-
const typedHook = hook as NonNullable<
|
|
137
|
-
PluginHooks['beforeStartDevelopment']
|
|
138
|
-
>;
|
|
139
|
-
const [context, startArgs] = args as Parameters<typeof typedHook>;
|
|
140
|
-
return typedHook(context, startArgs);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
if (hookName === 'afterStartDevelopment') {
|
|
144
|
-
const typedHook = hook as NonNullable<
|
|
145
|
-
PluginHooks['afterStartDevelopment']
|
|
146
|
-
>;
|
|
147
|
-
const [context, startArgs, result] = args as Parameters<typeof typedHook>;
|
|
148
|
-
return typedHook(context, startArgs, result);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if (hookName === 'beforePhaseTransition') {
|
|
152
|
-
const typedHook = hook as NonNullable<
|
|
153
|
-
PluginHooks['beforePhaseTransition']
|
|
154
|
-
>;
|
|
155
|
-
const [context, currentPhase, targetPhase] = args as Parameters<
|
|
156
|
-
typeof typedHook
|
|
157
|
-
>;
|
|
158
|
-
return typedHook(context, currentPhase, targetPhase);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// This should never be reached due to type system, but ensures exhaustiveness
|
|
162
|
-
const exhaustiveCheck: never = hookName;
|
|
163
|
-
throw new Error(`Unknown hook: ${exhaustiveCheck}`);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Check if any enabled plugin implements a specific hook
|
|
168
|
-
*/
|
|
169
|
-
hasHook(hookName: keyof PluginHooks): boolean {
|
|
170
|
-
const enabledPlugins = this.getEnabledPlugins();
|
|
171
|
-
return enabledPlugins.some(plugin => {
|
|
172
|
-
const hooks = plugin.getHooks();
|
|
173
|
-
return hooks[hookName] !== undefined;
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Get names of all registered plugins (for debugging)
|
|
179
|
-
*/
|
|
180
|
-
getPluginNames(): string[] {
|
|
181
|
-
return Array.from(this.plugins.keys());
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* Clear all plugins (mainly for testing)
|
|
186
|
-
*/
|
|
187
|
-
clear(): void {
|
|
188
|
-
this.plugins.clear();
|
|
189
|
-
}
|
|
190
|
-
}
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Conversation State Resource Handler
|
|
3
|
-
*
|
|
4
|
-
* Handles the conversation-state resource which provides access to current
|
|
5
|
-
* conversation state and phase information including conversation ID, project context,
|
|
6
|
-
* current development phase, and plan file location.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { createLogger } from '@codemcp/workflows-core';
|
|
10
|
-
import {
|
|
11
|
-
ResourceHandler,
|
|
12
|
-
ServerContext,
|
|
13
|
-
HandlerResult,
|
|
14
|
-
ResourceContent,
|
|
15
|
-
} from '../types.js';
|
|
16
|
-
import { safeExecute } from '../server-helpers.js';
|
|
17
|
-
|
|
18
|
-
const logger = createLogger('ConversationStateResourceHandler');
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Conversation State resource handler implementation
|
|
22
|
-
*/
|
|
23
|
-
export class ConversationStateResourceHandler implements ResourceHandler {
|
|
24
|
-
async handle(
|
|
25
|
-
uri: URL,
|
|
26
|
-
context: ServerContext
|
|
27
|
-
): Promise<HandlerResult<ResourceContent>> {
|
|
28
|
-
logger.debug('Processing conversation state resource request', {
|
|
29
|
-
uri: uri.href,
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
return safeExecute(async () => {
|
|
33
|
-
// Get conversation context
|
|
34
|
-
const conversationContext =
|
|
35
|
-
await context.conversationManager.getConversationContext();
|
|
36
|
-
|
|
37
|
-
// Build state information
|
|
38
|
-
const stateInfo = {
|
|
39
|
-
conversationId: conversationContext.conversationId,
|
|
40
|
-
projectPath: conversationContext.projectPath,
|
|
41
|
-
gitBranch: conversationContext.gitBranch,
|
|
42
|
-
currentPhase: conversationContext.currentPhase,
|
|
43
|
-
planFilePath: conversationContext.planFilePath,
|
|
44
|
-
timestamp: new Date().toISOString(),
|
|
45
|
-
description: 'Current state of the development workflow conversation',
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
return {
|
|
49
|
-
uri: uri.href,
|
|
50
|
-
text: JSON.stringify(stateInfo, null, 2),
|
|
51
|
-
mimeType: 'application/json',
|
|
52
|
-
};
|
|
53
|
-
}, 'Failed to retrieve conversation state resource');
|
|
54
|
-
}
|
|
55
|
-
}
|