feat: Implement comprehensive IssueCreator system and create CLI roadmap issues

IssueCreator Implementation:
- Add tddai/issue_creator.py with full POST API functionality for issue creation
- Support multiple creation methods: basic, enhancement, bug, template-based
- Include structured issue formatting with acceptance criteria and dependencies
- Template system with variable substitution for reusable issue creation

Authentication Fix:
- Fix critical authentication bug: use GITEA_API_TOKEN instead of GITEA_TOKEN
- Update both IssueCreator and IssueWriter for consistency
- Update all tests and documentation to reflect correct environment variable

Comprehensive Test Suite:
- Add 15 unit tests for IssueCreator (tests/test_issue_creator.py)
- Add 5 integration tests for full API lifecycle (tests/test_issue_integration.py)
- Create test_environment_variable_detection to prevent future auth issues
- Total 33 tests covering complete issue handling workflow

CLI Integration:
- Enhance tddai_cli.py with 3 new commands: create-issue, create-enhancement, create-from-template
- Add comprehensive argument parsing with optional fields and priority support
- Include user-friendly output with next step guidance
- Update package exports to include IssueCreator

CLI Roadmap Execution:
- Successfully create 8 CLI implementation issues (#12-#19) in Gitea
- Resolve mismatch between NEXT.md roadmap and actual Gitea issues
- Issues prioritized for core USPs: Database Query CLI and AST Query CLI
- Remove local MISSING_ISSUES.md file after successful creation

Framework Maturity:
- Complete CRUD operations for issue management (Create, Read, Update, Delete)
- Robust error handling and API integration patterns
- Full authentication and environment variable management
- Ready for production CLI implementation workflow

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-09-24 23:36:07 +02:00
parent 978e925b60
commit 72f341279a
10 changed files with 1180 additions and 3 deletions

233
tddai/issue_creator.py Normal file
View File

@@ -0,0 +1,233 @@
"""
Issue creation for Gitea API.
"""
import json
import os
import subprocess
from subprocess import PIPE
from typing import Dict, Any, Optional, List
from .config import get_config
from .exceptions import IssueError
class IssueCreator:
"""Creates new issues via Gitea API."""
def __init__(self, config=None, auth_token=None):
self.config = config or get_config()
self.auth_token = auth_token or os.getenv('GITEA_API_TOKEN')
def create_issue(self, title: str, body: str, **kwargs) -> Dict[str, Any]:
"""Create a new issue via POST operation.
Args:
title: Issue title (required)
body: Issue description/body (required)
**kwargs: Optional fields (assignees, milestone, labels, etc.)
Returns:
Dict containing created issue data including issue number
Raises:
IssueError: If creation fails
"""
if not self.auth_token:
raise IssueError("Authentication token required for issue creation")
if not title.strip():
raise IssueError("Issue title cannot be empty")
# Prepare issue data
issue_data = {
'title': title.strip(),
'body': body.strip() if body else ''
}
# Add optional fields
if 'assignees' in kwargs and kwargs['assignees']:
issue_data['assignees'] = kwargs['assignees']
if 'milestone' in kwargs and kwargs['milestone']:
issue_data['milestone'] = kwargs['milestone']
if 'labels' in kwargs and kwargs['labels']:
issue_data['labels'] = kwargs['labels']
url = self.config.issues_api_url
try:
# Prepare curl command with authentication
curl_cmd = [
'curl', '-s', '-X', 'POST',
'-H', 'Content-Type: application/json',
'-H', f'Authorization: token {self.auth_token}',
'-d', json.dumps(issue_data),
url
]
result = subprocess.run(
curl_cmd,
stdout=PIPE,
stderr=PIPE,
universal_newlines=True,
check=True
)
if result.returncode != 0:
raise IssueError(f"Failed to create issue: {result.stderr}")
response_data = json.loads(result.stdout)
# Check for API error responses
if 'message' in response_data and 'number' not in response_data:
raise IssueError(f"Failed to create issue: {response_data['message']}")
return response_data
except subprocess.CalledProcessError as e:
raise IssueError(f"Failed to create issue: {e}")
except json.JSONDecodeError as e:
raise IssueError(f"Failed to parse response data: {e}")
def create_enhancement_issue(self, title: str, use_case: str,
technical_requirements: str = "",
acceptance_criteria: List[str] = None,
dependencies: List[str] = None,
priority: str = "Medium") -> Dict[str, Any]:
"""Create an enhancement issue with structured format.
Args:
title: Issue title
use_case: UseCase description
technical_requirements: Technical implementation details
acceptance_criteria: List of acceptance criteria
dependencies: List of dependency descriptions
priority: Priority level (High, Medium, Low)
Returns:
Dict containing created issue data
"""
# Build structured body
body_parts = [f"UseCase: {use_case}"]
if technical_requirements:
body_parts.extend([
"",
"Technical Requirements:",
technical_requirements
])
if acceptance_criteria:
body_parts.extend([
"",
"Acceptance Criteria:"
])
for criterion in acceptance_criteria:
body_parts.append(f"- [ ] {criterion}")
if dependencies:
body_parts.extend([
"",
"Dependencies:"
])
for dep in dependencies:
body_parts.append(f"- {dep}")
body = "\n".join(body_parts)
# Create with enhancement label
return self.create_issue(
title=title,
body=body,
labels=[priority.lower(), "enhancement"]
)
def create_bug_issue(self, title: str, description: str,
steps_to_reproduce: List[str] = None,
expected_behavior: str = "",
actual_behavior: str = "",
environment: str = "") -> Dict[str, Any]:
"""Create a bug issue with structured format.
Args:
title: Bug title
description: Bug description
steps_to_reproduce: List of reproduction steps
expected_behavior: What should happen
actual_behavior: What actually happens
environment: Environment details
Returns:
Dict containing created issue data
"""
body_parts = [description]
if steps_to_reproduce:
body_parts.extend([
"",
"Steps to Reproduce:"
])
for i, step in enumerate(steps_to_reproduce, 1):
body_parts.append(f"{i}. {step}")
if expected_behavior:
body_parts.extend([
"",
f"Expected Behavior: {expected_behavior}"
])
if actual_behavior:
body_parts.extend([
"",
f"Actual Behavior: {actual_behavior}"
])
if environment:
body_parts.extend([
"",
f"Environment: {environment}"
])
body = "\n".join(body_parts)
# Create with bug label
return self.create_issue(
title=title,
body=body,
labels=["bug"]
)
def create_from_template(self, template_file: str, **template_vars) -> Dict[str, Any]:
"""Create issue from a template file.
Args:
template_file: Path to template file
**template_vars: Variables to substitute in template
Returns:
Dict containing created issue data
"""
try:
with open(template_file, 'r') as f:
template_content = f.read()
# Simple template variable substitution
for key, value in template_vars.items():
template_content = template_content.replace(f"{{{key}}}", str(value))
# Extract title (first line) and body (rest)
lines = template_content.strip().split('\n')
if not lines or (len(lines) == 1 and not lines[0].strip()):
raise IssueError("Template file is empty")
title = lines[0].replace('Title: ', '').strip()
body = '\n'.join(lines[1:]).strip()
return self.create_issue(title=title, body=body)
except FileNotFoundError:
raise IssueError(f"Template file not found: {template_file}")
except Exception as e:
raise IssueError(f"Failed to process template: {e}")