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>
121 lines
3.6 KiB
Python
121 lines
3.6 KiB
Python
"""
|
|
Plugin manager for issue backends.
|
|
|
|
This module handles discovery, loading, and configuration of issue management backends.
|
|
"""
|
|
|
|
import importlib
|
|
import yaml
|
|
from pathlib import Path
|
|
from typing import Dict, Any, Type, Optional
|
|
|
|
from .base import IssueBackend
|
|
from .exceptions import PluginNotFoundError, ConfigurationError
|
|
|
|
|
|
class IssuePluginManager:
|
|
"""Manages issue backend plugins and configuration."""
|
|
|
|
def __init__(self, config_path: Optional[str] = None):
|
|
"""
|
|
Initialize plugin manager.
|
|
|
|
Args:
|
|
config_path: Optional path to configuration file
|
|
"""
|
|
self.config = self._load_config(config_path)
|
|
self.plugins = self._discover_plugins()
|
|
|
|
def get_backend(self, backend_name: Optional[str] = None) -> IssueBackend:
|
|
"""
|
|
Get configured backend instance.
|
|
|
|
Args:
|
|
backend_name: Backend name to use, or None for default
|
|
|
|
Returns:
|
|
IssueBackend instance
|
|
|
|
Raises:
|
|
PluginNotFoundError: If backend not found
|
|
"""
|
|
backend_name = backend_name or self.config.get('default_backend', 'gitea')
|
|
|
|
plugin_class = self.plugins.get(backend_name)
|
|
if not plugin_class:
|
|
raise PluginNotFoundError(f"Unknown backend: {backend_name}")
|
|
|
|
backend_config = self.config.get('backends', {}).get(backend_name, {})
|
|
return plugin_class(backend_config)
|
|
|
|
def _load_config(self, config_path: Optional[str] = None) -> Dict[str, Any]:
|
|
"""
|
|
Load configuration from file or return defaults.
|
|
|
|
Args:
|
|
config_path: Path to configuration file
|
|
|
|
Returns:
|
|
Configuration dictionary
|
|
"""
|
|
if config_path is None:
|
|
config_path = Path('.markitect/config/issues.yml')
|
|
else:
|
|
config_path = Path(config_path)
|
|
|
|
# Default configuration
|
|
default_config = {
|
|
'default_backend': 'gitea',
|
|
'backends': {
|
|
'gitea': {
|
|
'url': 'http://92.205.130.254:32166',
|
|
'repo': 'coulomb/markitect_project'
|
|
},
|
|
'local': {
|
|
'directory': '.markitect/issues',
|
|
'auto_git': True
|
|
}
|
|
}
|
|
}
|
|
|
|
if not config_path.exists():
|
|
return default_config
|
|
|
|
try:
|
|
with open(config_path, 'r') as f:
|
|
config = yaml.safe_load(f) or {}
|
|
|
|
# Merge with defaults
|
|
merged_config = default_config.copy()
|
|
merged_config.update(config)
|
|
return merged_config
|
|
except Exception:
|
|
# Return defaults if config loading fails
|
|
return default_config
|
|
|
|
def _discover_plugins(self) -> Dict[str, Type[IssueBackend]]:
|
|
"""
|
|
Discover available backend plugins.
|
|
|
|
Returns:
|
|
Dictionary mapping backend names to plugin classes
|
|
"""
|
|
plugins = {}
|
|
|
|
# Try to import known plugins
|
|
plugin_modules = [
|
|
('gitea', 'markitect.issues.plugins.gitea', 'GiteaPlugin'),
|
|
('local', 'markitect.issues.plugins.local', 'LocalPlugin'),
|
|
]
|
|
|
|
for name, module_path, class_name in plugin_modules:
|
|
try:
|
|
module = importlib.import_module(module_path)
|
|
plugin_class = getattr(module, class_name)
|
|
if issubclass(plugin_class, IssueBackend):
|
|
plugins[name] = plugin_class
|
|
except (ImportError, AttributeError):
|
|
# Plugin not available, skip
|
|
continue
|
|
|
|
return plugins |