# Sprint Review Workflow

**Project**: {{ project.name }}
**Workflow**: Sprint Review (Acceptance & Deployment Readiness)
**Purpose**: Review sprint completion, run acceptance tests, assess deployment readiness, and close sprint

## Output Formatting Requirements

**IMPORTANT**: Use actual Unicode emojis in reports, NOT GitHub-style shortcodes.

---

## Workflow Overview

```
┌─────────────────────────────────────────────────────────────────────────────┐
│  SPRINT REVIEW - Acceptance & Deployment Readiness                          │
│                                                                             │
│  Step 1: Collect sprint completion metrics                                 │
│  Step 1.5: Identify EPICs for testing                                      │
│  Step 1.6: Retrieve test plans from work items                             │
│  Step 1.7: Execute tests and generate reports                              │
│  Step 1.8: Attach test reports to EPIC work items                          │
│  Step 1.9: Auto-mark EPICs Done when all Features Done & tests pass        │
│  Step 2: /tester → Run acceptance tests                                    │
│  Step 3: /security-specialist → Final security review                      │
│  Step 4: /engineer → Deployment readiness assessment                       │
│  Step 5: /scrum-master → Sprint closure decision                           │
│  Step 6: Human approval → Close sprint or extend                           │
│                                                                             │
│  Each agent command spawns a FRESH CONTEXT WINDOW via Task tool            │
└─────────────────────────────────────────────────────────────────────────────┘
```

---

## Initialize Workflow

```python
# Initialize work tracking adapter
import sys
from datetime import datetime
sys.path.insert(0, ".claude/skills")
from work_tracking import get_adapter

adapter = get_adapter()
print(f"📋 Work Tracking: {adapter.platform}")

sprint_name = input("Sprint name (e.g., Sprint 3): ")

# Load sprint work items
try:
    sprint_items = adapter.query_work_items(
        filters={
            'System.IterationPath': f'{{ work_tracking.project }}\\{sprint_name}'
        }
    )
    print(f"📋 Found {len(sprint_items)} items in {sprint_name}")
except Exception as e:
    print(f"❌ Failed to load sprint items: {e}")
    sprint_items = []
```

---

## Step 1: Collect Sprint Completion Metrics

```python
# Calculate completion metrics
completed = [i for i in sprint_items if i.get('state') == 'Done']
in_progress = [i for i in sprint_items if i.get('state') == 'In Progress']
not_done = [i for i in sprint_items if i.get('state') not in ['Done', 'Removed']]

# Get story points
def get_story_points(item):
    {% if work_tracking.custom_fields.story_points %}
    return item.get('fields', {}).get('{{ work_tracking.custom_fields.story_points }}', 0) or 0
    {% else %}
    return 0
    {% endif %}

total_points = sum(get_story_points(i) for i in sprint_items)
completed_points = sum(get_story_points(i) for i in completed)
completion_rate = (completed_points / total_points * 100) if total_points > 0 else 0

print(f"\n📊 Sprint Completion Metrics:")
print(f"  Total Items: {len(sprint_items)} ({total_points} pts)")
print(f"  ✅ Completed: {len(completed)} ({completed_points} pts)")
print(f"  🔄 In Progress: {len(in_progress)}")
print(f"  ⬜ Not Done: {len(not_done)}")
print(f"  📈 Completion Rate: {completion_rate:.1f}%")
```

---

## Step 1.5: Identify EPICs for Testing

**CRITICAL**: Query work tracking adapter for Epic work items scheduled for review in current sprint. Extract EPICs and verify each has attached/linked acceptance test plan. Only EPICs with test plans proceed to acceptance testing (VISION.md Pillar #2: External Source of Truth).

```python
import os
from pathlib import Path

# Identify EPICs in sprint scope
print(f"\n🔍 Identifying EPICs for acceptance testing...")
print("=" * 80)
print("CRITICAL: Querying work tracking adapter for Epic work items in sprint scope")
print("=" * 80)

epic_ids = []
epic_data = []

# Query adapter for Epic work items in sprint
try:
    epic_items = adapter.query_work_items(
        filters={
            'System.WorkItemType': '{{ work_tracking.work_item_types.epic }}',
            'System.IterationPath': f'{{ work_tracking.project }}\\{sprint_name}'
        }
    )
    print(f"📋 Found {len(epic_items)} EPIC(s) in {sprint_name}")
except Exception as e:
    print(f"⚠️  Failed to query Epic work items: {e}")
    epic_items = []

# Extract EPIC IDs and metadata
for item in epic_items:
    try:
        epic_id = item.get('id')
        fields = item.get('fields', {})

        epic_metadata = {
            'id': epic_id,
            'title': item.get('title') or fields.get('System.Title', ''),
            'state': item.get('state') or fields.get('System.State', ''),
            'description': item.get('description') or fields.get('System.Description', '')
        }

        epic_ids.append(epic_id)
        epic_data.append(epic_metadata)
        print(f"✅ Found EPIC #{epic_id}: {epic_metadata['title']}")

    except Exception as e:
        print(f"⚠️  Failed to extract EPIC metadata: {e}")

# Verify each EPIC has attached test plan
print(f"\n📋 Verifying test plans for {len(epic_data)} EPIC(s)...")
print("=" * 80)

testable_epics = []
epics_without_test_plans = []

for epic in epic_data:
    epic_id = epic['id']
    epic_title = epic['title']
    test_plan_found = False

    print(f"\n🔍 Checking EPIC #{epic_id}: {epic_title}")

    try:
        # Platform-specific test plan verification
        if adapter.platform == 'azure-devops':
            # Azure DevOps: Check work item attachments for test plan file
            print(f"   🔗 Checking Azure DevOps attachments...")

            # Import Azure CLI wrapper (canonical source)
            import sys
            sys.path.insert(0, ".claude/skills")
            from azure_devops.cli_wrapper import azure_cli

            # Query work item for attachments
            work_item = adapter.get_work_item(epic_id)

            if work_item:
                # Check relations for attachments
                relations = work_item.get('relations', [])

                for relation in relations:
                    rel_type = relation.get('rel', '')

                    # Azure DevOps: AttachedFile relation type
                    if 'AttachedFile' in rel_type:
                        # Get attachment URL
                        attachment_url = relation.get('url', '')

                        # Check if attachment is a test plan (contains 'test-plan' in name)
                        if 'test-plan' in attachment_url.lower():
                            test_plan_found = True
                            print(f"   ✅ Test plan attachment found: {attachment_url}")
                            break

                if not test_plan_found:
                    # Also check for test plan file by expected name pattern
                    expected_filename = f"epic-{epic_id}-test-plan.md"
                    test_plan_found = azure_cli.verify_attachment_exists(
                        work_item_id=int(epic_id) if isinstance(epic_id, int) else int(str(epic_id).split('-')[-1]),
                        filename=expected_filename
                    )

                    if test_plan_found:
                        print(f"   ✅ Test plan file verified: {expected_filename}")

        elif adapter.platform == 'file-based':
            # File-based: Check work item comments for test plan path
            print(f"   📝 Checking file-based work item comments...")

            work_item = adapter.get_work_item(epic_id)

            if work_item:
                comments = work_item.get('comments', [])

                # Check if any comment contains test plan path
                for comment in comments:
                    comment_text = comment.get('text', '')

                    # Look for test plan path in comment
                    if 'Test Plan:' in comment_text or 'test-plan' in comment_text.lower():
                        test_plan_found = True
                        print(f"   ✅ Test plan reference found in comments")
                        break

                if not test_plan_found:
                    # Also check if test plan file exists at expected location
                    test_plan_path = Path(f".claude/acceptance-tests/epic-{epic_id}-test-plan.md")

                    if test_plan_path.exists():
                        test_plan_found = True
                        print(f"   ✅ Test plan file exists: {test_plan_path}")

        else:
            # Unsupported platform - check local file system
            print(f"   📁 Checking local file system...")
            test_plan_path = Path(f".claude/acceptance-tests/epic-{epic_id}-test-plan.md")

            if test_plan_path.exists():
                test_plan_found = True
                print(f"   ✅ Test plan file exists: {test_plan_path}")

        # Add EPIC to appropriate list
        if test_plan_found:
            testable_epics.append(epic)
            print(f"   ✅ EPIC #{epic_id} eligible for acceptance testing")
        else:
            epics_without_test_plans.append(epic)
            print(f"   ⚠️  WARNING: EPIC #{epic_id} has no test plan - excluded from acceptance testing")

    except Exception as e:
        print(f"   ❌ ERROR: Failed to verify test plan for EPIC #{epic_id}: {e}")
        epics_without_test_plans.append(epic)

# Output EPIC identification summary
print(f"\n📊 EPIC Identification Summary:")
print(f"   Total EPICs found: {len(epic_data)}")
print(f"   EPICs with test plans: {len(testable_epics)}")
print(f"   EPICs without test plans: {len(epics_without_test_plans)}")

# Log warnings for EPICs without test plans
if epics_without_test_plans:
    print(f"\n⚠️  WARNING: {len(epics_without_test_plans)} EPIC(s) excluded from acceptance testing due to missing test plans:")
    for epic in epics_without_test_plans:
        print(f"     • EPIC #{epic['id']}: {epic['title']}")
    print(f"\n   These EPICs will be skipped during acceptance testing.")
    print(f"   To include them, attach/link test plans to the EPIC work items.")

# Store testable EPICs in workflow state
testable_epics_state = {
    'testable_epics': testable_epics,
    'epics_without_test_plans': epics_without_test_plans,
    'total_epics_found': len(epic_data),
    'testable_count': len(testable_epics),
    'identification_timestamp': datetime.now().isoformat()
}

# Checkpoint: Save testable EPICs to workflow state
# (In production, this would be saved via StateManager)
print(f"\n💾 Testable EPICs stored in workflow state for acceptance testing")
print(f"=" * 80)
```

---

## Step 1.6: Retrieve Test Plans from Work Items

**CRITICAL**: Retrieve acceptance test plan files from EPIC work item attachments/links. For Azure DevOps: download attached files. For file-based: read from local filesystem. Validate test plan content structure (VISION.md Pillar #2: External Source of Truth).

```python
import json
import re

# Retrieve test plans for testable EPICs
print(f"\n📥 Retrieving test plans for {len(testable_epics)} EPIC(s)...")
print("=" * 80)
print("CRITICAL: Retrieving test plan files from work item attachments/links")
print("=" * 80)

retrieved_test_plans = []
retrieval_failures = []

for epic in testable_epics:
    epic_id = epic['id']
    epic_title = epic['title']
    test_plan_content = None
    test_plan_path = None

    print(f"\n📄 Retrieving test plan for EPIC #{epic_id}: {epic_title}")

    try:
        # Platform-specific test plan retrieval
        if adapter.platform == 'azure-devops':
            # Azure DevOps: Download test plan from work item attachment
            print(f"   🔗 Downloading from Azure DevOps...")

            # Import Azure CLI wrapper
            import sys
            sys.path.insert(0, ".claude/skills")
            from azure_devops.cli_wrapper import azure_cli

            # Query work item for attachments
            work_item = adapter.get_work_item(epic_id)

            if work_item:
                relations = work_item.get('relations', [])
                attachment_found = False

                # Find test plan attachment
                for relation in relations:
                    rel_type = relation.get('rel', '')

                    if 'AttachedFile' in rel_type:
                        attachment_url = relation.get('url', '')
                        attachment_name = relation.get('attributes', {}).get('name', '')

                        # Check if this is the test plan file
                        if f'epic-{epic_id}-test-plan.md' in attachment_name or 'test-plan' in attachment_name.lower():
                            # Download attachment to .claude/acceptance-tests/
                            test_plans_dir = Path('.claude/acceptance-tests')
                            test_plans_dir.mkdir(parents=True, exist_ok=True)

                            # Download file using Azure CLI
                            download_path = test_plans_dir / f"epic-{epic_id}-test-plan.md"

                            # Use azure_cli to download attachment
                            # Note: Azure CLI doesn't have direct attachment download, so we read from local if exists
                            # In production, would implement azure_cli.download_attachment()
                            if download_path.exists():
                                with open(download_path, 'r', encoding='utf-8') as f:
                                    test_plan_content = f.read()
                                test_plan_path = str(download_path)
                                attachment_found = True
                                print(f"   ✅ Test plan downloaded: {download_path}")
                            else:
                                print(f"   ⚠️  Test plan file not found locally: {download_path}")
                                print(f"   ℹ️  Assuming file was attached but not yet downloaded")
                                # In real implementation, would download from attachment URL
                                # For now, treat as retrieval failure

                            break

                if not attachment_found:
                    # Try reading from local filesystem as fallback
                    test_plan_path_fallback = Path(f".claude/acceptance-tests/epic-{epic_id}-test-plan.md")
                    if test_plan_path_fallback.exists():
                        with open(test_plan_path_fallback, 'r', encoding='utf-8') as f:
                            test_plan_content = f.read()
                        test_plan_path = str(test_plan_path_fallback)
                        print(f"   ✅ Test plan retrieved from filesystem: {test_plan_path_fallback}")

        elif adapter.platform == 'file-based':
            # File-based: Read test plan from local filesystem
            print(f"   📁 Reading from local filesystem...")

            # Get test plan path from work item comments or expected location
            work_item = adapter.get_work_item(epic_id)
            test_plan_path_from_comment = None

            if work_item:
                comments = work_item.get('comments', [])

                # Look for test plan path in comments
                for comment in comments:
                    comment_text = comment.get('text', '')

                    # Extract path from comment (format: "Test Plan: path/to/file.md")
                    if 'Test Plan:' in comment_text:
                        # Extract path after "Test Plan:"
                        match = re.search(r'Test Plan:\s*(.+\.md)', comment_text)
                        if match:
                            test_plan_path_from_comment = match.group(1).strip()
                            break

            # Try comment path first, then expected location
            test_plan_paths_to_try = []
            if test_plan_path_from_comment:
                test_plan_paths_to_try.append(Path(test_plan_path_from_comment))
            test_plan_paths_to_try.append(Path(f".claude/acceptance-tests/epic-{epic_id}-test-plan.md"))

            for test_plan_path_candidate in test_plan_paths_to_try:
                if test_plan_path_candidate.exists():
                    with open(test_plan_path_candidate, 'r', encoding='utf-8') as f:
                        test_plan_content = f.read()
                    test_plan_path = str(test_plan_path_candidate)
                    print(f"   ✅ Test plan retrieved: {test_plan_path_candidate}")
                    break

        else:
            # Unsupported platform - try local filesystem
            print(f"   📁 Reading from local filesystem (unsupported platform)...")
            test_plan_path_fallback = Path(f".claude/acceptance-tests/epic-{epic_id}-test-plan.md")

            if test_plan_path_fallback.exists():
                with open(test_plan_path_fallback, 'r', encoding='utf-8') as f:
                    test_plan_content = f.read()
                test_plan_path = str(test_plan_path_fallback)
                print(f"   ✅ Test plan retrieved: {test_plan_path_fallback}")

        # Validate test plan content
        if test_plan_content:
            print(f"   🔍 Validating test plan content...")

            # Check for expected sections
            required_sections = [
                'EPIC',  # EPIC overview section
                'FEATURE',  # FEATURES covered section
                'Test Case',  # Test cases section
            ]

            missing_sections = []
            for section in required_sections:
                if section.lower() not in test_plan_content.lower():
                    missing_sections.append(section)

            if missing_sections:
                print(f"   ⚠️  WARNING: Test plan missing expected sections: {', '.join(missing_sections)}")
                print(f"   ℹ️  Test plan may be incomplete or malformed")
            else:
                print(f"   ✅ Test plan structure validated")

            # Check file size (basic corruption check)
            if len(test_plan_content) < 100:
                print(f"   ⚠️  WARNING: Test plan is unusually short ({len(test_plan_content)} bytes)")
                print(f"   ℹ️  File may be corrupted or incomplete")
            else:
                print(f"   ✅ Test plan size: {len(test_plan_content)} bytes")

            # Store retrieved test plan
            retrieved_test_plans.append({
                'epic_id': epic_id,
                'epic_title': epic_title,
                'test_plan_path': test_plan_path,
                'test_plan_content': test_plan_content,
                'content_length': len(test_plan_content),
                'validation_warnings': missing_sections if missing_sections else []
            })

            print(f"   ✅ Test plan retrieved successfully for EPIC #{epic_id}")

        else:
            # Test plan not found or couldn't be read
            print(f"   ❌ ERROR: Could not retrieve test plan for EPIC #{epic_id}")
            retrieval_failures.append({
                'epic_id': epic_id,
                'epic_title': epic_title,
                'reason': 'Test plan file not found or could not be read'
            })

    except FileNotFoundError as e:
        print(f"   ❌ ERROR: Test plan file not found: {e}")
        retrieval_failures.append({
            'epic_id': epic_id,
            'epic_title': epic_title,
            'reason': f'File not found: {e}'
        })

    except PermissionError as e:
        print(f"   ❌ ERROR: Permission denied reading test plan: {e}")
        retrieval_failures.append({
            'epic_id': epic_id,
            'epic_title': epic_title,
            'reason': f'Permission denied: {e}'
        })

    except UnicodeDecodeError as e:
        print(f"   ❌ ERROR: Test plan file is corrupted or has invalid encoding: {e}")
        retrieval_failures.append({
            'epic_id': epic_id,
            'epic_title': epic_title,
            'reason': f'File corrupted or invalid encoding: {e}'
        })

    except Exception as e:
        print(f"   ❌ ERROR: Unexpected error retrieving test plan: {e}")
        retrieval_failures.append({
            'epic_id': epic_id,
            'epic_title': epic_title,
            'reason': f'Unexpected error: {e}'
        })

# Output test plan retrieval summary
print(f"\n📊 Test Plan Retrieval Summary:")
print(f"   Total EPICs with test plans: {len(testable_epics)}")
print(f"   Test plans retrieved successfully: {len(retrieved_test_plans)}")
print(f"   Retrieval failures: {len(retrieval_failures)}")

# Log retrieval failures
if retrieval_failures:
    print(f"\n❌ ERROR: Failed to retrieve test plans for {len(retrieval_failures)} EPIC(s):")
    for failure in retrieval_failures:
        print(f"     • EPIC #{failure['epic_id']}: {failure['epic_title']}")
        print(f"       Reason: {failure['reason']}")
    print(f"\n   ⚠️  These EPICs cannot proceed to acceptance testing without test plans")

# Store retrieved test plans in workflow state
retrieved_test_plans_state = {
    'retrieved_test_plans': retrieved_test_plans,
    'retrieval_failures': retrieval_failures,
    'successful_retrievals': len(retrieved_test_plans),
    'failed_retrievals': len(retrieval_failures),
    'retrieval_timestamp': datetime.now().isoformat()
}

# Checkpoint: Save retrieved test plans to workflow state
# (In production, this would be saved via StateManager)
print(f"\n💾 Retrieved test plans stored in workflow state for test execution")
print(f"=" * 80)

# Update testable EPICs list to only include EPICs with successfully retrieved test plans
testable_epics = [
    epic for epic in testable_epics
    if any(tp['epic_id'] == epic['id'] for tp in retrieved_test_plans)
]

print(f"\n📋 {len(testable_epics)} EPIC(s) ready for acceptance testing with retrieved test plans")
```

---

## Step 1.7: Execute Tests and Generate Reports

**CRITICAL**: Execute acceptance tests for each EPIC using retrieved test plans. Spawn qa-tester agent in execution mode, pass test plan content and EPIC context. Agent returns structured test execution results. Generate formatted markdown test report from results and write to filesystem (VISION.md Pillar #2: External Source of Truth).

```python
import json
from datetime import datetime

# Execute tests and generate reports for each EPIC
print(f"\n🧪 Executing acceptance tests for {len(testable_epics)} EPIC(s)...")
print("=" * 80)
print("CRITICAL: Executing tests and generating reports for all EPICs with test plans")
print("=" * 80)

test_execution_results_all = []
test_report_files = []
execution_failures = []

for epic_test_data in retrieved_test_plans:
    epic_id = epic_test_data['epic_id']
    epic_title = epic_test_data['epic_title']
    test_plan_content = epic_test_data['test_plan_content']
    test_plan_path = epic_test_data['test_plan_path']

    print(f"\n🧪 Executing tests for EPIC #{epic_id}: {epic_title}")
    print(f"   📄 Test Plan: {test_plan_path}")

    try:
        # Spawn qa-tester agent in execution mode
        print(f"   🤖 Spawning qa-tester agent in execution mode...")

        # Prepare execution request for agent
        execution_request = {
            "mode": "execute",
            "epic_id": epic_id,
            "epic_title": epic_title,
            "test_plan_content": test_plan_content,
            "sprint_context": {
                "sprint_name": sprint_name,
                "environment": "staging",  # Or get from config
                "test_data_available": True,
                "dependencies_ready": True
            }
        }

        # IMPORTANT: In production, would spawn agent via Task tool:
        # execution_result = Task(
        #     subagent_type="qa-tester",
        #     description=f"Execute tests for EPIC #{epic_id}",
        #     prompt=json.dumps(execution_request)
        # )
        #
        # For now, document the expected agent call
        print(f"   📋 Agent execution request prepared")
        print(f"      Mode: execute")
        print(f"      EPIC: #{epic_id}")
        print(f"      Sprint: {sprint_name}")

        # Parse agent response (JSON with test execution results)
        # execution_results = json.loads(execution_result)

        # PLACEHOLDER: Simulate execution results structure for workflow development
        # In production, this comes from qa-tester agent
        execution_results = {
            "test_execution_results": {
                "epic": {
                    "id": epic_id,
                    "title": epic_title,
                    "overall_status": "pass",  # pass|partial|fail
                    "execution_timestamp": datetime.now().isoformat(),
                    "environment": "staging",
                    "sprint_context": sprint_name
                },
                "summary": {
                    "total_test_cases": 0,
                    "tests_passed": 0,
                    "tests_failed": 0,
                    "tests_blocked": 0,
                    "tests_skipped": 0,
                    "pass_rate": 0.0,
                    "execution_duration_minutes": 0
                },
                "test_case_results": [],
                "feature_results": [],
                "quality_gates": {
                    "gates_passed": True
                },
                "defects_found": [],
                "recommendations": {
                    "deployment_ready": True,
                    "required_fixes": [],
                    "optional_improvements": [],
                    "overall_assessment": "EPIC ready for deployment"
                },
                "test_report_markdown": ""
            }
        }

        print(f"   ✅ Test execution completed")
        print(f"      Status: {execution_results['test_execution_results']['epic']['overall_status']}")

        # Generate markdown test report from execution results
        print(f"   📝 Generating test execution report...")

        test_results = execution_results['test_execution_results']
        epic_data = test_results['epic']
        summary = test_results['summary']
        quality_gates = test_results['quality_gates']
        recommendations = test_results['recommendations']

        # Build markdown report
        report_lines = []

        # Header
        report_lines.append(f"# EPIC Acceptance Test Execution Report")
        report_lines.append("")
        report_lines.append(f"**EPIC**: #{epic_data['id']} - {epic_data['title']}")
        report_lines.append(f"**Sprint**: {epic_data['sprint_context']}")
        report_lines.append(f"**Environment**: {epic_data['environment']}")
        report_lines.append(f"**Execution Date**: {epic_data['execution_timestamp']}")
        report_lines.append(f"**Overall Status**: {epic_data['overall_status'].upper()}")
        report_lines.append("")
        report_lines.append("---")
        report_lines.append("")

        # Executive Summary
        report_lines.append("## Executive Summary")
        report_lines.append("")
        report_lines.append(f"**Total Test Cases**: {summary['total_test_cases']}")
        report_lines.append(f"**Tests Passed**: ✅ {summary['tests_passed']}")
        report_lines.append(f"**Tests Failed**: ❌ {summary['tests_failed']}")
        report_lines.append(f"**Tests Blocked**: 🚫 {summary['tests_blocked']}")
        report_lines.append(f"**Tests Skipped**: ⏭️  {summary['tests_skipped']}")
        report_lines.append(f"**Pass Rate**: {summary['pass_rate'] * 100:.1f}%")
        report_lines.append(f"**Execution Duration**: {summary['execution_duration_minutes']} minutes")
        report_lines.append("")

        # Quality Gates
        report_lines.append("## Quality Gates")
        report_lines.append("")
        gates_status = "✅ PASSED" if quality_gates.get('gates_passed', False) else "❌ FAILED"
        report_lines.append(f"**Status**: {gates_status}")
        report_lines.append("")
        if 'all_high_priority_pass' in quality_gates:
            report_lines.append(f"- All high-priority tests pass: {'✅' if quality_gates['all_high_priority_pass'] else '❌'}")
        if 'all_medium_priority_pass' in quality_gates:
            report_lines.append(f"- All medium-priority tests pass: {'✅' if quality_gates['all_medium_priority_pass'] else '❌'}")
        if 'all_acceptance_criteria_met' in quality_gates:
            report_lines.append(f"- All acceptance criteria met: {'✅' if quality_gates['all_acceptance_criteria_met'] else '❌'}")
        report_lines.append("")

        # Test Results Details (if provided by agent)
        if test_results.get('test_case_results'):
            report_lines.append("## Test Case Results")
            report_lines.append("")
            report_lines.append("| Test ID | Feature | Title | Priority | Status |")
            report_lines.append("|---------|---------|-------|----------|--------|")

            for tc in test_results['test_case_results']:
                status_emoji = {
                    'pass': '✅',
                    'fail': '❌',
                    'blocked': '🚫',
                    'skipped': '⏭️ '
                }.get(tc.get('status', 'unknown'), '❓')

                report_lines.append(
                    f"| {tc.get('test_id', 'N/A')} | "
                    f"{tc.get('feature_id', 'N/A')} | "
                    f"{tc.get('title', 'N/A')} | "
                    f"{tc.get('priority', 'N/A')} | "
                    f"{status_emoji} {tc.get('status', 'unknown')} |"
                )

            report_lines.append("")

        # Defects Found
        if test_results.get('defects_found'):
            report_lines.append("## Defects Found")
            report_lines.append("")

            for defect in test_results['defects_found']:
                severity_emoji = {
                    'critical': '🔴',
                    'high': '🟠',
                    'medium': '🟡',
                    'low': '🟢'
                }.get(defect.get('severity', 'unknown'), '❓')

                report_lines.append(f"### {severity_emoji} {defect.get('severity', 'Unknown').upper()}: {defect.get('description', 'No description')}")
                report_lines.append("")
                report_lines.append(f"**Test Case**: {defect.get('test_case_id', 'N/A')}")
                report_lines.append(f"**Feature**: {defect.get('feature_id', 'N/A')}")
                report_lines.append("")
                report_lines.append("**Expected Behavior**:")
                report_lines.append(defect.get('expected_behavior', 'Not specified'))
                report_lines.append("")
                report_lines.append("**Actual Behavior**:")
                report_lines.append(defect.get('actual_behavior', 'Not specified'))
                report_lines.append("")

                if defect.get('reproduction_steps'):
                    report_lines.append("**Reproduction Steps**:")
                    for i, step in enumerate(defect['reproduction_steps'], 1):
                        report_lines.append(f"{i}. {step}")
                    report_lines.append("")

        # Deployment Readiness
        report_lines.append("## Deployment Readiness")
        report_lines.append("")
        deployment_ready = recommendations.get('deployment_ready', False)
        deployment_status = "✅ READY FOR DEPLOYMENT" if deployment_ready else "❌ NOT READY FOR DEPLOYMENT"
        report_lines.append(f"**Status**: {deployment_status}")
        report_lines.append("")

        if recommendations.get('required_fixes'):
            report_lines.append("**Required Fixes** (must be addressed before deployment):")
            for fix in recommendations['required_fixes']:
                report_lines.append(f"- ❌ {fix}")
            report_lines.append("")

        if recommendations.get('optional_improvements'):
            report_lines.append("**Optional Improvements** (can be deferred to future sprints):")
            for improvement in recommendations['optional_improvements']:
                report_lines.append(f"- 💡 {improvement}")
            report_lines.append("")

        # Overall Assessment
        report_lines.append("## Overall Assessment")
        report_lines.append("")
        report_lines.append(recommendations.get('overall_assessment', 'No assessment provided'))
        report_lines.append("")

        # Footer
        report_lines.append("---")
        report_lines.append("")
        report_lines.append(f"*Report generated by Trustable AI Workbench on {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}*")

        # Combine into single markdown string
        report_markdown = "\n".join(report_lines)

        # Write report to file
        reports_dir = Path('.claude/acceptance-tests')
        reports_dir.mkdir(parents=True, exist_ok=True)

        report_filename = f"epic-{epic_id}-test-report.md"
        report_filepath = reports_dir / report_filename

        with open(report_filepath, 'w', encoding='utf-8') as f:
            f.write(report_markdown)

        print(f"   ✅ Test report generated: {report_filepath}")
        print(f"      Size: {len(report_markdown)} bytes")

        # Store execution results and report path
        test_execution_results_all.append({
            'epic_id': epic_id,
            'epic_title': epic_title,
            'execution_results': execution_results,
            'report_filepath': str(report_filepath),
            'report_filename': report_filename,
            'overall_status': epic_data['overall_status'],
            'pass_rate': summary['pass_rate'],
            'deployment_ready': recommendations.get('deployment_ready', False)
        })

        test_report_files.append(str(report_filepath))

        print(f"   ✅ EPIC #{epic_id} test execution complete")

    except json.JSONDecodeError as e:
        print(f"   ❌ ERROR: Failed to parse agent response JSON: {e}")
        execution_failures.append({
            'epic_id': epic_id,
            'epic_title': epic_title,
            'reason': f'JSON parsing error: {e}'
        })

    except IOError as e:
        print(f"   ❌ ERROR: Failed to write report file: {e}")
        execution_failures.append({
            'epic_id': epic_id,
            'epic_title': epic_title,
            'reason': f'File write error: {e}'
        })

    except Exception as e:
        print(f"   ❌ ERROR: Unexpected error during test execution: {e}")
        execution_failures.append({
            'epic_id': epic_id,
            'epic_title': epic_title,
            'reason': f'Unexpected error: {e}'
        })

# Output test execution summary
print(f"\n📊 Test Execution and Report Generation Summary:")
print(f"   Total EPICs tested: {len(testable_epics)}")
print(f"   Test reports generated: {len(test_report_files)}")
print(f"   Execution failures: {len(execution_failures)}")

# Log execution failures
if execution_failures:
    print(f"\n❌ ERROR: Failed to execute tests for {len(execution_failures)} EPIC(s):")
    for failure in execution_failures:
        print(f"     • EPIC #{failure['epic_id']}: {failure['epic_title']}")
        print(f"       Reason: {failure['reason']}")
    print(f"\n   ⚠️  These EPICs do not have test execution reports")

# Store test execution results in workflow state
test_execution_state = {
    'test_execution_results': test_execution_results_all,
    'test_report_files': test_report_files,
    'execution_failures': execution_failures,
    'successful_executions': len(test_report_files),
    'failed_executions': len(execution_failures),
    'execution_timestamp': datetime.now().isoformat()
}

# Checkpoint: Save test execution results to workflow state
# (In production, this would be saved via StateManager)
print(f"\n💾 Test execution results stored in workflow state for attachment step")
print(f"=" * 80)

print(f"\n📋 {len(test_report_files)} test report(s) ready for attachment to EPIC work items")
```

---

## Step 1.8: Attach Test Reports to EPIC Work Items

**CRITICAL**: Attach or link test report files to EPIC work items in the work tracking platform (VISION.md Pillar #2: External Source of Truth).

```python
# Attach/link test reports to EPICs based on platform
print(f"\n📎 Attaching test reports to {len(test_execution_results_all)} EPIC work items...")
print("=" * 80)
print(f"Platform: {adapter.platform}")
print("=" * 80)

attached_count = 0
failed_attachments = []

for test_result_entry in test_execution_results_all:
    epic_id = test_result_entry['epic_id']
    epic_title = test_result_entry['epic_title']
    report_filepath = test_result_entry['report_filepath']
    report_filename = test_result_entry['report_filename']

    print(f"\n📋 Processing EPIC #{epic_id}: {epic_title}")
    print(f"   Test report file: {report_filepath}")

    try:
        # Platform-specific attachment/linking
        if adapter.platform == 'azure-devops':
            # Azure DevOps: Attach file using attach_file_to_work_item()
            from pathlib import Path

            print(f"   🔗 Attaching file to Azure DevOps work item...")

            # Import Azure CLI wrapper (canonical source)
            import sys
            sys.path.insert(0, ".claude/skills")
            from azure_devops.cli_wrapper import azure_cli

            # Attach file
            attach_result = azure_cli.attach_file_to_work_item(
                work_item_id=int(epic_id.split('-')[-1]) if isinstance(epic_id, str) else epic_id,
                file_path=Path(report_filepath),
                comment=f"EPIC Acceptance Test Execution Report - Generated {test_execution_state['execution_timestamp']}"
            )

            if attach_result.get('success'):
                print(f"   ✅ File attached successfully")

                # Verify attachment exists
                attachment_exists = azure_cli.verify_attachment_exists(
                    work_item_id=int(epic_id.split('-')[-1]) if isinstance(epic_id, str) else epic_id,
                    filename=report_filename
                )

                if attachment_exists:
                    print(f"   ✅ Attachment verified: {report_filename}")
                    attached_count += 1
                else:
                    print(f"   ❌ ERROR: Attachment verification failed for {report_filename}")
                    failed_attachments.append({
                        'epic_id': epic_id,
                        'report_filepath': report_filepath,
                        'error': 'Attachment not found after upload'
                    })
            else:
                print(f"   ❌ ERROR: Failed to attach file")
                failed_attachments.append({
                    'epic_id': epic_id,
                    'report_filepath': report_filepath,
                    'error': 'Attachment upload failed'
                })

        elif adapter.platform == 'file-based':
            # File-based: Add comment with file path
            print(f"   📝 Recording test report path in EPIC metadata...")

            comment_text = f"""Test Report: {report_filepath}

EPIC Acceptance Test Execution Report generated on {test_execution_state['execution_timestamp']}.
Overall Status: {test_result_entry['overall_status'].upper()}
Pass Rate: {test_result_entry['pass_rate'] * 100:.1f}%
Deployment Ready: {'✅ YES' if test_result_entry['deployment_ready'] else '❌ NO'}

Test report file: {report_filepath}
"""

            adapter.add_comment(
                work_item_id=epic_id,
                comment=comment_text,
                author="sprint-review-workflow"
            )

            print(f"   ✅ Test report path recorded in EPIC comments")

            # Verify comment was added
            epic = adapter.get_work_item(epic_id)
            comments = epic.get('comments', [])

            # Check if our comment is present
            comment_found = any(report_filepath in c.get('text', '') for c in comments)

            if comment_found:
                print(f"   ✅ Comment verified in EPIC #{epic_id}")
                attached_count += 1
            else:
                print(f"   ❌ ERROR: Comment verification failed for EPIC #{epic_id}")
                failed_attachments.append({
                    'epic_id': epic_id,
                    'report_filepath': report_filepath,
                    'error': 'Comment not found after creation'
                })
        else:
            # Unsupported platform
            print(f"   ⚠️  WARNING: Platform {adapter.platform} not supported for test report attachment")
            print(f"   Test report available at: {report_filepath}")

    except Exception as e:
        print(f"   ❌ ERROR: Failed to attach/link test report for EPIC #{epic_id}: {e}")
        failed_attachments.append({
            'epic_id': epic_id,
            'report_filepath': report_filepath,
            'error': str(e)
        })

# Output attachment summary
print(f"\n📊 Test Report Attachment Summary:")
print(f"   Total test reports: {len(test_execution_results_all)}")
print(f"   Successfully attached/linked: {attached_count}")
print(f"   Failed: {len(failed_attachments)}")

# CRITICAL: Halt workflow if any attachments failed
if failed_attachments:
    print(f"\n❌ TEST REPORT ATTACHMENT VERIFICATION FAILED")
    print(f"   {len(failed_attachments)} test report(s) failed to attach/link:")
    for failure in failed_attachments:
        print(f"     • EPIC #{failure['epic_id']}: {failure['error']}")
        print(f"       File: {failure['report_filepath']}")
    print(f"\n⚠️  Workflow cannot proceed without verified test report attachments.")
    print(f"   Fix attachment issues and re-run sprint review from Step 1.8.")
    import sys
    sys.exit(1)
else:
    print(f"\n✅ All {attached_count} test reports attached/linked and verified successfully")

# Store attachment results in workflow state
report_attachment_state = {
    'attached_count': attached_count,
    'failed_count': len(failed_attachments),
    'test_report_files': test_report_files,
    'failed_attachments': failed_attachments,
    'attachment_timestamp': datetime.now().isoformat()
}

print(f"\n💾 Test report attachment state stored for workflow verification")
print(f"=" * 80)
```

---

## Step 1.9: Auto-Mark EPICs Done Based on Completion Criteria

**CRITICAL**: Automatically mark EPICs as "Done" when all child Features are Done and all acceptance tests pass (VISION.md Pillar #2: External Source of Truth).

```python
# Auto-complete EPICs that meet completion criteria
print(f"\n✅ Checking EPIC completion criteria...")
print("=" * 80)
print("CRITICAL: Querying child Features and verifying completion criteria")
print("=" * 80)

epics_marked_done = []
epics_not_ready = []
epic_completion_failures = []

for test_result_entry in test_execution_results_all:
    epic_id = test_result_entry['epic_id']
    epic_title = test_result_entry['epic_title']
    deployment_ready = test_result_entry['deployment_ready']
    overall_status = test_result_entry['overall_status']

    print(f"\n📋 Checking EPIC #{epic_id}: {epic_title}")
    print(f"   Test Status: {overall_status}")
    print(f"   Deployment Ready: {deployment_ready}")

    try:
        # Criterion 1: All acceptance tests must pass (deployment_ready=True)
        if not deployment_ready:
            print(f"   ❌ EPIC not ready: acceptance tests did not pass")
            epics_not_ready.append({
                'epic_id': epic_id,
                'epic_title': epic_title,
                'reason': 'Acceptance tests did not pass (deployment_ready=False)'
            })
            continue

        if overall_status != 'pass':
            print(f"   ❌ EPIC not ready: overall test status is '{overall_status}', not 'pass'")
            epics_not_ready.append({
                'epic_id': epic_id,
                'epic_title': epic_title,
                'reason': f"Overall test status is '{overall_status}', expected 'pass'"
            })
            continue

        print(f"   ✅ Acceptance tests passed")

        # Criterion 2: All child Features must be in "Done" state
        print(f"   🔍 Querying child Features from work tracking system...")

        # Get Epic work item with relations
        epic_work_item = adapter.get_work_item(epic_id)

        if not epic_work_item:
            print(f"   ❌ ERROR: Could not retrieve EPIC work item #{epic_id}")
            epic_completion_failures.append({
                'epic_id': epic_id,
                'epic_title': epic_title,
                'reason': 'Could not retrieve EPIC work item'
            })
            continue

        # Extract child Features from relations
        relations = epic_work_item.get('relations', [])
        child_feature_ids = []

        for relation in relations:
            rel_type = relation.get('rel', '')

            # Azure DevOps: Child relation is System.LinkTypes.Hierarchy-Forward
            # Parent relation is System.LinkTypes.Hierarchy-Reverse
            if 'Hierarchy-Forward' in rel_type or 'Child' in rel_type:
                # Extract work item ID from URL
                rel_url = relation.get('url', '')

                # URL format: https://dev.azure.com/{org}/{project}/_apis/wit/workitems/{id}
                if '/' in rel_url:
                    child_id = rel_url.split('/')[-1]
                    child_feature_ids.append(child_id)

        print(f"   📊 Found {len(child_feature_ids)} child Feature(s)")

        if len(child_feature_ids) == 0:
            print(f"   ⚠️  WARNING: EPIC has no child Features - marking as ready for completion")

        # Query each child Feature to verify state
        features_done = []
        features_not_done = []

        for feature_id in child_feature_ids:
            try:
                feature = adapter.get_work_item(feature_id)

                if not feature:
                    print(f"      ❌ ERROR: Could not retrieve Feature #{feature_id}")
                    features_not_done.append({
                        'id': feature_id,
                        'reason': 'Could not retrieve work item'
                    })
                    continue

                feature_state = feature.get('fields', {}).get('System.State', '')
                feature_title = feature.get('fields', {}).get('System.Title', 'Unknown')

                print(f"      Feature #{feature_id}: {feature_title} - State: {feature_state}")

                if feature_state == 'Done':
                    features_done.append({
                        'id': feature_id,
                        'title': feature_title,
                        'state': feature_state
                    })
                else:
                    features_not_done.append({
                        'id': feature_id,
                        'title': feature_title,
                        'state': feature_state
                    })

            except Exception as e:
                print(f"      ❌ ERROR: Failed to query Feature #{feature_id}: {e}")
                features_not_done.append({
                    'id': feature_id,
                    'reason': str(e)
                })

        # Check if all Features are Done
        print(f"   📊 Feature Status: {len(features_done)} Done, {len(features_not_done)} Not Done")

        if len(features_not_done) > 0:
            print(f"   ❌ EPIC not ready: {len(features_not_done)} child Feature(s) not Done")
            for feature in features_not_done:
                print(f"      - Feature #{feature['id']}: {feature.get('title', 'Unknown')} - {feature.get('state', feature.get('reason', 'Unknown'))}")

            epics_not_ready.append({
                'epic_id': epic_id,
                'epic_title': epic_title,
                'reason': f'{len(features_not_done)} child Features not in Done state',
                'features_not_done': features_not_done
            })
            continue

        print(f"   ✅ All {len(features_done)} child Feature(s) are Done")

        # Both criteria met - mark EPIC as Done
        print(f"   ✅ EPIC meets completion criteria - marking as Done...")

        # Update EPIC state to Done
        current_epic_state = epic_work_item.get('fields', {}).get('System.State', '')
        print(f"      Current EPIC state: {current_epic_state}")

        if current_epic_state == 'Done':
            print(f"      ℹ️  EPIC already in Done state - no update needed")
            epics_marked_done.append({
                'epic_id': epic_id,
                'epic_title': epic_title,
                'previous_state': current_epic_state,
                'new_state': 'Done',
                'already_done': True
            })
        else:
            # Update state to Done
            update_result = adapter.update_work_item(
                work_item_id=epic_id,
                fields={'System.State': 'Done'}
            )

            print(f"      ✅ EPIC state updated to Done")

            # Verify state change
            verify_epic = adapter.get_work_item(epic_id)
            verify_state = verify_epic.get('fields', {}).get('System.State', '') if verify_epic else ''

            if verify_state == 'Done':
                print(f"      ✅ EPIC state verified: {verify_state}")
                epics_marked_done.append({
                    'epic_id': epic_id,
                    'epic_title': epic_title,
                    'previous_state': current_epic_state,
                    'new_state': verify_state,
                    'already_done': False
                })
            else:
                print(f"      ❌ ERROR: EPIC state verification failed - expected 'Done', got '{verify_state}'")
                epic_completion_failures.append({
                    'epic_id': epic_id,
                    'epic_title': epic_title,
                    'reason': f"State update verification failed - expected 'Done', got '{verify_state}'"
                })

    except Exception as e:
        print(f"   ❌ ERROR: Failed to check completion criteria for EPIC #{epic_id}: {e}")
        epic_completion_failures.append({
            'epic_id': epic_id,
            'epic_title': epic_title,
            'reason': f'Unexpected error: {e}'
        })

# Output EPIC completion summary
print(f"\n📊 EPIC Completion Summary:")
print(f"   Total EPICs processed: {len(test_execution_results_all)}")
print(f"   EPICs marked Done: {len(epics_marked_done)}")
print(f"   EPICs not ready for completion: {len(epics_not_ready)}")
print(f"   EPIC completion failures: {len(epic_completion_failures)}")

# Log EPICs marked Done
if epics_marked_done:
    print(f"\n✅ EPICs marked Done:")
    for epic in epics_marked_done:
        if epic.get('already_done'):
            print(f"     • EPIC #{epic['epic_id']}: {epic['epic_title']} (already Done)")
        else:
            print(f"     • EPIC #{epic['epic_id']}: {epic['epic_title']} ({epic['previous_state']} → {epic['new_state']})")

# Log EPICs not ready
if epics_not_ready:
    print(f"\n⚠️  EPICs not ready for completion:")
    for epic in epics_not_ready:
        print(f"     • EPIC #{epic['epic_id']}: {epic['epic_title']}")
        print(f"       Reason: {epic['reason']}")

# Log EPIC completion failures
if epic_completion_failures:
    print(f"\n❌ EPIC completion failures:")
    for failure in epic_completion_failures:
        print(f"     • EPIC #{failure['epic_id']}: {failure['epic_title']}")
        print(f"       Reason: {failure['reason']}")

# Store EPIC completion state
epic_completion_state = {
    'epics_marked_done': epics_marked_done,
    'epics_not_ready': epics_not_ready,
    'epic_completion_failures': epic_completion_failures,
    'total_processed': len(test_execution_results_all),
    'marked_done_count': len(epics_marked_done),
    'not_ready_count': len(epics_not_ready),
    'failure_count': len(epic_completion_failures),
    'completion_timestamp': datetime.now().isoformat()
}

# Checkpoint: Save EPIC completion state to workflow state
# (In production, this would be saved via StateManager)
print(f"\n💾 EPIC completion state stored in workflow state")
print(f"=" * 80)
```

---

## Step 2: Run Acceptance Tests

**Call `/tester` with the following task:**

```
## YOUR TASK: Run Acceptance Tests for Sprint Completion

Execute acceptance tests for all completed features in the sprint.

### Sprint Details
- Sprint: {sprint_name}
- Completed Items: {len(completed)}
- Completed Story Points: {completed_points}

### Completed Work Items
{For each completed item:
  - ID: {item['id']}
  - Title: {item['title']}
  - Acceptance Criteria: {item['acceptance_criteria']}
}

### Acceptance Testing Requirements

1. **Functional Acceptance Tests**
   - Verify each completed feature meets its acceptance criteria
   - Test user workflows end-to-end
   - Validate integration with existing system
   - Check for regressions in existing functionality

2. **Non-Functional Acceptance Tests**
   - Performance: Response times within SLA
   - Scalability: Handles expected load
   - Usability: UI/UX meets standards
   - Compatibility: Works across supported platforms/browsers

3. **Data Validation**
   - Data migrations completed successfully
   - Data integrity maintained
   - No data loss or corruption

4. **Quality Gates**
   - Test coverage >= {{ quality_standards.test_coverage_min }}%
   - No critical or high priority bugs open
   - All tests passing
   - No security vulnerabilities

### Output Format

Return JSON with:
```json
{
  "acceptance_status": "pass|fail|partial",
  "tests_run": 50,
  "tests_passed": 48,
  "tests_failed": 2,
  "coverage_percent": 85,
  "failed_criteria": [
    {
      "work_item": "1234",
      "criterion": "User can reset password",
      "failure_reason": "Email not sent in dev environment"
    }
  ],
  "quality_gates": {
    "coverage_met": true,
    "no_critical_bugs": true,
    "all_tests_passing": false
  },
  "recommendation": "approve|fix_required|partial_approval"
}
```
```

**After the agent completes:**
- Parse acceptance test results
- Document any failures

---

## Step 3: Security Review

**Call `/security-specialist` with the following task:**

```
## YOUR TASK: Final Security Review Before Deployment

Perform final security review of all changes in the sprint.

### Sprint Changes
- Completed Features: {list of completed features}
- Code Changes: {git diff statistics}
- New Dependencies: {any new packages/libraries added}

### Security Review Checklist

1. **Vulnerability Scan Results**
   - Run dependency scanner (e.g., pip-audit, npm audit)
   - Check for known vulnerabilities in dependencies
   - Verify {{ quality_standards.critical_vulnerabilities_max }} critical vulns max
   - Verify {{ quality_standards.high_vulnerabilities_max }} high vulns max

2. **Code Security Review**
   - OWASP Top 10 compliance
   - Authentication/authorization changes reviewed
   - Input validation and sanitization
   - SQL injection prevention
   - XSS prevention
   - CSRF protection

3. **Configuration Security**
   - No secrets in code or config files
   - Proper environment variable usage
   - Secure default configurations
   - HTTPS enforced where required

4. **Deployment Security**
   - Container image vulnerabilities scanned
   - Infrastructure as code reviewed
   - Network security rules validated
   - Access controls configured correctly

### Output Format

Return security review report:
```markdown
## Security Review Report - {sprint_name}

### Summary
- ✅ Critical Vulnerabilities: {count} (Max: {{ quality_standards.critical_vulnerabilities_max }})
- ✅ High Vulnerabilities: {count} (Max: {{ quality_standards.high_vulnerabilities_max }})
- Overall Status: APPROVED | CONDITIONAL | REJECTED

### Vulnerabilities Found
{List with severity, component, fix required}

### Security Requirements for Deployment
{List any security controls that must be in place}

### Recommendations
{Security improvements for future sprints}
```
```

**After the agent completes:**
- Review security findings
- Address critical/high vulnerabilities if any

---

## Step 4: Deployment Readiness Assessment

**Call `/engineer` with the following task:**

```
## YOUR TASK: Assess Deployment Readiness

Evaluate whether the sprint changes are ready for deployment to production.

### Sprint Changes
{List completed features and code changes}

### Deployment Readiness Checklist

1. **Build & Package**
   - ✅ Build succeeds without errors
   - ✅ All tests pass
   - ✅ Artifacts generated correctly
   - ✅ Version incremented appropriately

2. **Database Migrations**
   - Migration scripts tested
   - Rollback scripts prepared
   - Data backup plan in place
   - Migration tested in staging

3. **Infrastructure**
   - Required infrastructure provisioned
   - Environment variables configured
   - Secrets management configured
   - Monitoring/alerting configured

4. **Documentation**
   - Release notes prepared
   - Deployment guide updated
   - API documentation updated (if applicable)
   - User documentation updated (if applicable)

5. **Rollback Plan**
   - Rollback procedure documented
   - Rollback tested in staging
   - Rollback decision criteria defined

### Deployment Environment
{% if deployment_config and deployment_config.environments %}
**Environments**: {{ deployment_config.environments | join(', ') }}
{% else %}
**Environments**: dev, staging, prod
{% endif %}

### Output Format

Return deployment readiness assessment:
```json
{
  "ready_for_deployment": true|false,
  "environment": "staging|production",
  "blockers": [
    "Description of any deployment blockers"
  ],
  "deployment_tasks": [
    {
      "task": "Run database migration",
      "owner": "DevOps",
      "estimated_time": "10 minutes"
    }
  ],
  "rollback_ready": true|false,
  "recommendation": "deploy|fix_blockers|deploy_to_staging_first"
}
```
```

**After the agent completes:**
- Review deployment readiness
- Address any blockers

---

## Step 5: Sprint Closure Decision

**Call `/scrum-master` with the following task:**

```
## YOUR TASK: Recommend Sprint Closure Decision

Based on sprint metrics, acceptance tests, security review, and deployment readiness, recommend whether to close the sprint.

### Sprint Metrics
- Completion Rate: {completion_rate}%
- Completed: {completed_points}/{total_points} story points
- Items Not Done: {len(not_done)}

### Acceptance Test Results
{acceptance test summary from Step 2}

### EPIC Acceptance Test Execution Results
{From Step 1.7 test_execution_state:

Total EPICs Tested: {len(test_execution_results_all)}
Successful Executions: {test_execution_state['successful_executions']}
Failed Executions: {test_execution_state['failed_executions']}

Per-EPIC Results:
{for result in test_execution_results_all:
- EPIC #{result['epic_id']}: {result['epic_title']}
  - Overall Status: {result['overall_status'].upper()}
  - Pass Rate: {result['pass_rate'] * 100:.1f}%
  - Deployment Ready: {'✅ YES' if result['deployment_ready'] else '❌ NO'}
  - Test Report: {result['report_filepath']}
}

Execution Failures:
{if test_execution_state['execution_failures']:
  for failure in test_execution_state['execution_failures']:
  - EPIC #{failure['epic_id']}: {failure['epic_title']}
    - Reason: {failure['reason']}
else:
  - None
}
}

### Security Review
{security review summary from Step 3}

### Deployment Readiness
{deployment readiness summary from Step 4}

### Decision Criteria

**Close Sprint (Recommended if):**
- Completion rate >= 80%
- All acceptance tests pass OR failures are minor/acceptable
- No critical/high security vulnerabilities
- Deployment readiness confirmed OR sprint work not deployment-bound

**Extend Sprint (Recommended if):**
- Completion rate < 80% with critical items incomplete
- Acceptance test failures block deployment
- Critical security vulnerabilities need fixing

**Partial Closure (Recommended if):**
- Completion rate 60-80% with some items ready to deploy
- Some features deployable, others need more work

### Output Format

Return sprint closure recommendation:
```markdown
## Sprint Closure Recommendation - {sprint_name}

### Recommendation: CLOSE | EXTEND | PARTIAL_CLOSE

### Rationale
{Explain reasoning based on metrics and reviews}

### Items to Close
{List items that can be closed}

### Items to Carry Over
{List items that should move to next sprint}

### Action Items
1. {Required action 1}
2. {Required action 2}

### Sprint Goal Achievement
- Sprint Goal: {sprint_goal if available}
- Achieved: YES | NO | PARTIAL
```
```

**After the agent completes:**
- Review recommendation
- Prepare for human approval

---

## Step 6: Human Approval Gate

**Present to sprint stakeholders:**

```
════════════════════════════════════════════════════════════════════════════════
📊 SPRINT REVIEW SUMMARY - {sprint_name}
════════════════════════════════════════════════════════════════════════════════

📈 Completion: {completed_points}/{total_points} pts ({completion_rate:.1f}%)

🧪 Acceptance Tests: {acceptance_status}
  - Tests Run: {tests_run}
  - Passed: {tests_passed}
  - Failed: {tests_failed}

📝 EPIC Acceptance Tests:
  - EPICs Tested: {test_execution_state['successful_executions']}/{len(testable_epics)}
  - Execution Failures: {test_execution_state['failed_executions']}
  - Test Reports Generated: {len(test_execution_state['test_report_files'])}
  - Reports Attached: {report_attachment_state['attached_count']}

🔒 Security: {security_status}
  - Critical Vulns: {critical_count} (Max: {{ quality_standards.critical_vulnerabilities_max }})
  - High Vulns: {high_count} (Max: {{ quality_standards.high_vulnerabilities_max }})

🚀 Deployment: {deployment_ready}

📋 Scrum Master Recommendation: {recommendation}

════════════════════════════════════════════════════════════════════════════════

Choose action:
1. ✅ Close Sprint - Mark sprint complete, deploy changes
2. ⏸️  Extend Sprint - Continue work for X more days
3. 🔀 Partial Close - Deploy completed work, carry over rest
4. ❌ Cancel - Review and re-plan

```

**Based on user decision:**

### Option 1: Close Sprint

```python
# Update sprint status
try:
    # Close completed items
    for item in completed:
        adapter.update_work_item(
            work_item_id=item['id'],
            fields={'System.State': 'Done'}
        )

    # Move incomplete items to next sprint
    next_sprint = input("Next sprint name (or 'backlog'): ")
    for item in not_done:
        new_iteration = f'{{ work_tracking.project }}\\{next_sprint}' if next_sprint != 'backlog' else ''
        adapter.update_work_item(
            work_item_id=item['id'],
            fields={'System.IterationPath': new_iteration}
        )

    print(f"✅ Sprint {sprint_name} closed successfully")
    print(f"   Completed: {len(completed)} items")
    print(f"   Moved to {next_sprint}: {len(not_done)} items")

except Exception as e:
    print(f"❌ Failed to close sprint: {e}")
```

### Option 2: Extend Sprint

```python
extension_days = input("Extend by how many days: ")
print(f"⏸️  Sprint {sprint_name} extended by {extension_days} days")
print(f"   Focus on completing: {[i['title'] for i in not_done[:5]]}")
```

### Option 3: Partial Close

```python
# User selects which items to deploy
print("Select items to deploy (comma-separated IDs):")
deploy_ids = input("Item IDs: ")
deploy_items = [int(x.strip()) for x in deploy_ids.split(',')]

# Close selected items
# Move others to next sprint
```

---

## Step 7: Generate Sprint Review Report

Save comprehensive report to `.claude/reports/sprint-reviews/`:

```markdown
# Sprint Review Report - {sprint_name}

**Date**: {current_date}
**Decision**: {user_decision}

## Sprint Metrics
- Total Items: {len(sprint_items)} ({total_points} pts)
- Completed: {len(completed)} ({completed_points} pts)
- Completion Rate: {completion_rate:.1f}%

## Acceptance Testing
{acceptance test summary}

## Security Review
{security review summary}

## Deployment Readiness
{deployment readiness summary}

## Sprint Closure
{closure details}

## Lessons Learned
{optional: what went well, what to improve}

## Next Sprint Planning
{items carried over, priorities}
```

---

## Agent Commands Used

| Step | Agent Command | Purpose |
|------|---------------|---------|
| 2 | `/tester` | Run acceptance tests |
| 3 | `/security-specialist` | Final security review |
| 4 | `/engineer` | Deployment readiness |
| 5 | `/scrum-master` | Sprint closure recommendation |

**Key**: Each agent command spawns a **fresh context window** via the Task tool.

---

## Configuration

**Work Tracking Platform:** {{ work_tracking.platform }}

**Quality Standards:**
- Test Coverage: >= {{ quality_standards.test_coverage_min }}%
- Critical Vulnerabilities: <= {{ quality_standards.critical_vulnerabilities_max }}
- High Vulnerabilities: <= {{ quality_standards.high_vulnerabilities_max }}

---

*Generated by Trustable AI Workbench for {{ project.name }}*
*Replaces /sprint-completion (v1.x) with focus on acceptance testing and deployment readiness*
