@codemcp/workflows 4.6.1 → 4.8.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.
Files changed (52) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/components/beads/beads-instruction-generator.d.ts +48 -0
  3. package/dist/components/beads/beads-instruction-generator.d.ts.map +1 -0
  4. package/dist/components/beads/beads-instruction-generator.js +182 -0
  5. package/dist/components/beads/beads-instruction-generator.js.map +1 -0
  6. package/dist/components/beads/beads-plan-manager.d.ts +66 -0
  7. package/dist/components/beads/beads-plan-manager.d.ts.map +1 -0
  8. package/dist/components/beads/beads-plan-manager.js +288 -0
  9. package/dist/components/beads/beads-plan-manager.js.map +1 -0
  10. package/dist/components/beads/beads-task-backend-client.d.ts +43 -0
  11. package/dist/components/beads/beads-task-backend-client.d.ts.map +1 -0
  12. package/dist/components/beads/beads-task-backend-client.js +178 -0
  13. package/dist/components/beads/beads-task-backend-client.js.map +1 -0
  14. package/dist/components/server-components-factory.d.ts +39 -0
  15. package/dist/components/server-components-factory.d.ts.map +1 -0
  16. package/dist/components/server-components-factory.js +62 -0
  17. package/dist/components/server-components-factory.js.map +1 -0
  18. package/dist/server-config.d.ts.map +1 -1
  19. package/dist/server-config.js +8 -4
  20. package/dist/server-config.js.map +1 -1
  21. package/dist/server-implementation.d.ts +1 -1
  22. package/dist/tool-handlers/proceed-to-phase.d.ts +5 -0
  23. package/dist/tool-handlers/proceed-to-phase.d.ts.map +1 -1
  24. package/dist/tool-handlers/proceed-to-phase.js +95 -0
  25. package/dist/tool-handlers/proceed-to-phase.js.map +1 -1
  26. package/dist/tool-handlers/start-development.d.ts.map +1 -1
  27. package/dist/tool-handlers/start-development.js +9 -3
  28. package/dist/tool-handlers/start-development.js.map +1 -1
  29. package/dist/tool-handlers/whats-next.d.ts +0 -12
  30. package/dist/tool-handlers/whats-next.d.ts.map +1 -1
  31. package/dist/tool-handlers/whats-next.js +1 -88
  32. package/dist/tool-handlers/whats-next.js.map +1 -1
  33. package/dist/types.d.ts +7 -4
  34. package/dist/types.d.ts.map +1 -1
  35. package/package.json +2 -2
  36. package/src/components/beads/beads-instruction-generator.ts +261 -0
  37. package/src/components/beads/beads-plan-manager.ts +358 -0
  38. package/src/components/beads/beads-task-backend-client.ts +232 -0
  39. package/src/components/server-components-factory.ts +86 -0
  40. package/src/server-config.ts +9 -4
  41. package/src/tool-handlers/proceed-to-phase.ts +140 -0
  42. package/src/tool-handlers/start-development.ts +17 -3
  43. package/src/tool-handlers/whats-next.ts +4 -117
  44. package/src/types.ts +7 -4
  45. package/test/e2e/component-substitution.test.ts +208 -0
  46. package/test/unit/beads-instruction-generator.test.ts +847 -0
  47. package/test/unit/beads-phase-task-id-integration.test.ts +557 -0
  48. package/test/unit/server-components-factory.test.ts +279 -0
  49. package/test/unit/setup-project-docs-handler.test.ts +3 -2
  50. package/test/utils/e2e-test-setup.ts +0 -1
  51. package/test/utils/temp-files.ts +12 -0
  52. package/tsconfig.build.tsbuildinfo +1 -1
@@ -0,0 +1,279 @@
1
+ /**
2
+ * ServerComponentsFactory Tests
3
+ *
4
+ * Tests the factory pattern implementation for component creation
5
+ * based on task backend configuration.
6
+ */
7
+
8
+ import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
9
+ import { ServerComponentsFactory } from '../../src/components/server-components-factory.js';
10
+ import {
11
+ TaskBackendManager,
12
+ type TaskBackendConfig,
13
+ } from '@codemcp/workflows-core';
14
+
15
+ // Mock TaskBackendManager
16
+ vi.mock('@codemcp/workflows-core', async () => {
17
+ const actual = await vi.importActual('@codemcp/workflows-core');
18
+ return {
19
+ ...actual,
20
+ TaskBackendManager: {
21
+ detectTaskBackend: vi.fn(),
22
+ },
23
+ };
24
+ });
25
+
26
+ describe('ServerComponentsFactory', () => {
27
+ let factory: ServerComponentsFactory;
28
+ let mockDetectTaskBackend: ReturnType<typeof vi.fn>;
29
+
30
+ beforeEach(() => {
31
+ mockDetectTaskBackend = vi.mocked(TaskBackendManager.detectTaskBackend);
32
+ // Default to markdown backend
33
+ mockDetectTaskBackend.mockReturnValue({
34
+ backend: 'markdown',
35
+ isAvailable: true,
36
+ });
37
+ });
38
+
39
+ afterEach(() => {
40
+ vi.clearAllMocks();
41
+ });
42
+
43
+ describe('Constructor', () => {
44
+ it('should use TaskBackendManager.detectTaskBackend() when no options provided', () => {
45
+ factory = new ServerComponentsFactory();
46
+
47
+ expect(mockDetectTaskBackend).toHaveBeenCalled();
48
+ });
49
+
50
+ it('should use provided task backend configuration', () => {
51
+ const customBackend: TaskBackendConfig = {
52
+ backend: 'beads',
53
+ isAvailable: true,
54
+ };
55
+
56
+ factory = new ServerComponentsFactory({ taskBackend: customBackend });
57
+
58
+ // Should not call detectTaskBackend when explicit config provided
59
+ expect(mockDetectTaskBackend).not.toHaveBeenCalled();
60
+ });
61
+
62
+ it('should handle undefined options gracefully', () => {
63
+ factory = new ServerComponentsFactory(undefined);
64
+
65
+ expect(mockDetectTaskBackend).toHaveBeenCalled();
66
+ });
67
+ });
68
+
69
+ describe('createPlanManager', () => {
70
+ it('should create default PlanManager for markdown backend', () => {
71
+ mockDetectTaskBackend.mockReturnValue({
72
+ backend: 'markdown',
73
+ isAvailable: true,
74
+ });
75
+
76
+ factory = new ServerComponentsFactory();
77
+ const planManager = factory.createPlanManager();
78
+
79
+ expect(planManager).toBeDefined();
80
+ expect(planManager.constructor.name).toBe('PlanManager');
81
+ });
82
+
83
+ it('should create default PlanManager when beads backend unavailable', () => {
84
+ mockDetectTaskBackend.mockReturnValue({
85
+ backend: 'beads',
86
+ isAvailable: false,
87
+ });
88
+
89
+ factory = new ServerComponentsFactory();
90
+ const planManager = factory.createPlanManager();
91
+
92
+ expect(planManager).toBeDefined();
93
+ expect(planManager.constructor.name).toBe('PlanManager');
94
+ });
95
+
96
+ it('should create BeadsPlanManager for beads backend when available', () => {
97
+ // Now that BeadsPlanManager is implemented, test that it's created correctly
98
+ const beadsBackend: TaskBackendConfig = {
99
+ backend: 'beads',
100
+ isAvailable: true,
101
+ };
102
+
103
+ factory = new ServerComponentsFactory({ taskBackend: beadsBackend });
104
+ const planManager = factory.createPlanManager();
105
+
106
+ expect(planManager).toBeDefined();
107
+ expect(planManager.constructor.name).toBe('BeadsPlanManager');
108
+ });
109
+
110
+ it('should create consistent instances across multiple calls', () => {
111
+ factory = new ServerComponentsFactory();
112
+
113
+ const planManager1 = factory.createPlanManager();
114
+ const planManager2 = factory.createPlanManager();
115
+
116
+ // Should create new instances (not singletons)
117
+ expect(planManager1).not.toBe(planManager2);
118
+ expect(planManager1.constructor.name).toBe(planManager2.constructor.name);
119
+ });
120
+ });
121
+
122
+ describe('createInstructionGenerator', () => {
123
+ it('should create default InstructionGenerator for markdown backend', () => {
124
+ mockDetectTaskBackend.mockReturnValue({
125
+ backend: 'markdown',
126
+ isAvailable: true,
127
+ });
128
+
129
+ factory = new ServerComponentsFactory();
130
+ const instructionGenerator = factory.createInstructionGenerator();
131
+
132
+ expect(instructionGenerator).toBeDefined();
133
+ expect(instructionGenerator.constructor.name).toBe(
134
+ 'InstructionGenerator'
135
+ );
136
+ });
137
+
138
+ it('should create default InstructionGenerator when beads backend unavailable', () => {
139
+ mockDetectTaskBackend.mockReturnValue({
140
+ backend: 'beads',
141
+ isAvailable: false,
142
+ });
143
+
144
+ factory = new ServerComponentsFactory();
145
+ const instructionGenerator = factory.createInstructionGenerator();
146
+
147
+ expect(instructionGenerator).toBeDefined();
148
+ expect(instructionGenerator.constructor.name).toBe(
149
+ 'InstructionGenerator'
150
+ );
151
+ });
152
+
153
+ it('should create BeadsInstructionGenerator for beads backend when available', () => {
154
+ // Now that BeadsInstructionGenerator is implemented, test that it's created correctly
155
+ const beadsBackend: TaskBackendConfig = {
156
+ backend: 'beads',
157
+ isAvailable: true,
158
+ };
159
+
160
+ factory = new ServerComponentsFactory({ taskBackend: beadsBackend });
161
+ const instructionGenerator = factory.createInstructionGenerator();
162
+
163
+ expect(instructionGenerator).toBeDefined();
164
+ expect(instructionGenerator.constructor.name).toBe(
165
+ 'BeadsInstructionGenerator'
166
+ );
167
+ });
168
+
169
+ it('should create InstructionGenerator with proper dependency injection', () => {
170
+ factory = new ServerComponentsFactory();
171
+ const instructionGenerator = factory.createInstructionGenerator();
172
+
173
+ expect(instructionGenerator).toBeDefined();
174
+ expect(instructionGenerator.constructor.name).toBe(
175
+ 'InstructionGenerator'
176
+ );
177
+
178
+ // Verify the instance is functional (dependency was injected correctly)
179
+ // without checking internal implementation details
180
+ expect(typeof instructionGenerator.generateInstructions).toBe('function');
181
+ });
182
+
183
+ it('should create consistent instances across multiple calls', () => {
184
+ factory = new ServerComponentsFactory();
185
+
186
+ const instructionGenerator1 = factory.createInstructionGenerator();
187
+ const instructionGenerator2 = factory.createInstructionGenerator();
188
+
189
+ // Should create new instances (not singletons)
190
+ expect(instructionGenerator1).not.toBe(instructionGenerator2);
191
+ expect(instructionGenerator1.constructor.name).toBe(
192
+ instructionGenerator2.constructor.name
193
+ );
194
+ });
195
+ });
196
+
197
+ describe('getTaskBackend', () => {
198
+ it('should return the current task backend configuration', () => {
199
+ const customBackend: TaskBackendConfig = {
200
+ backend: 'beads',
201
+ isAvailable: true,
202
+ };
203
+
204
+ factory = new ServerComponentsFactory({ taskBackend: customBackend });
205
+ const taskBackend = factory.getTaskBackend();
206
+
207
+ expect(taskBackend).toEqual(customBackend);
208
+ });
209
+
210
+ it('should return detected task backend when no explicit config provided', () => {
211
+ const detectedBackend: TaskBackendConfig = {
212
+ backend: 'markdown',
213
+ isAvailable: true,
214
+ };
215
+ mockDetectTaskBackend.mockReturnValue(detectedBackend);
216
+
217
+ factory = new ServerComponentsFactory();
218
+ const taskBackend = factory.getTaskBackend();
219
+
220
+ expect(taskBackend).toEqual(detectedBackend);
221
+ });
222
+ });
223
+
224
+ describe('Backend Integration', () => {
225
+ it('should handle unknown backend types gracefully', () => {
226
+ const unknownBackend: TaskBackendConfig = {
227
+ backend: 'beads' as TaskBackendConfig['backend'],
228
+ isAvailable: true,
229
+ };
230
+ // Simulate an unknown backend type for testing
231
+ (unknownBackend as unknown as { backend: string }).backend =
232
+ 'unknown-backend';
233
+
234
+ factory = new ServerComponentsFactory({ taskBackend: unknownBackend });
235
+
236
+ // Should fall back to default implementations
237
+ const planManager = factory.createPlanManager();
238
+ const instructionGenerator = factory.createInstructionGenerator();
239
+
240
+ expect(planManager.constructor.name).toBe('PlanManager');
241
+ expect(instructionGenerator.constructor.name).toBe(
242
+ 'InstructionGenerator'
243
+ );
244
+ });
245
+
246
+ it('should handle backend availability flag correctly', () => {
247
+ const unavailableBackend: TaskBackendConfig = {
248
+ backend: 'beads',
249
+ isAvailable: false,
250
+ };
251
+
252
+ factory = new ServerComponentsFactory({
253
+ taskBackend: unavailableBackend,
254
+ });
255
+
256
+ // Should fall back to default implementations when backend unavailable
257
+ const planManager = factory.createPlanManager();
258
+ const instructionGenerator = factory.createInstructionGenerator();
259
+
260
+ expect(planManager.constructor.name).toBe('PlanManager');
261
+ expect(instructionGenerator.constructor.name).toBe(
262
+ 'InstructionGenerator'
263
+ );
264
+ });
265
+ });
266
+
267
+ describe('Future Extensibility', () => {
268
+ // These tests document the expected behavior once beads implementations are created
269
+ it.todo(
270
+ 'should create BeadsPlanManager when beads backend is available and implemented'
271
+ );
272
+ it.todo(
273
+ 'should create BeadsInstructionGenerator when beads backend is available and implemented'
274
+ );
275
+ it.todo(
276
+ 'should support additional backends (github, linear) through same factory pattern'
277
+ );
278
+ });
279
+ });
@@ -20,7 +20,8 @@ import { TemplateManager } from '@codemcp/workflows-core';
20
20
  import { ServerContext } from '../../src/types';
21
21
  import { join } from 'node:path';
22
22
  import { tmpdir } from 'node:os';
23
- import { mkdir, rmdir } from 'node:fs/promises';
23
+ import { mkdir } from 'node:fs/promises';
24
+ import { cleanupDirectory } from '../utils/temp-files.js';
24
25
 
25
26
  // Mock ProjectDocsManager and TemplateManager
26
27
  vi.mock('../../src/project-docs-manager.js');
@@ -72,7 +73,7 @@ describe('SetupProjectDocsHandler', () => {
72
73
  afterEach(async () => {
73
74
  // Clean up test directory
74
75
  try {
75
- await rmdir(testProjectPath, { recursive: true });
76
+ await cleanupDirectory(testProjectPath);
76
77
  } catch {
77
78
  // Ignore cleanup errors
78
79
  }
@@ -17,7 +17,6 @@ import { join } from 'node:path';
17
17
  import { tmpdir } from 'node:os';
18
18
  import { mkdirSync, rmSync, existsSync } from 'node:fs';
19
19
  import type { ServerContext } from '../../src/types';
20
- import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
21
20
 
22
21
  // Disable fs mocking for E2E tests
23
22
  vi.unmock('fs');
@@ -6,6 +6,7 @@
6
6
  */
7
7
 
8
8
  import { mkdirSync, writeFileSync, rmSync, existsSync } from 'node:fs';
9
+ import { rm } from 'node:fs/promises';
9
10
  import { join } from 'node:path';
10
11
  import { tmpdir } from 'node:os';
11
12
 
@@ -306,3 +307,14 @@ export function safeParseServerResponse(
306
307
  );
307
308
  }
308
309
  }
310
+
311
+ /**
312
+ * Async helper to clean up a directory
313
+ * Uses fs.rm which is the Node.js 14.14+ recommended approach
314
+ * @param dirPath - Path to directory to remove
315
+ */
316
+ export async function cleanupDirectory(dirPath: string): Promise<void> {
317
+ if (existsSync(dirPath)) {
318
+ await rm(dirPath, { recursive: true, force: true });
319
+ }
320
+ }