@codemcp/workflows 4.9.1 → 4.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -220,7 +220,8 @@ export class StartDevelopmentHandler extends BaseToolHandler<
220
220
  stateMachine,
221
221
  selectedWorkflow,
222
222
  conversationContext.planFilePath,
223
- conversationContext.conversationId
223
+ conversationContext.conversationId,
224
+ context
224
225
  );
225
226
  }
226
227
 
@@ -653,23 +654,41 @@ ${templateOptionsText}
653
654
  stateMachine: YamlStateMachine,
654
655
  workflowName: string,
655
656
  planFilePath: string,
656
- conversationId: string
657
+ conversationId: string,
658
+ context: ServerContext
657
659
  ): Promise<void> {
658
660
  try {
659
661
  const beadsIntegration = new BeadsIntegration(projectPath);
660
662
  const projectName = projectPath.split('/').pop() || 'Unknown Project';
661
663
 
664
+ // Extract goal from plan file if it exists and has meaningful content
665
+ let goalDescription: string | undefined;
666
+ try {
667
+ const planFileContent =
668
+ await context.planManager.getPlanFileContent(planFilePath);
669
+ goalDescription = this.extractGoalFromPlan(planFileContent);
670
+ } catch (error) {
671
+ this.logger.warn('Could not extract goal from plan file', {
672
+ error: error instanceof Error ? error.message : String(error),
673
+ planFilePath,
674
+ });
675
+ }
676
+
677
+ // Extract plan filename for use in epic title
678
+ const planFilename = planFilePath.split('/').pop();
679
+
662
680
  // Create project epic
663
681
  const epicId = await beadsIntegration.createProjectEpic(
664
682
  projectName,
665
- workflowName
683
+ workflowName,
684
+ goalDescription,
685
+ planFilename
666
686
  );
667
687
 
668
688
  // Create phase tasks for all workflow phases
669
- const phases = Object.keys(stateMachine.states);
670
689
  const phaseTasks = await beadsIntegration.createPhaseTasks(
671
690
  epicId,
672
- phases,
691
+ stateMachine.states,
673
692
  workflowName
674
693
  );
675
694
 
@@ -841,4 +860,55 @@ conversations/
841
860
  );
842
861
  }
843
862
  }
863
+
864
+ /**
865
+ * Extract Goal section content from plan file
866
+ * Returns the goal content if it exists and is meaningful, otherwise undefined
867
+ */
868
+ private extractGoalFromPlan(planContent: string): string | undefined {
869
+ if (!planContent || typeof planContent !== 'string') {
870
+ return undefined;
871
+ }
872
+
873
+ // Split content into lines for more reliable parsing
874
+ const lines = planContent.split('\n');
875
+ const goalIndex = lines.findIndex(line => line.trim() === '## Goal');
876
+
877
+ if (goalIndex === -1) {
878
+ return undefined;
879
+ }
880
+
881
+ // Find the next section (## anything) after the Goal section
882
+ const nextSectionIndex = lines.findIndex(
883
+ (line, index) => index > goalIndex && line.trim().startsWith('## ')
884
+ );
885
+
886
+ // Extract content between Goal and next section (or end of content)
887
+ const contentLines =
888
+ nextSectionIndex === -1
889
+ ? lines.slice(goalIndex + 1)
890
+ : lines.slice(goalIndex + 1, nextSectionIndex);
891
+
892
+ const goalContent = contentLines.join('\n').trim();
893
+
894
+ // Check if the goal content is meaningful (not just a placeholder or comment)
895
+ const meaninglessPatterns = [
896
+ /^\*.*\*$/, // Enclosed in asterisks like "*Define what you're building...*"
897
+ /^To be defined/i,
898
+ /^TBD$/i,
899
+ /^TODO/i,
900
+ /^Define what you're building/i,
901
+ /^This will be updated/i,
902
+ ];
903
+
904
+ const isMeaningless = meaninglessPatterns.some(pattern =>
905
+ pattern.test(goalContent)
906
+ );
907
+
908
+ if (isMeaningless || goalContent.length < 10) {
909
+ return undefined;
910
+ }
911
+
912
+ return goalContent;
913
+ }
844
914
  }
@@ -62,7 +62,7 @@ describe('BeadsInstructionGenerator Content Validation', () => {
62
62
  'bd list --parent <phase-task-id> --status open'
63
63
  );
64
64
  expect(result.instructions).toContain(
65
- "bd create 'Task title' --parent <phase-task-id> -p 2"
65
+ "bd create 'Task title' --parent <phase-task-id> -p <priority>"
66
66
  );
67
67
  expect(result.instructions).toContain(
68
68
  'bd update <task-id> --status in_progress'
@@ -643,12 +643,12 @@ describe('BeadsInstructionGenerator Content Validation', () => {
643
643
  - Analyze existing markdown-based systems
644
644
  - Review plan file management approaches
645
645
  - Document [x] checkbox patterns
646
-
646
+
647
647
  ### Layer 2: Implementation
648
648
  - Implement beads CLI integration
649
649
  - Create hierarchical task structures
650
650
  - Build robust error handling
651
-
651
+
652
652
  ### Layer 3: Validation
653
653
  - Test cross-backend compatibility
654
654
  - Verify anti-contamination measures
@@ -689,7 +689,7 @@ describe('BeadsInstructionGenerator Content Validation', () => {
689
689
  - Has bd CLI-like commands but for different purposes
690
690
  - Manages plan files with markdown syntax
691
691
  - Creates beads-style hierarchical structures
692
-
692
+
693
693
  This should test anti-contamination thoroughly.
694
694
  `.trim();
695
695
 
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Unit tests for BeadsIntegration epic title formatting with filename
3
+ *
4
+ * Tests that epic titles include plan filenames correctly
5
+ */
6
+
7
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
8
+ import { BeadsIntegration } from '@codemcp/workflows-core';
9
+
10
+ // Mock execSync to avoid actual beads CLI calls
11
+ vi.mock('node:child_process', () => ({
12
+ execSync: vi.fn().mockReturnValue('✓ Created issue: test-epic-123'),
13
+ }));
14
+
15
+ describe('BeadsIntegration - Epic Title with Filename', () => {
16
+ let beadsIntegration: BeadsIntegration;
17
+
18
+ beforeEach(() => {
19
+ beadsIntegration = new BeadsIntegration('/test/project');
20
+ });
21
+
22
+ it('should include filename in epic title when provided', async () => {
23
+ const { execSync } = await import('node:child_process');
24
+ const mockExecSync = vi.mocked(execSync);
25
+
26
+ await beadsIntegration.createProjectEpic(
27
+ 'my-project',
28
+ 'epcc',
29
+ 'Build an awesome feature',
30
+ 'development-plan-feature-auth.md'
31
+ );
32
+
33
+ // Verify that execSync was called with the expected title format
34
+ expect(mockExecSync).toHaveBeenCalledWith(
35
+ expect.stringContaining(
36
+ 'bd create "my-project: epcc (development-plan-feature-auth.md)"'
37
+ ),
38
+ expect.any(Object)
39
+ );
40
+ });
41
+
42
+ it('should use original title format when filename not provided', async () => {
43
+ const { execSync } = await import('node:child_process');
44
+ const mockExecSync = vi.mocked(execSync);
45
+
46
+ await beadsIntegration.createProjectEpic(
47
+ 'my-project',
48
+ 'epcc',
49
+ 'Build an awesome feature'
50
+ );
51
+
52
+ // Verify that execSync was called with the original title format
53
+ expect(mockExecSync).toHaveBeenCalledWith(
54
+ expect.stringContaining('bd create "my-project: epcc"'),
55
+ expect.any(Object)
56
+ );
57
+
58
+ // Ensure it doesn't contain parentheses when no filename
59
+ const call = mockExecSync.mock.calls[0][0] as string;
60
+ expect(call).not.toContain('(');
61
+ expect(call).not.toContain(')');
62
+ });
63
+
64
+ it('should handle various filename formats', async () => {
65
+ const { execSync } = await import('node:child_process');
66
+ const mockExecSync = vi.mocked(execSync);
67
+
68
+ const testCases = [
69
+ 'development-plan-main.md',
70
+ 'development-plan-feature-dashboard.md',
71
+ 'development-plan-bugfix-123.md',
72
+ 'plan.md',
73
+ ];
74
+
75
+ for (const filename of testCases) {
76
+ mockExecSync.mockClear();
77
+
78
+ await beadsIntegration.createProjectEpic(
79
+ 'test-project',
80
+ 'waterfall',
81
+ undefined,
82
+ filename
83
+ );
84
+
85
+ expect(mockExecSync).toHaveBeenCalledWith(
86
+ expect.stringContaining(
87
+ `bd create "test-project: waterfall (${filename})"`
88
+ ),
89
+ expect.any(Object)
90
+ );
91
+ }
92
+ });
93
+ });
@@ -87,7 +87,7 @@ Some implementation tasks here.
87
87
  'bd list --parent project-epic-1.2 --status open'
88
88
  );
89
89
  expect(result.instructions).toContain(
90
- "bd create 'Task description' --parent project-epic-1.2 -p 2"
90
+ "bd create 'Task description' --parent project-epic-1.2"
91
91
  );
92
92
  expect(result.instructions).toContain('bd show project-epic-1.2');
93
93
  expect(result.instructions).toContain(
@@ -127,7 +127,7 @@ Some implementation tasks here.
127
127
  result.instructions,
128
128
  `Should use ID in create command: ${testCase.id}`
129
129
  ).toContain(
130
- `bd create 'Task description' --parent ${testCase.id} -p 2`
130
+ `bd create 'Task description' --parent ${testCase.id} -p <priority>`
131
131
  );
132
132
  }
133
133
  });
@@ -176,7 +176,7 @@ Some implementation tasks here.
176
176
  - Research requirements
177
177
  - Analyze existing solutions
178
178
 
179
- ## Design
179
+ ## Design
180
180
  <!-- beads-phase-id: design-task-2 -->
181
181
  - Create system design
182
182
  - Design database schema
@@ -236,7 +236,7 @@ Some implementation tasks here.
236
236
  'bd list --parent <phase-task-id> --status open'
237
237
  );
238
238
  expect(result.instructions).toContain(
239
- "bd create 'Task title' --parent <phase-task-id> -p 2"
239
+ "bd create 'Task title' --parent <phase-task-id> -p <priority>"
240
240
  );
241
241
  expect(result.instructions).toContain('Use bd CLI tool exclusively');
242
242
  expect(result.instructions).not.toContain('bd list --parent design-');
@@ -373,7 +373,7 @@ ${edgeCase.comment}
373
373
  // Check all BD CLI commands contain the extracted ID
374
374
  const expectedCommands = [
375
375
  'bd list --parent design-epic-789 --status open',
376
- "bd create 'Task description' --parent design-epic-789 -p 2",
376
+ "bd create 'Task description' --parent design-epic-789",
377
377
  'bd show design-epic-789',
378
378
  ];
379
379
 
@@ -0,0 +1,220 @@
1
+ /**
2
+ * Unit tests for StartDevelopmentHandler Goal extraction functionality
3
+ *
4
+ * Tests the extractGoalFromPlan method that extracts meaningful goal content
5
+ * from development plan files for use in beads integration
6
+ */
7
+
8
+ import { describe, it, expect, beforeEach } from 'vitest';
9
+ import { TestAccess } from '../utils/test-access.js';
10
+ import { StartDevelopmentHandler } from '../../src/tool-handlers/start-development.js';
11
+
12
+ describe('StartDevelopmentHandler - Goal Extraction', () => {
13
+ let handler: StartDevelopmentHandler;
14
+
15
+ beforeEach(() => {
16
+ handler = new StartDevelopmentHandler();
17
+ });
18
+
19
+ describe('extractGoalFromPlan', () => {
20
+ it('should extract meaningful goal content', () => {
21
+ const planContent = `# Development Plan: Test Project
22
+
23
+ ## Goal
24
+ Build a user authentication system with JWT tokens and password reset functionality.
25
+
26
+ ## Explore
27
+ ### Tasks
28
+ - [ ] Analyze requirements
29
+ `;
30
+
31
+ const result = TestAccess.callMethod(
32
+ handler,
33
+ 'extractGoalFromPlan',
34
+ planContent
35
+ );
36
+
37
+ expect(result).toBe(
38
+ 'Build a user authentication system with JWT tokens and password reset functionality.'
39
+ );
40
+ });
41
+
42
+ it('should return undefined for placeholder goal content', () => {
43
+ const planContent = `# Development Plan: Test Project
44
+
45
+ ## Goal
46
+ *Define what you're building or fixing - this will be updated as requirements are gathered*
47
+
48
+ ## Explore
49
+ ### Tasks
50
+ - [ ] Analyze requirements
51
+ `;
52
+
53
+ const result = TestAccess.callMethod(
54
+ handler,
55
+ 'extractGoalFromPlan',
56
+ planContent
57
+ );
58
+
59
+ expect(result).toBeUndefined();
60
+ });
61
+
62
+ it('should return undefined for "To be defined" content', () => {
63
+ const planContent = `# Development Plan: Test Project
64
+
65
+ ## Goal
66
+ To be defined during exploration
67
+
68
+ ## Explore
69
+ ### Tasks
70
+ - [ ] Analyze requirements
71
+ `;
72
+
73
+ const result = TestAccess.callMethod(
74
+ handler,
75
+ 'extractGoalFromPlan',
76
+ planContent
77
+ );
78
+
79
+ expect(result).toBeUndefined();
80
+ });
81
+
82
+ it('should return undefined for very short content', () => {
83
+ const planContent = `# Development Plan: Test Project
84
+
85
+ ## Goal
86
+ Fix bug
87
+
88
+ ## Explore
89
+ ### Tasks
90
+ - [ ] Analyze requirements
91
+ `;
92
+
93
+ const result = TestAccess.callMethod(
94
+ handler,
95
+ 'extractGoalFromPlan',
96
+ planContent
97
+ );
98
+
99
+ expect(result).toBeUndefined();
100
+ });
101
+
102
+ it('should handle multiline goal content correctly', () => {
103
+ const planContent = `# Development Plan: Test Project
104
+
105
+ ## Goal
106
+ Implement a comprehensive logging system that captures:
107
+ - User actions and authentication events
108
+ - API request/response cycles
109
+ - System errors with stack traces
110
+ - Performance metrics
111
+
112
+ The system should support different log levels and output formats.
113
+
114
+ ## Explore
115
+ ### Tasks
116
+ - [ ] Analyze requirements
117
+ `;
118
+
119
+ const result = TestAccess.callMethod(
120
+ handler,
121
+ 'extractGoalFromPlan',
122
+ planContent
123
+ );
124
+
125
+ expect(result)
126
+ .toBe(`Implement a comprehensive logging system that captures:
127
+ - User actions and authentication events
128
+ - API request/response cycles
129
+ - System errors with stack traces
130
+ - Performance metrics
131
+
132
+ The system should support different log levels and output formats.`);
133
+ });
134
+
135
+ it('should return undefined when no Goal section exists', () => {
136
+ const planContent = `# Development Plan: Test Project
137
+
138
+ ## Explore
139
+ ### Tasks
140
+ - [ ] Analyze requirements
141
+ `;
142
+
143
+ const result = TestAccess.callMethod(
144
+ handler,
145
+ 'extractGoalFromPlan',
146
+ planContent
147
+ );
148
+
149
+ expect(result).toBeUndefined();
150
+ });
151
+
152
+ it('should return undefined for empty or null input', () => {
153
+ expect(
154
+ TestAccess.callMethod(handler, 'extractGoalFromPlan', '')
155
+ ).toBeUndefined();
156
+
157
+ expect(
158
+ TestAccess.callMethod(handler, 'extractGoalFromPlan', null)
159
+ ).toBeUndefined();
160
+
161
+ expect(
162
+ TestAccess.callMethod(handler, 'extractGoalFromPlan', undefined)
163
+ ).toBeUndefined();
164
+ });
165
+
166
+ it('should stop at the next section boundary', () => {
167
+ const planContent = `# Development Plan: Test Project
168
+
169
+ ## Goal
170
+ Build a user authentication system with secure login and registration.
171
+
172
+ ## Key Decisions
173
+ - Using JWT for token-based authentication
174
+ - Password hashing with bcrypt
175
+ `;
176
+
177
+ const result = TestAccess.callMethod(
178
+ handler,
179
+ 'extractGoalFromPlan',
180
+ planContent
181
+ );
182
+
183
+ expect(result).toBe(
184
+ 'Build a user authentication system with secure login and registration.'
185
+ );
186
+ });
187
+ });
188
+
189
+ describe('plan filename extraction', () => {
190
+ it('should extract filename from plan file path correctly', () => {
191
+ // Test the logic used in setupBeadsIntegration
192
+ const planFilePath = '/project/.vibe/development-plan-feature-auth.md';
193
+ const planFilename = planFilePath.split('/').pop();
194
+
195
+ expect(planFilename).toBe('development-plan-feature-auth.md');
196
+ });
197
+
198
+ it('should handle various plan file path formats', () => {
199
+ const testCases = [
200
+ {
201
+ path: '/Users/dev/my-project/.vibe/development-plan-main.md',
202
+ expected: 'development-plan-main.md',
203
+ },
204
+ {
205
+ path: 'development-plan-bugfix.md',
206
+ expected: 'development-plan-bugfix.md',
207
+ },
208
+ {
209
+ path: '/deep/nested/path/to/.vibe/development-plan-feature-dashboard.md',
210
+ expected: 'development-plan-feature-dashboard.md',
211
+ },
212
+ ];
213
+
214
+ for (const { path, expected } of testCases) {
215
+ const filename = path.split('/').pop();
216
+ expect(filename).toBe(expected);
217
+ }
218
+ });
219
+ });
220
+ });