## Major Integration - ✅ Integrated Requirements Engineering Agent into development workflow - ✅ Enhanced Makefile with requirements validation targets - ✅ Added pre-commit validation with mock compatibility checking - ✅ Enhanced TDD workflow to include foundation analysis ## Test Fixes - ✅ Fixed GiteaPlugin missing _add_comment_async method - ✅ Fixed LocalPlugin config.yml file not found errors in tests - ✅ Enhanced mock objects in CLI tests with proper domain model attributes - ✅ All Issue #59 tests now passing (38/38 tests pass) ## New Capabilities - `make validate-requirements` - Foundation analysis before development - `make check-interface-compatibility INTERFACE=Name` - Interface compatibility checking - `make generate-dev-checklist FEATURE='Name'` - Development checklist generation - `make validate-mocks` - Mock object compatibility validation - `make pre-commit-validate` - Complete pre-commit validation workflow ## Problem Prevention This integration prevents the exact interface compatibility issues and mock object mismatches that caused hours of debugging in Issue #59. The Requirements Engineering Agent provides proactive foundation analysis and catches problems before they occur. 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
165 lines
5.7 KiB
Python
165 lines
5.7 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,
|
|
'updated_at': getattr(issue, 'updated_at', issue.created_at),
|
|
'labels': [label.name if hasattr(label, 'name') else str(label) for label in issue.labels],
|
|
'assignees': getattr(issue, 'assignees', []) or [],
|
|
'assignee': getattr(issue, 'assignee', None),
|
|
'milestone': getattr(issue, 'milestone', None),
|
|
'html_url': getattr(issue, 'html_url', ''),
|
|
'state_label': getattr(issue, 'state_label', issue.state.value if hasattr(issue.state, 'value') else str(issue.state)),
|
|
'priority_label': getattr(issue, 'priority_label', 'Normal'),
|
|
'type_labels': getattr(issue, 'type_labels', []),
|
|
'other_labels': getattr(issue, 'other_labels', []),
|
|
'kanban_column': getattr(issue, 'kanban_column', 'To Do')
|
|
}
|
|
|
|
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 |