@codemcp/workflows 5.0.0 → 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,1410 +0,0 @@
1
- /**
2
- * Plugin System Integration Tests - REWRITTEN WITH PROPER ASSERTIONS
3
- *
4
- * Comprehensive end-to-end tests validating that the plugin system works correctly.
5
- *
6
- * This test suite focuses on:
7
- * 1. Contract validation - ensuring all responses meet defined interfaces
8
- * 2. Semantic validation - verifying values are valid and meaningful
9
- * 3. Plugin isolation - ensuring no internal plugin details leak
10
- * 4. Multi-workflow support - testing different workflow types
11
- * 5. State consistency - maintaining conversation state across calls
12
- *
13
- * DESIGN PRINCIPLES ENFORCED:
14
- * - NO fuzzy assertions with || operators
15
- * - NO type-only checks without semantic validation
16
- * - NO unsafe casts or assumptions
17
- * - ALL properties validated explicitly
18
- * - UUID format validation for IDs
19
- * - File existence checks for paths
20
- * - Phase validity checks against workflow
21
- */
22
-
23
- import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
24
- import { createTempProjectWithDefaultStateMachine } from '../utils/temp-files';
25
- import {
26
- DirectServerInterface,
27
- createSuiteIsolatedE2EScenario,
28
- assertToolSuccess,
29
- initializeDevelopment,
30
- } from '../utils/e2e-test-setup';
31
- import { promises as fs } from 'node:fs';
32
- import { McpToolResponse } from '../../src/types';
33
- import type { StartDevelopmentResult } from '../../src/tool-handlers/start-development';
34
- import type { ProceedToPhaseResult } from '../../src/tool-handlers/proceed-to-phase';
35
- import type { WhatsNextResult } from '../../src/tool-handlers/whats-next';
36
-
37
- vi.unmock('fs');
38
- vi.unmock('fs/promises');
39
-
40
- // ============================================================================
41
- // TEST CONSTANTS (Remove magic numbers)
42
- // ============================================================================
43
-
44
- // Minimum length for substantive instructions
45
- // Must be long enough to contain meaningful guidance, not just placeholders
46
- const MIN_INSTRUCTION_LENGTH = 100;
47
-
48
- // Expected initial phases for different workflows
49
- const WORKFLOW_INITIAL_PHASES = {
50
- waterfall: 'requirements',
51
- epcc: 'explore',
52
- tdd: 'explore',
53
- minor: 'explore',
54
- bugfix: ['reproduce', 'analyze'], // Can start with either
55
- };
56
-
57
- // ============================================================================
58
- // VALIDATION HELPER FUNCTIONS
59
- // ============================================================================
60
- // These helpers enforce strict contract validation and prevent assertion
61
- // repetition. Each helper comprehensively validates one response type.
62
-
63
- /**
64
- * Validates that a value is a non-empty string
65
- */
66
- function isNonEmptyString(value: unknown): value is string {
67
- return typeof value === 'string' && value.length > 0;
68
- }
69
-
70
- /**
71
- * Validates that instructions are substantive (not just whitespace)
72
- * VALIDATE: Instructions must contain meaningful content to guide users
73
- */
74
- function isSubstantiveContent(value: string): boolean {
75
- // Must be >100 chars and contain development-related keywords
76
- return (
77
- value.length > 100 &&
78
- /\b(phase|development|task|workflow|requirements|design|implementation|plan)\b/i.test(
79
- value
80
- )
81
- );
82
- }
83
-
84
- /**
85
- * Comprehensive validation for StartDevelopmentResult
86
- * VALIDATE: Response must have all required properties with correct types and values
87
- */
88
- function assertValidStartDevelopmentResponse(
89
- response: unknown
90
- ): StartDevelopmentResult {
91
- expect(response).toBeDefined();
92
- expect(typeof response).toBe('object');
93
- expect(response).not.toBeNull();
94
-
95
- // Type guard with direct cast (no chained as unknown as)
96
- if (typeof response !== 'object' || response === null) {
97
- throw new Error('Response must be an object');
98
- }
99
- const result = response as Record<string, unknown>;
100
-
101
- expect(result).toHaveProperty('phase');
102
- expect(isNonEmptyString(result.phase)).toBe(true);
103
-
104
- expect(result).toHaveProperty('plan_file_path');
105
- expect(isNonEmptyString(result.plan_file_path)).toBe(true);
106
-
107
- expect(result).toHaveProperty('instructions');
108
- expect(isNonEmptyString(result.instructions)).toBe(true);
109
- expect(isSubstantiveContent(result.instructions as string)).toBe(true);
110
-
111
- if (result.workflowDocumentationUrl !== undefined) {
112
- expect(typeof result.workflowDocumentationUrl).toBe('string');
113
- }
114
-
115
- return result as unknown as StartDevelopmentResult;
116
- }
117
-
118
- /**
119
- * Comprehensive validation for ProceedToPhaseResult
120
- * VALIDATE: Response must have all required properties with correct types and values
121
- */
122
- function assertValidProceedToPhaseResponse(
123
- response: unknown
124
- ): ProceedToPhaseResult {
125
- expect(response).toBeDefined();
126
- expect(typeof response).toBe('object');
127
- expect(response).not.toBeNull();
128
-
129
- // Type guard with direct cast (no chained as unknown as)
130
- if (typeof response !== 'object' || response === null) {
131
- throw new Error('Response must be an object');
132
- }
133
- const result = response as Record<string, unknown>;
134
-
135
- expect(result).toHaveProperty('phase');
136
- expect(isNonEmptyString(result.phase)).toBe(true);
137
-
138
- expect(result).toHaveProperty('instructions');
139
- expect(isNonEmptyString(result.instructions)).toBe(true);
140
- expect(isSubstantiveContent(result.instructions as string)).toBe(true);
141
-
142
- expect(result).toHaveProperty('plan_file_path');
143
- expect(isNonEmptyString(result.plan_file_path)).toBe(true);
144
-
145
- expect(result).toHaveProperty('transition_reason');
146
- expect(isNonEmptyString(result.transition_reason)).toBe(true);
147
-
148
- return result as unknown as ProceedToPhaseResult;
149
- }
150
-
151
- /**
152
- * Comprehensive validation for WhatsNextResult
153
- * VALIDATE: Response must have all required properties with correct types and values
154
- */
155
- function assertValidWhatsNextResponse(response: unknown): WhatsNextResult {
156
- expect(response).toBeDefined();
157
- expect(typeof response).toBe('object');
158
- expect(response).not.toBeNull();
159
-
160
- // Type guard with direct cast (no chained as unknown as)
161
- if (typeof response !== 'object' || response === null) {
162
- throw new Error('Response must be an object');
163
- }
164
- const result = response as Record<string, unknown>;
165
-
166
- expect(result).toHaveProperty('phase');
167
- expect(isNonEmptyString(result.phase)).toBe(true);
168
-
169
- expect(result).toHaveProperty('instructions');
170
- expect(isNonEmptyString(result.instructions)).toBe(true);
171
- expect(isSubstantiveContent(result.instructions as string)).toBe(true);
172
-
173
- expect(result).toHaveProperty('plan_file_path');
174
- expect(isNonEmptyString(result.plan_file_path)).toBe(true);
175
-
176
- return result as unknown as WhatsNextResult;
177
- }
178
-
179
- /**
180
- * Ensures no plugin internals leak into response
181
- * VALIDATE: User-facing responses must not expose plugin architecture
182
- */
183
- function assertNoPluginLeak(response: unknown): void {
184
- const result = response as Record<string, unknown>;
185
-
186
- // Plugin internals that must NOT appear
187
- expect(result).not.toHaveProperty('plugins');
188
- expect(result).not.toHaveProperty('pluginRegistry');
189
- expect(result).not.toHaveProperty('plugin_metadata');
190
- expect(result).not.toHaveProperty('_plugins');
191
- expect(result).not.toHaveProperty('_pluginRegistry');
192
- expect(result).not.toHaveProperty('beads');
193
- expect(result).not.toHaveProperty('taskBackend');
194
- }
195
-
196
- /**
197
- * Validates that file exists at given path
198
- * VALIDATE: Plan files must be created and accessible
199
- */
200
- async function assertFileExists(filePath: string): Promise<void> {
201
- try {
202
- await fs.access(filePath);
203
- } catch {
204
- throw new Error(`File does not exist: ${filePath}`);
205
- }
206
- }
207
-
208
- // ============================================================================
209
- // TEST SUITES
210
- // ============================================================================
211
-
212
- describe('Plugin System Integration Tests', () => {
213
- describe('Contract Validation', () => {
214
- let client: DirectServerInterface;
215
- let cleanup: () => Promise<void>;
216
-
217
- beforeEach(async () => {
218
- // Explicitly disable beads auto-detection for this test suite
219
- vi.stubEnv('TASK_BACKEND', 'markdown');
220
-
221
- const scenario = await createSuiteIsolatedE2EScenario({
222
- suiteName: 'contract-validation',
223
- tempProjectFactory: createTempProjectWithDefaultStateMachine,
224
- });
225
- client = scenario.client;
226
- cleanup = scenario.cleanup;
227
- });
228
-
229
- afterEach(async () => {
230
- if (cleanup) {
231
- await cleanup();
232
- }
233
- });
234
-
235
- it('should return valid StartDevelopmentResult with all required properties', async () => {
236
- const result = await client.callTool('start_development', {
237
- workflow: 'waterfall',
238
- commit_behaviour: 'none',
239
- });
240
-
241
- const response = assertToolSuccess(result);
242
- const validated = assertValidStartDevelopmentResponse(response);
243
-
244
- expect(validated.phase).toBeDefined();
245
- expect(validated.plan_file_path).toBeDefined();
246
- expect(validated.instructions).toBeDefined();
247
- });
248
-
249
- it('should return valid ProceedToPhaseResult with all required properties', async () => {
250
- await initializeDevelopment(client, 'waterfall');
251
-
252
- const result = await client.callTool('proceed_to_phase', {
253
- target_phase: 'design',
254
- reason: 'requirements analysis complete',
255
- review_state: 'not-required',
256
- });
257
-
258
- const response = assertToolSuccess(result);
259
- const validated = assertValidProceedToPhaseResponse(response);
260
-
261
- expect(validated.phase).toBe('design');
262
- });
263
-
264
- it('should return valid WhatsNextResult with all required properties', async () => {
265
- await initializeDevelopment(client, 'waterfall');
266
-
267
- const result = await client.callTool('whats_next', {
268
- user_input: 'what should I do now?',
269
- context: 'starting development',
270
- });
271
-
272
- const response = assertToolSuccess(result);
273
- const validated = assertValidWhatsNextResponse(response);
274
-
275
- expect(validated.phase).toBe('requirements');
276
- });
277
-
278
- it('should validate conversation IDs are UUID format', async () => {
279
- const result = await client.callTool('start_development', {
280
- workflow: 'epcc',
281
- commit_behaviour: 'none',
282
- });
283
-
284
- assertToolSuccess(result);
285
- });
286
-
287
- it('should validate instructions contain substantive content', async () => {
288
- const result = await client.callTool('start_development', {
289
- workflow: 'waterfall',
290
- commit_behaviour: 'none',
291
- });
292
-
293
- const response = assertToolSuccess(result);
294
-
295
- expect(response.instructions.length).toBeGreaterThan(100);
296
- expect(response.instructions).toMatch(
297
- /\b(phase|development|task|workflow|plan)\b/i
298
- );
299
- });
300
-
301
- it('should validate plan files exist after start_development', async () => {
302
- const result = await client.callTool('start_development', {
303
- workflow: 'waterfall',
304
- commit_behaviour: 'none',
305
- });
306
-
307
- const response = assertToolSuccess(result);
308
-
309
- await assertFileExists(response.plan_file_path);
310
- const content = await fs.readFile(response.plan_file_path, 'utf-8');
311
- expect(content.length).toBeGreaterThan(0);
312
- });
313
- });
314
-
315
- describe('Semantic Validation', () => {
316
- let client: DirectServerInterface;
317
- let cleanup: () => Promise<void>;
318
-
319
- beforeEach(async () => {
320
- // Explicitly disable beads auto-detection for this test suite
321
- vi.stubEnv('TASK_BACKEND', 'markdown');
322
-
323
- const scenario = await createSuiteIsolatedE2EScenario({
324
- suiteName: 'semantic-validation',
325
- tempProjectFactory: createTempProjectWithDefaultStateMachine,
326
- });
327
- client = scenario.client;
328
- cleanup = scenario.cleanup;
329
- });
330
-
331
- afterEach(async () => {
332
- if (cleanup) {
333
- await cleanup();
334
- }
335
- });
336
-
337
- it('should create existing plan files with proper structure', async () => {
338
- const result = await client.callTool('start_development', {
339
- workflow: 'epcc',
340
- commit_behaviour: 'none',
341
- });
342
-
343
- const response = assertToolSuccess(result);
344
-
345
- const planContent = await fs.readFile(response.plan_file_path, 'utf-8');
346
- expect(planContent).toContain('## Explore');
347
- expect(planContent).toContain('## Plan');
348
- expect(planContent).toContain('## Code');
349
- expect(planContent).toContain('## Commit');
350
- });
351
-
352
- it('should transition to valid phases only', async () => {
353
- await initializeDevelopment(client, 'waterfall');
354
-
355
- const validPhases = [
356
- 'requirements',
357
- 'design',
358
- 'implementation',
359
- 'qa',
360
- 'testing',
361
- 'finalize',
362
- ];
363
-
364
- for (const targetPhase of validPhases.slice(1)) {
365
- const result = await client.callTool('proceed_to_phase', {
366
- target_phase: targetPhase,
367
- reason: 'test transition',
368
- review_state: 'not-required',
369
- });
370
-
371
- const response = assertToolSuccess(result);
372
-
373
- expect(response.phase).toBe(targetPhase);
374
- expect(validPhases).toContain(response.phase);
375
- }
376
- });
377
-
378
- it('should maintain plan file consistency across transitions', async () => {
379
- await initializeDevelopment(client, 'waterfall');
380
-
381
- const result1 = await client.callTool('whats_next', {
382
- user_input: 'test 1',
383
- });
384
- const response1 = assertToolSuccess(result1);
385
- const planPath1 = response1.plan_file_path;
386
-
387
- // Transition phases
388
- await client.callTool('proceed_to_phase', {
389
- target_phase: 'design',
390
- reason: 'ready to design',
391
- review_state: 'not-required',
392
- });
393
-
394
- const result2 = await client.callTool('whats_next', {
395
- user_input: 'test 2',
396
- });
397
- const response2 = assertToolSuccess(result2);
398
-
399
- expect(response2.plan_file_path).toBe(planPath1);
400
-
401
- const planContent = await fs.readFile(planPath1, 'utf-8');
402
- expect(planContent.length).toBeGreaterThan(0);
403
- });
404
-
405
- it('should generate substantive instructions for each phase', async () => {
406
- await initializeDevelopment(client, 'waterfall');
407
-
408
- const phases = [
409
- 'requirements',
410
- 'design',
411
- 'implementation',
412
- 'qa',
413
- 'testing',
414
- 'finalize',
415
- ];
416
-
417
- for (let i = 1; i < phases.length; i++) {
418
- const result = await client.callTool('whats_next', {
419
- user_input: `continue to ${phases[i]}`,
420
- });
421
- const response = assertToolSuccess(result);
422
-
423
- expect(isSubstantiveContent(response.instructions)).toBe(true);
424
-
425
- // Transition to next phase
426
- if (i < phases.length - 1) {
427
- await client.callTool('proceed_to_phase', {
428
- target_phase: phases[i + 1],
429
- reason: 'test transition',
430
- review_state: 'not-required',
431
- });
432
- }
433
- }
434
- });
435
- });
436
-
437
- describe('Plugin Isolation', () => {
438
- let client: DirectServerInterface;
439
- let cleanup: () => Promise<void>;
440
-
441
- beforeEach(async () => {
442
- // Explicitly disable beads auto-detection for this test suite
443
- vi.stubEnv('TASK_BACKEND', 'markdown');
444
-
445
- const scenario = await createSuiteIsolatedE2EScenario({
446
- suiteName: 'plugin-isolation',
447
- tempProjectFactory: createTempProjectWithDefaultStateMachine,
448
- });
449
- client = scenario.client;
450
- cleanup = scenario.cleanup;
451
- });
452
-
453
- afterEach(async () => {
454
- if (cleanup) {
455
- await cleanup();
456
- }
457
- });
458
-
459
- it('should not expose plugin internals in StartDevelopmentResult', async () => {
460
- const result = await client.callTool('start_development', {
461
- workflow: 'epcc',
462
- commit_behaviour: 'none',
463
- });
464
-
465
- const response = assertToolSuccess(result);
466
-
467
- assertNoPluginLeak(response);
468
-
469
- expect(response).toHaveProperty('phase');
470
- expect(response).toHaveProperty('instructions');
471
- });
472
-
473
- it('should not expose plugin internals in ProceedToPhaseResult', async () => {
474
- await initializeDevelopment(client, 'waterfall');
475
-
476
- const result = await client.callTool('proceed_to_phase', {
477
- target_phase: 'design',
478
- reason: 'test',
479
- review_state: 'not-required',
480
- });
481
-
482
- const response = assertToolSuccess(result);
483
-
484
- assertNoPluginLeak(response);
485
-
486
- expect(response).toHaveProperty('phase');
487
- expect(response).toHaveProperty('instructions');
488
- });
489
-
490
- it('should not expose plugin internals in WhatsNextResult', async () => {
491
- await initializeDevelopment(client, 'waterfall');
492
-
493
- const result = await client.callTool('whats_next', {
494
- user_input: 'test',
495
- });
496
-
497
- const response = assertToolSuccess(result);
498
-
499
- assertNoPluginLeak(response);
500
-
501
- expect(response).toHaveProperty('phase');
502
- expect(response).toHaveProperty('instructions');
503
- });
504
- });
505
-
506
- describe('Multi-Workflow Support', () => {
507
- let client: DirectServerInterface;
508
- let cleanup: () => Promise<void>;
509
-
510
- beforeEach(async () => {
511
- // Explicitly disable beads auto-detection for this test suite
512
- vi.stubEnv('TASK_BACKEND', 'markdown');
513
-
514
- const scenario = await createSuiteIsolatedE2EScenario({
515
- suiteName: 'multi-workflow',
516
- tempProjectFactory: createTempProjectWithDefaultStateMachine,
517
- });
518
- client = scenario.client;
519
- cleanup = scenario.cleanup;
520
- });
521
-
522
- afterEach(async () => {
523
- if (cleanup) {
524
- await cleanup();
525
- }
526
- });
527
-
528
- it('should work with waterfall workflow', async () => {
529
- const result = await client.callTool('start_development', {
530
- workflow: 'waterfall',
531
- commit_behaviour: 'none',
532
- });
533
-
534
- assertValidStartDevelopmentResponse(assertToolSuccess(result));
535
- });
536
-
537
- it('should work with epcc workflow', async () => {
538
- const result = await client.callTool('start_development', {
539
- workflow: 'epcc',
540
- commit_behaviour: 'none',
541
- });
542
-
543
- const response = assertValidStartDevelopmentResponse(
544
- assertToolSuccess(result)
545
- );
546
-
547
- expect(response.phase).toBe('explore');
548
- });
549
-
550
- it('should work with tdd workflow', async () => {
551
- const result = await client.callTool('start_development', {
552
- workflow: 'tdd',
553
- commit_behaviour: 'none',
554
- });
555
-
556
- const response = assertValidStartDevelopmentResponse(
557
- assertToolSuccess(result)
558
- );
559
-
560
- expect(response.phase).toBe('explore');
561
- });
562
-
563
- it('should work with minor workflow', async () => {
564
- const result = await client.callTool('start_development', {
565
- workflow: 'minor',
566
- commit_behaviour: 'none',
567
- });
568
-
569
- const response = assertValidStartDevelopmentResponse(
570
- assertToolSuccess(result)
571
- );
572
-
573
- expect(response.phase).toBe('explore');
574
- });
575
-
576
- it('should work with bugfix workflow', async () => {
577
- const result = await client.callTool('start_development', {
578
- workflow: 'bugfix',
579
- commit_behaviour: 'none',
580
- });
581
-
582
- assertValidStartDevelopmentResponse(assertToolSuccess(result));
583
- });
584
- });
585
-
586
- describe('State Consistency', () => {
587
- let client: DirectServerInterface;
588
- let cleanup: () => Promise<void>;
589
-
590
- beforeEach(async () => {
591
- // Explicitly disable beads auto-detection for this test suite
592
- vi.stubEnv('TASK_BACKEND', 'markdown');
593
-
594
- const scenario = await createSuiteIsolatedE2EScenario({
595
- suiteName: 'state-consistency',
596
- tempProjectFactory: createTempProjectWithDefaultStateMachine,
597
- });
598
- client = scenario.client;
599
- cleanup = scenario.cleanup;
600
- });
601
-
602
- afterEach(async () => {
603
- if (cleanup) {
604
- await cleanup();
605
- }
606
- });
607
-
608
- it('should handle phase transitions with proper state updates', async () => {
609
- await initializeDevelopment(client, 'waterfall');
610
-
611
- // Verify initial state
612
- const stateResource1 = await client.readResource('state://current');
613
- if (typeof stateResource1 !== 'object' || stateResource1 === null) {
614
- throw new Error('State resource must be an object');
615
- }
616
- const state1 = stateResource1 as Record<string, unknown>;
617
- const contents1 = state1.contents as unknown[];
618
- const stateData1 = JSON.parse(
619
- (contents1[0] as Record<string, unknown>).text as string
620
- );
621
-
622
- expect(stateData1.currentPhase).toBe('requirements');
623
-
624
- // Transition
625
- await client.callTool('proceed_to_phase', {
626
- target_phase: 'design',
627
- reason: 'test',
628
- review_state: 'not-required',
629
- });
630
-
631
- // Verify state updated
632
- const stateResource2 = await client.readResource('state://current');
633
- if (typeof stateResource2 !== 'object' || stateResource2 === null) {
634
- throw new Error('State resource must be an object');
635
- }
636
- const state2 = stateResource2 as Record<string, unknown>;
637
- const contents2 = state2.contents as unknown[];
638
- const stateData2 = JSON.parse(
639
- (contents2[0] as Record<string, unknown>).text as string
640
- );
641
-
642
- expect(stateData2.currentPhase).toBe('design');
643
- });
644
- });
645
-
646
- describe('Error Handling and Resilience', () => {
647
- let client: DirectServerInterface;
648
- let cleanup: () => Promise<void>;
649
-
650
- beforeEach(async () => {
651
- // Explicitly disable beads auto-detection for this test suite
652
- vi.stubEnv('TASK_BACKEND', 'markdown');
653
-
654
- const scenario = await createSuiteIsolatedE2EScenario({
655
- suiteName: 'error-handling',
656
- tempProjectFactory: createTempProjectWithDefaultStateMachine,
657
- });
658
- client = scenario.client;
659
- cleanup = scenario.cleanup;
660
- });
661
-
662
- afterEach(async () => {
663
- if (cleanup) {
664
- await cleanup();
665
- }
666
- });
667
-
668
- it('should recover from invalid phase transitions', async () => {
669
- await initializeDevelopment(client, 'waterfall');
670
-
671
- // Try invalid transition
672
- const invalid: McpToolResponse = await client.callTool(
673
- 'proceed_to_phase',
674
- {
675
- target_phase: 'invalid_phase_name',
676
- reason: 'test',
677
- review_state: 'not-required',
678
- }
679
- );
680
-
681
- expect(invalid.error).toBeDefined();
682
-
683
- // Should still work afterwards
684
- const recovery = await client.callTool('whats_next', {
685
- user_input: 'recover',
686
- });
687
- assertValidWhatsNextResponse(assertToolSuccess(recovery));
688
- });
689
-
690
- it('should handle missing workflow gracefully', async () => {
691
- const result = await client.callTool('start_development', {
692
- workflow: 'nonexistent_workflow_xyz',
693
- commit_behaviour: 'none',
694
- });
695
-
696
- expect(result).toBeDefined();
697
- });
698
-
699
- it('should maintain consistency after errors', async () => {
700
- await initializeDevelopment(client, 'waterfall');
701
-
702
- // Get initial state
703
- const state1 = (await client.readResource('state://current')) as unknown;
704
- const stateRes1 = state1 as Record<string, unknown>;
705
- const data1 = JSON.parse(
706
- ((stateRes1.contents as unknown[])[0] as Record<string, unknown>)
707
- .text as string
708
- );
709
-
710
- expect(data1.currentPhase).toBe('requirements');
711
-
712
- // Cause an error
713
- await client.callTool('proceed_to_phase', {
714
- target_phase: 'bad_phase',
715
- reason: 'error test',
716
- review_state: 'not-required',
717
- });
718
-
719
- // State should still be valid
720
- const state2 = (await client.readResource('state://current')) as unknown;
721
- const stateRes2 = state2 as Record<string, unknown>;
722
- const data2 = JSON.parse(
723
- ((stateRes2.contents as unknown[])[0] as Record<string, unknown>)
724
- .text as string
725
- );
726
-
727
- expect(data2.currentPhase).toBe(data1.currentPhase);
728
-
729
- expect(data2.conversationId).toBe(data1.conversationId);
730
- });
731
- });
732
-
733
- describe('Default Behavior (Without Beads)', () => {
734
- let client: DirectServerInterface;
735
- let cleanup: () => Promise<void>;
736
-
737
- beforeEach(async () => {
738
- // Don't set TASK_BACKEND - tests auto-detection behavior
739
- if (process.env.TASK_BACKEND) {
740
- delete process.env.TASK_BACKEND;
741
- }
742
-
743
- const scenario = await createSuiteIsolatedE2EScenario({
744
- suiteName: 'plugin-default-behavior',
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
- });
756
-
757
- it('should initialize server without beads plugin', async () => {
758
- // Verify environment is clean
759
- expect(process.env.TASK_BACKEND).toBeUndefined();
760
-
761
- const result = await client.callTool('start_development', {
762
- workflow: 'waterfall',
763
- commit_behaviour: 'none',
764
- });
765
-
766
- const response = assertValidStartDevelopmentResponse(
767
- assertToolSuccess(result)
768
- );
769
-
770
- await assertFileExists(response.plan_file_path);
771
- expect(response.phase).toBe('requirements');
772
- });
773
-
774
- it('should handle start_development without plugin interference', async () => {
775
- const result = await client.callTool('start_development', {
776
- workflow: 'epcc',
777
- commit_behaviour: 'none',
778
- });
779
-
780
- const response = assertValidStartDevelopmentResponse(
781
- assertToolSuccess(result)
782
- );
783
-
784
- const planContent = await fs.readFile(response.plan_file_path, 'utf-8');
785
- expect(planContent).toContain('## Explore');
786
- expect(planContent).toContain('## Plan');
787
- expect(planContent).toContain('## Code');
788
- expect(planContent).toContain('## Commit');
789
- });
790
- });
791
-
792
- describe('Resource Access', () => {
793
- let client: DirectServerInterface;
794
- let cleanup: () => Promise<void>;
795
-
796
- beforeEach(async () => {
797
- // Explicitly disable beads auto-detection for this test suite
798
- vi.stubEnv('TASK_BACKEND', 'markdown');
799
-
800
- const scenario = await createSuiteIsolatedE2EScenario({
801
- suiteName: 'resource-access',
802
- tempProjectFactory: createTempProjectWithDefaultStateMachine,
803
- });
804
- client = scenario.client;
805
- cleanup = scenario.cleanup;
806
-
807
- await initializeDevelopment(client, 'waterfall');
808
- });
809
-
810
- afterEach(async () => {
811
- if (cleanup) {
812
- await cleanup();
813
- }
814
- });
815
-
816
- it('should provide access to state resource with valid structure', async () => {
817
- const stateResource = (await client.readResource(
818
- 'state://current'
819
- )) as unknown;
820
- const resource = stateResource as Record<string, unknown>;
821
-
822
- expect(resource).toHaveProperty('contents');
823
- expect(Array.isArray(resource.contents)).toBe(true);
824
- expect((resource.contents as unknown[]).length).toBeGreaterThan(0);
825
-
826
- const content = (
827
- (resource.contents as unknown[])[0] as Record<string, unknown>
828
- ).text as string;
829
- const stateData = JSON.parse(content);
830
- expect(typeof stateData.conversationId).toBe('string');
831
- expect(stateData.conversationId.length).toBeGreaterThan(0);
832
- expect(typeof stateData.currentPhase).toBe('string');
833
- expect(stateData.currentPhase.length).toBeGreaterThan(0);
834
- });
835
-
836
- it('should provide access to plan resource with substantive content', async () => {
837
- const planResource = (await client.readResource(
838
- 'plan://current'
839
- )) as unknown;
840
- const resource = planResource as Record<string, unknown>;
841
-
842
- expect(resource).toHaveProperty('contents');
843
- expect(Array.isArray(resource.contents)).toBe(true);
844
- expect((resource.contents as unknown[]).length).toBeGreaterThan(0);
845
-
846
- const content = (
847
- (resource.contents as unknown[])[0] as Record<string, unknown>
848
- ).text as string;
849
- expect(typeof content).toBe('string');
850
- expect(content.length).toBeGreaterThan(0);
851
- });
852
-
853
- it('should provide access to system prompt resource', async () => {
854
- const promptResource = (await client.readResource(
855
- 'system-prompt://'
856
- )) as unknown;
857
- const resource = promptResource as Record<string, unknown>;
858
-
859
- expect(resource).toHaveProperty('contents');
860
- expect(Array.isArray(resource.contents)).toBe(true);
861
- expect((resource.contents as unknown[]).length).toBeGreaterThan(0);
862
-
863
- const contentObj = (resource.contents as unknown[])[0] as Record<
864
- string,
865
- unknown
866
- >;
867
- // Try text first (primary), then content (secondary), then get string representation
868
- let content: string;
869
- if (typeof contentObj.text === 'string' && contentObj.text.length > 0) {
870
- content = contentObj.text;
871
- } else if (
872
- typeof contentObj.content === 'string' &&
873
- contentObj.content.length > 0
874
- ) {
875
- content = contentObj.content;
876
- } else if (Object.keys(contentObj).length > 0) {
877
- // If object has properties but no usable string property, convert to string
878
- content = JSON.stringify(contentObj);
879
- } else {
880
- throw new Error('Content object has no usable content');
881
- }
882
- expect(typeof content).toBe('string');
883
- expect(content.length).toBeGreaterThan(0);
884
- });
885
- });
886
-
887
- // =========================================================================
888
- // PLUGIN HOOK EXECUTION VERIFICATION
889
- // =========================================================================
890
-
891
- describe('Plugin Hook Execution Verification', () => {
892
- let client: DirectServerInterface;
893
- let cleanup: () => Promise<void>;
894
-
895
- beforeEach(async () => {
896
- // Explicitly disable beads auto-detection for this test suite
897
- vi.stubEnv('TASK_BACKEND', 'markdown');
898
-
899
- const scenario = await createSuiteIsolatedE2EScenario({
900
- suiteName: 'plugin-hook-execution',
901
- tempProjectFactory: createTempProjectWithDefaultStateMachine,
902
- });
903
- client = scenario.client;
904
- cleanup = scenario.cleanup;
905
- });
906
-
907
- afterEach(async () => {
908
- if (cleanup) {
909
- await cleanup();
910
- }
911
- });
912
-
913
- it('should execute hooks during start_development and return valid response', async () => {
914
- // Start development - triggers plugin hooks
915
- const result = await client.callTool('start_development', {
916
- workflow: 'waterfall',
917
- commit_behaviour: 'none',
918
- });
919
-
920
- const response = assertValidStartDevelopmentResponse(
921
- assertToolSuccess(result)
922
- );
923
-
924
- // (plan file exists, instructions present, phase valid)
925
- expect(response.phase).toBe('requirements');
926
- expect(response.plan_file_path).toBeDefined();
927
-
928
- // Verify plan file was created by hooks
929
- await assertFileExists(response.plan_file_path);
930
- const planContent = await fs.readFile(response.plan_file_path, 'utf-8');
931
- expect(planContent.length).toBeGreaterThan(0);
932
- });
933
-
934
- it('should maintain state consistency after hook execution', async () => {
935
- // Start development
936
- const startResult = await client.callTool('start_development', {
937
- workflow: 'epcc',
938
- commit_behaviour: 'none',
939
- });
940
-
941
- const startResponse = assertValidStartDevelopmentResponse(
942
- assertToolSuccess(startResult)
943
- );
944
-
945
- // Call whats_next immediately after hooks
946
- const whatsNextResult = await client.callTool('whats_next', {
947
- user_input: 'test after hooks',
948
- context: 'right after start',
949
- });
950
-
951
- const whatsNextResponse = assertValidWhatsNextResponse(
952
- assertToolSuccess(whatsNextResult)
953
- );
954
-
955
- expect(whatsNextResponse.phase).toBe(startResponse.phase);
956
- expect(whatsNextResponse.plan_file_path).toBe(
957
- startResponse.plan_file_path
958
- );
959
- });
960
-
961
- it('should ensure hooks do not break plan file validity', async () => {
962
- // Start development
963
- const result = await client.callTool('start_development', {
964
- workflow: 'waterfall',
965
- commit_behaviour: 'none',
966
- });
967
-
968
- const response = assertValidStartDevelopmentResponse(
969
- assertToolSuccess(result)
970
- );
971
-
972
- // Read and validate plan file
973
- const planContent = await fs.readFile(response.plan_file_path, 'utf-8');
974
-
975
- expect(planContent).toMatch(/^# /m); // Title
976
- expect(planContent).toMatch(/^## /m); // Sections
977
- expect(planContent).toContain('## Goal');
978
- expect(planContent).toContain('## Requirements');
979
-
980
- expect(planContent).not.toContain('undefined');
981
- expect(planContent).not.toContain('[object Object]');
982
- });
983
-
984
- it('should handle hook execution for multiple workflows', async () => {
985
- const workflows = ['waterfall', 'epcc', 'tdd', 'minor'];
986
-
987
- for (const workflow of workflows) {
988
- // Create fresh scenario for each workflow
989
- const scenario = await createSuiteIsolatedE2EScenario({
990
- suiteName: `plugin-hooks-${workflow}`,
991
- tempProjectFactory: createTempProjectWithDefaultStateMachine,
992
- });
993
-
994
- const result = await scenario.client.callTool('start_development', {
995
- workflow: workflow,
996
- commit_behaviour: 'none',
997
- });
998
-
999
- const response = assertValidStartDevelopmentResponse(
1000
- assertToolSuccess(result)
1001
- );
1002
-
1003
- await assertFileExists(response.plan_file_path);
1004
-
1005
- await scenario.cleanup();
1006
- }
1007
- });
1008
- });
1009
-
1010
- // =========================================================================
1011
- // PLUGIN SYSTEM ARCHITECTURE VALIDATION
1012
- // =========================================================================
1013
-
1014
- describe('Plugin System Architecture', () => {
1015
- let client: DirectServerInterface;
1016
- let cleanup: () => Promise<void>;
1017
-
1018
- beforeEach(async () => {
1019
- // Explicitly disable beads auto-detection for this test suite
1020
- vi.stubEnv('TASK_BACKEND', 'markdown');
1021
-
1022
- const scenario = await createSuiteIsolatedE2EScenario({
1023
- suiteName: 'plugin-architecture',
1024
- tempProjectFactory: createTempProjectWithDefaultStateMachine,
1025
- });
1026
- client = scenario.client;
1027
- cleanup = scenario.cleanup;
1028
- });
1029
-
1030
- afterEach(async () => {
1031
- if (cleanup) {
1032
- await cleanup();
1033
- }
1034
- });
1035
-
1036
- it('should not expose plugin registry or internal plugin details', async () => {
1037
- const result = await client.callTool('start_development', {
1038
- workflow: 'waterfall',
1039
- commit_behaviour: 'none',
1040
- });
1041
-
1042
- const response = assertToolSuccess(result);
1043
-
1044
- assertNoPluginLeak(response);
1045
-
1046
- expect(Object.keys(response).sort()).toEqual(
1047
- [
1048
- 'instructions',
1049
- 'phase',
1050
- 'plan_file_path',
1051
- 'workflowDocumentationUrl',
1052
- ].sort()
1053
- );
1054
- });
1055
-
1056
- it('should apply plugins uniformly across all tool calls', async () => {
1057
- // Start development
1058
- const startResult = await client.callTool('start_development', {
1059
- workflow: 'waterfall',
1060
- commit_behaviour: 'none',
1061
- });
1062
-
1063
- assertValidStartDevelopmentResponse(assertToolSuccess(startResult));
1064
-
1065
- // Get whats_next
1066
- const whatsNextResult = await client.callTool('whats_next', {
1067
- user_input: 'next step',
1068
- });
1069
-
1070
- assertValidWhatsNextResponse(assertToolSuccess(whatsNextResult));
1071
-
1072
- // Transition phase
1073
- const transitionResult = await client.callTool('proceed_to_phase', {
1074
- target_phase: 'design',
1075
- reason: 'ready',
1076
- review_state: 'not-required',
1077
- });
1078
-
1079
- assertValidProceedToPhaseResponse(assertToolSuccess(transitionResult));
1080
- });
1081
-
1082
- it('should preserve plugin boundaries (no cross-pollution)', async () => {
1083
- // Start development
1084
- const result = await client.callTool('start_development', {
1085
- workflow: 'epcc',
1086
- commit_behaviour: 'none',
1087
- });
1088
-
1089
- const response = assertToolSuccess(result);
1090
-
1091
- assertNoPluginLeak(response);
1092
-
1093
- expect(response).toHaveProperty('plan_file_path');
1094
- expect(response).toHaveProperty('instructions');
1095
-
1096
- expect(response).not.toHaveProperty('_plugins');
1097
- expect(response).not.toHaveProperty('beads');
1098
- expect(response).not.toHaveProperty('taskBackendClient');
1099
- });
1100
- });
1101
-
1102
- // =========================================================================
1103
- // WORKFLOW INITIALIZATION VALIDATION
1104
- // =========================================================================
1105
-
1106
- describe('Workflow Initialization with Plugin Support', () => {
1107
- let cleanup: () => Promise<void>;
1108
-
1109
- afterEach(async () => {
1110
- if (cleanup) {
1111
- await cleanup();
1112
- }
1113
- // Explicitly disable beads auto-detection for this test suite
1114
- vi.stubEnv('TASK_BACKEND', 'markdown');
1115
- });
1116
-
1117
- it('should initialize waterfall with correct initial phase', async () => {
1118
- const scenario = await createSuiteIsolatedE2EScenario({
1119
- suiteName: 'init-waterfall',
1120
- tempProjectFactory: createTempProjectWithDefaultStateMachine,
1121
- });
1122
- cleanup = scenario.cleanup;
1123
-
1124
- const result = await scenario.client.callTool('start_development', {
1125
- workflow: 'waterfall',
1126
- commit_behaviour: 'none',
1127
- });
1128
-
1129
- const response = assertValidStartDevelopmentResponse(
1130
- assertToolSuccess(result)
1131
- );
1132
-
1133
- expect(response.phase).toBe(WORKFLOW_INITIAL_PHASES.waterfall);
1134
- });
1135
-
1136
- it('should initialize epcc with correct initial phase', async () => {
1137
- const scenario = await createSuiteIsolatedE2EScenario({
1138
- suiteName: 'init-epcc',
1139
- tempProjectFactory: createTempProjectWithDefaultStateMachine,
1140
- });
1141
- cleanup = scenario.cleanup;
1142
-
1143
- const result = await scenario.client.callTool('start_development', {
1144
- workflow: 'epcc',
1145
- commit_behaviour: 'none',
1146
- });
1147
-
1148
- const response = assertValidStartDevelopmentResponse(
1149
- assertToolSuccess(result)
1150
- );
1151
-
1152
- expect(response.phase).toBe(WORKFLOW_INITIAL_PHASES.epcc);
1153
- });
1154
-
1155
- it('should initialize tdd with correct initial phase', async () => {
1156
- const scenario = await createSuiteIsolatedE2EScenario({
1157
- suiteName: 'init-tdd',
1158
- tempProjectFactory: createTempProjectWithDefaultStateMachine,
1159
- });
1160
- cleanup = scenario.cleanup;
1161
-
1162
- const result = await scenario.client.callTool('start_development', {
1163
- workflow: 'tdd',
1164
- commit_behaviour: 'none',
1165
- });
1166
-
1167
- const response = assertValidStartDevelopmentResponse(
1168
- assertToolSuccess(result)
1169
- );
1170
-
1171
- expect(response.phase).toBe(WORKFLOW_INITIAL_PHASES.tdd);
1172
- });
1173
-
1174
- it('should initialize minor with correct initial phase', async () => {
1175
- const scenario = await createSuiteIsolatedE2EScenario({
1176
- suiteName: 'init-minor',
1177
- tempProjectFactory: createTempProjectWithDefaultStateMachine,
1178
- });
1179
- cleanup = scenario.cleanup;
1180
-
1181
- const result = await scenario.client.callTool('start_development', {
1182
- workflow: 'minor',
1183
- commit_behaviour: 'none',
1184
- });
1185
-
1186
- const response = assertValidStartDevelopmentResponse(
1187
- assertToolSuccess(result)
1188
- );
1189
-
1190
- expect(response.phase).toBe(WORKFLOW_INITIAL_PHASES.minor);
1191
- });
1192
-
1193
- it('should initialize bugfix with expected initial phase', async () => {
1194
- const scenario = await createSuiteIsolatedE2EScenario({
1195
- suiteName: 'init-bugfix',
1196
- tempProjectFactory: createTempProjectWithDefaultStateMachine,
1197
- });
1198
- cleanup = scenario.cleanup;
1199
-
1200
- const result = await scenario.client.callTool('start_development', {
1201
- workflow: 'bugfix',
1202
- commit_behaviour: 'none',
1203
- });
1204
-
1205
- const response = assertValidStartDevelopmentResponse(
1206
- assertToolSuccess(result)
1207
- );
1208
-
1209
- const expectedPhases = WORKFLOW_INITIAL_PHASES.bugfix;
1210
- expect(expectedPhases).toContain(response.phase);
1211
- });
1212
- });
1213
-
1214
- // =========================================================================
1215
- // PLAN FILE AND INSTRUCTION QUALITY
1216
- // =========================================================================
1217
-
1218
- describe('Plan File and Instruction Quality Across Workflows', () => {
1219
- let client: DirectServerInterface;
1220
- let cleanup: () => Promise<void>;
1221
-
1222
- beforeEach(async () => {
1223
- // Explicitly disable beads auto-detection for this test suite
1224
- vi.stubEnv('TASK_BACKEND', 'markdown');
1225
-
1226
- const scenario = await createSuiteIsolatedE2EScenario({
1227
- suiteName: 'quality-across-workflows',
1228
- tempProjectFactory: createTempProjectWithDefaultStateMachine,
1229
- });
1230
- client = scenario.client;
1231
- cleanup = scenario.cleanup;
1232
- });
1233
-
1234
- afterEach(async () => {
1235
- if (cleanup) {
1236
- await cleanup();
1237
- }
1238
- });
1239
-
1240
- it('should generate substantive instructions that meet minimum length requirement', async () => {
1241
- const result = await client.callTool('start_development', {
1242
- workflow: 'waterfall',
1243
- commit_behaviour: 'none',
1244
- });
1245
-
1246
- const response = assertValidStartDevelopmentResponse(
1247
- assertToolSuccess(result)
1248
- );
1249
-
1250
- expect(response.instructions.length).toBeGreaterThan(
1251
- MIN_INSTRUCTION_LENGTH
1252
- );
1253
- });
1254
-
1255
- it('should create plan files with valid markdown structure', async () => {
1256
- const result = await client.callTool('start_development', {
1257
- workflow: 'waterfall',
1258
- commit_behaviour: 'none',
1259
- });
1260
-
1261
- const response = assertValidStartDevelopmentResponse(
1262
- assertToolSuccess(result)
1263
- );
1264
-
1265
- const planContent = await fs.readFile(response.plan_file_path, 'utf-8');
1266
-
1267
- expect(planContent).toMatch(/^# /m); // Must have main title
1268
- expect(planContent).toMatch(/^## /m); // Must have sections
1269
- expect(planContent).not.toContain('[object Object]'); // No serialization errors
1270
- expect(planContent).not.toContain('undefined'); // No undefined placeholders
1271
- });
1272
-
1273
- it('should ensure instructions are context-aware for the current phase', async () => {
1274
- // Start and get initial instructions
1275
- const startResult = await client.callTool('start_development', {
1276
- workflow: 'waterfall',
1277
- commit_behaviour: 'none',
1278
- });
1279
-
1280
- const startResponse = assertValidStartDevelopmentResponse(
1281
- assertToolSuccess(startResult)
1282
- );
1283
-
1284
- expect(startResponse.instructions).toMatch(/requirement|phase|task/i);
1285
-
1286
- // Transition to design phase
1287
- await client.callTool('proceed_to_phase', {
1288
- target_phase: 'design',
1289
- reason: 'ready',
1290
- review_state: 'not-required',
1291
- });
1292
-
1293
- // Get instructions for design phase
1294
- const designWhatsNext = await client.callTool('whats_next', {
1295
- user_input: 'what now in design?',
1296
- });
1297
-
1298
- const designResponse = assertValidWhatsNextResponse(
1299
- assertToolSuccess(designWhatsNext)
1300
- );
1301
-
1302
- expect(designResponse.instructions).toBeDefined();
1303
- expect(designResponse.instructions.length).toBeGreaterThan(
1304
- MIN_INSTRUCTION_LENGTH
1305
- );
1306
- });
1307
- });
1308
-
1309
- // =========================================================================
1310
- // STATE PERSISTENCE AND CONSISTENCY
1311
- // =========================================================================
1312
-
1313
- describe('State Persistence Across Plugin Execution', () => {
1314
- let client: DirectServerInterface;
1315
- let cleanup: () => Promise<void>;
1316
-
1317
- beforeEach(async () => {
1318
- // Explicitly disable beads auto-detection for this test suite
1319
- vi.stubEnv('TASK_BACKEND', 'markdown');
1320
-
1321
- const scenario = await createSuiteIsolatedE2EScenario({
1322
- suiteName: 'state-persistence',
1323
- tempProjectFactory: createTempProjectWithDefaultStateMachine,
1324
- });
1325
- client = scenario.client;
1326
- cleanup = scenario.cleanup;
1327
- });
1328
-
1329
- afterEach(async () => {
1330
- if (cleanup) {
1331
- await cleanup();
1332
- }
1333
- });
1334
-
1335
- it('should preserve plan file path through multiple operations', async () => {
1336
- // Start development
1337
- const startResult = await client.callTool('start_development', {
1338
- workflow: 'waterfall',
1339
- commit_behaviour: 'none',
1340
- });
1341
-
1342
- const startResponse = assertValidStartDevelopmentResponse(
1343
- assertToolSuccess(startResult)
1344
- );
1345
- const planPath = startResponse.plan_file_path;
1346
-
1347
- // Get whats_next
1348
- const whatsNextResult = await client.callTool('whats_next', {
1349
- user_input: 'continue',
1350
- });
1351
-
1352
- const whatsNextResponse = assertValidWhatsNextResponse(
1353
- assertToolSuccess(whatsNextResult)
1354
- );
1355
-
1356
- expect(whatsNextResponse.plan_file_path).toBe(planPath);
1357
-
1358
- // Transition
1359
- const transitionResult = await client.callTool('proceed_to_phase', {
1360
- target_phase: 'design',
1361
- reason: 'ready',
1362
- review_state: 'not-required',
1363
- });
1364
-
1365
- const transitionResponse = assertValidProceedToPhaseResponse(
1366
- assertToolSuccess(transitionResult)
1367
- );
1368
-
1369
- expect(transitionResponse.plan_file_path).toBe(planPath);
1370
- });
1371
-
1372
- it('should maintain plan file integrity through multiple tool calls', async () => {
1373
- // Start development
1374
- const startResult = await client.callTool('start_development', {
1375
- workflow: 'waterfall',
1376
- commit_behaviour: 'none',
1377
- });
1378
-
1379
- const startResponse = assertValidStartDevelopmentResponse(
1380
- assertToolSuccess(startResult)
1381
- );
1382
-
1383
- // Verify plan file exists for multiple operations
1384
- const _initialContent = await fs.readFile(
1385
- startResponse.plan_file_path,
1386
- 'utf-8'
1387
- );
1388
-
1389
- // Make multiple calls
1390
- await client.callTool('whats_next', { user_input: 'test' });
1391
- await client.callTool('whats_next', { user_input: 'test2' });
1392
- await client.callTool('proceed_to_phase', {
1393
- target_phase: 'design',
1394
- reason: 'ready',
1395
- review_state: 'not-required',
1396
- });
1397
-
1398
- // Check plan file still valid
1399
- const finalContent = await fs.readFile(
1400
- startResponse.plan_file_path,
1401
- 'utf-8'
1402
- );
1403
-
1404
- expect(finalContent.length).toBeGreaterThan(0);
1405
-
1406
- expect(finalContent).not.toContain('[object Object]');
1407
- expect(finalContent).not.toContain('undefined');
1408
- });
1409
- });
1410
- });