Complete architectural separation of concerns implementing clean layered design: • Services Layer: Pure business logic isolated from presentation - WorkspaceService: TDD workspace operations - IssueService: Issue management and creation - ProjectService: Project management and milestones - ExportService: Unix-friendly data export • CLI Layer: Clean presentation with command/presenter separation - Commands delegate to services for all business operations - Presenters handle formatted output and error messaging - Framework provides unified interface • Benefits: - Eliminates mixed concerns in 943-line CLI monolith - Enables easier testing and maintenance - Preserves all existing functionality and Unix pipeline compatibility - Provides foundation for future CLI development Resolves issue #20: CLI separation from core logic 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
284 lines
11 KiB
Python
284 lines
11 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
CLI interface for tddai library.
|
|
|
|
This module now uses the separated architecture with services and presenters.
|
|
Business logic is handled by services, presentation by CLI framework.
|
|
"""
|
|
|
|
import sys
|
|
import argparse
|
|
from pathlib import Path
|
|
|
|
# Add current directory to path so we can import modules
|
|
sys.path.insert(0, str(Path(__file__).parent))
|
|
|
|
from cli import CLIFramework
|
|
|
|
# Initialize CLI framework
|
|
cli = CLIFramework()
|
|
|
|
|
|
def workspace_status():
|
|
"""Show current workspace status."""
|
|
cli.workspace_status()
|
|
|
|
|
|
def start_issue(issue_number: int):
|
|
"""Start working on an issue."""
|
|
cli.start_issue(issue_number)
|
|
|
|
|
|
def finish_issue():
|
|
"""Finish current issue workspace."""
|
|
cli.finish_issue()
|
|
|
|
|
|
def add_test_guidance():
|
|
"""Show guidance for adding tests."""
|
|
cli.add_test_guidance()
|
|
|
|
|
|
def list_issues():
|
|
"""List all issues."""
|
|
cli.list_issues()
|
|
|
|
|
|
def list_open_issues():
|
|
"""List only open issues."""
|
|
cli.list_open_issues()
|
|
|
|
|
|
def show_issue(issue_number: int):
|
|
"""Show detailed issue information."""
|
|
cli.show_issue(issue_number)
|
|
|
|
|
|
def create_issue(title: str, body: str, issue_type: str = "enhancement"):
|
|
"""Create a new issue."""
|
|
cli.create_issue(title, body, issue_type)
|
|
|
|
|
|
def create_enhancement_issue(title: str, use_case: str, technical_requirements: str = "",
|
|
acceptance_criteria: str = "", dependencies: str = "",
|
|
priority: str = "Medium"):
|
|
"""Create a structured enhancement issue."""
|
|
# Parse acceptance criteria if provided
|
|
criteria_list = []
|
|
if acceptance_criteria:
|
|
criteria_list = [line.strip() for line in acceptance_criteria.split('\n') if line.strip()]
|
|
|
|
# Parse dependencies if provided
|
|
deps_list = []
|
|
if dependencies:
|
|
deps_list = [line.strip() for line in dependencies.split('\n') if line.strip()]
|
|
|
|
cli.create_enhancement_issue(
|
|
title=title,
|
|
use_case=use_case,
|
|
technical_requirements=technical_requirements,
|
|
acceptance_criteria=criteria_list,
|
|
dependencies=deps_list,
|
|
priority=priority
|
|
)
|
|
|
|
|
|
def create_from_template(template_file: str, **kwargs):
|
|
"""Create issue from template file."""
|
|
cli.create_from_template(template_file, **kwargs)
|
|
|
|
|
|
def analyze_coverage(issue_number: int):
|
|
"""Analyze test coverage for a specific issue."""
|
|
cli.analyze_coverage(issue_number)
|
|
|
|
|
|
def setup_project_management():
|
|
"""Setup project management labels and milestones."""
|
|
cli.setup_project_management()
|
|
|
|
|
|
def move_issue_to_state(issue_number: int, state: str):
|
|
"""Move issue to a specific project state."""
|
|
cli.move_issue_to_state(issue_number, state)
|
|
|
|
|
|
def set_issue_priority(issue_number: int, priority: str):
|
|
"""Set issue priority."""
|
|
cli.set_issue_priority(issue_number, priority)
|
|
|
|
|
|
def create_milestone(title: str, description: str = ""):
|
|
"""Create a new milestone (project)."""
|
|
cli.create_milestone(title, description)
|
|
|
|
|
|
def list_milestones():
|
|
"""List all milestones."""
|
|
cli.list_milestones()
|
|
|
|
|
|
def assign_issue_to_milestone(issue_number: int, milestone_id: int):
|
|
"""Assign issue to a milestone."""
|
|
cli.assign_issue_to_milestone(issue_number, milestone_id)
|
|
|
|
|
|
def project_overview():
|
|
"""Show project management overview."""
|
|
cli.project_overview()
|
|
|
|
|
|
def issue_index(format_type="tsv", sort_by="number", filter_state=None, filter_priority=None, include_state=False):
|
|
"""Output compact index of all issues for Unix processing."""
|
|
cli.issue_index(
|
|
format_type=format_type,
|
|
sort_by=sort_by,
|
|
filter_state=filter_state,
|
|
filter_priority=filter_priority,
|
|
include_state=include_state
|
|
)
|
|
|
|
|
|
def main():
|
|
"""Main CLI entry point."""
|
|
parser = argparse.ArgumentParser(description="tddai CLI tool")
|
|
subparsers = parser.add_subparsers(dest='command', help='Available commands')
|
|
|
|
# Workspace commands
|
|
subparsers.add_parser('workspace-status', help='Show workspace status')
|
|
|
|
start_parser = subparsers.add_parser('start-issue', help='Start working on issue')
|
|
start_parser.add_argument('issue_number', type=int, help='Issue number')
|
|
|
|
subparsers.add_parser('finish-issue', help='Finish current issue')
|
|
subparsers.add_parser('add-test', help='Show guidance for adding tests')
|
|
|
|
# Issue commands
|
|
subparsers.add_parser('list-issues', help='List all issues')
|
|
subparsers.add_parser('list-open-issues', help='List open issues')
|
|
|
|
index_parser = subparsers.add_parser('issue-index', help='Output compact issue index for Unix processing')
|
|
index_parser.add_argument('--format', choices=['tsv', 'csv', 'json', 'fields'], default='tsv',
|
|
help='Output format (default: tsv)')
|
|
index_parser.add_argument('--sort', choices=['number', 'title', 'priority', 'state', 'created', 'updated'],
|
|
default='number', help='Sort by field (default: number)')
|
|
index_parser.add_argument('--filter-state', choices=['open', 'closed'],
|
|
help='Filter by issue state')
|
|
index_parser.add_argument('--filter-priority', choices=['low', 'medium', 'high', 'critical', 'none'],
|
|
help='Filter by priority level')
|
|
index_parser.add_argument('--include-state', action='store_true',
|
|
help='Include state column in output')
|
|
|
|
show_parser = subparsers.add_parser('show-issue', help='Show issue details')
|
|
show_parser.add_argument('issue_number', type=int, help='Issue number')
|
|
|
|
coverage_parser = subparsers.add_parser('analyze-coverage', help='Analyze test coverage for issue')
|
|
coverage_parser.add_argument('issue_number', type=int, help='Issue number')
|
|
|
|
# Issue creation commands
|
|
create_parser = subparsers.add_parser('create-issue', help='Create a new issue')
|
|
create_parser.add_argument('title', help='Issue title')
|
|
create_parser.add_argument('body', help='Issue body/description')
|
|
create_parser.add_argument('--type', choices=['enhancement', 'bug'], default='enhancement', help='Issue type')
|
|
|
|
create_enh_parser = subparsers.add_parser('create-enhancement', help='Create a structured enhancement issue')
|
|
create_enh_parser.add_argument('title', help='Issue title')
|
|
create_enh_parser.add_argument('use_case', help='UseCase description')
|
|
create_enh_parser.add_argument('--technical', help='Technical requirements', default='')
|
|
create_enh_parser.add_argument('--criteria', help='Acceptance criteria (newline separated)', default='')
|
|
create_enh_parser.add_argument('--dependencies', help='Dependencies (newline separated)', default='')
|
|
create_enh_parser.add_argument('--priority', choices=['High', 'Medium', 'Low'], default='Medium', help='Priority level')
|
|
|
|
template_parser = subparsers.add_parser('create-from-template', help='Create issue from template')
|
|
template_parser.add_argument('template_file', help='Template file path')
|
|
template_parser.add_argument('--vars', help='Template variables in key=value format', nargs='*', default=[])
|
|
|
|
# Project management commands
|
|
subparsers.add_parser('setup-project-mgmt', help='Setup project management labels and milestones')
|
|
subparsers.add_parser('project-overview', help='Show project management overview')
|
|
|
|
state_parser = subparsers.add_parser('set-issue-state', help='Set issue project state')
|
|
state_parser.add_argument('issue_number', type=int, help='Issue number')
|
|
state_parser.add_argument('state', choices=['todo', 'active', 'review', 'done', 'blocked'], help='Project state')
|
|
|
|
priority_parser = subparsers.add_parser('set-issue-priority', help='Set issue priority')
|
|
priority_parser.add_argument('issue_number', type=int, help='Issue number')
|
|
priority_parser.add_argument('priority', choices=['low', 'medium', 'high', 'critical'], help='Priority level')
|
|
|
|
milestone_parser = subparsers.add_parser('create-milestone', help='Create a new milestone (project)')
|
|
milestone_parser.add_argument('title', help='Milestone title')
|
|
milestone_parser.add_argument('--description', help='Milestone description', default='')
|
|
|
|
subparsers.add_parser('list-milestones', help='List all milestones')
|
|
|
|
assign_parser = subparsers.add_parser('assign-to-milestone', help='Assign issue to milestone')
|
|
assign_parser.add_argument('issue_number', type=int, help='Issue number')
|
|
assign_parser.add_argument('milestone_id', type=int, help='Milestone ID')
|
|
|
|
args = parser.parse_args()
|
|
|
|
if not args.command:
|
|
parser.print_help()
|
|
return
|
|
|
|
try:
|
|
if args.command == 'workspace-status':
|
|
workspace_status()
|
|
elif args.command == 'start-issue':
|
|
start_issue(args.issue_number)
|
|
elif args.command == 'finish-issue':
|
|
finish_issue()
|
|
elif args.command == 'add-test':
|
|
add_test_guidance()
|
|
elif args.command == 'list-issues':
|
|
list_issues()
|
|
elif args.command == 'list-open-issues':
|
|
list_open_issues()
|
|
elif args.command == 'issue-index':
|
|
issue_index(
|
|
format_type=args.format,
|
|
sort_by=args.sort,
|
|
filter_state=args.filter_state,
|
|
filter_priority=args.filter_priority,
|
|
include_state=args.include_state
|
|
)
|
|
elif args.command == 'show-issue':
|
|
show_issue(args.issue_number)
|
|
elif args.command == 'analyze-coverage':
|
|
analyze_coverage(args.issue_number)
|
|
elif args.command == 'create-issue':
|
|
create_issue(args.title, args.body, args.type)
|
|
elif args.command == 'create-enhancement':
|
|
create_enhancement_issue(
|
|
args.title, args.use_case, args.technical,
|
|
args.criteria, args.dependencies, args.priority
|
|
)
|
|
elif args.command == 'create-from-template':
|
|
# Parse template variables
|
|
template_vars = {}
|
|
for var in args.vars:
|
|
if '=' in var:
|
|
key, value = var.split('=', 1)
|
|
template_vars[key] = value
|
|
create_from_template(args.template_file, **template_vars)
|
|
elif args.command == 'setup-project-mgmt':
|
|
setup_project_management()
|
|
elif args.command == 'project-overview':
|
|
project_overview()
|
|
elif args.command == 'set-issue-state':
|
|
move_issue_to_state(args.issue_number, args.state)
|
|
elif args.command == 'set-issue-priority':
|
|
set_issue_priority(args.issue_number, args.priority)
|
|
elif args.command == 'create-milestone':
|
|
create_milestone(args.title, args.description)
|
|
elif args.command == 'list-milestones':
|
|
list_milestones()
|
|
elif args.command == 'assign-to-milestone':
|
|
assign_issue_to_milestone(args.issue_number, args.milestone_id)
|
|
except KeyboardInterrupt:
|
|
print("\n⚠️ Operation cancelled")
|
|
sys.exit(1)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main() |