Implement comprehensive issue management system with pluggable backend support: ARCHITECTURE: - Abstract IssueBackend base class with standardized interface - Plugin discovery and configuration management system - Unified CLI integration with markitect issues commands BACKENDS IMPLEMENTED: - Gitea plugin: Integrates with existing GiteaIssueRepository infrastructure - Local plugin: File-based issue management with markdown + YAML frontmatter CLI COMMANDS: - markitect issues list [--state open|closed|all] [--backend name] - markitect issues show <id> [--backend name] - markitect issues create <title> <body> [--backend name] - markitect issues close <id> [--backend name] - markitect issues comment <id> <text> [--backend name] CONFIGURATION: - YAML-based backend configuration (.markitect/config/issues.yml) - Default backends: gitea (remote) and local (file-based) - Seamless backend switching via CLI options LOCAL FILE STRUCTURE: - .markitect/issues/open/ - Active issues as markdown files - .markitect/issues/closed/ - Completed issues - YAML frontmatter with issue metadata + markdown body - Git integration for version control of local issues TESTING: - Comprehensive test suite for plugin manager (15/17 tests passing) - Plugin interface validation and error handling - CLI integration tests (functional verification complete) This addresses the original problem where Claude sometimes missed existing issue functions and tried direct API calls. Now provides consistent, unified interface regardless of backend. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
156 lines
5.1 KiB
Python
156 lines
5.1 KiB
Python
"""
|
|
CLI commands for issue management.
|
|
|
|
This module provides Click commands for the unified issue management interface.
|
|
"""
|
|
|
|
import click
|
|
from typing import Optional
|
|
|
|
from .manager import IssuePluginManager
|
|
from .exceptions import PluginNotFoundError, ConfigurationError
|
|
from cli.presenters.views import IssueView
|
|
|
|
|
|
@click.group()
|
|
def issues():
|
|
"""Issue management with multiple backend support."""
|
|
pass
|
|
|
|
|
|
@issues.command()
|
|
@click.option('--state', type=click.Choice(['open', 'closed', 'all']), default='all',
|
|
help='Filter issues by state')
|
|
@click.option('--backend', help='Override configured backend')
|
|
def list(state: str, backend: Optional[str]):
|
|
"""List issues from configured backend."""
|
|
try:
|
|
manager = IssuePluginManager()
|
|
backend_instance = manager.get_backend(backend)
|
|
issues_list = backend_instance.list_issues(state=state)
|
|
|
|
if state == 'open':
|
|
IssueView.show_open_issues(issues_list)
|
|
else:
|
|
IssueView.show_list(issues_list, f"Issues ({state})")
|
|
|
|
except (PluginNotFoundError, ConfigurationError) as e:
|
|
click.echo(f"Error: {e}", err=True)
|
|
raise click.Abort()
|
|
except Exception as e:
|
|
click.echo(f"Unexpected error: {e}", err=True)
|
|
raise click.Abort()
|
|
|
|
|
|
@issues.command()
|
|
@click.argument('issue_id')
|
|
@click.option('--backend', help='Override configured backend')
|
|
def show(issue_id: str, backend: Optional[str]):
|
|
"""Show details of a specific issue."""
|
|
try:
|
|
manager = IssuePluginManager()
|
|
backend_instance = manager.get_backend(backend)
|
|
issue = backend_instance.get_issue(issue_id)
|
|
|
|
# Convert issue to dict for display
|
|
issue_data = {
|
|
'number': issue.number,
|
|
'title': issue.title,
|
|
'body': getattr(issue, '_body', ''),
|
|
'state': issue.state.value if hasattr(issue.state, 'value') else str(issue.state),
|
|
'created_at': issue.created_at,
|
|
'labels': [label.name if hasattr(label, 'name') else str(label) for label in issue.labels],
|
|
'assignees': getattr(issue, 'assignees', []) or []
|
|
}
|
|
|
|
IssueView.show_issue_details(issue_data)
|
|
|
|
except FileNotFoundError:
|
|
click.echo(f"Error: Issue {issue_id} not found", err=True)
|
|
raise click.Abort()
|
|
except (PluginNotFoundError, ConfigurationError) as e:
|
|
click.echo(f"Error: {e}", err=True)
|
|
raise click.Abort()
|
|
except Exception as e:
|
|
click.echo(f"Unexpected error: {e}", err=True)
|
|
raise click.Abort()
|
|
|
|
|
|
@issues.command()
|
|
@click.argument('title')
|
|
@click.argument('body')
|
|
@click.option('--backend', help='Override configured backend')
|
|
def create(title: str, body: str, backend: Optional[str]):
|
|
"""Create a new issue."""
|
|
try:
|
|
manager = IssuePluginManager()
|
|
backend_instance = manager.get_backend(backend)
|
|
issue = backend_instance.create_issue(title, body)
|
|
|
|
# Convert issue to dict for display
|
|
result = {
|
|
'number': issue.number,
|
|
'title': issue.title,
|
|
'state': issue.state.value if hasattr(issue.state, 'value') else str(issue.state)
|
|
}
|
|
|
|
IssueView.show_creation_success(result, "issue")
|
|
click.echo(f"Issue #{issue.number} created successfully")
|
|
|
|
except (PluginNotFoundError, ConfigurationError) as e:
|
|
click.echo(f"Error: {e}", err=True)
|
|
raise click.Abort()
|
|
except Exception as e:
|
|
click.echo(f"Unexpected error: {e}", err=True)
|
|
raise click.Abort()
|
|
|
|
|
|
@issues.command()
|
|
@click.argument('issue_id')
|
|
@click.argument('comment')
|
|
@click.option('--backend', help='Override configured backend')
|
|
def comment(issue_id: str, comment: str, backend: Optional[str]):
|
|
"""Add a comment to an issue."""
|
|
try:
|
|
manager = IssuePluginManager()
|
|
backend_instance = manager.get_backend(backend)
|
|
result = backend_instance.add_comment(issue_id, comment)
|
|
|
|
click.echo(f"Comment added to issue #{issue_id}")
|
|
|
|
except ValueError as e:
|
|
click.echo(f"Error: {e}", err=True)
|
|
raise click.Abort()
|
|
except (PluginNotFoundError, ConfigurationError) as e:
|
|
click.echo(f"Error: {e}", err=True)
|
|
raise click.Abort()
|
|
except Exception as e:
|
|
click.echo(f"Unexpected error: {e}", err=True)
|
|
raise click.Abort()
|
|
|
|
|
|
@issues.command()
|
|
@click.argument('issue_id')
|
|
@click.option('--backend', help='Override configured backend')
|
|
def close(issue_id: str, backend: Optional[str]):
|
|
"""Close an issue."""
|
|
try:
|
|
manager = IssuePluginManager()
|
|
backend_instance = manager.get_backend(backend)
|
|
issue = backend_instance.close_issue(issue_id)
|
|
|
|
click.echo(f"Issue #{issue_id} closed successfully")
|
|
|
|
except FileNotFoundError:
|
|
click.echo(f"Error: Issue {issue_id} not found", err=True)
|
|
raise click.Abort()
|
|
except (PluginNotFoundError, ConfigurationError) as e:
|
|
click.echo(f"Error: {e}", err=True)
|
|
raise click.Abort()
|
|
except Exception as e:
|
|
click.echo(f"Unexpected error: {e}", err=True)
|
|
raise click.Abort()
|
|
|
|
|
|
# Make issues_group available for import
|
|
issues_group = issues |