@codemcp/workflows 5.0.1 → 5.1.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 (77) hide show
  1. package/SKILL.md +23 -0
  2. package/package.json +6 -2
  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,545 +0,0 @@
1
- /**
2
- * Comprehensive Behavioral Tests for BeadsPlugin
3
- *
4
- * Tests validate:
5
- * - Actual beads task creation and management
6
- * - User experience preservation (same inputs → same outputs)
7
- * - Plan file enhancement with task IDs
8
- * - Error handling and graceful degradation
9
- * - Integration between plugin hooks and beads backend
10
- */
11
-
12
- import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
13
- import { mkdir, writeFile, readFile, rm } from 'node:fs/promises';
14
- import { existsSync } from 'node:fs';
15
- import { join } from 'node:path';
16
- import { tmpdir } from 'node:os';
17
- import { execSync } from 'node:child_process';
18
- import type { PluginHookContext } from '../../src/plugin-system/plugin-interfaces.js';
19
- import { TaskBackendManager } from '@codemcp/workflows-core';
20
-
21
- // Mock child_process to intercept beads commands
22
- vi.mock('node:child_process', () => ({
23
- execSync: vi.fn(),
24
- }));
25
-
26
- // Mock TaskBackendManager to control beads detection in unit tests
27
- vi.mock('@codemcp/workflows-core', async importOriginal => {
28
- const original =
29
- await importOriginal<typeof import('@codemcp/workflows-core')>();
30
- return {
31
- ...original,
32
- TaskBackendManager: {
33
- ...original.TaskBackendManager,
34
- detectTaskBackend: vi.fn(),
35
- },
36
- };
37
- });
38
-
39
- import { BeadsPlugin } from '../../src/plugin-system/beads-plugin.js';
40
-
41
- describe('BeadsPlugin - Comprehensive Behavioral Tests', () => {
42
- let testProjectPath: string;
43
- let testPlanFilePath: string;
44
-
45
- const createPlanFileContent = () => `# Development Plan
46
-
47
- ## Goal
48
- Build a comprehensive feature for task management with beads integration
49
-
50
- ## Explore
51
- <!-- beads-phase-id: TBD -->
52
- Research existing implementation
53
-
54
- ## Plan
55
- <!-- beads-phase-id: TBD -->
56
- Design the solution
57
-
58
- ## Code
59
- <!-- beads-phase-id: TBD -->
60
- Implement the feature
61
-
62
- ## Test
63
- <!-- beads-phase-id: TBD -->
64
- Test all functionality`;
65
-
66
- const createMockContext = (overrides: Record<string, unknown> = {}) =>
67
- ({
68
- conversationId: 'test-conversation-123',
69
- planFilePath: testPlanFilePath,
70
- currentPhase: 'explore',
71
- workflow: 'epcc',
72
- projectPath: testProjectPath,
73
- gitBranch: 'feature/test-branch',
74
- stateMachine: {
75
- name: 'epcc',
76
- description: 'Explore Plan Code Commit workflow',
77
- initial_state: 'explore',
78
- states: {
79
- explore: {
80
- description: 'Exploration phase',
81
- default_instructions: 'Explore the codebase',
82
- transitions: [],
83
- },
84
- plan: {
85
- description: 'Planning phase',
86
- default_instructions: 'Plan the feature',
87
- transitions: [],
88
- },
89
- code: {
90
- description: 'Coding phase',
91
- default_instructions: 'Code the feature',
92
- transitions: [],
93
- },
94
- test: {
95
- description: 'Testing phase',
96
- default_instructions: 'Test the feature',
97
- transitions: [],
98
- },
99
- },
100
- },
101
- ...overrides,
102
- }) as unknown as PluginHookContext;
103
-
104
- beforeEach(async () => {
105
- testProjectPath = join(tmpdir(), `beads-plugin-test-${Date.now()}`);
106
- testPlanFilePath = join(testProjectPath, '.vibe', 'plan.md');
107
-
108
- await mkdir(join(testProjectPath, '.vibe'), { recursive: true });
109
- await writeFile(testPlanFilePath, createPlanFileContent());
110
-
111
- vi.clearAllMocks();
112
-
113
- // Mock TaskBackendManager.detectTaskBackend() to return beads as available
114
- vi.mocked(TaskBackendManager.detectTaskBackend).mockReturnValue({
115
- backend: 'beads',
116
- isAvailable: true,
117
- });
118
-
119
- // Mock bd --version to return success (for other uses of execSync)
120
- vi.mocked(execSync).mockImplementation((command: string) => {
121
- if (command === 'bd --version') {
122
- return Buffer.from('beads v1.0.0\n');
123
- }
124
- throw new Error(`Unexpected command: ${command}`);
125
- });
126
- });
127
-
128
- afterEach(async () => {
129
- if (existsSync(testProjectPath)) {
130
- await rm(testProjectPath, { recursive: true, force: true });
131
- }
132
- vi.clearAllMocks();
133
- });
134
-
135
- // ============================================================================
136
- // Test Suite A: Plugin Interface and Metadata
137
- // ============================================================================
138
-
139
- describe('Test Suite A: Plugin Interface and Metadata', () => {
140
- it('A1: should implement complete IPlugin interface', () => {
141
- const plugin = new BeadsPlugin({ projectPath: testProjectPath });
142
-
143
- expect(typeof plugin.getName).toBe('function');
144
- expect(typeof plugin.getSequence).toBe('function');
145
- expect(typeof plugin.isEnabled).toBe('function');
146
- expect(typeof plugin.getHooks).toBe('function');
147
-
148
- expect(typeof plugin.getName()).toBe('string');
149
- expect(typeof plugin.getSequence()).toBe('number');
150
- expect(typeof plugin.isEnabled()).toBe('boolean');
151
- expect(typeof plugin.getHooks()).toBe('object');
152
- });
153
-
154
- it('A2: should provide all required hooks', () => {
155
- const plugin = new BeadsPlugin({ projectPath: testProjectPath });
156
- const hooks = plugin.getHooks();
157
-
158
- expect(hooks.afterStartDevelopment).toBeDefined();
159
- expect(hooks.beforePhaseTransition).toBeDefined();
160
- expect(hooks.afterPlanFileCreated).toBeDefined();
161
-
162
- expect(typeof hooks.afterStartDevelopment).toBe('function');
163
- expect(typeof hooks.beforePhaseTransition).toBe('function');
164
- expect(typeof hooks.afterPlanFileCreated).toBe('function');
165
- });
166
-
167
- it('A3: should have correct plugin metadata', () => {
168
- const plugin = new BeadsPlugin({ projectPath: testProjectPath });
169
-
170
- expect(plugin.getName()).toBe('BeadsPlugin');
171
- expect(plugin.getSequence()).toBe(100);
172
- });
173
-
174
- it('A4: should be enabled when TASK_BACKEND is beads', () => {
175
- const plugin = new BeadsPlugin({ projectPath: testProjectPath });
176
- expect(plugin.isEnabled()).toBe(true);
177
- });
178
-
179
- it('A5: should not be enabled when TASK_BACKEND is explicitly set to markdown', () => {
180
- // Mock TaskBackendManager to return markdown backend
181
- vi.mocked(TaskBackendManager.detectTaskBackend).mockReturnValue({
182
- backend: 'markdown',
183
- isAvailable: true,
184
- });
185
- const plugin = new BeadsPlugin({ projectPath: testProjectPath });
186
- expect(plugin.isEnabled()).toBe(false);
187
- });
188
-
189
- it('A6: should not crash when plugin not enabled', () => {
190
- // Mock TaskBackendManager to return markdown backend
191
- vi.mocked(TaskBackendManager.detectTaskBackend).mockReturnValue({
192
- backend: 'markdown',
193
- isAvailable: true,
194
- });
195
- const plugin = new BeadsPlugin({ projectPath: testProjectPath });
196
- const isEnabled = plugin.isEnabled();
197
- expect(isEnabled).toBe(false);
198
- });
199
- });
200
-
201
- // ============================================================================
202
- // Test Suite B: Hook Basic Functionality
203
- // ============================================================================
204
-
205
- describe('Test Suite B: Hook Basic Functionality', () => {
206
- it('B1: should handle afterPlanFileCreated without modifications', async () => {
207
- const plugin = new BeadsPlugin({ projectPath: testProjectPath });
208
- const context = createMockContext();
209
- const planContent = 'test plan content';
210
-
211
- const hooks = plugin.getHooks();
212
- const result = await hooks.afterPlanFileCreated?.(
213
- context,
214
- testPlanFilePath,
215
- planContent
216
- );
217
-
218
- expect(result).toBe(planContent);
219
- });
220
- });
221
-
222
- // ============================================================================
223
- // Test Suite C: Plan File Enhancement
224
- // ============================================================================
225
-
226
- describe('Test Suite C: Plan File Enhancement', () => {
227
- it('C1: should gracefully handle missing plan file', async () => {
228
- // Remove the plan file to simulate read error
229
- await rm(testPlanFilePath);
230
-
231
- const plugin = new BeadsPlugin({ projectPath: testProjectPath });
232
- const context = createMockContext();
233
- const args = { workflow: 'epcc', commit_behaviour: 'end' as const };
234
-
235
- // Setup mocks for execSync
236
- vi.mocked(execSync).mockImplementation((command: string) => {
237
- if (command === 'bd list --limit 1') {
238
- return 'No issues found\n';
239
- }
240
- throw new Error(`Unexpected command: ${command}`);
241
- });
242
-
243
- const hooks = plugin.getHooks();
244
-
245
- // Plugin handles missing plan file gracefully in goal extraction
246
- // It continues without a goal description
247
- const promise = hooks.afterStartDevelopment?.(context, args, {
248
- conversationId: context.conversationId,
249
- planFilePath: context.planFilePath,
250
- phase: context.currentPhase,
251
- workflow: args.workflow,
252
- });
253
-
254
- // Goal extraction error should not crash the system
255
- // Result depends on whether execSync supports the command
256
- if (promise) {
257
- await expect(promise).resolves.not.toThrow('Goal extraction');
258
- }
259
- });
260
-
261
- it('C2: should update plan file with beads task IDs when successful', async () => {
262
- const plugin = new BeadsPlugin({ projectPath: testProjectPath });
263
- const context = createMockContext();
264
- const args = { workflow: 'epcc', commit_behaviour: 'end' as const };
265
-
266
- // Mock execSync to simulate beads commands
267
- let callCount = 0;
268
- vi.mocked(execSync).mockImplementation((command: string) => {
269
- callCount++;
270
-
271
- if (command === 'bd list --limit 1') {
272
- return 'No issues found\n';
273
- }
274
-
275
- // Return different task IDs for each phase task creation
276
- if (command.includes('bd create')) {
277
- if (callCount === 2) return '✓ Created issue: epic-1\n'; // main epic
278
- if (callCount === 3) return '✓ Created issue: epic-1.1\n'; // explore
279
- if (callCount === 4) return '✓ Created issue: epic-1.2\n'; // plan
280
- if (callCount === 5) return '✓ Created issue: epic-1.3\n'; // code
281
- if (callCount === 6) return '✓ Created issue: epic-1.4\n'; // test
282
- if (callCount === 7) return '✓ Dependency created\n'; // dependency
283
- if (callCount === 8) return '✓ Dependency created\n';
284
- if (callCount === 9) return '✓ Dependency created\n';
285
- }
286
-
287
- throw new Error(`Unexpected command: ${command}`);
288
- });
289
-
290
- const hooks = plugin.getHooks();
291
- await hooks.afterStartDevelopment?.(context, args, {
292
- conversationId: context.conversationId,
293
- planFilePath: context.planFilePath,
294
- phase: context.currentPhase,
295
- workflow: args.workflow,
296
- });
297
-
298
- // Verify plan file was updated
299
- const updatedContent = await readFile(testPlanFilePath, 'utf-8');
300
-
301
- // Should have replaced all TBD placeholders
302
- expect(updatedContent).not.toMatch(/<!-- beads-phase-id: TBD -->/);
303
-
304
- // Should have actual task IDs
305
- expect(updatedContent).toContain('beads-phase-id: epic-1');
306
- });
307
- });
308
-
309
- // ============================================================================
310
- // Test Suite D: User Experience Preservation
311
- // ============================================================================
312
-
313
- describe('Test Suite D: User Experience Preservation', () => {
314
- it('D1: should handle beads backend unavailability gracefully', async () => {
315
- const plugin = new BeadsPlugin({ projectPath: testProjectPath });
316
- const context = createMockContext();
317
-
318
- // Mock the backend client to return unavailable
319
- vi.mocked(execSync).mockImplementation((command: string) => {
320
- if (command.includes('--version')) {
321
- throw new Error('beads CLI not found');
322
- }
323
- throw new Error(`Unexpected command: ${command}`);
324
- });
325
-
326
- const hooks = plugin.getHooks();
327
-
328
- // Should not throw when backend unavailable
329
- await expect(
330
- hooks.beforePhaseTransition?.(context, 'explore', 'plan')
331
- ).resolves.not.toThrow();
332
- });
333
-
334
- it('D2: should allow phase transitions without beads tasks present', async () => {
335
- const plugin = new BeadsPlugin({ projectPath: testProjectPath });
336
- const context = createMockContext();
337
-
338
- vi.mocked(execSync).mockImplementation((command: string) => {
339
- if (command === 'bd --version') {
340
- return 'beads v1.0.0\n';
341
- }
342
- // Simulate no beads state found
343
- throw new Error('No beads state');
344
- });
345
-
346
- const hooks = plugin.getHooks();
347
-
348
- // Should not throw even if beads state not found
349
- await expect(
350
- hooks.beforePhaseTransition?.(context, 'explore', 'plan')
351
- ).resolves.not.toThrow();
352
- });
353
-
354
- it('D3: should preserve identical interface with and without beads', () => {
355
- vi.stubEnv('TASK_BACKEND', 'beads');
356
- const pluginWithBeads = new BeadsPlugin({ projectPath: testProjectPath });
357
-
358
- vi.stubEnv('TASK_BACKEND', 'none');
359
- const pluginWithoutBeads = new BeadsPlugin({
360
- projectPath: testProjectPath,
361
- });
362
-
363
- // Both should have same interface
364
- expect(pluginWithBeads.getName()).toBe(pluginWithoutBeads.getName());
365
- expect(pluginWithBeads.getSequence()).toBe(
366
- pluginWithoutBeads.getSequence()
367
- );
368
-
369
- // Hooks should exist for both
370
- const beadsHooks = pluginWithBeads.getHooks();
371
- const nonBeadsHooks = pluginWithoutBeads.getHooks();
372
-
373
- expect(Object.keys(beadsHooks)).toEqual(Object.keys(nonBeadsHooks));
374
- });
375
-
376
- it('D4: should provide meaningful error messages', async () => {
377
- const plugin = new BeadsPlugin({ projectPath: testProjectPath });
378
- const context = createMockContext();
379
-
380
- vi.mocked(execSync).mockImplementation((_command: string) => {
381
- throw new Error('beads CLI not found or not in PATH');
382
- });
383
-
384
- const hooks = plugin.getHooks();
385
-
386
- try {
387
- await hooks.afterStartDevelopment?.(
388
- context,
389
- {
390
- workflow: 'epcc',
391
- commit_behaviour: 'end' as const,
392
- } as unknown,
393
- {
394
- conversationId: context.conversationId,
395
- planFilePath: context.planFilePath,
396
- phase: context.currentPhase,
397
- workflow: 'epcc',
398
- }
399
- );
400
- } catch (error) {
401
- const message = error instanceof Error ? error.message : String(error);
402
- // Error should be clear and actionable
403
- expect(message).toContain('BeadsPlugin');
404
- }
405
- });
406
- });
407
-
408
- // ============================================================================
409
- // Test Suite E: Goal Extraction
410
- // ============================================================================
411
-
412
- describe('Test Suite E: Goal Extraction', () => {
413
- it('E1: should handle missing goal section gracefully', async () => {
414
- // Create plan without goal section
415
- const planWithoutGoal = `# Development Plan
416
-
417
- ## Explore
418
- <!-- beads-phase-id: TBD -->`;
419
-
420
- await writeFile(testPlanFilePath, planWithoutGoal);
421
-
422
- const plugin = new BeadsPlugin({ projectPath: testProjectPath });
423
- const context = createMockContext();
424
- const args = { workflow: 'epcc', commit_behaviour: 'end' as const };
425
-
426
- let _epicCreateCmd = '';
427
- vi.mocked(execSync).mockImplementation((command: string) => {
428
- if (command === 'bd list --limit 1') {
429
- return 'No issues found\n';
430
- }
431
- if (command.includes('bd create') && callCount === 1) {
432
- _epicCreateCmd = command;
433
- }
434
- if (command.includes('bd create')) {
435
- return '✓ Created issue: epic-1\n';
436
- }
437
- if (command.includes('bd') && command.includes('--parent')) {
438
- return '✓ Created issue: epic-1.1\n';
439
- }
440
- throw new Error(`Unexpected command: ${command}`);
441
- });
442
-
443
- let callCount = 0;
444
-
445
- const hooks = plugin.getHooks();
446
- await hooks.afterStartDevelopment?.(context, args, {
447
- conversationId: context.conversationId,
448
- planFilePath: context.planFilePath,
449
- phase: context.currentPhase,
450
- workflow: args.workflow,
451
- });
452
-
453
- // Should have called create without goal description being undefined
454
- // The goal extraction should fail gracefully
455
- expect(vi.mocked(execSync)).toHaveBeenCalled();
456
- });
457
-
458
- it('E2: should reject placeholder goals', async () => {
459
- const planWithPlaceholder = `# Development Plan
460
-
461
- ## Goal
462
- *Define what you're building...*
463
-
464
- ## Explore
465
- <!-- beads-phase-id: TBD -->`;
466
-
467
- await writeFile(testPlanFilePath, planWithPlaceholder);
468
-
469
- const plugin = new BeadsPlugin({ projectPath: testProjectPath });
470
- const context = createMockContext();
471
- const args = { workflow: 'epcc', commit_behaviour: 'end' as const };
472
-
473
- vi.mocked(execSync).mockImplementation((command: string) => {
474
- if (command === 'bd list --limit 1') {
475
- return 'No issues found\n';
476
- }
477
- if (command.includes('bd create')) {
478
- return '✓ Created issue: epic-1\n';
479
- }
480
- if (command.includes('bd') && command.includes('--parent')) {
481
- return '✓ Created issue: epic-1.1\n';
482
- }
483
- throw new Error(`Unexpected command: ${command}`);
484
- });
485
-
486
- const hooks = plugin.getHooks();
487
- await hooks.afterStartDevelopment?.(context, args, {
488
- conversationId: context.conversationId,
489
- planFilePath: context.planFilePath,
490
- phase: context.currentPhase,
491
- workflow: args.workflow,
492
- });
493
-
494
- // Should complete without throwing
495
- expect(vi.mocked(execSync)).toHaveBeenCalled();
496
- });
497
- });
498
-
499
- // ============================================================================
500
- // Test Suite F: Error Recovery
501
- // ============================================================================
502
-
503
- describe('Test Suite F: Error Recovery', () => {
504
- it('F2: should handle plan file write errors gracefully', async () => {
505
- const plugin = new BeadsPlugin({ projectPath: testProjectPath });
506
- const context = createMockContext();
507
- const args = { workflow: 'epcc', commit_behaviour: 'end' as const };
508
-
509
- // Remove write permissions on plan file by replacing with directory
510
- await rm(testPlanFilePath);
511
- await mkdir(testPlanFilePath);
512
-
513
- vi.mocked(execSync).mockImplementation((command: string) => {
514
- if (command === 'bd list --limit 1') {
515
- return 'No issues found\n';
516
- }
517
- if (command.includes('bd create')) {
518
- return '✓ Created issue: epic-1\n';
519
- }
520
- if (command.includes('bd')) {
521
- return '✓ Created issue: epic-1.1\n';
522
- }
523
- throw new Error(`Unexpected command: ${command}`);
524
- });
525
-
526
- const hooks = plugin.getHooks();
527
-
528
- try {
529
- await hooks.afterStartDevelopment?.(context, args, {
530
- conversationId: context.conversationId,
531
- planFilePath: testPlanFilePath,
532
- phase: context.currentPhase,
533
- workflow: args.workflow,
534
- });
535
- } catch (error) {
536
- // Expected to fail when writing plan file
537
- expect(error instanceof Error).toBe(true);
538
- return;
539
- }
540
-
541
- // If it gets here, the write might have succeeded despite the directory
542
- // which is fine for this test
543
- });
544
- });
545
- });
@@ -1,117 +0,0 @@
1
- /**
2
- * Basic tests for BeadsPlugin implementation
3
- */
4
-
5
- import { BeadsPlugin } from '../../src/plugin-system/beads-plugin.js';
6
- import { describe, it, expect, beforeEach, vi } from 'vitest';
7
- import { TaskBackendManager } from '@codemcp/workflows-core';
8
-
9
- // Mock TaskBackendManager to control beads detection
10
- vi.mock('@codemcp/workflows-core', async importOriginal => {
11
- const original =
12
- await importOriginal<typeof import('@codemcp/workflows-core')>();
13
- return {
14
- ...original,
15
- TaskBackendManager: {
16
- ...original.TaskBackendManager,
17
- detectTaskBackend: vi.fn(),
18
- },
19
- };
20
- });
21
-
22
- describe('BeadsPlugin', () => {
23
- let plugin: BeadsPlugin;
24
- const mockProjectPath = '/test/project/path';
25
-
26
- beforeEach(() => {
27
- // Mock TaskBackendManager.detectTaskBackend() to return beads as available
28
- vi.mocked(TaskBackendManager.detectTaskBackend).mockReturnValue({
29
- backend: 'beads',
30
- isAvailable: true,
31
- });
32
-
33
- plugin = new BeadsPlugin({ projectPath: mockProjectPath });
34
- });
35
-
36
- describe('Basic Interface Implementation', () => {
37
- it('should return correct name', () => {
38
- expect(plugin.getName()).toBe('BeadsPlugin');
39
- });
40
-
41
- it('should return correct sequence', () => {
42
- expect(plugin.getSequence()).toBe(100);
43
- });
44
-
45
- it('should be enabled when TASK_BACKEND is beads', () => {
46
- expect(plugin.isEnabled()).toBe(true);
47
- });
48
-
49
- it('should not be enabled when TASK_BACKEND is explicitly set to markdown', () => {
50
- // Mock TaskBackendManager to return markdown backend
51
- vi.mocked(TaskBackendManager.detectTaskBackend).mockReturnValue({
52
- backend: 'markdown',
53
- isAvailable: true,
54
- });
55
- vi.stubEnv('TASK_BACKEND', 'markdown');
56
- const testPlugin = new BeadsPlugin({ projectPath: mockProjectPath });
57
- expect(testPlugin.isEnabled()).toBe(false);
58
- });
59
-
60
- it('should provide required hooks', () => {
61
- const hooks = plugin.getHooks();
62
- expect(hooks.afterStartDevelopment).toBeDefined();
63
- expect(hooks.beforePhaseTransition).toBeDefined();
64
- expect(hooks.afterPlanFileCreated).toBeDefined();
65
- });
66
- });
67
-
68
- describe('Hook Implementation', () => {
69
- const mockContext = {
70
- conversationId: 'test-conversation',
71
- planFilePath: '/test/plan.md',
72
- currentPhase: 'test-phase',
73
- workflow: 'test-workflow',
74
- projectPath: mockProjectPath,
75
- gitBranch: 'test-branch',
76
- };
77
-
78
- it('should handle afterStartDevelopment hook without errors', async () => {
79
- const hooks = plugin.getHooks();
80
- const result = hooks.afterStartDevelopment;
81
- expect(result).toBeDefined();
82
-
83
- // This should not throw because it's just logging a warning
84
- // about architectural limitations
85
- if (result) {
86
- await expect(
87
- result(
88
- mockContext,
89
- { workflow: 'test-workflow', commit_behaviour: 'end' },
90
- {
91
- conversationId: 'test',
92
- planFilePath: '/test/plan.md',
93
- phase: 'test-phase',
94
- workflow: 'test-workflow',
95
- }
96
- )
97
- ).resolves.not.toThrow();
98
- }
99
- });
100
-
101
- it('should handle afterPlanFileCreated hook', async () => {
102
- const hooks = plugin.getHooks();
103
- const result = hooks.afterPlanFileCreated;
104
- expect(result).toBeDefined();
105
-
106
- if (result) {
107
- const content = 'test plan content';
108
- const processedContent = await result(
109
- mockContext,
110
- '/test/plan.md',
111
- content
112
- );
113
- expect(processedContent).toBe(content); // Should return unchanged
114
- }
115
- });
116
- });
117
- });