agentic-qe 1.8.1 → 1.8.3

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 (27) hide show
  1. package/.claude/agents/qe-test-generator.md +580 -0
  2. package/.claude/agents/subagents/qe-code-reviewer.md +86 -0
  3. package/.claude/agents/subagents/qe-coverage-gap-analyzer.md +485 -0
  4. package/.claude/agents/subagents/qe-data-generator.md +86 -0
  5. package/.claude/agents/subagents/qe-flaky-investigator.md +416 -0
  6. package/.claude/agents/subagents/qe-integration-tester.md +87 -0
  7. package/.claude/agents/subagents/qe-performance-validator.md +98 -0
  8. package/.claude/agents/subagents/qe-security-auditor.md +86 -0
  9. package/.claude/agents/subagents/qe-test-data-architect-sub.md +553 -0
  10. package/.claude/agents/subagents/qe-test-implementer.md +229 -15
  11. package/.claude/agents/subagents/qe-test-refactorer.md +265 -15
  12. package/.claude/agents/subagents/qe-test-writer.md +180 -20
  13. package/CHANGELOG.md +182 -0
  14. package/README.md +52 -35
  15. package/dist/core/hooks/validators/TDDPhaseValidator.d.ts +110 -0
  16. package/dist/core/hooks/validators/TDDPhaseValidator.d.ts.map +1 -0
  17. package/dist/core/hooks/validators/TDDPhaseValidator.js +287 -0
  18. package/dist/core/hooks/validators/TDDPhaseValidator.js.map +1 -0
  19. package/dist/core/hooks/validators/index.d.ts +3 -1
  20. package/dist/core/hooks/validators/index.d.ts.map +1 -1
  21. package/dist/core/hooks/validators/index.js +4 -2
  22. package/dist/core/hooks/validators/index.js.map +1 -1
  23. package/dist/core/memory/RealAgentDBAdapter.d.ts +77 -2
  24. package/dist/core/memory/RealAgentDBAdapter.d.ts.map +1 -1
  25. package/dist/core/memory/RealAgentDBAdapter.js +259 -3
  26. package/dist/core/memory/RealAgentDBAdapter.js.map +1 -1
  27. package/package.json +1 -1
@@ -268,38 +268,252 @@ class IncrementalDeveloper {
268
268
  }
269
269
  ```
270
270
 
271
+ ## TDD Coordination Protocol
272
+
273
+ ### Cycle-Based Memory Namespace
274
+
275
+ All TDD subagents share context through a cycle-specific namespace:
276
+
277
+ ```
278
+ aqe/tdd/cycle-{cycleId}/
279
+ ├── context # Shared workflow context (created by parent)
280
+ ├── red/
281
+ │ ├── tests # Test file content from RED phase
282
+ │ └── validation # RED phase validation results
283
+ ├── green/
284
+ │ ├── impl # Implementation from GREEN phase
285
+ │ └── validation # GREEN phase validation results
286
+ └── refactor/
287
+ ├── result # Final refactored code
288
+ └── validation # REFACTOR phase validation results
289
+ ```
290
+
291
+ ### Input Protocol (from qe-test-writer)
292
+
293
+ **Required Input Structure:**
294
+ ```typescript
295
+ interface REDPhaseOutput {
296
+ cycleId: string; // Unique identifier for this TDD cycle
297
+ phase: 'RED';
298
+ timestamp: number;
299
+ testFile: {
300
+ path: string; // Absolute path to test file
301
+ content: string; // Full test file content
302
+ hash: string; // SHA256 hash for validation
303
+ };
304
+ tests: Array<{
305
+ name: string; // Test description
306
+ type: 'unit' | 'integration' | 'e2e';
307
+ assertion: string; // What it asserts
308
+ givenWhenThen: {
309
+ given: string;
310
+ when: string;
311
+ then: string;
312
+ };
313
+ }>;
314
+ validation: {
315
+ allTestsFailing: boolean; // MUST be true
316
+ failureCount: number;
317
+ errorMessages: string[];
318
+ };
319
+ nextPhase: 'GREEN';
320
+ readyForHandoff: boolean; // MUST be true to proceed
321
+ }
322
+
323
+ // Retrieve RED phase output
324
+ const redOutput = await this.memoryStore.retrieve(`aqe/tdd/cycle-${cycleId}/red/tests`, {
325
+ partition: 'coordination'
326
+ });
327
+
328
+ // Validate RED phase is complete
329
+ if (!redOutput.readyForHandoff || !redOutput.validation.allTestsFailing) {
330
+ throw new Error('Cannot proceed to GREEN phase - RED phase incomplete');
331
+ }
332
+ ```
333
+
334
+ ### Output Protocol (for qe-test-refactorer)
335
+
336
+ **Required Output Structure:**
337
+ ```typescript
338
+ interface GREENPhaseOutput {
339
+ cycleId: string; // Must match input cycleId
340
+ phase: 'GREEN';
341
+ timestamp: number;
342
+ testFile: {
343
+ path: string; // SAME path from RED phase
344
+ hash: string; // SAME hash - tests unchanged
345
+ };
346
+ implFile: {
347
+ path: string; // Absolute path to implementation
348
+ content: string; // Full implementation content
349
+ hash: string; // SHA256 hash for validation
350
+ };
351
+ implementation: {
352
+ className: string; // e.g., 'UserAuthenticationService'
353
+ methods: Array<{
354
+ name: string;
355
+ signature: string;
356
+ complexity: number;
357
+ }>;
358
+ };
359
+ validation: {
360
+ allTestsPassing: boolean; // MUST be true
361
+ passCount: number;
362
+ totalCount: number;
363
+ coverage: number; // Line coverage percentage
364
+ };
365
+ nextPhase: 'REFACTOR';
366
+ readyForHandoff: boolean; // MUST be true to proceed
367
+ }
368
+
369
+ // Store GREEN phase output
370
+ await this.memoryStore.store(`aqe/tdd/cycle-${cycleId}/green/impl`, output, {
371
+ partition: 'coordination',
372
+ ttl: 86400
373
+ });
374
+ ```
375
+
376
+ ### Handoff Validation
377
+
378
+ Before emitting completion, validate handoff readiness:
379
+
380
+ ```typescript
381
+ async function validateGREENHandoff(
382
+ output: GREENPhaseOutput,
383
+ redOutput: REDPhaseOutput
384
+ ): Promise<boolean> {
385
+ const errors: string[] = [];
386
+
387
+ // 1. Verify cycle IDs match
388
+ if (output.cycleId !== redOutput.cycleId) {
389
+ errors.push(`Cycle ID mismatch: ${output.cycleId} !== ${redOutput.cycleId}`);
390
+ }
391
+
392
+ // 2. Verify test file unchanged
393
+ if (output.testFile.hash !== redOutput.testFile.hash) {
394
+ errors.push('Test file was modified during GREEN phase - tests must remain unchanged');
395
+ }
396
+
397
+ // 3. Verify implementation file exists
398
+ if (!existsSync(output.implFile.path)) {
399
+ errors.push(`Implementation file not found: ${output.implFile.path}`);
400
+ } else {
401
+ const actualContent = readFileSync(output.implFile.path, 'utf-8');
402
+ const actualHash = createHash('sha256').update(actualContent).digest('hex');
403
+ if (actualHash !== output.implFile.hash) {
404
+ errors.push(`Implementation file content mismatch: hash differs`);
405
+ }
406
+ }
407
+
408
+ // 4. Verify all tests are passing
409
+ if (!output.validation.allTestsPassing) {
410
+ errors.push(`GREEN phase violation: ${output.validation.totalCount - output.validation.passCount} tests still failing`);
411
+ }
412
+
413
+ // 5. Set handoff readiness
414
+ output.readyForHandoff = errors.length === 0;
415
+
416
+ if (errors.length > 0) {
417
+ console.error('GREEN phase handoff validation failed:', errors);
418
+ }
419
+
420
+ return output.readyForHandoff;
421
+ }
422
+ ```
423
+
271
424
  ## Integration with Parent Agents
272
425
 
273
426
  ### Input from qe-test-writer
274
427
 
275
428
  ```typescript
276
- // Read failing tests from memory
277
- const failingTests = await this.memoryStore.retrieve('aqe/test-writer/results', {
429
+ // Retrieve cycle context for file paths
430
+ const context = await this.memoryStore.retrieve(`aqe/tdd/cycle-${cycleId}/context`, {
431
+ partition: 'coordination'
432
+ });
433
+
434
+ // Retrieve RED phase output with tests
435
+ const redOutput = await this.memoryStore.retrieve(`aqe/tdd/cycle-${cycleId}/red/tests`, {
278
436
  partition: 'coordination'
279
437
  });
280
438
 
281
- // Verify tests are failing (RED phase complete)
282
- if (!failingTests.allTestsFailing) {
439
+ // Validate RED phase is complete and ready for handoff
440
+ if (!redOutput) {
441
+ throw new Error(`RED phase output not found for cycle ${cycleId}`);
442
+ }
443
+
444
+ if (!redOutput.readyForHandoff) {
445
+ throw new Error('Cannot proceed to GREEN phase - RED phase handoff not ready');
446
+ }
447
+
448
+ if (!redOutput.validation.allTestsFailing) {
283
449
  throw new Error('Cannot proceed to GREEN phase - tests are not failing');
284
450
  }
451
+
452
+ // Verify test file exists and matches expected content
453
+ const actualTestContent = readFileSync(redOutput.testFile.path, 'utf-8');
454
+ const actualHash = createHash('sha256').update(actualTestContent).digest('hex');
455
+ if (actualHash !== redOutput.testFile.hash) {
456
+ throw new Error('Test file has been modified since RED phase - cannot proceed');
457
+ }
458
+
459
+ // Now implement code to make these EXACT tests pass
460
+ console.log(`Implementing code to pass ${redOutput.tests.length} tests from ${redOutput.testFile.path}`);
285
461
  ```
286
462
 
287
463
  ### Output to qe-test-refactorer
288
464
 
289
465
  ```typescript
290
- // Store implementations for refactoring
291
- await this.memoryStore.store('aqe/test-implementer/results', {
292
- implementations: generatedCode,
293
- testsPass: true,
294
- greenPhaseComplete: true,
295
- readyForRefactoring: true
296
- }, { partition: 'coordination' });
297
-
298
- // Emit completion event
466
+ // Create GREEN phase output with implementation
467
+ const greenOutput: GREENPhaseOutput = {
468
+ cycleId: redOutput.cycleId,
469
+ phase: 'GREEN',
470
+ timestamp: Date.now(),
471
+ testFile: {
472
+ path: redOutput.testFile.path, // SAME test file
473
+ hash: redOutput.testFile.hash // SAME hash - tests unchanged
474
+ },
475
+ implFile: {
476
+ path: context.implFilePath,
477
+ content: generatedImplementation,
478
+ hash: createHash('sha256').update(generatedImplementation).digest('hex')
479
+ },
480
+ implementation: {
481
+ className: context.module.name,
482
+ methods: extractedMethods.map(m => ({
483
+ name: m.name,
484
+ signature: m.signature,
485
+ complexity: calculateComplexity(m)
486
+ }))
487
+ },
488
+ validation: {
489
+ allTestsPassing: testResults.passed === testResults.total,
490
+ passCount: testResults.passed,
491
+ totalCount: testResults.total,
492
+ coverage: testResults.coverage
493
+ },
494
+ nextPhase: 'REFACTOR',
495
+ readyForHandoff: true
496
+ };
497
+
498
+ // Validate before storing
499
+ await validateGREENHandoff(greenOutput, redOutput);
500
+
501
+ // Store for REFACTOR phase
502
+ await this.memoryStore.store(`aqe/tdd/cycle-${cycleId}/green/impl`, greenOutput, {
503
+ partition: 'coordination',
504
+ ttl: 86400
505
+ });
506
+
507
+ // Emit completion event with cycle reference
299
508
  this.eventBus.emit('test-implementer:completed', {
300
509
  agentId: this.agentId,
301
- implementationsCreated: generatedCode.length,
302
- nextPhase: 'REFACTOR'
510
+ cycleId: context.cycleId,
511
+ implementationPath: context.implFilePath,
512
+ testsPassing: greenOutput.validation.passCount,
513
+ testsTotal: greenOutput.validation.totalCount,
514
+ coverage: greenOutput.validation.coverage,
515
+ nextPhase: 'REFACTOR',
516
+ readyForHandoff: greenOutput.readyForHandoff
303
517
  });
304
518
  ```
305
519
 
@@ -350,39 +350,289 @@ class ContinuousTester {
350
350
  }
351
351
  ```
352
352
 
353
+ ## TDD Coordination Protocol
354
+
355
+ ### Cycle-Based Memory Namespace
356
+
357
+ All TDD subagents share context through a cycle-specific namespace:
358
+
359
+ ```
360
+ aqe/tdd/cycle-{cycleId}/
361
+ ├── context # Shared workflow context (created by parent)
362
+ ├── red/
363
+ │ ├── tests # Test file content from RED phase
364
+ │ └── validation # RED phase validation results
365
+ ├── green/
366
+ │ ├── impl # Implementation from GREEN phase
367
+ │ └── validation # GREEN phase validation results
368
+ └── refactor/
369
+ ├── result # Final refactored code
370
+ └── validation # REFACTOR phase validation results
371
+ ```
372
+
373
+ ### Input Protocol (from qe-test-implementer)
374
+
375
+ **Required Input Structure:**
376
+ ```typescript
377
+ interface GREENPhaseOutput {
378
+ cycleId: string; // Unique identifier for this TDD cycle
379
+ phase: 'GREEN';
380
+ timestamp: number;
381
+ testFile: {
382
+ path: string; // Absolute path to test file
383
+ hash: string; // SHA256 hash - tests unchanged
384
+ };
385
+ implFile: {
386
+ path: string; // Absolute path to implementation
387
+ content: string; // Full implementation content
388
+ hash: string; // SHA256 hash for validation
389
+ };
390
+ implementation: {
391
+ className: string;
392
+ methods: Array<{
393
+ name: string;
394
+ signature: string;
395
+ complexity: number;
396
+ }>;
397
+ };
398
+ validation: {
399
+ allTestsPassing: boolean; // MUST be true
400
+ passCount: number;
401
+ totalCount: number;
402
+ coverage: number;
403
+ };
404
+ nextPhase: 'REFACTOR';
405
+ readyForHandoff: boolean; // MUST be true to proceed
406
+ }
407
+
408
+ // Retrieve GREEN phase output
409
+ const greenOutput = await this.memoryStore.retrieve(`aqe/tdd/cycle-${cycleId}/green/impl`, {
410
+ partition: 'coordination'
411
+ });
412
+
413
+ // Validate GREEN phase is complete
414
+ if (!greenOutput.readyForHandoff || !greenOutput.validation.allTestsPassing) {
415
+ throw new Error('Cannot proceed to REFACTOR phase - GREEN phase incomplete');
416
+ }
417
+ ```
418
+
419
+ ### Output Protocol (Final TDD Cycle Output)
420
+
421
+ **Required Output Structure:**
422
+ ```typescript
423
+ interface REFACTORPhaseOutput {
424
+ cycleId: string; // Must match input cycleId
425
+ phase: 'REFACTOR';
426
+ timestamp: number;
427
+ testFile: {
428
+ path: string; // SAME path from RED/GREEN phases
429
+ hash: string; // SAME hash - tests unchanged throughout
430
+ };
431
+ implFile: {
432
+ path: string; // SAME path from GREEN phase
433
+ content: string; // Refactored implementation content
434
+ hash: string; // New hash after refactoring
435
+ originalHash: string; // Hash from GREEN phase for comparison
436
+ };
437
+ refactoring: {
438
+ applied: Array<{
439
+ type: string; // e.g., 'extract-function', 'rename-variable'
440
+ description: string;
441
+ linesAffected: number;
442
+ }>;
443
+ metrics: {
444
+ complexityBefore: number;
445
+ complexityAfter: number;
446
+ maintainabilityBefore: number;
447
+ maintainabilityAfter: number;
448
+ duplicateCodeReduced: number; // Percentage
449
+ };
450
+ };
451
+ validation: {
452
+ allTestsPassing: boolean; // MUST be true - behavior unchanged
453
+ passCount: number;
454
+ totalCount: number;
455
+ coverage: number; // Should be same or better
456
+ };
457
+ cycleComplete: boolean; // MUST be true
458
+ readyForReview: boolean; // MUST be true to proceed
459
+ }
460
+
461
+ // Store REFACTOR phase output
462
+ await this.memoryStore.store(`aqe/tdd/cycle-${cycleId}/refactor/result`, output, {
463
+ partition: 'coordination',
464
+ ttl: 86400
465
+ });
466
+ ```
467
+
468
+ ### Handoff Validation
469
+
470
+ Before emitting completion, validate REFACTOR phase:
471
+
472
+ ```typescript
473
+ async function validateREFACTORHandoff(
474
+ output: REFACTORPhaseOutput,
475
+ greenOutput: GREENPhaseOutput,
476
+ redOutput: REDPhaseOutput
477
+ ): Promise<boolean> {
478
+ const errors: string[] = [];
479
+
480
+ // 1. Verify cycle IDs match throughout
481
+ if (output.cycleId !== greenOutput.cycleId || output.cycleId !== redOutput.cycleId) {
482
+ errors.push(`Cycle ID mismatch across phases`);
483
+ }
484
+
485
+ // 2. Verify test file unchanged throughout TDD cycle
486
+ if (output.testFile.hash !== redOutput.testFile.hash) {
487
+ errors.push('Test file was modified during TDD cycle - tests must remain unchanged');
488
+ }
489
+
490
+ // 3. Verify implementation file path is same
491
+ if (output.implFile.path !== greenOutput.implFile.path) {
492
+ errors.push('Implementation file path changed during REFACTOR phase');
493
+ }
494
+
495
+ // 4. Verify implementation file exists with new content
496
+ if (!existsSync(output.implFile.path)) {
497
+ errors.push(`Implementation file not found: ${output.implFile.path}`);
498
+ } else {
499
+ const actualContent = readFileSync(output.implFile.path, 'utf-8');
500
+ const actualHash = createHash('sha256').update(actualContent).digest('hex');
501
+ if (actualHash !== output.implFile.hash) {
502
+ errors.push(`Implementation file content mismatch: hash differs`);
503
+ }
504
+ }
505
+
506
+ // 5. Verify all tests still pass (behavior unchanged)
507
+ if (!output.validation.allTestsPassing) {
508
+ errors.push(`REFACTOR phase violation: ${output.validation.totalCount - output.validation.passCount} tests now failing`);
509
+ }
510
+
511
+ // 6. Verify coverage didn't decrease
512
+ if (output.validation.coverage < greenOutput.validation.coverage - 0.01) {
513
+ errors.push(`Coverage decreased: ${greenOutput.validation.coverage} -> ${output.validation.coverage}`);
514
+ }
515
+
516
+ // 7. Set completion status
517
+ output.cycleComplete = errors.length === 0;
518
+ output.readyForReview = errors.length === 0;
519
+
520
+ if (errors.length > 0) {
521
+ console.error('REFACTOR phase validation failed:', errors);
522
+ }
523
+
524
+ return output.readyForReview;
525
+ }
526
+ ```
527
+
353
528
  ## Integration with Parent Agents
354
529
 
355
530
  ### Input from qe-test-implementer
356
531
 
357
532
  ```typescript
358
- // Read GREEN phase implementation
359
- const greenCode = await this.memoryStore.retrieve('aqe/test-implementer/results', {
533
+ // Retrieve cycle context
534
+ const context = await this.memoryStore.retrieve(`aqe/tdd/cycle-${cycleId}/context`, {
360
535
  partition: 'coordination'
361
536
  });
362
537
 
363
- // Verify GREEN phase is complete
364
- if (!greenCode.testsPass) {
365
- throw new Error('Cannot refactor - GREEN phase incomplete');
538
+ // Retrieve RED phase output (need test file reference)
539
+ const redOutput = await this.memoryStore.retrieve(`aqe/tdd/cycle-${cycleId}/red/tests`, {
540
+ partition: 'coordination'
541
+ });
542
+
543
+ // Retrieve GREEN phase output with implementation
544
+ const greenOutput = await this.memoryStore.retrieve(`aqe/tdd/cycle-${cycleId}/green/impl`, {
545
+ partition: 'coordination'
546
+ });
547
+
548
+ // Validate GREEN phase is complete and ready for handoff
549
+ if (!greenOutput) {
550
+ throw new Error(`GREEN phase output not found for cycle ${cycleId}`);
551
+ }
552
+
553
+ if (!greenOutput.readyForHandoff) {
554
+ throw new Error('Cannot proceed to REFACTOR phase - GREEN phase handoff not ready');
366
555
  }
556
+
557
+ if (!greenOutput.validation.allTestsPassing) {
558
+ throw new Error('Cannot refactor - GREEN phase tests not passing');
559
+ }
560
+
561
+ // Verify implementation file exists and matches expected content
562
+ const actualImplContent = readFileSync(greenOutput.implFile.path, 'utf-8');
563
+ const actualHash = createHash('sha256').update(actualImplContent).digest('hex');
564
+ if (actualHash !== greenOutput.implFile.hash) {
565
+ throw new Error('Implementation file has been modified since GREEN phase - cannot proceed');
566
+ }
567
+
568
+ // Now refactor this EXACT code while keeping tests passing
569
+ console.log(`Refactoring ${greenOutput.implFile.path} while keeping ${redOutput.tests.length} tests passing`);
367
570
  ```
368
571
 
369
572
  ### Output to qe-code-reviewer
370
573
 
371
574
  ```typescript
372
- // Store refactored code for review
373
- await this.memoryStore.store('aqe/test-refactorer/results', {
374
- refactoredCode: improvedCode,
375
- improvements: improvements,
376
- testsStillPass: true,
377
- qualityMetrics: metrics,
575
+ // Create REFACTOR phase output with improved code
576
+ const refactorOutput: REFACTORPhaseOutput = {
577
+ cycleId: greenOutput.cycleId,
578
+ phase: 'REFACTOR',
579
+ timestamp: Date.now(),
580
+ testFile: {
581
+ path: redOutput.testFile.path, // SAME test file throughout
582
+ hash: redOutput.testFile.hash // SAME hash - tests unchanged
583
+ },
584
+ implFile: {
585
+ path: greenOutput.implFile.path, // SAME implementation path
586
+ content: refactoredCode,
587
+ hash: createHash('sha256').update(refactoredCode).digest('hex'),
588
+ originalHash: greenOutput.implFile.hash // Track what we started with
589
+ },
590
+ refactoring: {
591
+ applied: appliedRefactorings.map(r => ({
592
+ type: r.type,
593
+ description: r.description,
594
+ linesAffected: r.linesAffected
595
+ })),
596
+ metrics: {
597
+ complexityBefore: calculateComplexity(greenOutput.implFile.content),
598
+ complexityAfter: calculateComplexity(refactoredCode),
599
+ maintainabilityBefore: calculateMaintainability(greenOutput.implFile.content),
600
+ maintainabilityAfter: calculateMaintainability(refactoredCode),
601
+ duplicateCodeReduced: calculateDuplicateReduction(greenOutput.implFile.content, refactoredCode)
602
+ }
603
+ },
604
+ validation: {
605
+ allTestsPassing: testResults.passed === testResults.total,
606
+ passCount: testResults.passed,
607
+ totalCount: testResults.total,
608
+ coverage: testResults.coverage
609
+ },
610
+ cycleComplete: true,
378
611
  readyForReview: true
379
- }, { partition: 'coordination' });
612
+ };
613
+
614
+ // Validate before storing
615
+ await validateREFACTORHandoff(refactorOutput, greenOutput, redOutput);
616
+
617
+ // Store final TDD cycle result
618
+ await this.memoryStore.store(`aqe/tdd/cycle-${cycleId}/refactor/result`, refactorOutput, {
619
+ partition: 'coordination',
620
+ ttl: 86400
621
+ });
380
622
 
381
- // Emit completion event
623
+ // Emit completion event with full cycle summary
382
624
  this.eventBus.emit('test-refactorer:completed', {
383
625
  agentId: this.agentId,
384
- improvementsApplied: improvements.length,
385
- nextPhase: 'REVIEW'
626
+ cycleId: context.cycleId,
627
+ testFilePath: redOutput.testFile.path,
628
+ implFilePath: greenOutput.implFile.path,
629
+ refactoringsApplied: refactorOutput.refactoring.applied.length,
630
+ complexityReduction: refactorOutput.refactoring.metrics.complexityBefore -
631
+ refactorOutput.refactoring.metrics.complexityAfter,
632
+ testsStillPassing: refactorOutput.validation.allTestsPassing,
633
+ coverage: refactorOutput.validation.coverage,
634
+ cycleComplete: refactorOutput.cycleComplete,
635
+ readyForReview: refactorOutput.readyForReview
386
636
  });
387
637
  ```
388
638