@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,1623 +0,0 @@
1
- /**
2
- * Comprehensive Beads Plugin Integration Test
3
- *
4
- * This single test file validates ALL aspects of beads plugin behavior:
5
- * 1. Plan file structure with beads markers
6
- * 2. Beads instruction generation
7
- * 3. Beads task creation on start
8
- * 4. Plan file task ID integration
9
- * 5. Phase transition validation
10
- * 6. With vs without beads comparison
11
- * 7. Beads error handling
12
- * 8. Plugin hook integration
13
- *
14
- * Design Principles:
15
- * - NO fuzzy assertions
16
- * - EXPLICIT validation of content (not just existence)
17
- * - COMPREHENSIVE coverage of all beads functionality
18
- * - PROPER isolation and cleanup between tests
19
- * - MEANINGFUL test names that describe what is validated
20
- */
21
-
22
- import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
23
- import { createTempProjectWithDefaultStateMachine } from '../utils/temp-files';
24
- import {
25
- DirectServerInterface,
26
- createSuiteIsolatedE2EScenario,
27
- assertToolSuccess,
28
- } from '../utils/e2e-test-setup';
29
- import { promises as fs } from 'node:fs';
30
- import { execSync } from 'node:child_process';
31
- import type { StartDevelopmentResult } from '../../src/tool-handlers/start-development';
32
- import type { WhatsNextResult } from '../../src/tool-handlers/whats-next';
33
-
34
- vi.unmock('fs');
35
- vi.unmock('fs/promises');
36
-
37
- // Mock child_process to simulate beads CLI responses
38
- vi.mock('node:child_process', () => ({
39
- execSync: vi.fn(),
40
- }));
41
-
42
- // Track created task IDs for consistent mock responses
43
- let mockTaskIdCounter = 0;
44
-
45
- /**
46
- * Setup beads CLI mock for tests
47
- * This simulates the bd CLI responses needed for beads integration
48
- */
49
- function setupBeadsCliMock(): void {
50
- mockTaskIdCounter = 0;
51
-
52
- vi.mocked(execSync).mockImplementation((command: string) => {
53
- // Handle bd --version check (used by TaskBackendManager)
54
- if (command === 'bd --version') {
55
- return 'beads v1.0.0\n';
56
- }
57
-
58
- // Handle bd list command (used to check if beads is initialized)
59
- if (command.includes('bd list')) {
60
- return ''; // Return empty list
61
- }
62
-
63
- // Handle bd init command
64
- if (command.includes('bd init')) {
65
- return 'Initialized beads repository\n';
66
- }
67
-
68
- // Handle bd create command for epic/phase tasks
69
- if (command.includes('bd create')) {
70
- mockTaskIdCounter++;
71
- const taskId = `mock-task-${mockTaskIdCounter}`;
72
- return `✓ Created issue: ${taskId}\n`;
73
- }
74
-
75
- // Handle bd dep command for dependencies
76
- if (command.includes('bd dep')) {
77
- return '✓ Dependency created\n';
78
- }
79
-
80
- // Handle bd show command
81
- if (command.includes('bd show')) {
82
- return 'Title: Mock Task\nStatus: open\n';
83
- }
84
-
85
- // Handle bd update command
86
- if (command.includes('bd update')) {
87
- return '✓ Updated\n';
88
- }
89
-
90
- // Handle bd close command
91
- if (command.includes('bd close')) {
92
- return '✓ Closed\n';
93
- }
94
-
95
- // Handle git commands (used in some tests)
96
- if (command === 'git symbolic-ref --short HEAD') {
97
- return 'feature/test-branch\n';
98
- }
99
-
100
- // For any other command, throw an error (unexpected command)
101
- throw new Error(`Unexpected command in beads test: ${command}`);
102
- });
103
- }
104
-
105
- // ============================================================================
106
- // TEST CONSTANTS (Remove magic numbers)
107
- // ============================================================================
108
-
109
- // Minimum number of phases in a workflow that should have beads markers
110
- const MIN_PHASES_WITH_MARKERS = 4;
111
-
112
- // Minimum length for substantive instructions
113
- // Must be long enough to contain meaningful guidance, not just placeholders
114
- const MIN_INSTRUCTION_LENGTH = 200;
115
-
116
- // ============================================================================
117
- // HELPER FUNCTIONS
118
- // ============================================================================
119
-
120
- /**
121
- * Verify beads plan file has the expected structure with markers
122
- */
123
- function validateBeadsPlanFileStructure(content: string): void {
124
- // Should have beads-phase-id markers for each phase
125
- expect(content).toContain('<!-- beads-phase-id:');
126
-
127
- // Should have HTML comment format with either TBD or actual task IDs
128
- // Task IDs can include alphanumerics, hyphens, and dots
129
- expect(content).toMatch(/<!-- beads-phase-id:\s*(TBD|[a-zA-Z0-9\-.]+)\s*-->/);
130
-
131
- // Should have phase headers
132
- expect(content).toMatch(/^## \w+/m);
133
-
134
- // Should have "Tasks managed via `bd` CLI" guidance
135
- expect(content).toContain('Tasks managed via');
136
- expect(content).toContain('bd');
137
- }
138
-
139
- /**
140
- * Verify instructions contain beads CLI references
141
- */
142
- function validateBeadsInstructions(instructions: string): void {
143
- // Should mention bd CLI tool
144
- expect(instructions.toLowerCase()).toContain('bd');
145
-
146
- // Should have bd commands
147
- const hasCommands = /bd\s+(list|create|update|close|show)/i.test(
148
- instructions
149
- );
150
- expect(hasCommands).toBe(true);
151
-
152
- // Should mention task management
153
- expect(instructions.toLowerCase()).toContain('task');
154
-
155
- // Should have beads-specific guidance
156
- expect(instructions).toContain('bd');
157
-
158
- // Should mention using ONLY bd CLI
159
- expect(instructions).toContain('Use ONLY bd CLI tool');
160
- }
161
-
162
- /**
163
- * Extract beads phase IDs from plan file content
164
- * Returns array of task IDs found in the plan
165
- */
166
- function extractBeadsPhaseIds(content: string): string[] {
167
- const matches =
168
- content.match(/<!-- beads-phase-id:\s*([a-zA-Z0-9\-.]+)\s*-->/g) || [];
169
- return matches
170
- .map(match => {
171
- const idMatch = match.match(/beads-phase-id:\s*([a-zA-Z0-9\-.]+)\s*-->/);
172
- return idMatch ? idMatch[1] : '';
173
- })
174
- .filter(id => id.length > 0);
175
- }
176
-
177
- // ============================================================================
178
- // TESTS
179
- // ============================================================================
180
-
181
- describe('Beads Plugin Comprehensive Integration', () => {
182
- // =========================================================================
183
- // 1. PLAN FILE STRUCTURE
184
- // =========================================================================
185
-
186
- describe('1. Plan File Structure with Beads Markers', () => {
187
- let client: DirectServerInterface;
188
- let cleanup: () => Promise<void>;
189
-
190
- beforeEach(async () => {
191
- // CRITICAL: Enable beads backend and mock CLI
192
- process.env.TASK_BACKEND = 'beads';
193
- setupBeadsCliMock();
194
-
195
- const scenario = await createSuiteIsolatedE2EScenario({
196
- suiteName: 'beads-plan-structure',
197
- tempProjectFactory: createTempProjectWithDefaultStateMachine,
198
- });
199
- client = scenario.client;
200
- cleanup = scenario.cleanup;
201
- });
202
-
203
- afterEach(async () => {
204
- if (cleanup) {
205
- await cleanup();
206
- }
207
- delete process.env.TASK_BACKEND;
208
- });
209
-
210
- it('should create plan file WITH beads-phase-id placeholders when TASK_BACKEND=beads', async () => {
211
- // Verify environment
212
- expect(process.env.TASK_BACKEND).toBe('beads');
213
-
214
- // Start development
215
- const result = await client.callTool('start_development', {
216
- workflow: 'epcc',
217
- commit_behaviour: 'none',
218
- });
219
-
220
- const response = assertToolSuccess(result) as StartDevelopmentResult;
221
- const planFilePath = response.plan_file_path;
222
-
223
- // Read plan file
224
- const planContent = await fs.readFile(planFilePath, 'utf-8');
225
-
226
- // VALIDATE: Plan file has beads markers
227
- validateBeadsPlanFileStructure(planContent);
228
-
229
- // VALIDATE: Each phase has beads-phase-id placeholder
230
- expect(planContent).toContain('## Explore');
231
- expect(planContent).toMatch(/## Explore\n<!-- beads-phase-id:/);
232
-
233
- expect(planContent).toContain('## Plan');
234
- expect(planContent).toMatch(/## Plan\n<!-- beads-phase-id:/);
235
-
236
- expect(planContent).toContain('## Code');
237
- expect(planContent).toMatch(/## Code\n<!-- beads-phase-id:/);
238
-
239
- expect(planContent).toContain('## Commit');
240
- expect(planContent).toMatch(/## Commit\n<!-- beads-phase-id:/);
241
- });
242
-
243
- it('should have beads phase IDs for each phase (updated by plugin hook)', async () => {
244
- // Start development
245
- const result = await client.callTool('start_development', {
246
- workflow: 'epcc',
247
- commit_behaviour: 'none',
248
- });
249
-
250
- const response = assertToolSuccess(result) as StartDevelopmentResult;
251
- const planContent = await fs.readFile(response.plan_file_path, 'utf-8');
252
-
253
- // VALIDATE: Plan has beads-phase-id markers (either TBD or actual IDs)
254
- // The plugin's afterStartDevelopment hook will update TBD placeholders with actual task IDs
255
- expect(planContent).toContain('<!-- beads-phase-id:');
256
-
257
- // Count beads-phase-id occurrences (should be one per phase)
258
- const phaseIdMatches = planContent.match(/<!-- beads-phase-id:/g);
259
- expect(phaseIdMatches).not.toBeNull();
260
- expect((phaseIdMatches || []).length).toBeGreaterThanOrEqual(
261
- MIN_PHASES_WITH_MARKERS
262
- ); // At least 4 phases
263
- });
264
-
265
- it('should format beads markers as HTML comments with proper structure', async () => {
266
- // Start development
267
- const result = await client.callTool('start_development', {
268
- workflow: 'epcc',
269
- commit_behaviour: 'none',
270
- });
271
-
272
- const response = assertToolSuccess(result) as StartDevelopmentResult;
273
- const planContent = await fs.readFile(response.plan_file_path, 'utf-8');
274
-
275
- // VALIDATE: Format matches the pattern (either TBD or actual task IDs with dots)
276
- expect(planContent).toMatch(
277
- /## \w+\n<!-- beads-phase-id:\s*(TBD|[a-zA-Z0-9\-.]+)\s*-->\n### Tasks/
278
- );
279
-
280
- // VALIDATE: Must be HTML comment format
281
- expect(planContent).toMatch(/<!-- beads-phase-id:/);
282
- expect(planContent).not.toMatch(/\/\/ beads-phase-id:/);
283
- });
284
- });
285
-
286
- // =========================================================================
287
- // 2. BEADS INSTRUCTION GENERATION
288
- // =========================================================================
289
-
290
- describe('2. Beads Instruction Generation', () => {
291
- let client: DirectServerInterface;
292
- let cleanup: () => Promise<void>;
293
-
294
- beforeEach(async () => {
295
- process.env.TASK_BACKEND = 'beads';
296
- setupBeadsCliMock();
297
-
298
- const scenario = await createSuiteIsolatedE2EScenario({
299
- suiteName: 'beads-instructions',
300
- tempProjectFactory: createTempProjectWithDefaultStateMachine,
301
- });
302
- client = scenario.client;
303
- cleanup = scenario.cleanup;
304
- });
305
-
306
- afterEach(async () => {
307
- if (cleanup) {
308
- await cleanup();
309
- }
310
- delete process.env.TASK_BACKEND;
311
- });
312
-
313
- it('should generate beads-specific instructions mentioning bd CLI', async () => {
314
- // Start development
315
- const startResult = await client.callTool('start_development', {
316
- workflow: 'epcc',
317
- commit_behaviour: 'none',
318
- });
319
- assertToolSuccess(startResult);
320
-
321
- // Get instructions
322
- const whatsNextResult = await client.callTool('whats_next', {
323
- context: 'Testing beads instructions',
324
- user_input: 'What should I do?',
325
- conversation_summary: 'Started development with beads',
326
- recent_messages: [],
327
- });
328
-
329
- const response = assertToolSuccess(whatsNextResult) as WhatsNextResult;
330
- const instructions = response.instructions;
331
-
332
- // VALIDATE: Instructions mention beads
333
- validateBeadsInstructions(instructions);
334
- });
335
-
336
- it('should include bd CLI commands in instructions', async () => {
337
- // Start development
338
- const startResult = await client.callTool('start_development', {
339
- workflow: 'epcc',
340
- commit_behaviour: 'none',
341
- });
342
- assertToolSuccess(startResult);
343
-
344
- // Get instructions
345
- const whatsNextResult = await client.callTool('whats_next', {
346
- context: 'Testing beads instructions',
347
- user_input: 'What should I do?',
348
- conversation_summary: 'Started development with beads',
349
- recent_messages: [],
350
- });
351
-
352
- const response = assertToolSuccess(whatsNextResult) as WhatsNextResult;
353
- const instructions = response.instructions;
354
-
355
- // VALIDATE: Should have multiple bd commands
356
- expect(instructions).toMatch(/`bd\s+list/);
357
- expect(instructions).toMatch(/`bd\s+create/);
358
- expect(instructions).toMatch(/`bd\s+(update|close)/);
359
- });
360
-
361
- it('should remind user to use ONLY bd CLI for task management', async () => {
362
- // Start development
363
- const startResult = await client.callTool('start_development', {
364
- workflow: 'epcc',
365
- commit_behaviour: 'none',
366
- });
367
- assertToolSuccess(startResult);
368
-
369
- // Get instructions
370
- const whatsNextResult = await client.callTool('whats_next', {
371
- context: 'Testing beads instructions',
372
- user_input: 'What should I do?',
373
- conversation_summary: 'Started development with beads',
374
- recent_messages: [],
375
- });
376
-
377
- const response = assertToolSuccess(whatsNextResult) as WhatsNextResult;
378
- const instructions = response.instructions;
379
-
380
- // VALIDATE: Clear instruction about bd CLI exclusivity
381
- expect(instructions).toContain('Use ONLY bd CLI tool');
382
- expect(instructions).toContain(
383
- 'do not use your own task management tools'
384
- );
385
- });
386
- });
387
-
388
- // =========================================================================
389
- // 3. WITH VS WITHOUT BEADS COMPARISON
390
- // =========================================================================
391
-
392
- describe('3. Plan File and Instructions: With vs Without Beads', () => {
393
- it('should produce DIFFERENT plan files with and without beads', async () => {
394
- // Create two independent scenarios
395
- let cleanupWith: () => Promise<void>;
396
- let cleanupWithout: () => Promise<void>;
397
-
398
- // WITH BEADS
399
- process.env.TASK_BACKEND = 'beads';
400
- setupBeadsCliMock();
401
- const scenarioWith = await createSuiteIsolatedE2EScenario({
402
- suiteName: 'beads-comparison-with',
403
- tempProjectFactory: createTempProjectWithDefaultStateMachine,
404
- });
405
- const clientWith = scenarioWith.client;
406
- cleanupWith = scenarioWith.cleanup;
407
-
408
- const resultWith = await clientWith.callTool('start_development', {
409
- workflow: 'epcc',
410
- commit_behaviour: 'none',
411
- });
412
- const responseWith = assertToolSuccess(
413
- resultWith
414
- ) as StartDevelopmentResult;
415
- const planContentWith = await fs.readFile(
416
- responseWith.plan_file_path,
417
- 'utf-8'
418
- );
419
-
420
- await cleanupWith();
421
-
422
- // WITHOUT BEADS - explicitly set markdown to disable auto-detection
423
- process.env.TASK_BACKEND = 'markdown';
424
- const scenarioWithout = await createSuiteIsolatedE2EScenario({
425
- suiteName: 'beads-comparison-without',
426
- tempProjectFactory: createTempProjectWithDefaultStateMachine,
427
- });
428
- const clientWithout = scenarioWithout.client;
429
- cleanupWithout = scenarioWithout.cleanup;
430
-
431
- const resultWithout = await clientWithout.callTool('start_development', {
432
- workflow: 'epcc',
433
- commit_behaviour: 'none',
434
- });
435
- const responseWithout = assertToolSuccess(
436
- resultWithout
437
- ) as StartDevelopmentResult;
438
- const planContentWithout = await fs.readFile(
439
- responseWithout.plan_file_path,
440
- 'utf-8'
441
- );
442
-
443
- await cleanupWithout();
444
- delete process.env.TASK_BACKEND;
445
-
446
- // VALIDATE: WITH beads has beads markers
447
- expect(planContentWith).toContain('<!-- beads-phase-id:');
448
-
449
- // VALIDATE: WITHOUT beads does NOT have beads markers
450
- expect(planContentWithout).not.toContain('<!-- beads-phase-id:');
451
-
452
- // VALIDATE: WITHOUT beads uses checkbox format
453
- expect(planContentWithout).toContain('- [ ]');
454
- });
455
-
456
- it('should generate DIFFERENT instructions with and without beads', async () => {
457
- // WITH BEADS
458
- process.env.TASK_BACKEND = 'beads';
459
- setupBeadsCliMock();
460
- const scenarioWith = await createSuiteIsolatedE2EScenario({
461
- suiteName: 'beads-instructions-comparison-with',
462
- tempProjectFactory: createTempProjectWithDefaultStateMachine,
463
- });
464
- const clientWith = scenarioWith.client;
465
-
466
- await clientWith.callTool('start_development', {
467
- workflow: 'epcc',
468
- commit_behaviour: 'none',
469
- });
470
-
471
- const whatsNextWith = await clientWith.callTool('whats_next', {
472
- context: 'Testing',
473
- user_input: 'What should I do?',
474
- conversation_summary: 'Started',
475
- recent_messages: [],
476
- });
477
- const responseWith = assertToolSuccess(whatsNextWith) as WhatsNextResult;
478
- const instructionsWithBeads = responseWith.instructions;
479
-
480
- await scenarioWith.cleanup();
481
-
482
- // WITHOUT BEADS - explicitly set markdown to disable auto-detection
483
- process.env.TASK_BACKEND = 'markdown';
484
- const scenarioWithout = await createSuiteIsolatedE2EScenario({
485
- suiteName: 'beads-instructions-comparison-without',
486
- tempProjectFactory: createTempProjectWithDefaultStateMachine,
487
- });
488
- const clientWithout = scenarioWithout.client;
489
-
490
- await clientWithout.callTool('start_development', {
491
- workflow: 'epcc',
492
- commit_behaviour: 'none',
493
- });
494
-
495
- const whatsNextWithout = await clientWithout.callTool('whats_next', {
496
- context: 'Testing',
497
- user_input: 'What should I do?',
498
- conversation_summary: 'Started',
499
- recent_messages: [],
500
- });
501
- const responseWithout = assertToolSuccess(
502
- whatsNextWithout
503
- ) as WhatsNextResult;
504
- const instructionsWithout = responseWithout.instructions;
505
-
506
- await scenarioWithout.cleanup();
507
- delete process.env.TASK_BACKEND;
508
-
509
- // VALIDATE: WITH beads mentions bd CLI
510
- expect(instructionsWithBeads.toLowerCase()).toContain('bd');
511
- expect(instructionsWithBeads).toContain('bd Task Management');
512
-
513
- // VALIDATE: WITHOUT beads does NOT mention bd CLI
514
- expect(instructionsWithout.toLowerCase()).not.toContain('bd cli');
515
- expect(instructionsWithout).not.toContain('bd Task Management');
516
- });
517
-
518
- it('should maintain identical response contracts regardless of beads', async () => {
519
- // WITH BEADS
520
- process.env.TASK_BACKEND = 'beads';
521
- setupBeadsCliMock();
522
- const scenarioWith = await createSuiteIsolatedE2EScenario({
523
- suiteName: 'beads-contract-with',
524
- tempProjectFactory: createTempProjectWithDefaultStateMachine,
525
- });
526
- const clientWith = scenarioWith.client;
527
-
528
- const resultWith = await clientWith.callTool('start_development', {
529
- workflow: 'epcc',
530
- commit_behaviour: 'none',
531
- });
532
- const responseWith = assertToolSuccess(resultWith);
533
-
534
- await scenarioWith.cleanup();
535
- delete process.env.TASK_BACKEND;
536
-
537
- // WITHOUT BEADS
538
- const scenarioWithout = await createSuiteIsolatedE2EScenario({
539
- suiteName: 'beads-contract-without',
540
- tempProjectFactory: createTempProjectWithDefaultStateMachine,
541
- });
542
- const clientWithout = scenarioWithout.client;
543
-
544
- const resultWithout = await clientWithout.callTool('start_development', {
545
- workflow: 'epcc',
546
- commit_behaviour: 'none',
547
- });
548
- const responseWithout = assertToolSuccess(resultWithout);
549
-
550
- await scenarioWithout.cleanup();
551
-
552
- // VALIDATE: Both responses have identical properties
553
- expect(responseWith).toHaveProperty('phase');
554
- expect(responseWith).toHaveProperty('plan_file_path');
555
- expect(responseWith).toHaveProperty('instructions');
556
-
557
- expect(responseWithout).toHaveProperty('phase');
558
- expect(responseWithout).toHaveProperty('plan_file_path');
559
- expect(responseWithout).toHaveProperty('instructions');
560
-
561
- // VALIDATE: Response structure identical
562
- expect(Object.keys(responseWith).sort()).toEqual(
563
- Object.keys(responseWithout).sort()
564
- );
565
- });
566
- });
567
-
568
- // =========================================================================
569
- // 4. PLAN FILE TASK ID INTEGRATION
570
- // =========================================================================
571
-
572
- describe('4. Plan File Task ID Integration', () => {
573
- let client: DirectServerInterface;
574
- let cleanup: () => Promise<void>;
575
-
576
- beforeEach(async () => {
577
- process.env.TASK_BACKEND = 'beads';
578
- setupBeadsCliMock();
579
-
580
- const scenario = await createSuiteIsolatedE2EScenario({
581
- suiteName: 'beads-task-ids',
582
- tempProjectFactory: createTempProjectWithDefaultStateMachine,
583
- });
584
- client = scenario.client;
585
- cleanup = scenario.cleanup;
586
- });
587
-
588
- afterEach(async () => {
589
- if (cleanup) {
590
- await cleanup();
591
- }
592
- delete process.env.TASK_BACKEND;
593
- });
594
-
595
- it('should have beads phase IDs after task creation by plugin hooks', async () => {
596
- // Start development
597
- const result = await client.callTool('start_development', {
598
- workflow: 'epcc',
599
- commit_behaviour: 'none',
600
- });
601
-
602
- const response = assertToolSuccess(result) as StartDevelopmentResult;
603
- const planFilePath = response.plan_file_path;
604
- const planContent = await fs.readFile(planFilePath, 'utf-8');
605
-
606
- // VALIDATE: Plan has beads markers (IDs updated by afterStartDevelopment hook)
607
- expect(planContent).toContain('<!-- beads-phase-id:');
608
-
609
- // VALIDATE: Has proper format with TBD or actual task IDs
610
- expect(planContent).toMatch(
611
- /## \w+\n<!-- beads-phase-id:\s*(TBD|[a-zA-Z0-9\-.]+)\s*-->\n### Tasks/
612
- );
613
- });
614
-
615
- it('should have valid beads-phase-id format (not TBD after plugin execution)', async () => {
616
- // Start development
617
- const result = await client.callTool('start_development', {
618
- workflow: 'epcc',
619
- commit_behaviour: 'none',
620
- });
621
-
622
- const response = assertToolSuccess(result) as StartDevelopmentResult;
623
- const planContent = await fs.readFile(response.plan_file_path, 'utf-8');
624
-
625
- // VALIDATE: Format must match - either TBD or actual task IDs with dots
626
- const validFormats = planContent.match(
627
- /<!-- beads-phase-id:\s*(TBD|[a-zA-Z0-9\-.]+)\s*-->/g
628
- );
629
- expect(validFormats).not.toBeNull();
630
- expect((validFormats || []).length).toBeGreaterThanOrEqual(1);
631
-
632
- // VALIDATE: No malformed placeholders
633
- expect(planContent).not.toMatch(/<!-- beads-phase-id:\s*-->/);
634
- });
635
-
636
- it('should preserve plan file structure when updating task IDs', async () => {
637
- // Start development
638
- const result = await client.callTool('start_development', {
639
- workflow: 'epcc',
640
- commit_behaviour: 'none',
641
- });
642
-
643
- const response = assertToolSuccess(result) as StartDevelopmentResult;
644
- const planContent = await fs.readFile(response.plan_file_path, 'utf-8');
645
-
646
- // VALIDATE: Plan structure intact
647
- expect(planContent).toContain('# Development Plan:');
648
- expect(planContent).toContain('## Goal');
649
- expect(planContent).toContain('## Explore');
650
- expect(planContent).toContain('## Plan');
651
- expect(planContent).toContain('## Code');
652
- expect(planContent).toContain('## Commit');
653
- expect(planContent).toContain('## Key Decisions');
654
- expect(planContent).toContain('## Notes');
655
-
656
- // VALIDATE: Markdown is valid
657
- expect(planContent).toMatch(/^# Development Plan:/m);
658
- expect(planContent).toMatch(/^## /m);
659
- });
660
- });
661
-
662
- // =========================================================================
663
- // 5. ERROR HANDLING AND DEGRADATION
664
- // =========================================================================
665
-
666
- describe('5. Beads Error Handling and Graceful Degradation', () => {
667
- let client: DirectServerInterface;
668
- let cleanup: () => Promise<void>;
669
-
670
- beforeEach(async () => {
671
- process.env.TASK_BACKEND = 'beads';
672
- setupBeadsCliMock();
673
-
674
- const scenario = await createSuiteIsolatedE2EScenario({
675
- suiteName: 'beads-error-handling',
676
- tempProjectFactory: createTempProjectWithDefaultStateMachine,
677
- });
678
- client = scenario.client;
679
- cleanup = scenario.cleanup;
680
- });
681
-
682
- afterEach(async () => {
683
- if (cleanup) {
684
- await cleanup();
685
- }
686
- delete process.env.TASK_BACKEND;
687
- });
688
-
689
- it('should create valid plan file even when beads unavailable', async () => {
690
- // Start development with beads enabled
691
- const result = await client.callTool('start_development', {
692
- workflow: 'epcc',
693
- commit_behaviour: 'none',
694
- });
695
-
696
- // Should NOT return error - graceful degradation
697
- expect(result).not.toHaveProperty('error');
698
-
699
- const response = assertToolSuccess(result) as StartDevelopmentResult;
700
-
701
- // VALIDATE: Plan file created successfully
702
- expect(response.plan_file_path).toBeDefined();
703
- expect(response.plan_file_path).toBeTruthy();
704
-
705
- // VALIDATE: Plan file exists and is readable
706
- const planContent = await fs.readFile(response.plan_file_path, 'utf-8');
707
- expect(planContent).toBeTruthy();
708
-
709
- // VALIDATE: Plan has beads markers even if tasks weren't created
710
- expect(planContent).toContain('<!-- beads-phase-id:');
711
- });
712
-
713
- it('should return success response even if beads operations fail', async () => {
714
- // Start development
715
- const result = await client.callTool('start_development', {
716
- workflow: 'epcc',
717
- commit_behaviour: 'none',
718
- });
719
-
720
- // VALIDATE: Response is successful (no error field)
721
- expect(result).not.toHaveProperty('error');
722
-
723
- // VALIDATE: Has all required response fields
724
- const response = assertToolSuccess(result) as StartDevelopmentResult;
725
- expect(response).toHaveProperty('phase');
726
- expect(response).toHaveProperty('plan_file_path');
727
- expect(response).toHaveProperty('instructions');
728
- });
729
- });
730
-
731
- // =========================================================================
732
- // 6. PLUGIN HOOK INTEGRATION
733
- // =========================================================================
734
-
735
- describe('6. Plugin Hook Integration', () => {
736
- let client: DirectServerInterface;
737
- let cleanup: () => Promise<void>;
738
-
739
- beforeEach(async () => {
740
- process.env.TASK_BACKEND = 'beads';
741
- setupBeadsCliMock();
742
-
743
- const scenario = await createSuiteIsolatedE2EScenario({
744
- suiteName: 'beads-plugin-hooks',
745
- tempProjectFactory: createTempProjectWithDefaultStateMachine,
746
- });
747
- client = scenario.client;
748
- cleanup = scenario.cleanup;
749
- });
750
-
751
- afterEach(async () => {
752
- if (cleanup) {
753
- await cleanup();
754
- }
755
- delete process.env.TASK_BACKEND;
756
- });
757
-
758
- it('should execute plugin hooks during start_development', async () => {
759
- // Start development - this should execute afterStartDevelopment hook
760
- const result = await client.callTool('start_development', {
761
- workflow: 'epcc',
762
- commit_behaviour: 'none',
763
- });
764
-
765
- const response = assertToolSuccess(result) as StartDevelopmentResult;
766
-
767
- // VALIDATE: Plan file was created by plugin
768
- const planContent = await fs.readFile(response.plan_file_path, 'utf-8');
769
- expect(planContent).toContain('<!-- beads-phase-id:');
770
-
771
- // VALIDATE: Response indicates successful hook execution
772
- expect(response.plan_file_path).toBeTruthy();
773
- expect(response.instructions).toBeTruthy();
774
- });
775
-
776
- it('should coordinate afterStartDevelopment and afterPlanFileCreated hooks', async () => {
777
- // Start development
778
- const result = await client.callTool('start_development', {
779
- workflow: 'epcc',
780
- commit_behaviour: 'none',
781
- });
782
-
783
- const response = assertToolSuccess(result) as StartDevelopmentResult;
784
- const planContent = await fs.readFile(response.plan_file_path, 'utf-8');
785
-
786
- // VALIDATE: Plan file has beads structure (created by hooks)
787
- expect(planContent).toContain('<!-- beads-phase-id:');
788
-
789
- // VALIDATE: Plan has proper content from both hooks
790
- expect(planContent).toContain('## Explore');
791
- expect(planContent).toContain('## Plan');
792
- expect(planContent).toContain('## Code');
793
- expect(planContent).toContain('## Commit');
794
-
795
- // VALIDATE: Each phase has a beads marker
796
- const phaseMatches = planContent.match(/## \w+\n<!-- beads-phase-id:/g);
797
- expect(phaseMatches).not.toBeNull();
798
- expect((phaseMatches || []).length).toBeGreaterThanOrEqual(4);
799
- });
800
-
801
- it('should maintain system in consistent state after hook execution', async () => {
802
- // Start development
803
- const startResult = await client.callTool('start_development', {
804
- workflow: 'epcc',
805
- commit_behaviour: 'none',
806
- });
807
-
808
- const startResponse = assertToolSuccess(
809
- startResult
810
- ) as StartDevelopmentResult;
811
-
812
- // Get current state
813
- const whatsNextResult = await client.callTool('whats_next', {
814
- context: 'Testing hook consistency',
815
- user_input: 'What should I do?',
816
- conversation_summary: 'Just started development',
817
- recent_messages: [],
818
- });
819
-
820
- const whatsNextResponse = assertToolSuccess(
821
- whatsNextResult
822
- ) as WhatsNextResult;
823
-
824
- // VALIDATE: State is consistent
825
- expect(whatsNextResponse.phase).toBe('explore');
826
- expect(whatsNextResponse.plan_file_path).toBe(
827
- startResponse.plan_file_path
828
- );
829
-
830
- // VALIDATE: Plan file is still valid
831
- const planContent = await fs.readFile(
832
- whatsNextResponse.plan_file_path,
833
- 'utf-8'
834
- );
835
- expect(planContent).toContain('<!-- beads-phase-id:');
836
- });
837
- });
838
-
839
- // =========================================================================
840
- // 7. BEADS ACTIVATION AND ENVIRONMENT CHECK
841
- // =========================================================================
842
-
843
- describe('7. Beads Environment Activation', () => {
844
- it('should apply beads when TASK_BACKEND=beads is set', async () => {
845
- process.env.TASK_BACKEND = 'beads';
846
- setupBeadsCliMock();
847
-
848
- const scenario = await createSuiteIsolatedE2EScenario({
849
- suiteName: 'beads-activation-with',
850
- tempProjectFactory: createTempProjectWithDefaultStateMachine,
851
- });
852
-
853
- const result = await scenario.client.callTool('start_development', {
854
- workflow: 'epcc',
855
- commit_behaviour: 'none',
856
- });
857
-
858
- const response = assertToolSuccess(result) as StartDevelopmentResult;
859
- const planContent = await fs.readFile(response.plan_file_path, 'utf-8');
860
-
861
- await scenario.cleanup();
862
- delete process.env.TASK_BACKEND;
863
-
864
- // VALIDATE: Beads features enabled
865
- expect(planContent).toContain('<!-- beads-phase-id:');
866
- });
867
-
868
- it('should auto-detect and apply beads when TASK_BACKEND is not set and bd is available', async () => {
869
- // Ensure env var is NOT set - auto-detection will check for bd command
870
- delete process.env.TASK_BACKEND;
871
- setupBeadsCliMock();
872
-
873
- const scenario = await createSuiteIsolatedE2EScenario({
874
- suiteName: 'beads-activation-auto',
875
- tempProjectFactory: createTempProjectWithDefaultStateMachine,
876
- });
877
-
878
- const result = await scenario.client.callTool('start_development', {
879
- workflow: 'epcc',
880
- commit_behaviour: 'none',
881
- });
882
-
883
- const response = assertToolSuccess(result) as StartDevelopmentResult;
884
- const planContent = await fs.readFile(response.plan_file_path, 'utf-8');
885
-
886
- await scenario.cleanup();
887
-
888
- // VALIDATE: Beads features auto-detected and enabled
889
- expect(planContent).toContain('<!-- beads-phase-id:');
890
- });
891
-
892
- it('should auto-detect beads when TASK_BACKEND has invalid value and bd is available', async () => {
893
- // Invalid values are treated as "not set" - triggers auto-detection
894
- process.env.TASK_BACKEND = 'other-backend';
895
- setupBeadsCliMock();
896
-
897
- const scenario = await createSuiteIsolatedE2EScenario({
898
- suiteName: 'beads-activation-invalid',
899
- tempProjectFactory: createTempProjectWithDefaultStateMachine,
900
- });
901
-
902
- const result = await scenario.client.callTool('start_development', {
903
- workflow: 'epcc',
904
- commit_behaviour: 'none',
905
- });
906
-
907
- const response = assertToolSuccess(result) as StartDevelopmentResult;
908
- const planContent = await fs.readFile(response.plan_file_path, 'utf-8');
909
-
910
- await scenario.cleanup();
911
- delete process.env.TASK_BACKEND;
912
-
913
- // VALIDATE: Beads features auto-detected and enabled
914
- expect(planContent).toContain('<!-- beads-phase-id:');
915
- });
916
-
917
- it('should NOT apply beads when TASK_BACKEND=markdown explicitly', async () => {
918
- // Explicitly setting markdown should disable beads even if bd is available
919
- process.env.TASK_BACKEND = 'markdown';
920
-
921
- const scenario = await createSuiteIsolatedE2EScenario({
922
- suiteName: 'beads-activation-disabled',
923
- tempProjectFactory: createTempProjectWithDefaultStateMachine,
924
- });
925
-
926
- const result = await scenario.client.callTool('start_development', {
927
- workflow: 'epcc',
928
- commit_behaviour: 'none',
929
- });
930
-
931
- const response = assertToolSuccess(result) as StartDevelopmentResult;
932
- const planContent = await fs.readFile(response.plan_file_path, 'utf-8');
933
-
934
- await scenario.cleanup();
935
- delete process.env.TASK_BACKEND;
936
-
937
- // VALIDATE: Beads features NOT enabled when markdown explicitly set
938
- expect(planContent).not.toContain('<!-- beads-phase-id:');
939
- });
940
- });
941
-
942
- // =========================================================================
943
- // 8. CONTENT VALIDATION AND SEMANTIC CHECKS
944
- // =========================================================================
945
-
946
- describe('8. Beads Content Validation and Semantic Checks', () => {
947
- let client: DirectServerInterface;
948
- let cleanup: () => Promise<void>;
949
-
950
- beforeEach(async () => {
951
- process.env.TASK_BACKEND = 'beads';
952
- setupBeadsCliMock();
953
-
954
- const scenario = await createSuiteIsolatedE2EScenario({
955
- suiteName: 'beads-content-validation',
956
- tempProjectFactory: createTempProjectWithDefaultStateMachine,
957
- });
958
- client = scenario.client;
959
- cleanup = scenario.cleanup;
960
- });
961
-
962
- afterEach(async () => {
963
- if (cleanup) {
964
- await cleanup();
965
- }
966
- delete process.env.TASK_BACKEND;
967
- });
968
-
969
- it('should generate substantive beads instructions with actual guidance', async () => {
970
- // Start development
971
- const startResult = await client.callTool('start_development', {
972
- workflow: 'epcc',
973
- commit_behaviour: 'none',
974
- });
975
- assertToolSuccess(startResult);
976
-
977
- // Get instructions
978
- const whatsNextResult = await client.callTool('whats_next', {
979
- context: 'Testing instruction quality',
980
- user_input: 'What should I do?',
981
- conversation_summary: 'Started development',
982
- recent_messages: [],
983
- });
984
-
985
- const response = assertToolSuccess(whatsNextResult) as WhatsNextResult;
986
- const instructions = response.instructions;
987
-
988
- // VALIDATE: Instructions are substantive (not just placeholders)
989
- expect(instructions.length).toBeGreaterThan(MIN_INSTRUCTION_LENGTH);
990
-
991
- // VALIDATE: Instructions contain beads-specific guidance
992
- validateBeadsInstructions(instructions);
993
-
994
- // VALIDATE: Instructions mention specific phases
995
- expect(instructions).toMatch(/explore|plan|code|commit/i);
996
- });
997
-
998
- it('should create valid markdown plan file structure', async () => {
999
- // Start development
1000
- const result = await client.callTool('start_development', {
1001
- workflow: 'epcc',
1002
- commit_behaviour: 'none',
1003
- });
1004
-
1005
- const response = assertToolSuccess(result) as StartDevelopmentResult;
1006
- const planContent = await fs.readFile(response.plan_file_path, 'utf-8');
1007
-
1008
- // VALIDATE: Markdown structure
1009
- expect(planContent).toMatch(/^# /m); // Title
1010
- expect(planContent).toMatch(/^## /m); // Sections
1011
- expect(planContent).toMatch(/^### /m); // Subsections
1012
-
1013
- // VALIDATE: No malformed headers
1014
- expect(planContent).not.toMatch(/^#$/m); // Empty header
1015
- expect(planContent).not.toMatch(/^## $/m); // Empty section
1016
-
1017
- // VALIDATE: Beads markers are valid comments
1018
- expect(planContent).toMatch(/<!-- beads-phase-id:/);
1019
- expect(planContent).not.toMatch(/<!-- -->/); // Empty comment
1020
- });
1021
-
1022
- it('should include beads CLI guidance in plan file', async () => {
1023
- // Start development
1024
- const result = await client.callTool('start_development', {
1025
- workflow: 'epcc',
1026
- commit_behaviour: 'none',
1027
- });
1028
-
1029
- const response = assertToolSuccess(result) as StartDevelopmentResult;
1030
- const planContent = await fs.readFile(response.plan_file_path, 'utf-8');
1031
-
1032
- // VALIDATE: Plan mentions beads CLI
1033
- expect(planContent).toContain('bd');
1034
- expect(planContent).toContain('Tasks managed via');
1035
- expect(planContent).toContain('beads CLI');
1036
- });
1037
- });
1038
-
1039
- // =========================================================================
1040
- // 9. TASK ID EXTRACTION AND VALIDATION
1041
- // =========================================================================
1042
-
1043
- describe('9. Task ID Extraction and Validation', () => {
1044
- let client: DirectServerInterface;
1045
- let cleanup: () => Promise<void>;
1046
-
1047
- beforeEach(async () => {
1048
- process.env.TASK_BACKEND = 'beads';
1049
- setupBeadsCliMock();
1050
-
1051
- const scenario = await createSuiteIsolatedE2EScenario({
1052
- suiteName: 'beads-task-id-extraction',
1053
- tempProjectFactory: createTempProjectWithDefaultStateMachine,
1054
- });
1055
- client = scenario.client;
1056
- cleanup = scenario.cleanup;
1057
- });
1058
-
1059
- afterEach(async () => {
1060
- if (cleanup) {
1061
- await cleanup();
1062
- }
1063
- delete process.env.TASK_BACKEND;
1064
- });
1065
-
1066
- it('should extract beads phase IDs from plan file', async () => {
1067
- // Start development
1068
- const result = await client.callTool('start_development', {
1069
- workflow: 'epcc',
1070
- commit_behaviour: 'none',
1071
- });
1072
-
1073
- const response = assertToolSuccess(result) as StartDevelopmentResult;
1074
- const planContent = await fs.readFile(response.plan_file_path, 'utf-8');
1075
-
1076
- // VALIDATE: Extract IDs and verify format
1077
- const extractedIds = extractBeadsPhaseIds(planContent);
1078
-
1079
- // VALIDATE: Should have extracted some IDs
1080
- expect(extractedIds).toBeDefined();
1081
- expect(Array.isArray(extractedIds)).toBe(true);
1082
-
1083
- // VALIDATE: Each ID should be a non-empty string
1084
- for (const id of extractedIds) {
1085
- expect(typeof id).toBe('string');
1086
- expect(id.length).toBeGreaterThan(0);
1087
- }
1088
- });
1089
-
1090
- it('should validate beads phase ID format', async () => {
1091
- // Start development
1092
- const result = await client.callTool('start_development', {
1093
- workflow: 'epcc',
1094
- commit_behaviour: 'none',
1095
- });
1096
-
1097
- const response = assertToolSuccess(result) as StartDevelopmentResult;
1098
- const planContent = await fs.readFile(response.plan_file_path, 'utf-8');
1099
-
1100
- // VALIDATE: IDs must match pattern (alphanumeric, hyphens, dots)
1101
- const allMatches = planContent.match(
1102
- /<!-- beads-phase-id:\s*([a-zA-Z0-9\-.]+)\s*-->/g
1103
- );
1104
- expect(allMatches).not.toBeNull();
1105
- expect((allMatches || []).length).toBeGreaterThan(0);
1106
-
1107
- // VALIDATE: Each match is properly formatted
1108
- for (const match of allMatches || []) {
1109
- expect(match).toMatch(/^<!-- beads-phase-id:/);
1110
- expect(match).toMatch(/-->$/);
1111
- }
1112
- });
1113
-
1114
- it('should not have empty beads-phase-id placeholders', async () => {
1115
- // Start development
1116
- const result = await client.callTool('start_development', {
1117
- workflow: 'epcc',
1118
- commit_behaviour: 'none',
1119
- });
1120
-
1121
- const response = assertToolSuccess(result) as StartDevelopmentResult;
1122
- const planContent = await fs.readFile(response.plan_file_path, 'utf-8');
1123
-
1124
- // VALIDATE: No malformed empty placeholders
1125
- expect(planContent).not.toMatch(/<!-- beads-phase-id:\s*-->/);
1126
- expect(planContent).not.toMatch(/<!-- beads-phase-id: -->/);
1127
- });
1128
-
1129
- it('should replace TBD placeholders with actual task IDs or keep TBD', async () => {
1130
- // Start development
1131
- const result = await client.callTool('start_development', {
1132
- workflow: 'epcc',
1133
- commit_behaviour: 'none',
1134
- });
1135
-
1136
- const response = assertToolSuccess(result) as StartDevelopmentResult;
1137
- const planContent = await fs.readFile(response.plan_file_path, 'utf-8');
1138
-
1139
- // VALIDATE: All placeholders are either TBD or actual IDs (not empty)
1140
- const placeholders = planContent.match(
1141
- /<!-- beads-phase-id:\s*(TBD|[a-zA-Z0-9\-.]+)\s*-->/g
1142
- );
1143
- expect(placeholders).not.toBeNull();
1144
-
1145
- for (const placeholder of placeholders || []) {
1146
- // Each must have either TBD or an actual ID
1147
- expect(placeholder).toMatch(/TBD|[a-zA-Z0-9\-.]+/);
1148
- }
1149
- });
1150
- });
1151
-
1152
- // =========================================================================
1153
- // 10. PHASE TRANSITION AND TASK COMPLETION VALIDATION
1154
- // =========================================================================
1155
-
1156
- describe('10. Phase Transition and Task Completion', () => {
1157
- let client: DirectServerInterface;
1158
- let cleanup: () => Promise<void>;
1159
-
1160
- beforeEach(async () => {
1161
- process.env.TASK_BACKEND = 'beads';
1162
- setupBeadsCliMock();
1163
-
1164
- const scenario = await createSuiteIsolatedE2EScenario({
1165
- suiteName: 'beads-phase-transitions',
1166
- tempProjectFactory: createTempProjectWithDefaultStateMachine,
1167
- });
1168
- client = scenario.client;
1169
- cleanup = scenario.cleanup;
1170
- });
1171
-
1172
- afterEach(async () => {
1173
- if (cleanup) {
1174
- await cleanup();
1175
- }
1176
- delete process.env.TASK_BACKEND;
1177
- });
1178
-
1179
- it('should maintain beads markers through phase transitions', async () => {
1180
- // Start development
1181
- const startResult = await client.callTool('start_development', {
1182
- workflow: 'epcc',
1183
- commit_behaviour: 'none',
1184
- });
1185
-
1186
- const startResponse = assertToolSuccess(
1187
- startResult
1188
- ) as StartDevelopmentResult;
1189
- let planContent = await fs.readFile(
1190
- startResponse.plan_file_path,
1191
- 'utf-8'
1192
- );
1193
- const initialMarkers = planContent.match(/<!-- beads-phase-id:/g);
1194
-
1195
- // VALIDATE: Initial markers exist
1196
- expect(initialMarkers).not.toBeNull();
1197
- expect((initialMarkers || []).length).toBeGreaterThanOrEqual(
1198
- MIN_PHASES_WITH_MARKERS
1199
- );
1200
-
1201
- // Transition to next phase
1202
- const transitionResult = await client.callTool('proceed_to_phase', {
1203
- target_phase: 'plan',
1204
- reason: 'exploration complete',
1205
- review_state: 'not-required',
1206
- });
1207
-
1208
- expect(transitionResult).not.toHaveProperty('error');
1209
-
1210
- // VALIDATE: Markers still present after transition
1211
- planContent = await fs.readFile(startResponse.plan_file_path, 'utf-8');
1212
- const afterTransitionMarkers = planContent.match(/<!-- beads-phase-id:/g);
1213
-
1214
- expect(afterTransitionMarkers).not.toBeNull();
1215
- expect(afterTransitionMarkers).toEqual(initialMarkers);
1216
- });
1217
-
1218
- it('should preserve plan file structure across multiple phase transitions', async () => {
1219
- // Start development
1220
- const result = await client.callTool('start_development', {
1221
- workflow: 'epcc',
1222
- commit_behaviour: 'none',
1223
- });
1224
-
1225
- const response = assertToolSuccess(result) as StartDevelopmentResult;
1226
- const initialContent = await fs.readFile(
1227
- response.plan_file_path,
1228
- 'utf-8'
1229
- );
1230
-
1231
- // Verify initial structure
1232
- expect(initialContent).toContain('## Explore');
1233
- expect(initialContent).toContain('## Plan');
1234
-
1235
- // Transition through phases
1236
- await client.callTool('proceed_to_phase', {
1237
- target_phase: 'plan',
1238
- reason: 'exploration complete',
1239
- review_state: 'not-required',
1240
- });
1241
-
1242
- // VALIDATE: Structure preserved
1243
- const afterFirstTransition = await fs.readFile(
1244
- response.plan_file_path,
1245
- 'utf-8'
1246
- );
1247
- expect(afterFirstTransition).toContain('## Explore');
1248
- expect(afterFirstTransition).toContain('## Plan');
1249
- expect(afterFirstTransition).toContain('## Code');
1250
- });
1251
-
1252
- it('should keep beads markers consistent with phase structure', async () => {
1253
- // Start development
1254
- const result = await client.callTool('start_development', {
1255
- workflow: 'epcc',
1256
- commit_behaviour: 'none',
1257
- });
1258
-
1259
- const response = assertToolSuccess(result) as StartDevelopmentResult;
1260
- const planContent = await fs.readFile(response.plan_file_path, 'utf-8');
1261
-
1262
- // VALIDATE: Each phase header has a corresponding beads marker
1263
- const phasePattern = /## (\w+)\n<!-- beads-phase-id:/g;
1264
- const phaseMatches: string[] = [];
1265
- let match: RegExpExecArray | null;
1266
- while ((match = phasePattern.exec(planContent)) !== null) {
1267
- if (match[1]) {
1268
- phaseMatches.push(match[1]);
1269
- }
1270
- }
1271
-
1272
- // VALIDATE: Found multiple phases with markers
1273
- expect(phaseMatches.length).toBeGreaterThanOrEqual(
1274
- MIN_PHASES_WITH_MARKERS
1275
- );
1276
-
1277
- // VALIDATE: Phases are expected workflow phases
1278
- const expectedPhases = ['Explore', 'Plan', 'Code', 'Commit'];
1279
- for (const phase of phaseMatches) {
1280
- expect(expectedPhases).toContain(phase);
1281
- }
1282
- });
1283
- });
1284
-
1285
- // =========================================================================
1286
- // 11. PLUGIN HOOK EXECUTION AND SIDE EFFECTS
1287
- // =========================================================================
1288
-
1289
- describe('11. Plugin Hook Execution and Side Effects', () => {
1290
- let client: DirectServerInterface;
1291
- let cleanup: () => Promise<void>;
1292
-
1293
- beforeEach(async () => {
1294
- process.env.TASK_BACKEND = 'beads';
1295
- setupBeadsCliMock();
1296
-
1297
- const scenario = await createSuiteIsolatedE2EScenario({
1298
- suiteName: 'beads-hook-execution',
1299
- tempProjectFactory: createTempProjectWithDefaultStateMachine,
1300
- });
1301
- client = scenario.client;
1302
- cleanup = scenario.cleanup;
1303
- });
1304
-
1305
- afterEach(async () => {
1306
- if (cleanup) {
1307
- await cleanup();
1308
- }
1309
- delete process.env.TASK_BACKEND;
1310
- });
1311
-
1312
- it('should execute hooks and update plan file with markers', async () => {
1313
- // Start development - triggers afterStartDevelopment hook
1314
- const result = await client.callTool('start_development', {
1315
- workflow: 'epcc',
1316
- commit_behaviour: 'none',
1317
- });
1318
-
1319
- const response = assertToolSuccess(result) as StartDevelopmentResult;
1320
- const planContent = await fs.readFile(response.plan_file_path, 'utf-8');
1321
-
1322
- // VALIDATE: Hook executed and updated plan file
1323
- expect(planContent).toContain('<!-- beads-phase-id:');
1324
-
1325
- // VALIDATE: Plan has beads-specific guidance
1326
- expect(planContent).toContain('Tasks managed via');
1327
- expect(planContent).toContain('bd');
1328
- });
1329
-
1330
- it('should ensure whats_next respects beads plan markers', async () => {
1331
- // Start development
1332
- const startResult = await client.callTool('start_development', {
1333
- workflow: 'epcc',
1334
- commit_behaviour: 'none',
1335
- });
1336
-
1337
- assertToolSuccess(startResult) as StartDevelopmentResult;
1338
-
1339
- // Get whats_next guidance
1340
- const whatsNextResult = await client.callTool('whats_next', {
1341
- context: 'Continuing development',
1342
- user_input: 'What should I do?',
1343
- conversation_summary: 'Started with beads',
1344
- recent_messages: [],
1345
- });
1346
-
1347
- const whatsNextResponse = assertToolSuccess(
1348
- whatsNextResult
1349
- ) as WhatsNextResult;
1350
-
1351
- // VALIDATE: Instructions include beads guidance
1352
- expect(whatsNextResponse.instructions).toContain('bd');
1353
- expect(whatsNextResponse.instructions).toContain('Use ONLY bd CLI tool');
1354
-
1355
- // VALIDATE: Plan file still has markers
1356
- const planContent = await fs.readFile(
1357
- whatsNextResponse.plan_file_path,
1358
- 'utf-8'
1359
- );
1360
- expect(planContent).toContain('<!-- beads-phase-id:');
1361
- });
1362
-
1363
- it('should maintain conversation state through plugin hook execution', async () => {
1364
- // Start development
1365
- const startResult = await client.callTool('start_development', {
1366
- workflow: 'epcc',
1367
- commit_behaviour: 'none',
1368
- });
1369
-
1370
- assertToolSuccess(startResult) as StartDevelopmentResult;
1371
-
1372
- // Get whats_next
1373
- const whatsNextResult = await client.callTool('whats_next', {
1374
- context: 'Testing conversation preservation',
1375
- user_input: 'What is the current state?',
1376
- conversation_summary: 'Started development',
1377
- recent_messages: [],
1378
- });
1379
-
1380
- const whatsNextResponse = assertToolSuccess(
1381
- whatsNextResult
1382
- ) as WhatsNextResult;
1383
-
1384
- expect(whatsNextResponse.phase).toBe('explore');
1385
- });
1386
- });
1387
-
1388
- // =========================================================================
1389
- // 12. MULTI-WORKFLOW BEADS SUPPORT
1390
- // =========================================================================
1391
-
1392
- describe('12. Multi-Workflow Beads Support', () => {
1393
- let cleanup: () => Promise<void>;
1394
-
1395
- afterEach(async () => {
1396
- if (cleanup) {
1397
- await cleanup();
1398
- }
1399
- });
1400
-
1401
- it('should apply beads markers to waterfall workflow', async () => {
1402
- process.env.TASK_BACKEND = 'beads';
1403
- setupBeadsCliMock();
1404
-
1405
- const scenario = await createSuiteIsolatedE2EScenario({
1406
- suiteName: 'beads-waterfall',
1407
- tempProjectFactory: createTempProjectWithDefaultStateMachine,
1408
- });
1409
- cleanup = scenario.cleanup;
1410
-
1411
- const result = await scenario.client.callTool('start_development', {
1412
- workflow: 'waterfall',
1413
- commit_behaviour: 'none',
1414
- });
1415
-
1416
- const response = assertToolSuccess(result) as StartDevelopmentResult;
1417
- const planContent = await fs.readFile(response.plan_file_path, 'utf-8');
1418
-
1419
- // VALIDATE: Beads markers present in waterfall
1420
- expect(planContent).toContain('<!-- beads-phase-id:');
1421
-
1422
- // VALIDATE: Waterfall phases have markers
1423
- const phases = ['Requirements', 'Design', 'Implementation'];
1424
- for (const phase of phases) {
1425
- expect(planContent).toContain(`## ${phase}`);
1426
- }
1427
-
1428
- delete process.env.TASK_BACKEND;
1429
- });
1430
-
1431
- it('should apply beads markers to tdd workflow', async () => {
1432
- process.env.TASK_BACKEND = 'beads';
1433
- setupBeadsCliMock();
1434
-
1435
- const scenario = await createSuiteIsolatedE2EScenario({
1436
- suiteName: 'beads-tdd',
1437
- tempProjectFactory: createTempProjectWithDefaultStateMachine,
1438
- });
1439
- cleanup = scenario.cleanup;
1440
-
1441
- const result = await scenario.client.callTool('start_development', {
1442
- workflow: 'tdd',
1443
- commit_behaviour: 'none',
1444
- });
1445
-
1446
- const response = assertToolSuccess(result) as StartDevelopmentResult;
1447
- const planContent = await fs.readFile(response.plan_file_path, 'utf-8');
1448
-
1449
- // VALIDATE: Beads markers present in tdd
1450
- expect(planContent).toContain('<!-- beads-phase-id:');
1451
-
1452
- // VALIDATE: TDD phases have markers
1453
- const phaseMatches = planContent.match(/## \w+\n<!-- beads-phase-id:/g);
1454
- expect(phaseMatches).not.toBeNull();
1455
- expect((phaseMatches || []).length).toBeGreaterThanOrEqual(3);
1456
-
1457
- delete process.env.TASK_BACKEND;
1458
- });
1459
-
1460
- it('should apply beads markers to bugfix workflow', async () => {
1461
- process.env.TASK_BACKEND = 'beads';
1462
- setupBeadsCliMock();
1463
-
1464
- const scenario = await createSuiteIsolatedE2EScenario({
1465
- suiteName: 'beads-bugfix',
1466
- tempProjectFactory: createTempProjectWithDefaultStateMachine,
1467
- });
1468
- cleanup = scenario.cleanup;
1469
-
1470
- const result = await scenario.client.callTool('start_development', {
1471
- workflow: 'bugfix',
1472
- commit_behaviour: 'none',
1473
- });
1474
-
1475
- const response = assertToolSuccess(result) as StartDevelopmentResult;
1476
- const planContent = await fs.readFile(response.plan_file_path, 'utf-8');
1477
-
1478
- // VALIDATE: Beads markers present in bugfix
1479
- expect(planContent).toContain('<!-- beads-phase-id:');
1480
-
1481
- // VALIDATE: Bugfix phases have markers
1482
- const phaseMatches = planContent.match(/## \w+\n<!-- beads-phase-id:/g);
1483
- expect(phaseMatches).not.toBeNull();
1484
- expect((phaseMatches || []).length).toBeGreaterThanOrEqual(2);
1485
-
1486
- delete process.env.TASK_BACKEND;
1487
- });
1488
- });
1489
-
1490
- // =========================================================================
1491
- // 13. BEADS INTEGRATION ROBUSTNESS
1492
- // =========================================================================
1493
-
1494
- describe('13. Beads Integration Robustness', () => {
1495
- let client: DirectServerInterface;
1496
- let cleanup: () => Promise<void>;
1497
-
1498
- beforeEach(async () => {
1499
- process.env.TASK_BACKEND = 'beads';
1500
- setupBeadsCliMock();
1501
-
1502
- const scenario = await createSuiteIsolatedE2EScenario({
1503
- suiteName: 'beads-robustness',
1504
- tempProjectFactory: createTempProjectWithDefaultStateMachine,
1505
- });
1506
- client = scenario.client;
1507
- cleanup = scenario.cleanup;
1508
- });
1509
-
1510
- afterEach(async () => {
1511
- if (cleanup) {
1512
- await cleanup();
1513
- }
1514
- delete process.env.TASK_BACKEND;
1515
- });
1516
-
1517
- it('should not break normal functionality when beads enabled', async () => {
1518
- // Start development
1519
- const startResult = await client.callTool('start_development', {
1520
- workflow: 'epcc',
1521
- commit_behaviour: 'none',
1522
- });
1523
-
1524
- const startResponse = assertToolSuccess(
1525
- startResult
1526
- ) as StartDevelopmentResult;
1527
-
1528
- // VALIDATE: All standard response properties present
1529
- expect(startResponse).toHaveProperty('phase');
1530
- expect(startResponse).toHaveProperty('plan_file_path');
1531
- expect(startResponse).toHaveProperty('instructions');
1532
-
1533
- // VALIDATE: Can transition phases normally
1534
- const transitionResult = await client.callTool('proceed_to_phase', {
1535
- target_phase: 'plan',
1536
- reason: 'exploration complete',
1537
- review_state: 'not-required',
1538
- });
1539
-
1540
- expect(transitionResult).not.toHaveProperty('error');
1541
-
1542
- // VALIDATE: Can get whats_next normally
1543
- const whatsNextResult = await client.callTool('whats_next', {
1544
- context: 'Testing',
1545
- user_input: 'What now?',
1546
- conversation_summary: 'Testing beads',
1547
- recent_messages: [],
1548
- });
1549
-
1550
- expect(whatsNextResult).not.toHaveProperty('error');
1551
- });
1552
-
1553
- it('should have consistent beads markers across all workflow operations', async () => {
1554
- // Start
1555
- const startResult = await client.callTool('start_development', {
1556
- workflow: 'epcc',
1557
- commit_behaviour: 'none',
1558
- });
1559
-
1560
- const startResponse = assertToolSuccess(
1561
- startResult
1562
- ) as StartDevelopmentResult;
1563
- let planContent = await fs.readFile(
1564
- startResponse.plan_file_path,
1565
- 'utf-8'
1566
- );
1567
- const startMarkers = planContent.match(/<!-- beads-phase-id:/g);
1568
-
1569
- // Get whats_next
1570
- const whatsNextResult = await client.callTool('whats_next', {
1571
- context: 'Checking markers',
1572
- user_input: 'What should I do?',
1573
- conversation_summary: 'Just started',
1574
- recent_messages: [],
1575
- });
1576
-
1577
- expect(whatsNextResult).not.toHaveProperty('error');
1578
-
1579
- // Transition
1580
- await client.callTool('proceed_to_phase', {
1581
- target_phase: 'plan',
1582
- reason: 'ready',
1583
- review_state: 'not-required',
1584
- });
1585
-
1586
- planContent = await fs.readFile(startResponse.plan_file_path, 'utf-8');
1587
- const transitionMarkers = planContent.match(/<!-- beads-phase-id:/g);
1588
-
1589
- // VALIDATE: Markers consistent
1590
- expect(startMarkers).toEqual(transitionMarkers);
1591
- });
1592
-
1593
- it('should generate consistent beads instructions across operations', async () => {
1594
- // Start development
1595
- const startResult = await client.callTool('start_development', {
1596
- workflow: 'epcc',
1597
- commit_behaviour: 'none',
1598
- });
1599
-
1600
- assertToolSuccess(startResult);
1601
-
1602
- // Get instructions in whats_next
1603
- const whatsNextResult = await client.callTool('whats_next', {
1604
- context: 'Testing instruction consistency',
1605
- user_input: 'What should I do?',
1606
- conversation_summary: 'Started',
1607
- recent_messages: [],
1608
- });
1609
-
1610
- const whatsNextResponse = assertToolSuccess(
1611
- whatsNextResult
1612
- ) as WhatsNextResult;
1613
-
1614
- // VALIDATE: Instructions have beads content
1615
- validateBeadsInstructions(whatsNextResponse.instructions);
1616
-
1617
- // VALIDATE: Instructions contain expected guidance
1618
- expect(whatsNextResponse.instructions).toContain('bd');
1619
- expect(whatsNextResponse.instructions).toContain('Task');
1620
- expect(whatsNextResponse.instructions).toContain('Use ONLY bd CLI tool');
1621
- });
1622
- });
1623
- });