@codemcp/workflows 5.0.1 → 5.1.1

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