Files
gh-epieczko-betty/skills/epic.write/epic_write.py
2025-11-29 18:26:08 +08:00

231 lines
7.0 KiB
Python
Executable File

#!/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()