Fix linting violations for v1.0.0 release preparation

- 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 <noreply@anthropic.com>
This commit is contained in:
2025-10-19 20:44:58 +02:00
parent 30daabf12c
commit d68310793b
9 changed files with 432 additions and 289 deletions

View File

@@ -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"; \

View File

@@ -17,10 +17,12 @@ def cli():
@cli.command()
@click.option('--category',
@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')
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 <agent-names>")
click.echo("\n✨ This project is ready for Kaizen Agentic installation!")
click.echo(" Run: kaizen-agentic install <agent-names>")
@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 <agent-names>")
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()

View File

@@ -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(
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}"
))
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(
agents.append(
DetectedAgent(
name="claude-integration",
type=AgentSystemType.CLAUDE_CODE,
file_path=claude_file,
description="Claude Code integration configuration"
))
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(
agents.append(
DetectedAgent(
name=agent_file.stem,
type=AgentSystemType.CUSTOM_AGENTS,
file_path=agent_file,
description=f"Custom agent in {dir_name}/"
))
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):
if agent1.name == agent2.name and agent1.type != agent2.type:
conflicts.append((
conflicts.append(
(
agent1.name,
agent2.name,
f"Name conflict between {agent1.type.value} and {agent2.type.value}"
))
f"Name conflict between {agent1.type.value} and {agent2.type.value}",
)
)
# Check for functional overlaps
functional_conflicts = {
@@ -325,11 +341,13 @@ class AgentSystemDetector:
if len(matching_agents) > 1:
for i, agent1 in enumerate(matching_agents):
for agent2 in matching_agents[i + 1 :]:
conflicts.append((
conflicts.append(
(
agent1.name,
agent2.name,
f"Functional overlap: {conflict_type}"
))
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,15 +392,23 @@ 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")

View File

@@ -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({
config.setdefault("extensions", []).append(
{
"name": extension.name,
"type": extension.extension_type.value,
"version": extension.version
})
"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"])

View File

@@ -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

View File

@@ -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([
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"
])
"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}"

View File

@@ -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()],
}

View File

@@ -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")

View File

@@ -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):