#!/usr/bin/env python3 """ epic.write - Generate an Agile Epic from a high-level goal, feature request, or strategic initiative. Creates structured Agile Epic document that follows best practices. Generated by meta.skill with Betty Framework certification """ import os import sys import json import yaml from pathlib import Path from typing import Dict, List, Any, Optional from datetime import datetime from betty.config import BASE_DIR from betty.logging_utils import setup_logger from betty.certification import certified_skill logger = setup_logger(__name__) class EpicWrite: """ Generate an Agile Epic from a high-level goal, feature request, or strategic initiative. Creates structured Agile Epic document that follows best practices. """ def __init__(self, base_dir: str = BASE_DIR): """Initialize skill""" self.base_dir = Path(base_dir) @certified_skill("epic.write") def execute(self, initiative_name: Optional[str] = None, context: Optional[str] = None, stakeholders: Optional[str] = None, output_path: Optional[str] = None) -> Dict[str, Any]: """ Execute the skill Args: initiative_name: The overarching initiative or product goal context: Relevant background, rationale, and success criteria stakeholders: Who cares about this and why (comma-separated) output_path: Where to save the epic.md file (default: ./epic.md) Returns: Dict with execution results """ try: logger.info("Executing epic.write...") # Validate required inputs if not initiative_name: raise ValueError("initiative_name is required") if not context: raise ValueError("context is required") if not stakeholders: raise ValueError("stakeholders is required") # Set default output path if not output_path: output_path = "./epic.md" # Parse stakeholders (comma-separated) stakeholder_list = [s.strip() for s in stakeholders.split(',')] # Generate Epic content epic_content = self._generate_epic(initiative_name, context, stakeholder_list) # Write to file output_file = Path(output_path) output_file.parent.mkdir(parents=True, exist_ok=True) output_file.write_text(epic_content) logger.info(f"Epic written to {output_path}") result = { "ok": True, "status": "success", "message": f"Epic '{initiative_name}' created successfully", "output_file": str(output_path), "artifact_type": "agile-epic", "next_step": "Use epic.decompose to break this Epic into user stories" } logger.info("Skill completed successfully") return result except Exception as e: logger.error(f"Error executing skill: {e}") return { "ok": False, "status": "failed", "error": str(e) } def _generate_epic(self, initiative_name: str, context: str, stakeholders: List[str]) -> str: """ Generate the Epic markdown content Args: initiative_name: Name of the initiative context: Background and context stakeholders: List of stakeholders Returns: Formatted Epic markdown """ # Extract or generate acceptance criteria from context acceptance_criteria = self._generate_acceptance_criteria(context) epic_md = f"""# Epic: {initiative_name} ## Summary {self._generate_summary(initiative_name, context)} ## Background {context} ## Acceptance Criteria {acceptance_criteria} ## Stakeholders {self._format_stakeholders(stakeholders)} ## Next Steps Pass to skill: `epic.decompose` This Epic should be decomposed into individual user stories using the `epic.decompose` skill. ```bash python3 skills/epic.decompose/epic_decompose.py \\ --epic-file {Path(initiative_name.lower().replace(' ', '_') + '_epic.md').name} \\ --max-stories 5 ``` --- **Epic Metadata** - Created: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - Type: agile-epic - Version: 1.0 - Status: Draft """ return epic_md def _generate_summary(self, initiative_name: str, context: str) -> str: """Generate a one-sentence business objective""" # Simple heuristic: use first sentence of context or generate from name sentences = context.split('.') if sentences: return sentences[0].strip() + '.' return f"Enable {initiative_name.lower()} capabilities for the organization." def _generate_acceptance_criteria(self, context: str) -> str: """Generate measurable acceptance criteria from context""" # Extract key outcomes or generate default criteria criteria = [ "All requirements defined in the Epic are implemented and tested", "Stakeholder approval obtained for the implementation", "Documentation is complete and accurate", "Solution meets performance and quality standards" ] return '\n'.join(f"- [ ] {c}" for c in criteria) def _format_stakeholders(self, stakeholders: List[str]) -> str: """Format stakeholders list""" return '\n'.join(f"- **{s}**" for s in stakeholders) def main(): """CLI entry point""" import argparse parser = argparse.ArgumentParser( description="Generate an Agile Epic from a high-level goal, feature request, or strategic initiative. Creates structured Agile Epic document that follows best practices." ) parser.add_argument( "--initiative-name", required=True, help="The overarching initiative or product goal" ) parser.add_argument( "--context", required=True, help="Relevant background, rationale, and success criteria" ) parser.add_argument( "--stakeholders", required=True, help="Who cares about this and why (comma-separated)" ) parser.add_argument( "--output-path", default="./epic.md", help="Where to save the epic.md file (default: ./epic.md)" ) parser.add_argument( "--output-format", choices=["json", "yaml"], default="json", help="Output format" ) args = parser.parse_args() # Create skill instance skill = EpicWrite() # Execute skill result = skill.execute( initiative_name=args.initiative_name, context=args.context, stakeholders=args.stakeholders, output_path=args.output_path, ) # Output result if args.output_format == "json": print(json.dumps(result, indent=2)) else: print(yaml.dump(result, default_flow_style=False)) # Exit with appropriate code sys.exit(0 if result.get("ok") else 1) if __name__ == "__main__": main()