Files
markitect-main/tddai_cli.py
tegwick a3093e1443 feat: Complete type safety improvements for CLI and service layers
Implement comprehensive type annotations and mypy configuration as part
of code quality initiative. Achieve 100% type annotation coverage for
main CLI entry points and resolve Optional type inconsistencies.

## Key Improvements

### CLI Layer (100% Type Coverage)
- tddai_cli.py: Complete type annotations for all 21 functions
- cli/core.py: Full type coverage for CLI framework (20 functions)
- cli/commands/issues.py: Fixed Optional[List[str]] parameter types
- cli/commands/workspace.py: Improved type checker logic for Optional handling

### Service Layer Type Safety
- services/issue_service.py: Fixed Optional parameter type signatures
- services/project_service.py: Updated Optional type annotations
- tddai/issue_creator.py: Proper Optional[List[str]] usage
- tddai/project_manager.py: Fixed Optional parameter handling

### Mypy Configuration
- pyproject.toml: Added comprehensive mypy configuration
- Gradual adoption strategy with module-specific strictness
- Python 3.12 compatibility for proper type checking
- Incremental typing approach for legacy modules

## Technical Details
- Proper Optional vs Union type usage throughout
- Generic type annotations for collections
- Return type annotations for all public functions
- Fixed implicit Optional violations (PEP 484)
- Type checker logic improvements for better safety

## Benefits
- Improved IDE autocomplete and error detection
- Compile-time type checking for CLI commands
- Better maintainability and debugging capabilities
- Foundation for expanding type safety to remaining modules

Resolves #27 - Type safety improvements

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-27 09:02:31 +02:00

292 lines
12 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
from typing import Optional, Any
# Add current directory to path so we can import modules
sys.path.insert(0, str(Path(__file__).parent))
from cli import CLIFramework
# Lazy initialization of CLI framework
_cli_framework: Optional[CLIFramework] = None
def _get_cli() -> CLIFramework:
"""Get CLI framework instance (lazy initialization)."""
global _cli_framework
if _cli_framework is None:
_cli_framework = CLIFramework()
return _cli_framework
def workspace_status() -> None:
"""Show current workspace status."""
_get_cli().workspace_status()
def start_issue(issue_number: int) -> None:
"""Start working on an issue."""
_get_cli().start_issue(issue_number)
def finish_issue() -> None:
"""Finish current issue workspace."""
_get_cli().finish_issue()
def add_test_guidance() -> None:
"""Show guidance for adding tests."""
_get_cli().add_test_guidance()
def list_issues() -> None:
"""List all issues."""
_get_cli().list_issues()
def list_open_issues() -> None:
"""List only open issues."""
_get_cli().list_open_issues()
def show_issue(issue_number: int) -> None:
"""Show detailed issue information."""
_get_cli().show_issue(issue_number)
def create_issue(title: str, body: str, issue_type: str = "enhancement") -> None:
"""Create a new issue."""
_get_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") -> None:
"""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()]
_get_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: Any) -> None:
"""Create issue from template file."""
_get_cli().create_from_template(template_file, **kwargs)
def analyze_coverage(issue_number: int) -> None:
"""Analyze test coverage for a specific issue."""
_get_cli().analyze_coverage(issue_number)
def setup_project_management() -> None:
"""Setup project management labels and milestones."""
_get_cli().setup_project_management()
def move_issue_to_state(issue_number: int, state: str) -> None:
"""Move issue to a specific project state."""
_get_cli().move_issue_to_state(issue_number, state)
def set_issue_priority(issue_number: int, priority: str) -> None:
"""Set issue priority."""
_get_cli().set_issue_priority(issue_number, priority)
def create_milestone(title: str, description: str = "") -> None:
"""Create a new milestone (project)."""
_get_cli().create_milestone(title, description)
def list_milestones() -> None:
"""List all milestones."""
_get_cli().list_milestones()
def assign_issue_to_milestone(issue_number: int, milestone_id: int) -> None:
"""Assign issue to a milestone."""
_get_cli().assign_issue_to_milestone(issue_number, milestone_id)
def project_overview() -> None:
"""Show project management overview."""
_get_cli().project_overview()
def issue_index(format_type: str = "tsv", sort_by: str = "number", filter_state: Optional[str] = None, filter_priority: Optional[str] = None, include_state: bool = False) -> None:
"""Output compact index of all issues for Unix processing."""
_get_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() -> None:
"""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()