@codemcp/workflows 3.1.21
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 +4 -0
- package/.vibe/conversation-state.sqlite +0 -0
- package/LICENSE +674 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +74 -0
- package/dist/index.js.map +1 -0
- package/dist/notification-service.d.ts +14 -0
- package/dist/notification-service.d.ts.map +1 -0
- package/dist/notification-service.js +18 -0
- package/dist/notification-service.js.map +1 -0
- package/dist/resource-handlers/conversation-state.d.ts +15 -0
- package/dist/resource-handlers/conversation-state.d.ts.map +1 -0
- package/dist/resource-handlers/conversation-state.js +40 -0
- package/dist/resource-handlers/conversation-state.js.map +1 -0
- package/dist/resource-handlers/development-plan.d.ts +14 -0
- package/dist/resource-handlers/development-plan.d.ts.map +1 -0
- package/dist/resource-handlers/development-plan.js +31 -0
- package/dist/resource-handlers/development-plan.js.map +1 -0
- package/dist/resource-handlers/index.d.ts +24 -0
- package/dist/resource-handlers/index.d.ts.map +1 -0
- package/dist/resource-handlers/index.js +62 -0
- package/dist/resource-handlers/index.js.map +1 -0
- package/dist/resource-handlers/system-prompt.d.ts +15 -0
- package/dist/resource-handlers/system-prompt.d.ts.map +1 -0
- package/dist/resource-handlers/system-prompt.js +40 -0
- package/dist/resource-handlers/system-prompt.js.map +1 -0
- package/dist/resource-handlers/workflow-resource.d.ts +15 -0
- package/dist/resource-handlers/workflow-resource.d.ts.map +1 -0
- package/dist/resource-handlers/workflow-resource.js +85 -0
- package/dist/resource-handlers/workflow-resource.js.map +1 -0
- package/dist/response-renderer.d.ts +30 -0
- package/dist/response-renderer.d.ts.map +1 -0
- package/dist/response-renderer.js +94 -0
- package/dist/response-renderer.js.map +1 -0
- package/dist/server-config.d.ts +34 -0
- package/dist/server-config.d.ts.map +1 -0
- package/dist/server-config.js +486 -0
- package/dist/server-config.js.map +1 -0
- package/dist/server-helpers.d.ts +62 -0
- package/dist/server-helpers.d.ts.map +1 -0
- package/dist/server-helpers.js +156 -0
- package/dist/server-helpers.js.map +1 -0
- package/dist/server-implementation.d.ts +74 -0
- package/dist/server-implementation.d.ts.map +1 -0
- package/dist/server-implementation.js +201 -0
- package/dist/server-implementation.js.map +1 -0
- package/dist/server.d.ts +6 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +5 -0
- package/dist/server.js.map +1 -0
- package/dist/tool-handlers/base-tool-handler.d.ts +50 -0
- package/dist/tool-handlers/base-tool-handler.d.ts.map +1 -0
- package/dist/tool-handlers/base-tool-handler.js +74 -0
- package/dist/tool-handlers/base-tool-handler.js.map +1 -0
- package/dist/tool-handlers/conduct-review.d.ts +49 -0
- package/dist/tool-handlers/conduct-review.d.ts.map +1 -0
- package/dist/tool-handlers/conduct-review.js +105 -0
- package/dist/tool-handlers/conduct-review.js.map +1 -0
- package/dist/tool-handlers/get-tool-info.d.ts +76 -0
- package/dist/tool-handlers/get-tool-info.d.ts.map +1 -0
- package/dist/tool-handlers/get-tool-info.js +168 -0
- package/dist/tool-handlers/get-tool-info.js.map +1 -0
- package/dist/tool-handlers/index.d.ts +42 -0
- package/dist/tool-handlers/index.d.ts.map +1 -0
- package/dist/tool-handlers/index.js +74 -0
- package/dist/tool-handlers/index.js.map +1 -0
- package/dist/tool-handlers/install-workflow.d.ts +48 -0
- package/dist/tool-handlers/install-workflow.d.ts.map +1 -0
- package/dist/tool-handlers/install-workflow.js +131 -0
- package/dist/tool-handlers/install-workflow.js.map +1 -0
- package/dist/tool-handlers/list-workflows.d.ts +47 -0
- package/dist/tool-handlers/list-workflows.d.ts.map +1 -0
- package/dist/tool-handlers/list-workflows.js +58 -0
- package/dist/tool-handlers/list-workflows.js.map +1 -0
- package/dist/tool-handlers/no-idea.d.ts +41 -0
- package/dist/tool-handlers/no-idea.d.ts.map +1 -0
- package/dist/tool-handlers/no-idea.js +29 -0
- package/dist/tool-handlers/no-idea.js.map +1 -0
- package/dist/tool-handlers/proceed-to-phase.d.ts +39 -0
- package/dist/tool-handlers/proceed-to-phase.d.ts.map +1 -0
- package/dist/tool-handlers/proceed-to-phase.js +109 -0
- package/dist/tool-handlers/proceed-to-phase.js.map +1 -0
- package/dist/tool-handlers/reset-development.d.ts +31 -0
- package/dist/tool-handlers/reset-development.d.ts.map +1 -0
- package/dist/tool-handlers/reset-development.js +48 -0
- package/dist/tool-handlers/reset-development.js.map +1 -0
- package/dist/tool-handlers/resume-workflow.d.ts +88 -0
- package/dist/tool-handlers/resume-workflow.d.ts.map +1 -0
- package/dist/tool-handlers/resume-workflow.js +213 -0
- package/dist/tool-handlers/resume-workflow.js.map +1 -0
- package/dist/tool-handlers/setup-project-docs.d.ts +36 -0
- package/dist/tool-handlers/setup-project-docs.d.ts.map +1 -0
- package/dist/tool-handlers/setup-project-docs.js +136 -0
- package/dist/tool-handlers/setup-project-docs.js.map +1 -0
- package/dist/tool-handlers/start-development.d.ts +82 -0
- package/dist/tool-handlers/start-development.d.ts.map +1 -0
- package/dist/tool-handlers/start-development.js +448 -0
- package/dist/tool-handlers/start-development.js.map +1 -0
- package/dist/tool-handlers/whats-next.d.ts +42 -0
- package/dist/tool-handlers/whats-next.d.ts.map +1 -0
- package/dist/tool-handlers/whats-next.js +118 -0
- package/dist/tool-handlers/whats-next.js.map +1 -0
- package/dist/types.d.ts +114 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/package.json +29 -0
- package/src/index.ts +93 -0
- package/src/notification-service.ts +23 -0
- package/src/resource-handlers/conversation-state.ts +55 -0
- package/src/resource-handlers/development-plan.ts +48 -0
- package/src/resource-handlers/index.ts +73 -0
- package/src/resource-handlers/system-prompt.ts +55 -0
- package/src/resource-handlers/workflow-resource.ts +126 -0
- package/src/response-renderer.ts +116 -0
- package/src/server-config.ts +744 -0
- package/src/server-helpers.ts +225 -0
- package/src/server-implementation.ts +277 -0
- package/src/server.ts +9 -0
- package/src/tool-handlers/base-tool-handler.ts +141 -0
- package/src/tool-handlers/conduct-review.ts +191 -0
- package/src/tool-handlers/get-tool-info.ts +274 -0
- package/src/tool-handlers/index.ts +117 -0
- package/src/tool-handlers/install-workflow.ts +185 -0
- package/src/tool-handlers/list-workflows.ts +94 -0
- package/src/tool-handlers/no-idea.ts +47 -0
- package/src/tool-handlers/proceed-to-phase.ts +205 -0
- package/src/tool-handlers/reset-development.ts +90 -0
- package/src/tool-handlers/resume-workflow.ts +380 -0
- package/src/tool-handlers/setup-project-docs.ts +226 -0
- package/src/tool-handlers/start-development.ts +685 -0
- package/src/tool-handlers/whats-next.ts +235 -0
- package/src/types.ts +130 -0
- package/test/e2e/core-functionality.test.ts +176 -0
- package/test/e2e/mcp-contract.test.ts +540 -0
- package/test/e2e/plan-management.test.ts +331 -0
- package/test/e2e/state-management.test.ts +392 -0
- package/test/e2e/workflow-integration.test.ts +506 -0
- package/test/unit/commit-behaviour-interface.test.ts +244 -0
- package/test/unit/conduct-review.test.ts +151 -0
- package/test/unit/reset-functionality.test.ts +72 -0
- package/test/unit/resume-workflow.test.ts +192 -0
- package/test/unit/server-tools.test.ts +311 -0
- package/test/unit/setup-project-docs-handler.test.ts +267 -0
- package/test/unit/start-development-artifact-detection.test.ts +387 -0
- package/test/unit/start-development-gitignore.test.ts +178 -0
- package/test/unit/system-prompt-resource.test.ts +101 -0
- package/test/unit/tool-handlers/no-idea.test.ts +40 -0
- package/test/utils/e2e-test-setup.ts +453 -0
- package/test/utils/run-server-in-dir.sh +27 -0
- package/test/utils/temp-files.ts +308 -0
- package/test/utils/test-access.ts +79 -0
- package/test/utils/test-helpers.ts +286 -0
- package/test/utils/test-setup.ts +78 -0
- package/tsconfig.build.json +9 -0
- package/tsconfig.build.tsbuildinfo +1 -0
- package/tsconfig.json +12 -0
- package/vitest.config.ts +17 -0
|
@@ -0,0 +1,685 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StartDevelopment Tool Handler
|
|
3
|
+
*
|
|
4
|
+
* Handles initialization of development workflow and transition to the initial
|
|
5
|
+
* development phase. Allows users to choose from predefined workflows or use a custom workflow.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { BaseToolHandler } from './base-tool-handler.js';
|
|
9
|
+
import { validateRequiredArgs } from '../server-helpers.js';
|
|
10
|
+
import { basename } from 'node:path';
|
|
11
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
12
|
+
import { resolve } from 'node:path';
|
|
13
|
+
import { GitCommitConfig } from '@codemcp/workflows-core';
|
|
14
|
+
import { GitManager } from '@codemcp/workflows-core';
|
|
15
|
+
import type { YamlStateMachine } from '@codemcp/workflows-core';
|
|
16
|
+
import { ProjectDocsManager, ProjectDocsInfo } from '@codemcp/workflows-core';
|
|
17
|
+
import { ServerContext } from '../types.js';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Arguments for the start_development tool
|
|
21
|
+
*/
|
|
22
|
+
export interface StartDevelopmentArgs {
|
|
23
|
+
workflow: string;
|
|
24
|
+
commit_behaviour?: 'step' | 'phase' | 'end' | 'none';
|
|
25
|
+
require_reviews?: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Response from the start_development tool
|
|
30
|
+
*/
|
|
31
|
+
export interface StartDevelopmentResult {
|
|
32
|
+
phase: string;
|
|
33
|
+
instructions: string;
|
|
34
|
+
plan_file_path: string;
|
|
35
|
+
conversation_id: string;
|
|
36
|
+
workflow: YamlStateMachine;
|
|
37
|
+
workflowDocumentationUrl?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* StartDevelopment tool handler implementation
|
|
42
|
+
*/
|
|
43
|
+
export class StartDevelopmentHandler extends BaseToolHandler<
|
|
44
|
+
StartDevelopmentArgs,
|
|
45
|
+
StartDevelopmentResult
|
|
46
|
+
> {
|
|
47
|
+
private projectDocsManager: ProjectDocsManager;
|
|
48
|
+
|
|
49
|
+
constructor() {
|
|
50
|
+
super();
|
|
51
|
+
this.projectDocsManager = new ProjectDocsManager();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
protected async executeHandler(
|
|
55
|
+
args: StartDevelopmentArgs,
|
|
56
|
+
context: ServerContext
|
|
57
|
+
): Promise<StartDevelopmentResult> {
|
|
58
|
+
// Validate required arguments
|
|
59
|
+
validateRequiredArgs(args, ['workflow']);
|
|
60
|
+
|
|
61
|
+
const selectedWorkflow = args.workflow;
|
|
62
|
+
const requireReviews = args.require_reviews ?? false;
|
|
63
|
+
|
|
64
|
+
// Process git commit configuration
|
|
65
|
+
const isGitRepository = GitManager.isGitRepository(context.projectPath);
|
|
66
|
+
|
|
67
|
+
// Translate commit_behaviour to internal git config
|
|
68
|
+
const commitBehaviour =
|
|
69
|
+
args.commit_behaviour ?? (isGitRepository ? 'end' : 'none');
|
|
70
|
+
const gitCommitConfig: GitCommitConfig = {
|
|
71
|
+
enabled: commitBehaviour !== 'none',
|
|
72
|
+
commitOnStep: commitBehaviour === 'step',
|
|
73
|
+
commitOnPhase: commitBehaviour === 'phase',
|
|
74
|
+
commitOnComplete:
|
|
75
|
+
commitBehaviour === 'end' ||
|
|
76
|
+
commitBehaviour === 'step' ||
|
|
77
|
+
commitBehaviour === 'phase',
|
|
78
|
+
initialMessage: 'Development session',
|
|
79
|
+
startCommitHash:
|
|
80
|
+
GitManager.getCurrentCommitHash(context.projectPath) || undefined,
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
this.logger.debug('Processing start_development request', {
|
|
84
|
+
selectedWorkflow,
|
|
85
|
+
projectPath: context.projectPath,
|
|
86
|
+
commitBehaviour,
|
|
87
|
+
gitCommitConfig,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Validate workflow selection (ensure project workflows are loaded first)
|
|
91
|
+
context.workflowManager.loadProjectWorkflows(context.projectPath);
|
|
92
|
+
if (
|
|
93
|
+
!context.workflowManager.validateWorkflowName(
|
|
94
|
+
selectedWorkflow,
|
|
95
|
+
context.projectPath
|
|
96
|
+
)
|
|
97
|
+
) {
|
|
98
|
+
const availableWorkflows = context.workflowManager.getWorkflowNames();
|
|
99
|
+
throw new Error(
|
|
100
|
+
`Invalid workflow: ${selectedWorkflow}. Available workflows: ${availableWorkflows.join(', ')}`
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Check for project documentation artifacts and guide setup if needed
|
|
105
|
+
const artifactGuidance = await this.checkProjectArtifacts(
|
|
106
|
+
context.projectPath,
|
|
107
|
+
selectedWorkflow,
|
|
108
|
+
context
|
|
109
|
+
);
|
|
110
|
+
if (artifactGuidance) {
|
|
111
|
+
return artifactGuidance;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Check if user is on main/master branch and prompt for branch creation
|
|
115
|
+
const currentBranch = this.getCurrentGitBranch(context.projectPath);
|
|
116
|
+
if (currentBranch === 'main' || currentBranch === 'master') {
|
|
117
|
+
const suggestedBranchName = this.generateBranchSuggestion();
|
|
118
|
+
const branchPromptResponse: StartDevelopmentResult = {
|
|
119
|
+
phase: 'branch-prompt',
|
|
120
|
+
instructions: `You're currently on the ${currentBranch} branch. It's recommended to create a feature branch for development. Propose a branch creation by suggesting a branch command to the user call start_development again.\n\nSuggested command: \`git checkout -b ${suggestedBranchName}\`\n\nPlease create a new branch and then call start_development again to begin development.`,
|
|
121
|
+
plan_file_path: '',
|
|
122
|
+
conversation_id: '',
|
|
123
|
+
workflow: {} as YamlStateMachine,
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
this.logger.debug(
|
|
127
|
+
'User on main/master branch, prompting for branch creation',
|
|
128
|
+
{
|
|
129
|
+
currentBranch,
|
|
130
|
+
suggestedBranchName,
|
|
131
|
+
}
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
return branchPromptResponse;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Create or get conversation context with the selected workflow
|
|
138
|
+
const conversationContext =
|
|
139
|
+
await context.conversationManager.createConversationContext(
|
|
140
|
+
selectedWorkflow
|
|
141
|
+
);
|
|
142
|
+
const currentPhase = conversationContext.currentPhase;
|
|
143
|
+
|
|
144
|
+
// Load the selected workflow
|
|
145
|
+
const stateMachine = context.workflowManager.loadWorkflowForProject(
|
|
146
|
+
conversationContext.projectPath,
|
|
147
|
+
selectedWorkflow
|
|
148
|
+
);
|
|
149
|
+
const initialState = stateMachine.initial_state;
|
|
150
|
+
|
|
151
|
+
// Check if development is already started
|
|
152
|
+
if (currentPhase !== initialState) {
|
|
153
|
+
throw new Error(
|
|
154
|
+
`Development already started. Current phase is '${currentPhase}', not initial state '${initialState}'. Use whats_next() to continue development.`
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// The initial state IS the first development phase - it's explicitly modeled
|
|
159
|
+
const targetPhase = initialState;
|
|
160
|
+
|
|
161
|
+
// Transition to the initial development phase
|
|
162
|
+
const transitionResult =
|
|
163
|
+
await context.transitionEngine.handleExplicitTransition(
|
|
164
|
+
currentPhase,
|
|
165
|
+
targetPhase,
|
|
166
|
+
conversationContext.projectPath,
|
|
167
|
+
'Development initialization',
|
|
168
|
+
selectedWorkflow
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
// Update conversation state with workflow, phase, and git commit configuration
|
|
172
|
+
await context.conversationManager.updateConversationState(
|
|
173
|
+
conversationContext.conversationId,
|
|
174
|
+
{
|
|
175
|
+
currentPhase: transitionResult.newPhase,
|
|
176
|
+
workflowName: selectedWorkflow,
|
|
177
|
+
gitCommitConfig: gitCommitConfig,
|
|
178
|
+
requireReviewsBeforePhaseTransition: requireReviews,
|
|
179
|
+
}
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
// Set state machine on plan manager before creating plan file
|
|
183
|
+
context.planManager.setStateMachine(stateMachine);
|
|
184
|
+
|
|
185
|
+
// Ensure plan file exists
|
|
186
|
+
await context.planManager.ensurePlanFile(
|
|
187
|
+
conversationContext.planFilePath,
|
|
188
|
+
conversationContext.projectPath,
|
|
189
|
+
conversationContext.gitBranch
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
// Ensure .vibe/.gitignore exists to exclude SQLite files for git repositories
|
|
193
|
+
this.ensureGitignoreEntry(conversationContext.projectPath);
|
|
194
|
+
|
|
195
|
+
// Generate workflow documentation URL
|
|
196
|
+
const workflowDocumentationUrl =
|
|
197
|
+
this.generateWorkflowDocumentationUrl(selectedWorkflow);
|
|
198
|
+
|
|
199
|
+
// Generate instructions with simple i18n guidance
|
|
200
|
+
const baseInstructions = `Look at the plan file (${conversationContext.planFilePath}). Define entrance criteria for each phase of the workflow except the initial phase. Those criteria shall be based on the contents of the previous phase. \n Example: \n \`\`\`\n ## Design\n\n ### Phase Entrance Criteria:\n - [ ] The requirements have been thoroughly defined.\n - [ ] Alternatives have been evaluated and are documented. \n - [ ] It's clear what's in scope and out of scope\n \`\`\`\n \n IMPORTANT: Once you added reasonable entrance call the whats_next() tool to get guided instructions for the next current phase.`;
|
|
201
|
+
|
|
202
|
+
const i18nGuidance = `\n\nNOTE: If the user is communicating in a non-English language, please translate the plan file content to that language while keeping the structure intact, and continue all interactions in the user's language.`;
|
|
203
|
+
|
|
204
|
+
// Add workflow documentation information if available
|
|
205
|
+
const workflowDocumentationInfo = workflowDocumentationUrl
|
|
206
|
+
? `\n\nInform the user about the chose workflow: He can visit: ${workflowDocumentationUrl} to get detailed information.`
|
|
207
|
+
: '';
|
|
208
|
+
|
|
209
|
+
const finalInstructions =
|
|
210
|
+
baseInstructions + workflowDocumentationInfo + i18nGuidance;
|
|
211
|
+
|
|
212
|
+
const response: StartDevelopmentResult = {
|
|
213
|
+
phase: transitionResult.newPhase,
|
|
214
|
+
instructions: finalInstructions,
|
|
215
|
+
plan_file_path: conversationContext.planFilePath,
|
|
216
|
+
conversation_id: conversationContext.conversationId,
|
|
217
|
+
workflow: stateMachine,
|
|
218
|
+
workflowDocumentationUrl,
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
// Log interaction
|
|
222
|
+
await this.logInteraction(
|
|
223
|
+
context,
|
|
224
|
+
conversationContext.conversationId,
|
|
225
|
+
'start_development',
|
|
226
|
+
args,
|
|
227
|
+
response,
|
|
228
|
+
transitionResult.newPhase
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
return response;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Check if project documentation artifacts exist and provide setup guidance if needed
|
|
236
|
+
* Dynamically analyzes the selected workflow to determine which documents are referenced
|
|
237
|
+
* Blocks workflow start if the workflow requires documentation
|
|
238
|
+
*/
|
|
239
|
+
private async checkProjectArtifacts(
|
|
240
|
+
projectPath: string,
|
|
241
|
+
workflowName: string,
|
|
242
|
+
context: ServerContext
|
|
243
|
+
): Promise<StartDevelopmentResult | null> {
|
|
244
|
+
try {
|
|
245
|
+
// Load the workflow to analyze its content
|
|
246
|
+
const stateMachine = context.workflowManager.loadWorkflowForProject(
|
|
247
|
+
projectPath,
|
|
248
|
+
workflowName
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
// Check if this workflow requires documentation (defaults to false)
|
|
252
|
+
const requiresDocumentation =
|
|
253
|
+
stateMachine.metadata?.requiresDocumentation ?? false;
|
|
254
|
+
|
|
255
|
+
// If workflow doesn't require documentation, skip artifact check entirely
|
|
256
|
+
if (!requiresDocumentation) {
|
|
257
|
+
this.logger.debug(
|
|
258
|
+
'Workflow does not require documentation, skipping artifact check',
|
|
259
|
+
{ workflowName, requiresDocumentation }
|
|
260
|
+
);
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Analyze workflow content to detect referenced document variables
|
|
265
|
+
const referencedVariables = this.analyzeWorkflowDocumentReferences(
|
|
266
|
+
stateMachine,
|
|
267
|
+
projectPath
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
// If no document variables are referenced, skip artifact check
|
|
271
|
+
if (referencedVariables.length === 0) {
|
|
272
|
+
this.logger.debug(
|
|
273
|
+
'No document variables found in workflow, skipping artifact check',
|
|
274
|
+
{ workflowName }
|
|
275
|
+
);
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Check which referenced documents are missing
|
|
280
|
+
const docsInfo =
|
|
281
|
+
await this.projectDocsManager.getProjectDocsInfo(projectPath);
|
|
282
|
+
const missingDocs = this.getMissingReferencedDocuments(
|
|
283
|
+
referencedVariables,
|
|
284
|
+
docsInfo,
|
|
285
|
+
projectPath
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
// If all referenced documents exist, continue with normal flow
|
|
289
|
+
if (missingDocs.length === 0) {
|
|
290
|
+
this.logger.debug(
|
|
291
|
+
'All referenced project artifacts exist, continuing with development',
|
|
292
|
+
{
|
|
293
|
+
workflowName,
|
|
294
|
+
referencedVariables,
|
|
295
|
+
}
|
|
296
|
+
);
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Generate guidance for setting up missing artifacts
|
|
301
|
+
const setupGuidance = await this.generateArtifactSetupGuidance(
|
|
302
|
+
missingDocs,
|
|
303
|
+
workflowName,
|
|
304
|
+
docsInfo,
|
|
305
|
+
referencedVariables
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
this.logger.info(
|
|
309
|
+
'Missing required project artifacts detected for workflow that requires documentation',
|
|
310
|
+
{
|
|
311
|
+
workflowName,
|
|
312
|
+
requiresDocumentation,
|
|
313
|
+
referencedVariables,
|
|
314
|
+
missingDocs,
|
|
315
|
+
projectPath,
|
|
316
|
+
}
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
return {
|
|
320
|
+
phase: 'artifact-setup',
|
|
321
|
+
instructions: setupGuidance,
|
|
322
|
+
plan_file_path: '',
|
|
323
|
+
conversation_id: '',
|
|
324
|
+
workflow: {} as YamlStateMachine,
|
|
325
|
+
};
|
|
326
|
+
} catch (error) {
|
|
327
|
+
this.logger.warn(
|
|
328
|
+
'Failed to analyze workflow for document references, proceeding without artifact check',
|
|
329
|
+
{
|
|
330
|
+
workflowName,
|
|
331
|
+
error: error instanceof Error ? error.message : String(error),
|
|
332
|
+
}
|
|
333
|
+
);
|
|
334
|
+
return null;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Analyze workflow content to detect document variable references
|
|
340
|
+
*/
|
|
341
|
+
private analyzeWorkflowDocumentReferences(
|
|
342
|
+
stateMachine: YamlStateMachine,
|
|
343
|
+
projectPath: string
|
|
344
|
+
): string[] {
|
|
345
|
+
// Get available document variables from ProjectDocsManager
|
|
346
|
+
const variableSubstitutions =
|
|
347
|
+
this.projectDocsManager.getVariableSubstitutions(projectPath);
|
|
348
|
+
const documentVariables = Object.keys(variableSubstitutions);
|
|
349
|
+
const referencedVariables: Set<string> = new Set();
|
|
350
|
+
|
|
351
|
+
// Convert the entire state machine to a string for analysis
|
|
352
|
+
const workflowContent = JSON.stringify(stateMachine);
|
|
353
|
+
|
|
354
|
+
// Check for each document variable
|
|
355
|
+
for (const variable of documentVariables) {
|
|
356
|
+
if (workflowContent.includes(variable)) {
|
|
357
|
+
referencedVariables.add(variable);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
this.logger.debug('Analyzed workflow for document references', {
|
|
362
|
+
workflowContent: workflowContent.length + ' characters',
|
|
363
|
+
availableVariables: documentVariables,
|
|
364
|
+
referencedVariables: Array.from(referencedVariables),
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
return Array.from(referencedVariables);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Determine which referenced documents are missing
|
|
372
|
+
*/
|
|
373
|
+
private getMissingReferencedDocuments(
|
|
374
|
+
referencedVariables: string[],
|
|
375
|
+
docsInfo: ProjectDocsInfo,
|
|
376
|
+
projectPath: string
|
|
377
|
+
): string[] {
|
|
378
|
+
const missingDocs: string[] = [];
|
|
379
|
+
|
|
380
|
+
// Get variable substitutions to derive the mapping
|
|
381
|
+
const variableSubstitutions =
|
|
382
|
+
this.projectDocsManager.getVariableSubstitutions(projectPath);
|
|
383
|
+
|
|
384
|
+
// Create reverse mapping from variable to document type
|
|
385
|
+
const variableToDocMap: { [key: string]: string } = {};
|
|
386
|
+
for (const [variable, path] of Object.entries(variableSubstitutions)) {
|
|
387
|
+
// Extract document type from path (e.g., 'architecture' from 'architecture.md')
|
|
388
|
+
const filename = path.split('/').pop() || '';
|
|
389
|
+
const docType = filename.replace('.md', '');
|
|
390
|
+
variableToDocMap[variable] = docType;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
for (const variable of referencedVariables) {
|
|
394
|
+
const docType = variableToDocMap[variable];
|
|
395
|
+
if (docType && docType in docsInfo) {
|
|
396
|
+
const docInfo = docsInfo[docType as keyof ProjectDocsInfo];
|
|
397
|
+
if (docInfo && !docInfo.exists) {
|
|
398
|
+
missingDocs.push(`${docType}.md`);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return missingDocs;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Generate guidance for setting up missing project artifacts
|
|
408
|
+
*/
|
|
409
|
+
private async generateArtifactSetupGuidance(
|
|
410
|
+
missingDocs: string[],
|
|
411
|
+
workflowName: string,
|
|
412
|
+
docsInfo: ProjectDocsInfo,
|
|
413
|
+
referencedVariables: string[]
|
|
414
|
+
): Promise<string> {
|
|
415
|
+
const missingList = missingDocs.map(doc => `- ${doc}`).join('\n');
|
|
416
|
+
const existingDocs = [];
|
|
417
|
+
|
|
418
|
+
if (docsInfo.architecture.exists) {
|
|
419
|
+
const fileName = basename(docsInfo.architecture.path);
|
|
420
|
+
existingDocs.push(`✅ ${fileName}`);
|
|
421
|
+
}
|
|
422
|
+
if (docsInfo.requirements.exists) {
|
|
423
|
+
const fileName = basename(docsInfo.requirements.path);
|
|
424
|
+
existingDocs.push(`✅ ${fileName}`);
|
|
425
|
+
}
|
|
426
|
+
if (docsInfo.design.exists) {
|
|
427
|
+
const fileName = basename(docsInfo.design.path);
|
|
428
|
+
existingDocs.push(`✅ ${fileName}`);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const existingList =
|
|
432
|
+
existingDocs.length > 0
|
|
433
|
+
? `\n\n**Existing Documents:**\n${existingDocs.join('\n')}`
|
|
434
|
+
: '';
|
|
435
|
+
|
|
436
|
+
const referencedVariablesList = referencedVariables
|
|
437
|
+
.map(v => `\`${v}\``)
|
|
438
|
+
.join(', ');
|
|
439
|
+
|
|
440
|
+
// Get available templates dynamically
|
|
441
|
+
const availableTemplates =
|
|
442
|
+
await this.projectDocsManager.templateManager.getAvailableTemplates();
|
|
443
|
+
const defaults =
|
|
444
|
+
await this.projectDocsManager.templateManager.getDefaults();
|
|
445
|
+
|
|
446
|
+
// Generate template options dynamically
|
|
447
|
+
const templateOptionsText =
|
|
448
|
+
this.generateTemplateOptionsText(availableTemplates);
|
|
449
|
+
|
|
450
|
+
return `## Project Documentation Setup Required
|
|
451
|
+
|
|
452
|
+
The **${workflowName}** workflow references project documentation that doesn't exist yet.
|
|
453
|
+
|
|
454
|
+
**Referenced Variables:** ${referencedVariablesList}
|
|
455
|
+
|
|
456
|
+
**Missing Documents:**
|
|
457
|
+
${missingList}${existingList}
|
|
458
|
+
|
|
459
|
+
## 🚀 **Quick Setup**
|
|
460
|
+
|
|
461
|
+
Use the \`setup_project_docs\` tool to create these documents with templates:
|
|
462
|
+
|
|
463
|
+
\`\`\`
|
|
464
|
+
setup_project_docs({
|
|
465
|
+
architecture: "${defaults.architecture}", // or other available options
|
|
466
|
+
requirements: "${defaults.requirements}", // or other available options
|
|
467
|
+
design: "${defaults.design}" // or other available options
|
|
468
|
+
})
|
|
469
|
+
\`\`\`
|
|
470
|
+
|
|
471
|
+
${templateOptionsText}
|
|
472
|
+
|
|
473
|
+
## ⚡ **Next Steps**
|
|
474
|
+
|
|
475
|
+
1. **Call \`setup_project_docs\`** with your preferred templates
|
|
476
|
+
2. **Call \`start_development\`** again to begin the ${workflowName} workflow
|
|
477
|
+
3. The workflow will reference these documents using the detected variables: ${referencedVariablesList}
|
|
478
|
+
|
|
479
|
+
**Note:** You can also proceed without structured docs, but the workflow instructions will reference missing files.`;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Generate template options text dynamically
|
|
484
|
+
*/
|
|
485
|
+
private generateTemplateOptionsText(availableTemplates: {
|
|
486
|
+
architecture: string[];
|
|
487
|
+
requirements: string[];
|
|
488
|
+
design: string[];
|
|
489
|
+
}): string {
|
|
490
|
+
const sections = [];
|
|
491
|
+
|
|
492
|
+
if (availableTemplates.architecture.length > 0) {
|
|
493
|
+
const archOptions = availableTemplates.architecture
|
|
494
|
+
.map(template => {
|
|
495
|
+
const description = this.getTemplateDescription(
|
|
496
|
+
template,
|
|
497
|
+
'architecture'
|
|
498
|
+
);
|
|
499
|
+
return `- **${template}**: ${description}`;
|
|
500
|
+
})
|
|
501
|
+
.join('\n');
|
|
502
|
+
sections.push(`**Architecture Templates:**\n${archOptions}`);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
if (availableTemplates.requirements.length > 0) {
|
|
506
|
+
const reqOptions = availableTemplates.requirements
|
|
507
|
+
.map(template => {
|
|
508
|
+
const description = this.getTemplateDescription(
|
|
509
|
+
template,
|
|
510
|
+
'requirements'
|
|
511
|
+
);
|
|
512
|
+
return `- **${template}**: ${description}`;
|
|
513
|
+
})
|
|
514
|
+
.join('\n');
|
|
515
|
+
sections.push(`**Requirements Templates:**\n${reqOptions}`);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
if (availableTemplates.design.length > 0) {
|
|
519
|
+
const designOptions = availableTemplates.design
|
|
520
|
+
.map(template => {
|
|
521
|
+
const description = this.getTemplateDescription(template, 'design');
|
|
522
|
+
return `- **${template}**: ${description}`;
|
|
523
|
+
})
|
|
524
|
+
.join('\n');
|
|
525
|
+
sections.push(`**Design Templates:**\n${designOptions}`);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
return sections.length > 0
|
|
529
|
+
? `## 📋 **Template Options**\n\n${sections.join('\n\n')}`
|
|
530
|
+
: '';
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Get description for a template based on its name and type
|
|
535
|
+
*/
|
|
536
|
+
private getTemplateDescription(template: string, type: string): string {
|
|
537
|
+
switch (template) {
|
|
538
|
+
case 'arc42':
|
|
539
|
+
return 'Comprehensive software architecture template with diagrams';
|
|
540
|
+
case 'ears':
|
|
541
|
+
return 'WHEN...THEN format for clear, testable requirements';
|
|
542
|
+
case 'comprehensive':
|
|
543
|
+
return 'Full implementation guide with testing strategy';
|
|
544
|
+
case 'freestyle':
|
|
545
|
+
return `Simple, flexible ${type} documentation`;
|
|
546
|
+
default:
|
|
547
|
+
return `${template} format for ${type} documentation`;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Generate workflow documentation URL for predefined workflows
|
|
553
|
+
* Returns undefined for custom workflows
|
|
554
|
+
*/
|
|
555
|
+
private generateWorkflowDocumentationUrl(
|
|
556
|
+
workflowName: string
|
|
557
|
+
): string | undefined {
|
|
558
|
+
// Don't generate URL for custom workflows
|
|
559
|
+
if (workflowName === 'custom') {
|
|
560
|
+
return undefined;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// Generate URL for predefined workflows
|
|
564
|
+
return `https://mrsimpson.github.io/responsible-vibe-mcp/workflows/${workflowName}`;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Get the current git branch for a project
|
|
569
|
+
* Uses the same logic as ConversationManager but locally accessible
|
|
570
|
+
*/
|
|
571
|
+
private getCurrentGitBranch(projectPath: string): string {
|
|
572
|
+
try {
|
|
573
|
+
const { execSync } = require('node:child_process');
|
|
574
|
+
const { existsSync } = require('node:fs');
|
|
575
|
+
|
|
576
|
+
// Check if this is a git repository
|
|
577
|
+
if (!existsSync(`${projectPath}/.git`)) {
|
|
578
|
+
this.logger.debug(
|
|
579
|
+
'Not a git repository, using "default" as branch name',
|
|
580
|
+
{ projectPath }
|
|
581
|
+
);
|
|
582
|
+
return 'default';
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// Get current branch name
|
|
586
|
+
const branch = execSync('git rev-parse --abbrev-ref HEAD', {
|
|
587
|
+
cwd: projectPath,
|
|
588
|
+
encoding: 'utf-8',
|
|
589
|
+
stdio: ['ignore', 'pipe', 'ignore'], // Suppress stderr
|
|
590
|
+
}).trim();
|
|
591
|
+
|
|
592
|
+
this.logger.debug('Detected git branch', { projectPath, branch });
|
|
593
|
+
|
|
594
|
+
return branch;
|
|
595
|
+
} catch (_error) {
|
|
596
|
+
this.logger.debug(
|
|
597
|
+
'Failed to get git branch, using "default" as branch name',
|
|
598
|
+
{ projectPath }
|
|
599
|
+
);
|
|
600
|
+
return 'default';
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* Generate a suggested branch name for feature development
|
|
606
|
+
*/
|
|
607
|
+
private generateBranchSuggestion(): string {
|
|
608
|
+
const timestamp = new Date().toISOString().slice(0, 10).replace(/-/g, '');
|
|
609
|
+
return `feature/development-${timestamp}`;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/**
|
|
613
|
+
* Ensure .gitignore exists in .vibe folder to exclude SQLite files
|
|
614
|
+
* This function is idempotent and self-contained within the .vibe directory
|
|
615
|
+
*/
|
|
616
|
+
private ensureGitignoreEntry(projectPath: string): void {
|
|
617
|
+
try {
|
|
618
|
+
// Check if this is a git repository
|
|
619
|
+
if (!existsSync(`${projectPath}/.git`)) {
|
|
620
|
+
this.logger.debug(
|
|
621
|
+
'Not a git repository, skipping .gitignore management',
|
|
622
|
+
{ projectPath }
|
|
623
|
+
);
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
const vibeDir = resolve(projectPath, '.vibe');
|
|
628
|
+
const gitignorePath = resolve(vibeDir, '.gitignore');
|
|
629
|
+
|
|
630
|
+
// Ensure .vibe directory exists
|
|
631
|
+
if (!existsSync(vibeDir)) {
|
|
632
|
+
mkdirSync(vibeDir, { recursive: true });
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// Content for .vibe/.gitignore
|
|
636
|
+
const gitignoreContent = `# Exclude SQLite database files
|
|
637
|
+
*.sqlite
|
|
638
|
+
*.sqlite-*
|
|
639
|
+
conversation-state.sqlite*
|
|
640
|
+
`;
|
|
641
|
+
|
|
642
|
+
// Check if .gitignore already exists and has the right content
|
|
643
|
+
if (existsSync(gitignorePath)) {
|
|
644
|
+
try {
|
|
645
|
+
const existingContent = readFileSync(gitignorePath, 'utf-8');
|
|
646
|
+
if (
|
|
647
|
+
existingContent.includes('*.sqlite') &&
|
|
648
|
+
existingContent.includes('conversation-state.sqlite')
|
|
649
|
+
) {
|
|
650
|
+
this.logger.debug(
|
|
651
|
+
'.vibe/.gitignore already exists with SQLite exclusions',
|
|
652
|
+
{ gitignorePath }
|
|
653
|
+
);
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
} catch (error) {
|
|
657
|
+
this.logger.warn(
|
|
658
|
+
'Failed to read existing .vibe/.gitignore, will recreate',
|
|
659
|
+
{
|
|
660
|
+
gitignorePath,
|
|
661
|
+
error: error instanceof Error ? error.message : String(error),
|
|
662
|
+
}
|
|
663
|
+
);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// Write the .gitignore file
|
|
668
|
+
writeFileSync(gitignorePath, gitignoreContent, 'utf-8');
|
|
669
|
+
|
|
670
|
+
this.logger.info('Created .vibe/.gitignore to exclude SQLite files', {
|
|
671
|
+
projectPath,
|
|
672
|
+
gitignorePath,
|
|
673
|
+
});
|
|
674
|
+
} catch (error) {
|
|
675
|
+
// Log warning but don't fail development start
|
|
676
|
+
this.logger.warn(
|
|
677
|
+
'Failed to create .vibe/.gitignore, continuing with development start',
|
|
678
|
+
{
|
|
679
|
+
projectPath,
|
|
680
|
+
error: error instanceof Error ? error.message : String(error),
|
|
681
|
+
}
|
|
682
|
+
);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}
|