From d68310793bef90fd8ae0caef5a5e7d459fa6c113 Mon Sep 17 00:00:00 2001 From: tegwick Date: Sun, 19 Oct 2025 20:44:58 +0200 Subject: [PATCH] Fix linting violations for v1.0.0 release preparation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Apply black formatting to all Python files - Fix various flake8 violations in agent system code - Clean up imports and whitespace issues šŸ¤– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- Makefile | 2 +- src/kaizen_agentic/cli.py | 163 +++++++++++++++++++------------ src/kaizen_agentic/detection.py | 131 +++++++++++++++---------- src/kaizen_agentic/extensions.py | 136 ++++++++++++++------------ src/kaizen_agentic/installer.py | 71 ++++++++------ src/kaizen_agentic/migration.py | 96 +++++++++++++----- src/kaizen_agentic/registry.py | 77 ++++++++------- tests/test_installer.py | 40 ++++---- tests/test_registry.py | 5 +- 9 files changed, 432 insertions(+), 289 deletions(-) diff --git a/Makefile b/Makefile index 0f25161..492b9b2 100644 --- a/Makefile +++ b/Makefile @@ -730,7 +730,7 @@ release-check: $(VENV)/bin/activate echo "šŸ“‹ Release Readiness Checklist:"; \ echo "==============================="; \ echo " • Version Consistency:"; \ - CHANGELOG_VERSION=$$(grep '^## \[' CHANGELOG.md | head -1 | sed 's/## \[\(.*\)\].*/\1/' | grep -v "Unreleased" || echo ""); \ + CHANGELOG_VERSION=$$(grep '^## \[' CHANGELOG.md | grep -v "Unreleased" | head -1 | sed 's/## \[\(.*\)\].*/\1/' || echo ""); \ PYPROJECT_VERSION=$$(grep '^version = ' pyproject.toml | sed 's/version = "\(.*\)"/\1/' || echo ""); \ if [ "$$CHANGELOG_VERSION" = "$$PYPROJECT_VERSION" ] && [ -n "$$CHANGELOG_VERSION" ]; then \ echo " āœ… Versions consistent: $$CHANGELOG_VERSION"; \ diff --git a/src/kaizen_agentic/cli.py b/src/kaizen_agentic/cli.py index 251175e..0b433b7 100644 --- a/src/kaizen_agentic/cli.py +++ b/src/kaizen_agentic/cli.py @@ -17,10 +17,12 @@ def cli(): @cli.command() -@click.option('--category', - type=click.Choice([c.value for c in AgentCategory]), - help='Filter by category') -@click.option('--verbose', '-v', is_flag=True, help='Show detailed information') +@click.option( + "--category", + type=click.Choice([c.value for c in AgentCategory]), + help="Filter by category", +) +@click.option("--verbose", "-v", is_flag=True, help="Show detailed information") def list(category: Optional[str], verbose: bool): """List available agents.""" registry = _get_registry() @@ -34,7 +36,9 @@ def list(category: Optional[str], verbose: bool): if verbose: categories = registry.get_categories() for cat, agents in categories.items(): - click.echo(f"\n{cat.value.replace('-', ' ').title()} ({len(agents)} agents):") + click.echo( + f"\n{cat.value.replace('-', ' ').title()} ({len(agents)} agents):" + ) click.echo("=" * 50) for agent in agents: click.echo(f" • {agent.name}: {agent.description}") @@ -56,10 +60,10 @@ def list(category: Optional[str], verbose: bool): @cli.command() -@click.argument('agents', nargs=-1, required=True) -@click.option('--target', '-t', default='.', help='Target directory (default: current)') -@click.option('--no-backup', is_flag=True, help='Skip creating backup') -@click.option('--no-docs', is_flag=True, help='Skip updating documentation') +@click.argument("agents", nargs=-1, required=True) +@click.option("--target", "-t", default=".", help="Target directory (default: current)") +@click.option("--no-backup", is_flag=True, help="Skip creating backup") +@click.option("--no-docs", is_flag=True, help="Skip updating documentation") def install(agents: List[str], target: str, no_backup: bool, no_docs: bool): """Install agents into a project.""" registry = _get_registry() @@ -72,7 +76,7 @@ def install(agents: List[str], target: str, no_backup: bool, no_docs: bool): claude_config_path=target_path / "CLAUDE.md", makefile_path=target_path / "Makefile", update_docs=not no_docs, - create_backup=not no_backup + create_backup=not no_backup, ) click.echo(f"Installing agents to: {target_path}") @@ -98,8 +102,8 @@ def install(agents: List[str], target: str, no_backup: bool, no_docs: bool): @cli.command() -@click.option('--target', '-t', default='.', help='Target directory (default: current)') -@click.argument('agents', nargs=-1) +@click.option("--target", "-t", default=".", help="Target directory (default: current)") +@click.argument("agents", nargs=-1) def update(target: str, agents: List[str]): """Update installed agents.""" registry = _get_registry() @@ -131,8 +135,8 @@ def update(target: str, agents: List[str]): @cli.command() -@click.argument('agents', nargs=-1, required=True) -@click.option('--target', '-t', default='.', help='Target directory (default: current)') +@click.argument("agents", nargs=-1, required=True) +@click.option("--target", "-t", default=".", help="Target directory (default: current)") def remove(agents: List[str], target: str): """Remove agents from a project.""" registry = _get_registry() @@ -154,11 +158,17 @@ def remove(agents: List[str], target: str): @cli.command() -@click.argument('project_name') -@click.option('--template', '-t', default='python-basic', - help='Project template (python-basic, python-web, python-cli, python-data)') -@click.option('--agents', '-a', help='Comma-separated list of agents to install') -@click.option('--parent-dir', default='.', help='Parent directory for project (default: current)') +@click.argument("project_name") +@click.option( + "--template", + "-t", + default="python-basic", + help="Project template (python-basic, python-web, python-cli, python-data)", +) +@click.option("--agents", "-a", help="Comma-separated list of agents to install") +@click.option( + "--parent-dir", default=".", help="Parent directory for project (default: current)" +) def init(project_name: str, template: str, agents: Optional[str], parent_dir: str): """Initialize a new project with agents.""" registry = _get_registry() @@ -173,7 +183,7 @@ def init(project_name: str, template: str, agents: Optional[str], parent_dir: st # Parse agent list agent_list = None if agents: - agent_list = [a.strip() for a in agents.split(',')] + agent_list = [a.strip() for a in agents.split(",")] click.echo(f"Initializing project: {project_name}") click.echo(f"Template: {template}") @@ -204,7 +214,7 @@ def init(project_name: str, template: str, agents: Optional[str], parent_dir: st @cli.command() -@click.option('--target', '-t', default='.', help='Target directory (default: current)') +@click.option("--target", "-t", default=".", help="Target directory (default: current)") def validate(target: str): """Validate agents in a project.""" registry = _get_registry() @@ -263,7 +273,7 @@ def templates(): @cli.command() -@click.option('--target', '-t', default='.', help='Target directory (default: current)') +@click.option("--target", "-t", default=".", help="Target directory (default: current)") def status(target: str): """Show status of agents in a project.""" registry = _get_registry() @@ -321,8 +331,8 @@ def status(target: str): @cli.command() -@click.option('--target', '-t', default='.', help='Target directory (default: current)') -@click.option('--detailed', '-d', is_flag=True, help='Show detailed analysis') +@click.option("--target", "-t", default=".", help="Target directory (default: current)") +@click.option("--detailed", "-d", is_flag=True, help="Show detailed analysis") def detect(target: str, detailed: bool): """Detect existing agent systems in a project.""" from .detection import AgentSystemDetector @@ -372,11 +382,13 @@ def detect(target: str, detailed: bool): # Show integration strategy if result.integration_strategy: - click.echo(f"\nšŸ’” Recommended Integration Strategy: {result.integration_strategy}") + click.echo( + f"\nšŸ’” Recommended Integration Strategy: {result.integration_strategy}" + ) # Show migration recommendations if result.migration_recommendations: - click.echo(f"\nšŸ“‹ Migration Recommendations:") + click.echo("\nšŸ“‹ Migration Recommendations:") for recommendation in result.migration_recommendations: if recommendation.startswith(" "): click.echo(f" {recommendation}") @@ -384,14 +396,18 @@ def detect(target: str, detailed: bool): click.echo(f" • {recommendation}") if not result.detected_systems: - click.echo(f"\n✨ This project is ready for Kaizen Agentic installation!") - click.echo(f" Run: kaizen-agentic install ") + click.echo("\n✨ This project is ready for Kaizen Agentic installation!") + click.echo(" Run: kaizen-agentic install ") @cli.command() -@click.option('--target', '-t', default='.', help='Target directory (default: current)') -@click.option('--dry-run', '-n', is_flag=True, help='Show what would be done without executing') -@click.option('--auto-resolve', '-a', is_flag=True, help='Automatically resolve simple conflicts') +@click.option("--target", "-t", default=".", help="Target directory (default: current)") +@click.option( + "--dry-run", "-n", is_flag=True, help="Show what would be done without executing" +) +@click.option( + "--auto-resolve", "-a", is_flag=True, help="Automatically resolve simple conflicts" +) def migrate(target: str, dry_run: bool, auto_resolve: bool): """Create migration plan for integrating Kaizen agents into existing project.""" from .migration import AgentMigrationPlanner, AgentMigrator @@ -408,7 +424,10 @@ def migrate(target: str, dry_run: bool, auto_resolve: bool): planner = AgentMigrationPlanner() integration_plan = planner.create_integration_plan(target_path) - if not integration_plan.migration_plans and not integration_plan.conflict_resolutions: + if ( + not integration_plan.migration_plans + and not integration_plan.conflict_resolutions + ): click.echo("✨ No migration needed - project is ready for Kaizen agents!") click.echo(" Run: kaizen-agentic install ") return @@ -418,12 +437,17 @@ def migrate(target: str, dry_run: bool, auto_resolve: bool): click.echo(f"\nšŸ”„ Migration Plans ({len(integration_plan.migration_plans)}):") for plan in integration_plan.migration_plans: strategy_emoji = { - "replace": "šŸ”„", "extend": "šŸ”—", "preserve": "šŸ’¾", - "merge": "šŸ”€", "remove": "šŸ—‘ļø" + "replace": "šŸ”„", + "extend": "šŸ”—", + "preserve": "šŸ’¾", + "merge": "šŸ”€", + "remove": "šŸ—‘ļø", } emoji = strategy_emoji.get(plan.strategy.value, "ā“") - click.echo(f" {emoji} {plan.source_agent.name} ({plan.source_agent.type.value})") + click.echo( + f" {emoji} {plan.source_agent.name} ({plan.source_agent.type.value})" + ) click.echo(f" Strategy: {plan.strategy.value}") if plan.target_agent: click.echo(f" Target: {plan.target_agent}") @@ -433,7 +457,9 @@ def migrate(target: str, dry_run: bool, auto_resolve: bool): # Show conflict resolutions if integration_plan.conflict_resolutions: - click.echo(f"\nāš ļø Conflict Resolutions ({len(integration_plan.conflict_resolutions)}):") + click.echo( + f"\nāš ļø Conflict Resolutions ({len(integration_plan.conflict_resolutions)}):" + ) for resolution in integration_plan.conflict_resolutions: click.echo(f" • {resolution.agent1} vs {resolution.agent2}") click.echo(f" Resolution: {resolution.resolution.value}") @@ -443,31 +469,35 @@ def migrate(target: str, dry_run: bool, auto_resolve: bool): # Show integration order if integration_plan.integration_order: - click.echo(f"\nšŸ“‹ Integration Order:") + click.echo("\nšŸ“‹ Integration Order:") for i, agent_name in enumerate(integration_plan.integration_order, 1): click.echo(f" {i}. {agent_name}") # Show post-migration tasks if integration_plan.post_migration_tasks: - click.echo(f"\nāœ… Post-Migration Tasks:") + click.echo("\nāœ… Post-Migration Tasks:") for task in integration_plan.post_migration_tasks: click.echo(f" • {task}") # Execute migration if requested if not dry_run: - click.echo(f"\nšŸš€ Executing migration...") + click.echo("\nšŸš€ Executing migration...") migrator = AgentMigrator() results = migrator.execute_migration(integration_plan, dry_run=False) - click.echo(f"\nšŸ“Š Migration Results:") + click.echo("\nšŸ“Š Migration Results:") for agent, result in results.items(): status_emoji = "āœ…" if "ERROR" not in result else "āŒ" click.echo(f" {status_emoji} {agent}: {result}") click.echo(f"\nšŸ’¾ Backup created at: {integration_plan.backup_directory}") else: - click.echo(f"\nšŸ” This was a dry run. Use --no-dry-run to execute the migration.") - click.echo(f" Backup would be created at: {integration_plan.backup_directory}") + click.echo( + "\nšŸ” This was a dry run. Use --no-dry-run to execute the migration." + ) + click.echo( + f" Backup would be created at: {integration_plan.backup_directory}" + ) @cli.group() @@ -477,8 +507,8 @@ def extensions(): @extensions.command() -@click.option('--target', '-t', default='.', help='Target directory (default: current)') -@click.option('--base-agent', '-b', help='Filter by base agent') +@click.option("--target", "-t", default=".", help="Target directory (default: current)") +@click.option("--base-agent", "-b", help="Filter by base agent") def list_extensions(target: str, base_agent: Optional[str]): """List installed extensions.""" from .extensions import ExtensionManager @@ -510,12 +540,14 @@ def list_extensions(target: str, base_agent: Optional[str]): @extensions.command() -@click.argument('name') -@click.argument('base_agent') -@click.option('--target', '-t', default='.', help='Target directory (default: current)') -@click.option('--description', '-d', help='Extension description') -@click.option('--template', default='basic', help='Template type (basic, advanced)') -def create(name: str, base_agent: str, target: str, description: Optional[str], template: str): +@click.argument("name") +@click.argument("base_agent") +@click.option("--target", "-t", default=".", help="Target directory (default: current)") +@click.option("--description", "-d", help="Extension description") +@click.option("--template", default="basic", help="Template type (basic, advanced)") +def create( + name: str, base_agent: str, target: str, description: Optional[str], template: str +): """Create a new agent extension.""" from .extensions import ExtensionManager, ExtensionType, create_extension_template @@ -523,7 +555,9 @@ def create(name: str, base_agent: str, target: str, description: Optional[str], manager = ExtensionManager(target_path) # Generate template - template_content = create_extension_template(name, base_agent, target_path, template) + template_content = create_extension_template( + name, base_agent, target_path, template + ) # Save template to file template_dir = target_path / ".kaizen" / "extensions" / name @@ -537,18 +571,20 @@ def create(name: str, base_agent: str, target: str, description: Optional[str], name=name, base_agent=base_agent, extension_type=ExtensionType.FUNCTIONAL_EXTENSION, - description=description or f"Custom extension for {base_agent}" + description=description or f"Custom extension for {base_agent}", ) click.echo(f"āœ… Created extension: {name}") click.echo(f" Base agent: {base_agent}") click.echo(f" Template saved to: {template_file}") - click.echo(f" Edit the configuration and run: kaizen-agentic extensions enable {name}") + click.echo( + f" Edit the configuration and run: kaizen-agentic extensions enable {name}" + ) @extensions.command() -@click.argument('name') -@click.option('--target', '-t', default='.', help='Target directory (default: current)') +@click.argument("name") +@click.option("--target", "-t", default=".", help="Target directory (default: current)") def enable(name: str, target: str): """Enable an extension.""" from .extensions import ExtensionManager @@ -563,8 +599,8 @@ def enable(name: str, target: str): @extensions.command() -@click.argument('name') -@click.option('--target', '-t', default='.', help='Target directory (default: current)') +@click.argument("name") +@click.option("--target", "-t", default=".", help="Target directory (default: current)") def disable(name: str, target: str): """Disable an extension.""" from .extensions import ExtensionManager @@ -579,9 +615,9 @@ def disable(name: str, target: str): @extensions.command() -@click.argument('name') -@click.option('--target', '-t', default='.', help='Target directory (default: current)') -@click.confirmation_option(prompt='Are you sure you want to remove this extension?') +@click.argument("name") +@click.option("--target", "-t", default=".", help="Target directory (default: current)") +@click.confirmation_option(prompt="Are you sure you want to remove this extension?") def remove(name: str, target: str): """Remove an extension.""" from .extensions import ExtensionManager @@ -610,6 +646,7 @@ def _get_registry() -> AgentRegistry: # Try to find installed package try: import kaizen_agentic + package_dir = Path(kaizen_agentic.__file__).parent.parent.parent agents_dir = package_dir / "agents" if not agents_dir.exists(): @@ -617,7 +654,9 @@ def _get_registry() -> AgentRegistry: agents_dir = Path(kaizen_agentic.__file__).parent / "data" / "agents" except ImportError: click.echo("Error: Could not find agents directory") - click.echo("Make sure you're in a kaizen-agentic project or have the package installed") + click.echo( + "Make sure you're in a kaizen-agentic project or have the package installed" + ) sys.exit(1) if not agents_dir.exists(): @@ -627,5 +666,5 @@ def _get_registry() -> AgentRegistry: return AgentRegistry(agents_dir) -if __name__ == '__main__': +if __name__ == "__main__": cli() diff --git a/src/kaizen_agentic/detection.py b/src/kaizen_agentic/detection.py index 72c3a9b..d12c0fb 100644 --- a/src/kaizen_agentic/detection.py +++ b/src/kaizen_agentic/detection.py @@ -1,15 +1,15 @@ """Detection and analysis of existing agent systems in projects.""" -import json import yaml from pathlib import Path -from typing import Dict, List, Optional, Set, Tuple +from typing import List, Optional, Set, Tuple from dataclasses import dataclass from enum import Enum class AgentSystemType(Enum): """Types of existing agent systems that might be found.""" + KAIZEN_AGENTIC = "kaizen-agentic" CLAUDE_CODE = "claude-code" GITHUB_COPILOT = "github-copilot" @@ -25,6 +25,7 @@ class AgentSystemType(Enum): @dataclass class DetectedAgent: """Information about a detected agent.""" + name: str type: AgentSystemType file_path: Path @@ -44,6 +45,7 @@ class DetectedAgent: @dataclass class AgentSystemDetectionResult: """Result of agent system detection in a project.""" + project_path: Path detected_systems: List[AgentSystemType] agents: List[DetectedAgent] @@ -197,33 +199,37 @@ class AgentSystemDetector: agents.append(agent) except Exception as e: # Create a detected agent with error info - agents.append(DetectedAgent( - name=agent_file.stem.replace("agent-", ""), - type=AgentSystemType.KAIZEN_AGENTIC, - file_path=agent_file, - can_migrate=False, - migration_notes=f"Parse error: {e}" - )) + agents.append( + DetectedAgent( + name=agent_file.stem.replace("agent-", ""), + type=AgentSystemType.KAIZEN_AGENTIC, + file_path=agent_file, + can_migrate=False, + migration_notes=f"Parse error: {e}", + ) + ) return agents def _parse_kaizen_agent_file(self, agent_file: Path) -> Optional[DetectedAgent]: """Parse a Kaizen Agentic agent file.""" try: - content = agent_file.read_text(encoding='utf-8') + content = agent_file.read_text(encoding="utf-8") # Extract YAML frontmatter - if content.startswith('---'): - parts = content.split('---', 2) + if content.startswith("---"): + parts = content.split("---", 2) if len(parts) >= 3: frontmatter = yaml.safe_load(parts[1]) return DetectedAgent( - name=frontmatter.get('name', agent_file.stem.replace("agent-", "")), + name=frontmatter.get( + "name", agent_file.stem.replace("agent-", "") + ), type=AgentSystemType.KAIZEN_AGENTIC, file_path=agent_file, - description=frontmatter.get('description'), - dependencies=set(frontmatter.get('dependencies', [])) + description=frontmatter.get("description"), + dependencies=set(frontmatter.get("dependencies", [])), ) except Exception: pass @@ -238,12 +244,14 @@ class AgentSystemDetector: if claude_file.exists(): # Claude Code typically doesn't have separate agent files # but might reference agent usage in CLAUDE.md - agents.append(DetectedAgent( - name="claude-integration", - type=AgentSystemType.CLAUDE_CODE, - file_path=claude_file, - description="Claude Code integration configuration" - )) + agents.append( + DetectedAgent( + name="claude-integration", + type=AgentSystemType.CLAUDE_CODE, + file_path=claude_file, + description="Claude Code integration configuration", + ) + ) return agents @@ -261,15 +269,20 @@ class AgentSystemDetector: for pattern in ["*.py", "*.yml", "*.yaml", "*.json", "*.md"]: for agent_file in agent_dir.glob(pattern): # Skip kaizen-agentic files - if agent_file.name.startswith("agent-") and agent_file.suffix == ".md": + if ( + agent_file.name.startswith("agent-") + and agent_file.suffix == ".md" + ): continue - agents.append(DetectedAgent( - name=agent_file.stem, - type=AgentSystemType.CUSTOM_AGENTS, - file_path=agent_file, - description=f"Custom agent in {dir_name}/" - )) + agents.append( + DetectedAgent( + name=agent_file.stem, + type=AgentSystemType.CUSTOM_AGENTS, + file_path=agent_file, + description=f"Custom agent in {dir_name}/", + ) + ) return agents @@ -286,7 +299,9 @@ class AgentSystemDetector: return config_files - def _analyze_conflicts(self, agents: List[DetectedAgent]) -> List[Tuple[str, str, str]]: + def _analyze_conflicts( + self, agents: List[DetectedAgent] + ) -> List[Tuple[str, str, str]]: """Analyze potential conflicts between agents.""" conflicts = [] @@ -298,15 +313,16 @@ class AgentSystemDetector: agents_by_type[agent.type].append(agent) # Check for naming conflicts - all_names = [agent.name for agent in agents] for i, agent1 in enumerate(agents): - for j, agent2 in enumerate(agents[i+1:], i+1): + for j, agent2 in enumerate(agents[i + 1 :], i + 1): if agent1.name == agent2.name and agent1.type != agent2.type: - conflicts.append(( - agent1.name, - agent2.name, - f"Name conflict between {agent1.type.value} and {agent2.type.value}" - )) + conflicts.append( + ( + agent1.name, + agent2.name, + f"Name conflict between {agent1.type.value} and {agent2.type.value}", + ) + ) # Check for functional overlaps functional_conflicts = { @@ -324,12 +340,14 @@ class AgentSystemDetector: if len(matching_agents) > 1: for i, agent1 in enumerate(matching_agents): - for agent2 in matching_agents[i+1:]: - conflicts.append(( - agent1.name, - agent2.name, - f"Functional overlap: {conflict_type}" - )) + for agent2 in matching_agents[i + 1 :]: + conflicts.append( + ( + agent1.name, + agent2.name, + f"Functional overlap: {conflict_type}", + ) + ) return conflicts @@ -343,7 +361,10 @@ class AgentSystemDetector: if AgentSystemType.KAIZEN_AGENTIC in detected_systems: return "update_existing" - if len(detected_systems) == 1 and detected_systems[0] == AgentSystemType.CLAUDE_CODE: + if ( + len(detected_systems) == 1 + and detected_systems[0] == AgentSystemType.CLAUDE_CODE + ): return "claude_compatible" if len([a for a in agents if a.type == AgentSystemType.CUSTOM_AGENTS]) > 5: @@ -355,13 +376,15 @@ class AgentSystemDetector: self, detected_systems: List[AgentSystemType], agents: List[DetectedAgent], - conflicts: List[Tuple[str, str, str]] + conflicts: List[Tuple[str, str, str]], ) -> List[str]: """Generate migration recommendations.""" recommendations = [] if not detected_systems: - recommendations.append("Clean installation - no existing agent systems detected") + recommendations.append( + "Clean installation - no existing agent systems detected" + ) return recommendations if AgentSystemType.KAIZEN_AGENTIC in detected_systems: @@ -369,18 +392,26 @@ class AgentSystemDetector: recommendations.append("Run 'kaizen-agentic update' to get latest agents") if conflicts: - recommendations.append(f"Resolve {len(conflicts)} naming/functional conflicts") + recommendations.append( + f"Resolve {len(conflicts)} naming/functional conflicts" + ) for agent1, agent2, reason in conflicts: recommendations.append(f" - Conflict: {agent1} vs {agent2} ({reason})") custom_agents = [a for a in agents if a.type == AgentSystemType.CUSTOM_AGENTS] if custom_agents: - recommendations.append(f"Consider migrating {len(custom_agents)} custom agents") - recommendations.append(" - Review custom agents for Kaizen Agentic equivalents") - recommendations.append(" - Create project-specific extensions for unique functionality") + recommendations.append( + f"Consider migrating {len(custom_agents)} custom agents" + ) + recommendations.append( + " - Review custom agents for Kaizen Agentic equivalents" + ) + recommendations.append( + " - Create project-specific extensions for unique functionality" + ) if AgentSystemType.CLAUDE_CODE in detected_systems: recommendations.append("Maintain Claude Code compatibility") recommendations.append(" - Update CLAUDE.md with new agent references") - return recommendations \ No newline at end of file + return recommendations diff --git a/src/kaizen_agentic/extensions.py b/src/kaizen_agentic/extensions.py index 33f561b..98ffed0 100644 --- a/src/kaizen_agentic/extensions.py +++ b/src/kaizen_agentic/extensions.py @@ -3,13 +3,14 @@ import json import yaml from pathlib import Path -from typing import Dict, List, Optional, Any, Union +from typing import Dict, List, Optional, Any from dataclasses import dataclass, field from enum import Enum class ExtensionType(Enum): """Types of agent extensions.""" + CONFIGURATION_OVERLAY = "config_overlay" # Override default configurations FUNCTIONAL_EXTENSION = "functional_extension" # Add new functionality WORKFLOW_INTEGRATION = "workflow_integration" # Integrate with project workflows @@ -21,6 +22,7 @@ class ExtensionType(Enum): @dataclass class AgentExtension: """Defines an extension to a Kaizen agent.""" + name: str base_agent: str # The Kaizen agent this extends extension_type: ExtensionType @@ -44,6 +46,7 @@ class AgentExtension: @dataclass class ProjectExtensionRegistry: """Registry of extensions for a project.""" + project_path: Path extensions: List[AgentExtension] = field(default_factory=list) global_config: Dict[str, Any] = field(default_factory=dict) @@ -63,7 +66,7 @@ class ExtensionManager: base_agent: str, extension_type: ExtensionType, description: str, - **kwargs + **kwargs, ) -> AgentExtension: """Create a new agent extension.""" extension = AgentExtension( @@ -71,7 +74,7 @@ class ExtensionManager: base_agent=base_agent, extension_type=extension_type, description=description, - **kwargs + **kwargs, ) self._save_extension(extension) @@ -79,14 +82,16 @@ class ExtensionManager: def install_extension(self, extension_path: Path) -> AgentExtension: """Install an extension from a file.""" - if extension_path.suffix == '.json': + if extension_path.suffix == ".json": with open(extension_path) as f: data = json.load(f) - elif extension_path.suffix in ['.yml', '.yaml']: + elif extension_path.suffix in [".yml", ".yaml"]: with open(extension_path) as f: data = yaml.safe_load(f) else: - raise ValueError(f"Unsupported extension file format: {extension_path.suffix}") + raise ValueError( + f"Unsupported extension file format: {extension_path.suffix}" + ) extension = AgentExtension(**data) self._save_extension(extension) @@ -127,6 +132,7 @@ class ExtensionManager: extension_dir = self.extensions_dir / name if extension_dir.exists(): import shutil + shutil.rmtree(extension_dir) return True @@ -136,8 +142,11 @@ class ExtensionManager: def get_effective_config(self, base_agent: str) -> Dict[str, Any]: """Get the effective configuration for an agent with all extensions applied.""" base_config = self._get_base_agent_config(base_agent) - extensions = [ext for ext in self._load_extensions() - if ext.base_agent == base_agent and ext.enabled] + extensions = [ + ext + for ext in self._load_extensions() + if ext.base_agent == base_agent and ext.enabled + ] # Apply extensions in order for extension in extensions: @@ -151,7 +160,7 @@ class ExtensionManager: base_agent: str, custom_instructions: str, custom_commands: Optional[Dict[str, str]] = None, - environment_config: Optional[Dict[str, Any]] = None + environment_config: Optional[Dict[str, Any]] = None, ) -> AgentExtension: """Create a project-specific agent based on a Kaizen agent.""" @@ -161,8 +170,8 @@ class ExtensionManager: "project_context": { "name": self.project_path.name, "path": str(self.project_path), - "type": self._detect_project_type() - } + "type": self._detect_project_type(), + }, } if environment_config: @@ -175,7 +184,7 @@ class ExtensionManager: description=f"Project-specific extension of {base_agent} for {self.project_path.name}", configuration=config, custom_commands=custom_commands or {}, - environment_overrides=environment_config or {} + environment_overrides=environment_config or {}, ) # Create agent file @@ -187,7 +196,7 @@ class ExtensionManager: self, legacy_agent_path: Path, target_kaizen_agent: str, - migration_strategy: str = "preserve_functionality" + migration_strategy: str = "preserve_functionality", ) -> AgentExtension: """Integrate a legacy agent as an extension to a Kaizen agent.""" @@ -201,7 +210,7 @@ class ExtensionManager: "legacy_source": str(legacy_agent_path), "migration_strategy": migration_strategy, "preserved_functionality": legacy_analysis.get("functionality", []), - "custom_config": legacy_analysis.get("config", {}) + "custom_config": legacy_analysis.get("config", {}), } extension = self.create_extension( @@ -209,7 +218,7 @@ class ExtensionManager: base_agent=target_kaizen_agent, extension_type=ExtensionType.WORKFLOW_INTEGRATION, description=f"Legacy integration of {legacy_agent_path.name}", - configuration=config + configuration=config, ) # Create migration wrapper @@ -232,23 +241,23 @@ class ExtensionManager: extension_dir.mkdir(parents=True, exist_ok=True) # Save extension definition - with open(extension_dir / "extension.yml", 'w') as f: + with open(extension_dir / "extension.yml", "w") as f: # Convert dataclass to dict for YAML serialization data = { - 'name': extension.name, - 'base_agent': extension.base_agent, - 'extension_type': extension.extension_type.value, - 'description': extension.description, - 'version': extension.version, - 'author': extension.author, - 'configuration': extension.configuration, - 'custom_commands': extension.custom_commands, - 'workflow_hooks': extension.workflow_hooks, - 'data_transformations': extension.data_transformations, - 'environment_overrides': extension.environment_overrides, - 'dependencies': extension.dependencies, - 'compatibility': extension.compatibility, - 'enabled': extension.enabled + "name": extension.name, + "base_agent": extension.base_agent, + "extension_type": extension.extension_type.value, + "description": extension.description, + "version": extension.version, + "author": extension.author, + "configuration": extension.configuration, + "custom_commands": extension.custom_commands, + "workflow_hooks": extension.workflow_hooks, + "data_transformations": extension.data_transformations, + "environment_overrides": extension.environment_overrides, + "dependencies": extension.dependencies, + "compatibility": extension.compatibility, + "enabled": extension.enabled, } yaml.dump(data, f, default_flow_style=False) @@ -262,9 +271,9 @@ class ExtensionManager: data = yaml.safe_load(f) or {} extensions = [] - for ext_data in data.get('extensions', []): + for ext_data in data.get("extensions", []): # Convert string back to enum - ext_data['extension_type'] = ExtensionType(ext_data['extension_type']) + ext_data["extension_type"] = ExtensionType(ext_data["extension_type"]) extensions.append(AgentExtension(**ext_data)) return extensions @@ -277,28 +286,28 @@ class ExtensionManager: # Convert to serializable format data = { - 'extensions': [ + "extensions": [ { - 'name': ext.name, - 'base_agent': ext.base_agent, - 'extension_type': ext.extension_type.value, - 'description': ext.description, - 'version': ext.version, - 'author': ext.author, - 'configuration': ext.configuration, - 'custom_commands': ext.custom_commands, - 'workflow_hooks': ext.workflow_hooks, - 'data_transformations': ext.data_transformations, - 'environment_overrides': ext.environment_overrides, - 'dependencies': ext.dependencies, - 'compatibility': ext.compatibility, - 'enabled': ext.enabled + "name": ext.name, + "base_agent": ext.base_agent, + "extension_type": ext.extension_type.value, + "description": ext.description, + "version": ext.version, + "author": ext.author, + "configuration": ext.configuration, + "custom_commands": ext.custom_commands, + "workflow_hooks": ext.workflow_hooks, + "data_transformations": ext.data_transformations, + "environment_overrides": ext.environment_overrides, + "dependencies": ext.dependencies, + "compatibility": ext.compatibility, + "enabled": ext.enabled, } for ext in extensions ] } - with open(self.config_file, 'w') as f: + with open(self.config_file, "w") as f: yaml.dump(data, f, default_flow_style=False) def _toggle_extension(self, name: str, enabled: bool) -> bool: @@ -321,7 +330,7 @@ class ExtensionManager: "name": base_agent, "type": "kaizen_agent", "enabled": True, - "config": {} + "config": {}, } def _apply_extension_config( @@ -343,11 +352,13 @@ class ExtensionManager: config.setdefault("environment", {}).update(extension.environment_overrides) # Add extension metadata - config.setdefault("extensions", []).append({ - "name": extension.name, - "type": extension.extension_type.value, - "version": extension.version - }) + config.setdefault("extensions", []).append( + { + "name": extension.name, + "type": extension.extension_type.value, + "version": extension.version, + } + ) return config @@ -370,7 +381,7 @@ class ExtensionManager: "functionality": [], "config": {}, "commands": [], - "dependencies": [] + "dependencies": [], } if agent_path.suffix == ".py": @@ -379,8 +390,9 @@ class ExtensionManager: # Simple analysis - look for class and function definitions import re - classes = re.findall(r'class\s+(\w+)', content) - functions = re.findall(r'def\s+(\w+)', content) + + classes = re.findall(r"class\s+(\w+)", content) + functions = re.findall(r"def\s+(\w+)", content) analysis["functionality"] = classes + functions @@ -487,10 +499,7 @@ class LegacyWrapper: def create_extension_template( - name: str, - base_agent: str, - project_path: Path, - template_type: str = "basic" + name: str, base_agent: str, project_path: Path, template_type: str = "basic" ) -> str: """Create a template for a new agent extension.""" @@ -535,7 +544,6 @@ Save this as `.kaizen/extensions/{name}/extension.yml` and run: kaizen-agentic extensions install {name} ``` """, - "advanced": f"""# Advanced Extension Template: {name} This template provides advanced customization options for the {base_agent} agent. @@ -610,7 +618,7 @@ Create these files in `.kaizen/extensions/{name}/scripts/`: 1. Save the configuration as `.kaizen/extensions/{name}/extension.yml` 2. Add any custom scripts to the scripts directory 3. Install with: `kaizen-agentic extensions install {name}` -""" +""", } - return templates.get(template_type, templates["basic"]) \ No newline at end of file + return templates.get(template_type, templates["basic"]) diff --git a/src/kaizen_agentic/installer.py b/src/kaizen_agentic/installer.py index d4bd5ad..d7a12e6 100644 --- a/src/kaizen_agentic/installer.py +++ b/src/kaizen_agentic/installer.py @@ -12,6 +12,7 @@ from .registry import AgentRegistry @dataclass class InstallationConfig: """Configuration for agent installation.""" + target_dir: Path claude_config_path: Optional[Path] = None makefile_path: Optional[Path] = None @@ -26,9 +27,7 @@ class AgentInstaller: self.registry = registry def install_agents( - self, - agent_names: List[str], - config: InstallationConfig + self, agent_names: List[str], config: InstallationConfig ) -> Dict[str, str]: """Install agents into a project. @@ -89,9 +88,7 @@ class AgentInstaller: return sorted(installed) def update_agents( - self, - project_dir: Path, - agent_names: Optional[List[str]] = None + self, project_dir: Path, agent_names: Optional[List[str]] = None ) -> Dict[str, str]: """Update installed agents to latest versions.""" if agent_names is None: @@ -101,9 +98,7 @@ class AgentInstaller: return self.install_agents(agent_names, config) def remove_agents( - self, - agent_names: List[str], - project_dir: Path + self, agent_names: List[str], project_dir: Path ) -> Dict[str, str]: """Remove agents from a project.""" results = {} @@ -162,7 +157,10 @@ class AgentInstaller: counter = 0 while backup_dir.exists(): counter += 1 - backup_dir = agents_dir.parent / f"agents_backup_{timestamp}_{microseconds}_{counter}" + backup_dir = ( + agents_dir.parent + / f"agents_backup_{timestamp}_{microseconds}_{counter}" + ) shutil.copytree(agents_dir, backup_dir) print(f"Created backup at: {backup_dir}") @@ -173,22 +171,22 @@ class AgentInstaller: # Read existing config config = {} if config_path.exists(): - with open(config_path, 'r') as f: + with open(config_path, "r") as f: config = json.load(f) # Ensure agents section exists - if 'agents' not in config: - config['agents'] = {} + if "agents" not in config: + config["agents"] = {} # Add agent references for agent_name in agent_names: - config['agents'][agent_name] = { + config["agents"][agent_name] = { "path": f"agents/agent-{agent_name}.md", - "enabled": True + "enabled": True, } # Write updated config - with open(config_path, 'w') as f: + with open(config_path, "w") as f: json.dump(config, f, indent=2) print(f"Updated Claude configuration: {config_path}") @@ -200,7 +198,7 @@ class AgentInstaller: """Update Makefile with agent-specific targets.""" try: # Read existing Makefile - with open(makefile_path, 'r') as f: + with open(makefile_path, "r") as f: content = f.read() # Add agent management targets if not present @@ -224,7 +222,7 @@ agents-validate: content += agent_targets # Write updated Makefile - with open(makefile_path, 'w') as f: + with open(makefile_path, "w") as f: f.write(content) print(f"Updated Makefile: {makefile_path}") @@ -238,7 +236,9 @@ agents-validate: claude_md = project_dir / "CLAUDE.md" agent_section = "## Installed Agents\n\n" - agent_section += "This project includes the following specialized agents:\n\n" + agent_section += ( + "This project includes the following specialized agents:\n\n" + ) # Group agents by category categories = {} @@ -257,34 +257,37 @@ agents-validate: agent_section += f"- **{agent.name}**: {agent.description}\n" agent_section += "\n" - agent_section += ("Use these agents by referencing them in your " - "Claude Code interactions.\n\n") + agent_section += ( + "Use these agents by referencing them in your " + "Claude Code interactions.\n\n" + ) # Update or create CLAUDE.md if claude_md.exists(): - with open(claude_md, 'r') as f: + with open(claude_md, "r") as f: content = f.read() # Replace existing agent section or append if "## Installed Agents" in content: import re + content = re.sub( - r'## Installed Agents.*?(?=##|\Z)', + r"## Installed Agents.*?(?=##|\Z)", agent_section, content, - flags=re.DOTALL + flags=re.DOTALL, ) else: content += "\n" + agent_section - with open(claude_md, 'w') as f: + with open(claude_md, "w") as f: f.write(content) else: # Create new CLAUDE.md header = "# Claude Code Configuration\n\n" header += "This file contains Claude Code configuration and agent information.\n\n" - with open(claude_md, 'w') as f: + with open(claude_md, "w") as f: f.write(header + agent_section) print(f"Updated documentation: {claude_md}") @@ -304,7 +307,7 @@ class ProjectInitializer: project_dir: Path, template: str = "python-basic", agent_names: Optional[List[str]] = None, - project_name: Optional[str] = None + project_name: Optional[str] = None, ) -> Dict[str, str]: """Initialize a new project with agents and structure.""" results = {} @@ -325,7 +328,7 @@ class ProjectInitializer: config = InstallationConfig( target_dir=project_dir, claude_config_path=project_dir / "CLAUDE.md", - makefile_path=project_dir / "Makefile" + makefile_path=project_dir / "Makefile", ) installer = AgentInstaller(self.registry) @@ -337,12 +340,16 @@ class ProjectInitializer: return results - def _create_project_structure(self, project_dir: Path, project_name: str, template: str): + def _create_project_structure( + self, project_dir: Path, project_name: str, template: str + ): """Create basic project structure based on template.""" # Create directories dirs_to_create = ["src", "tests", "docs"] if template.startswith("python"): - dirs_to_create.extend([f"src/{project_name.replace('-', '_')}", ".github/workflows"]) + dirs_to_create.extend( + [f"src/{project_name.replace('-', '_')}", ".github/workflows"] + ) for dir_name in dirs_to_create: (project_dir / dir_name).mkdir(parents=True, exist_ok=True) @@ -501,7 +508,7 @@ python_functions = ["test_*"] def _create_init_py(self, project_dir: Path, project_name: str): """Create package __init__.py file.""" - package_name = project_name.replace('-', '_') + package_name = project_name.replace("-", "_") init_content = f'''""" {project_name} - A Python project with Kaizen Agentic agents. """ @@ -512,7 +519,7 @@ __version__ = "0.1.0" def _create_makefile(self, project_dir: Path, project_name: str): """Create Makefile with standard targets.""" - package_name = project_name.replace('-', '_') + package_name = project_name.replace("-", "_") makefile_content = f"""# {project_name} - Makefile for development workflow # Generated by Kaizen Agentic diff --git a/src/kaizen_agentic/migration.py b/src/kaizen_agentic/migration.py index 2655d81..17b7d4d 100644 --- a/src/kaizen_agentic/migration.py +++ b/src/kaizen_agentic/migration.py @@ -13,6 +13,7 @@ from .detection import AgentSystemDetector, DetectedAgent, AgentSystemType class MigrationStrategy(Enum): """Strategies for migrating existing agents.""" + REPLACE = "replace" # Replace with Kaizen equivalent EXTEND = "extend" # Extend Kaizen agent with custom functionality PRESERVE = "preserve" # Keep custom agent alongside Kaizen agents @@ -22,6 +23,7 @@ class MigrationStrategy(Enum): class ConflictResolution(Enum): """Ways to resolve conflicts between agents.""" + RENAME = "rename" # Rename one of the conflicting agents NAMESPACE = "namespace" # Put agents in different namespaces MERGE_FUNCTIONALITY = "merge" # Combine functionality @@ -33,6 +35,7 @@ class ConflictResolution(Enum): @dataclass class MigrationPlan: """Plan for migrating an existing agent.""" + source_agent: DetectedAgent strategy: MigrationStrategy target_agent: Optional[str] = None # Kaizen agent name if applicable @@ -48,6 +51,7 @@ class MigrationPlan: @dataclass class ConflictResolutionPlan: """Plan for resolving a conflict between agents.""" + agent1: str agent2: str conflict_type: str @@ -62,6 +66,7 @@ class ConflictResolutionPlan: @dataclass class IntegrationPlan: """Complete plan for integrating Kaizen agents into an existing project.""" + project_path: Path migration_plans: List[MigrationPlan] conflict_resolutions: List[ConflictResolutionPlan] @@ -104,10 +109,18 @@ class AgentMigrationPlanner: } self.functional_categories = { - "project_management": ["keepaTodofile", "project-management", "priority-evaluation"], + "project_management": [ + "keepaTodofile", + "project-management", + "priority-evaluation", + ], "testing": ["testing-efficiency", "test-maintenance", "tdd-workflow"], "documentation": ["claude-documentation", "keepaContributingfile"], - "code_quality": ["code-refactoring", "datamodel-optimization", "optimization"], + "code_quality": [ + "code-refactoring", + "datamodel-optimization", + "optimization", + ], "infrastructure": ["setupRepository", "tooling-optimization"], "version_control": ["keepaChangelog"], } @@ -118,7 +131,9 @@ class AgentMigrationPlanner: detection_result = detector.detect_agent_systems(project_path) # Create backup directory - backup_dir = project_path / f".kaizen-migration-backup-{int(Path().stat().st_mtime)}" + backup_dir = ( + project_path / f".kaizen-migration-backup-{int(Path().stat().st_mtime)}" + ) # Create migration plans for each detected agent migration_plans = [] @@ -145,7 +160,7 @@ class AgentMigrationPlanner: conflict_resolutions=conflict_resolutions, backup_directory=backup_dir, integration_order=integration_order, - post_migration_tasks=post_migration_tasks + post_migration_tasks=post_migration_tasks, ) def _create_migration_plan( @@ -190,7 +205,7 @@ class AgentMigrationPlanner: strategy=strategy, target_agent=target_agent, backup_path=backup_path, - migration_notes=notes + migration_notes=notes, ) def _has_unique_functionality(self, agent: DetectedAgent) -> bool: @@ -203,8 +218,18 @@ class AgentMigrationPlanner: """Check if a custom agent is essential to the project.""" # Heuristics for essential agents essential_patterns = [ - "deploy", "ci", "cd", "build", "release", "secret", "auth", - "database", "api", "server", "client", "integration" + "deploy", + "ci", + "cd", + "build", + "release", + "secret", + "auth", + "database", + "api", + "server", + "client", + "integration", ] agent_name_lower = agent.name.lower() @@ -230,7 +255,7 @@ class AgentMigrationPlanner: resolution = ConflictResolution.RENAME action_details = { "rename_agent": agent2, # Rename the second agent - "new_name": f"{agent2}_custom" + "new_name": f"{agent2}_custom", } elif "Functional overlap" in reason: # Check if one is a Kaizen agent @@ -252,10 +277,12 @@ class AgentMigrationPlanner: agent2=agent2, conflict_type=reason, resolution=resolution, - action_details=action_details + action_details=action_details, ) - def _determine_integration_order(self, migration_plans: List[MigrationPlan]) -> List[str]: + def _determine_integration_order( + self, migration_plans: List[MigrationPlan] + ) -> List[str]: """Determine the order to perform migrations.""" # Order by dependency and risk order = [] @@ -265,7 +292,11 @@ class AgentMigrationPlanner: order.extend([p.source_agent.name for p in infra_agents]) # 2. Core functionality agents - core_agents = [p for p in migration_plans if self._is_core_agent(p) and p not in infra_agents] + core_agents = [ + p + for p in migration_plans + if self._is_core_agent(p) and p not in infra_agents + ] order.extend([p.source_agent.name for p in core_agents]) # 3. Optional/enhancement agents last @@ -287,7 +318,10 @@ class AgentMigrationPlanner: return any(keyword in agent_name for keyword in core_keywords) def _generate_migration_notes( - self, agent: DetectedAgent, strategy: MigrationStrategy, target_agent: Optional[str] + self, + agent: DetectedAgent, + strategy: MigrationStrategy, + target_agent: Optional[str], ) -> List[str]: """Generate helpful notes for the migration.""" notes = [] @@ -332,13 +366,15 @@ class AgentMigrationPlanner: if detection_result.config_files: tasks.append("Verify all configuration files are updated") - tasks.extend([ - "Run 'kaizen-agentic validate' to verify installation", - "Test all agent functionality", - "Update project documentation", - "Train team on new agent workflows", - "Archive or remove backup files after verification" - ]) + tasks.extend( + [ + "Run 'kaizen-agentic validate' to verify installation", + "Test all agent functionality", + "Update project documentation", + "Train team on new agent workflows", + "Archive or remove backup files after verification", + ] + ) return tasks @@ -349,7 +385,9 @@ class AgentMigrator: def __init__(self): self.planner = AgentMigrationPlanner() - def execute_migration(self, plan: IntegrationPlan, dry_run: bool = True) -> Dict[str, str]: + def execute_migration( + self, plan: IntegrationPlan, dry_run: bool = True + ) -> Dict[str, str]: """Execute a migration plan.""" results = {} @@ -361,7 +399,7 @@ class AgentMigrator: for agent_name in plan.integration_order: migration_plan = next( (p for p in plan.migration_plans if p.source_agent.name == agent_name), - None + None, ) if migration_plan: result = self._execute_single_migration(migration_plan, dry_run) @@ -413,11 +451,14 @@ class AgentMigrator: extension_config = { "base_agent": plan.target_agent, "custom_source": str(plan.source_agent.file_path), - "extension_type": "functional_overlay" + "extension_type": "functional_overlay", } - extension_path = plan.source_agent.file_path.parent / f"{plan.source_agent.name}_extension.json" - with open(extension_path, 'w') as f: + extension_path = ( + plan.source_agent.file_path.parent + / f"{plan.source_agent.name}_extension.json" + ) + with open(extension_path, "w") as f: json.dump(extension_config, f, indent=2) return f"EXTENDED: {plan.target_agent} with {plan.source_agent.name}" @@ -427,7 +468,10 @@ class AgentMigrator: # Rename if necessary to avoid conflicts if plan.source_agent.name in ["todo", "changelog", "test"]: new_name = f"{plan.source_agent.name}_custom" - new_path = plan.source_agent.file_path.parent / f"{new_name}{plan.source_agent.file_path.suffix}" + new_path = ( + plan.source_agent.file_path.parent + / f"{new_name}{plan.source_agent.file_path.suffix}" + ) shutil.move(plan.source_agent.file_path, new_path) return f"PRESERVED: {plan.source_agent.name} -> {new_name}" @@ -458,4 +502,4 @@ class AgentMigrator: elif plan.resolution == ConflictResolution.CHOOSE_KAIZEN: return f"RESOLVED: Chose Kaizen agent {plan.action_details['keep']}" else: - return f"RESOLUTION_PLANNED: {plan.resolution.value}" \ No newline at end of file + return f"RESOLUTION_PLANNED: {plan.resolution.value}" diff --git a/src/kaizen_agentic/registry.py b/src/kaizen_agentic/registry.py index de8b935..4479ba5 100644 --- a/src/kaizen_agentic/registry.py +++ b/src/kaizen_agentic/registry.py @@ -10,6 +10,7 @@ from enum import Enum class AgentCategory(Enum): """Categories of agents for organization.""" + PROJECT_MANAGEMENT = "project-management" DEVELOPMENT_PROCESS = "development-process" CODE_QUALITY = "code-quality" @@ -21,6 +22,7 @@ class AgentCategory(Enum): @dataclass class AgentDefinition: """Represents an agent definition with metadata.""" + name: str description: str file_path: Path @@ -31,11 +33,11 @@ class AgentDefinition: @classmethod def from_file(cls, file_path: Path) -> "AgentDefinition": """Create AgentDefinition from a markdown file.""" - with open(file_path, 'r', encoding='utf-8') as f: + with open(file_path, "r", encoding="utf-8") as f: content = f.read() # Extract YAML frontmatter - frontmatter_match = re.match(r'^---\n(.*?)\n---\n', content, re.DOTALL) + frontmatter_match = re.match(r"^---\n(.*?)\n---\n", content, re.DOTALL) if not frontmatter_match: raise ValueError(f"No YAML frontmatter found in {file_path}") @@ -45,15 +47,15 @@ class AgentDefinition: dependencies = cls._extract_dependencies(content, frontmatter) # Determine category from name or content - category = cls._determine_category(frontmatter['name'], content) + category = cls._determine_category(frontmatter["name"], content) return cls( - name=frontmatter['name'], - description=frontmatter['description'], + name=frontmatter["name"], + description=frontmatter["description"], file_path=file_path, category=category, dependencies=dependencies, - model=frontmatter.get('model') + model=frontmatter.get("model"), ) @staticmethod @@ -62,42 +64,42 @@ class AgentDefinition: dependencies = set() # Check frontmatter for explicit dependencies - for key in ['dependencies', 'depends_on', 'requires']: + for key in ["dependencies", "depends_on", "requires"]: if key in frontmatter: deps = frontmatter[key] if isinstance(deps, list): dependencies.update(deps) elif isinstance(deps, str): # Handle comma-separated string - dependencies.update([d.strip() for d in deps.split(',')]) + dependencies.update([d.strip() for d in deps.split(",")]) # Look for explicit dependencies in content dep_patterns = [ - r'depends_on:\s*\[(.*?)\]', - r'requires:\s*\[(.*?)\]', - r'dependencies:\s*\[(.*?)\]', + r"depends_on:\s*\[(.*?)\]", + r"requires:\s*\[(.*?)\]", + r"dependencies:\s*\[(.*?)\]", ] for pattern in dep_patterns: matches = re.findall(pattern, content, re.IGNORECASE) for match in matches: if isinstance(match, str): - deps = [d.strip().strip('"\'') for d in match.split(',')] + deps = [d.strip().strip("\"'") for d in match.split(",")] dependencies.update(deps) # Look for specific agent references in content (more precise) # Only look for full agent names like "todo-keeper agent" or "uses changelog-keeper" agent_patterns = [ - r'uses?\s+(\w+(?:-\w+)*-(?:keeper|agent|workflow|helper|manager))', - r'depends?\s+on\s+(\w+(?:-\w+)*-(?:keeper|agent|workflow|helper|manager))', - r'requires?\s+(\w+(?:-\w+)*-(?:keeper|agent|workflow|helper|manager))', + r"uses?\s+(\w+(?:-\w+)*-(?:keeper|agent|workflow|helper|manager))", + r"depends?\s+on\s+(\w+(?:-\w+)*-(?:keeper|agent|workflow|helper|manager))", + r"requires?\s+(\w+(?:-\w+)*-(?:keeper|agent|workflow|helper|manager))", ] for pattern in agent_patterns: matches = re.findall(pattern, content.lower()) for match in matches: - if match not in ['optimization', 'agentic', 'driven', 'assisted']: - dependencies.add(match.replace('-', '_')) + if match not in ["optimization", "agentic", "driven", "assisted"]: + dependencies.add(match.replace("-", "_")) return dependencies @@ -107,28 +109,33 @@ class AgentDefinition: name_lower = name.lower() # Project management agents - project_keywords = ['todo', 'changelog', 'contributing', 'project'] + project_keywords = ["todo", "changelog", "contributing", "project"] if any(keyword in name_lower for keyword in project_keywords): return AgentCategory.PROJECT_MANAGEMENT # Testing agents - if any(keyword in name_lower for keyword in ['test', 'tdd']): + if any(keyword in name_lower for keyword in ["test", "tdd"]): return AgentCategory.TESTING # Code quality agents - if any(keyword in name_lower for keyword in ['refactor', 'optimization', 'code']): + if any( + keyword in name_lower for keyword in ["refactor", "optimization", "code"] + ): return AgentCategory.CODE_QUALITY # Documentation agents - if any(keyword in name_lower for keyword in ['documentation', 'claude']): + if any(keyword in name_lower for keyword in ["documentation", "claude"]): return AgentCategory.DOCUMENTATION # Infrastructure agents - if any(keyword in name_lower for keyword in ['setup', 'repository', 'tooling']): + if any(keyword in name_lower for keyword in ["setup", "repository", "tooling"]): return AgentCategory.INFRASTRUCTURE # Development process agents - if any(keyword in name_lower for keyword in ['workflow', 'requirements', 'maintenance']): + if any( + keyword in name_lower + for keyword in ["workflow", "requirements", "maintenance"] + ): return AgentCategory.DEVELOPMENT_PROCESS # Default fallback @@ -159,7 +166,9 @@ class AgentRegistry: """Get agent definition by name.""" return self._agents.get(name) - def list_agents(self, category: Optional[AgentCategory] = None) -> List[AgentDefinition]: + def list_agents( + self, category: Optional[AgentCategory] = None + ) -> List[AgentDefinition]: """List all agents, optionally filtered by category.""" agents = list(self._agents.values()) if category: @@ -232,7 +241,9 @@ class AgentRegistry: return errors - def _has_circular_dependency(self, agent_name: str, visited: Optional[Set[str]] = None) -> bool: + def _has_circular_dependency( + self, agent_name: str, visited: Optional[Set[str]] = None + ) -> bool: """Check if an agent has circular dependencies.""" if visited is None: visited = set() @@ -255,18 +266,14 @@ class AgentRegistry: def get_agent_templates(self) -> Dict[str, List[str]]: """Get predefined agent templates for different project types.""" return { - "python-basic": [ - "setupRepository", - "keepaTodofile", - "keepaChangelog" - ], + "python-basic": ["setupRepository", "keepaTodofile", "keepaChangelog"], "python-web": [ "setupRepository", "tdd-workflow", "code-refactoring", "keepaTodofile", "keepaChangelog", - "keepaContributingfile" + "keepaContributingfile", ], "python-cli": [ "setupRepository", @@ -274,7 +281,7 @@ class AgentRegistry: "testing-efficiency", "claude-documentation", "keepaTodofile", - "keepaChangelog" + "keepaChangelog", ], "python-data": [ "setupRepository", @@ -282,9 +289,7 @@ class AgentRegistry: "testing-efficiency", "requirements-engineering", "keepaTodofile", - "keepaChangelog" + "keepaChangelog", ], - "comprehensive": [ - agent.name for agent in self.list_agents() - ] + "comprehensive": [agent.name for agent in self.list_agents()], } diff --git a/tests/test_installer.py b/tests/test_installer.py index 253f85a..86a6f5e 100644 --- a/tests/test_installer.py +++ b/tests/test_installer.py @@ -3,7 +3,11 @@ import json import pytest from pathlib import Path -from kaizen_agentic.installer import AgentInstaller, ProjectInitializer, InstallationConfig +from kaizen_agentic.installer import ( + AgentInstaller, + ProjectInitializer, + InstallationConfig, +) from kaizen_agentic.registry import AgentRegistry @@ -72,9 +76,7 @@ def test_install_agents(test_registry, tmp_path): project_dir = tmp_path / "test_project" config = InstallationConfig( - target_dir=project_dir, - create_backup=False, - update_docs=False + target_dir=project_dir, create_backup=False, update_docs=False ) results = installer.install_agents(["base-agent"], config) @@ -89,9 +91,7 @@ def test_install_agents_with_dependencies(test_registry, tmp_path): project_dir = tmp_path / "test_project" config = InstallationConfig( - target_dir=project_dir, - create_backup=False, - update_docs=False + target_dir=project_dir, create_backup=False, update_docs=False ) # Install an agent that depends on others @@ -111,7 +111,9 @@ def test_list_installed_agents(test_registry, tmp_path): assert installed == [] # Install some agents - config = InstallationConfig(target_dir=project_dir, create_backup=False, update_docs=False) + config = InstallationConfig( + target_dir=project_dir, create_backup=False, update_docs=False + ) installer.install_agents(["base-agent", "keepaTodofile"], config) # Check installed agents @@ -127,7 +129,9 @@ def test_update_agents(test_registry, tmp_path): project_dir = tmp_path / "test_project" # Install initial agents - config = InstallationConfig(target_dir=project_dir, create_backup=False, update_docs=False) + config = InstallationConfig( + target_dir=project_dir, create_backup=False, update_docs=False + ) installer.install_agents(["base-agent"], config) # Update all agents @@ -146,7 +150,9 @@ def test_remove_agents(test_registry, tmp_path): project_dir = tmp_path / "test_project" # Install agents first - config = InstallationConfig(target_dir=project_dir, create_backup=False, update_docs=False) + config = InstallationConfig( + target_dir=project_dir, create_backup=False, update_docs=False + ) installer.install_agents(["base-agent", "keepaTodofile"], config) # Remove an agent @@ -170,7 +176,9 @@ def test_validate_installation(test_registry, tmp_path): assert "No agents directory found" in errors["project"] # Install agents and validate - config = InstallationConfig(target_dir=project_dir, create_backup=False, update_docs=False) + config = InstallationConfig( + target_dir=project_dir, create_backup=False, update_docs=False + ) installer.install_agents(["base-agent"], config) errors = installer.validate_installation(project_dir) @@ -187,7 +195,7 @@ def test_update_claude_config(test_registry, tmp_path): target_dir=project_dir, claude_config_path=claude_config, create_backup=False, - update_docs=False + update_docs=False, ) installer.install_agents(["base-agent"], config) @@ -208,9 +216,7 @@ def test_project_initializer(test_registry, tmp_path): project_dir = tmp_path / "new_project" initializer.init_project( - project_dir, - template="python-basic", - project_name="new_project" + project_dir, template="python-basic", project_name="new_project" ) # Check that project structure was created @@ -236,7 +242,7 @@ def test_project_initializer_custom_agents(test_registry, tmp_path): project_dir, template="python-basic", agent_names=["base-agent", "keepaTodofile"], - project_name="custom_project" + project_name="custom_project", ) # Check that specific agents were installed @@ -251,7 +257,7 @@ def test_installation_config(): claude_config_path=Path("/tmp/test/claude.json"), makefile_path=Path("/tmp/test/Makefile"), update_docs=True, - create_backup=False + create_backup=False, ) assert config.target_dir == Path("/tmp/test") diff --git a/tests/test_registry.py b/tests/test_registry.py index a7e9d8b..071d049 100644 --- a/tests/test_registry.py +++ b/tests/test_registry.py @@ -158,7 +158,10 @@ This agent uses both base-agent and dependent-agent. # Test complex dependency resolution resolved = registry.resolve_dependencies(["complex-agent"]) - assert all(agent in resolved for agent in ["base-agent", "dependent-agent", "complex-agent"]) + assert all( + agent in resolved + for agent in ["base-agent", "dependent-agent", "complex-agent"] + ) def test_agent_registry_get_templates(tmp_path):