Implement hybrid agent distribution system
Complete implementation of the agent distribution framework including: CORE INFRASTRUCTURE: - AgentRegistry: Agent discovery, categorization, and dependency management - AgentInstaller: Agent installation, updates, and removal with safety measures - ProjectInitializer: Template-based project initialization with agent integration - CLI Tool: Comprehensive kaizen-agentic command-line interface DISTRIBUTION FEATURES: - Python package distribution with console script entry point - Agent categorization (project-management, development-process, code-quality, etc.) - Project templates (python-basic, python-web, python-cli, python-data, comprehensive) - Dependency resolution and validation - Idempotent operations with backup and rollback support CLI COMMANDS: - kaizen-agentic init: Initialize new projects with agents - kaizen-agentic install/update/remove: Manage agents in existing projects - kaizen-agentic list/status/validate: Discovery and maintenance - kaizen-agentic templates: Project template management INTEGRATION & DOCUMENTATION: - Makefile targets for agent management (list-agents, update-agents, etc.) - Automatic Claude Code configuration updates (CLAUDE.md) - Comprehensive documentation (GETTING_STARTED, AGENT_DISTRIBUTION, CLI_CHEAT_SHEET) - Multi-language build system integration examples - Complete test coverage for all components PACKAGE STRUCTURE: - Console script: kaizen-agentic command available globally - Package data: All agents included for distribution - Dependencies: click, pyyaml for CLI and parsing - Testing: Comprehensive test suite for registry and installer This enables sharing specialized AI agents across projects with easy installation, updates, and management through both CLI and integrated Makefile targets. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
246
tests/test_installer.py
Normal file
246
tests/test_installer.py
Normal file
@@ -0,0 +1,246 @@
|
||||
"""Tests for agent installer functionality."""
|
||||
|
||||
import pytest
|
||||
import json
|
||||
from pathlib import Path
|
||||
from kaizen_agentic.installer import AgentInstaller, ProjectInitializer, InstallationConfig
|
||||
from kaizen_agentic.registry import AgentRegistry
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_registry(tmp_path):
|
||||
"""Create a test registry with sample agents."""
|
||||
# Create test agents
|
||||
agents_dir = tmp_path / "agents"
|
||||
agents_dir.mkdir()
|
||||
|
||||
base_agent = """---
|
||||
name: base-agent
|
||||
description: Base test agent
|
||||
---
|
||||
|
||||
# Base Agent
|
||||
|
||||
This is a base agent for testing.
|
||||
"""
|
||||
|
||||
setup_agent = """---
|
||||
name: setup-repository
|
||||
description: Repository setup agent
|
||||
---
|
||||
|
||||
# Setup Repository
|
||||
|
||||
Sets up repository structure.
|
||||
"""
|
||||
|
||||
todo_agent = """---
|
||||
name: todo-keeper
|
||||
description: TODO management agent
|
||||
---
|
||||
|
||||
# TODO Keeper
|
||||
|
||||
Manages TODO files.
|
||||
"""
|
||||
|
||||
(agents_dir / "agent-base-agent.md").write_text(base_agent)
|
||||
(agents_dir / "agent-setup-repository.md").write_text(setup_agent)
|
||||
(agents_dir / "agent-todo-keeper.md").write_text(todo_agent)
|
||||
|
||||
return AgentRegistry(agents_dir)
|
||||
|
||||
|
||||
def test_install_agents(test_registry, tmp_path):
|
||||
"""Test installing agents into a project."""
|
||||
installer = AgentInstaller(test_registry)
|
||||
project_dir = tmp_path / "test_project"
|
||||
|
||||
config = InstallationConfig(
|
||||
target_dir=project_dir,
|
||||
create_backup=False,
|
||||
update_docs=False
|
||||
)
|
||||
|
||||
results = installer.install_agents(["base-agent"], config)
|
||||
|
||||
assert results["base-agent"] == "INSTALLED"
|
||||
assert (project_dir / "agents" / "agent-base-agent.md").exists()
|
||||
|
||||
|
||||
def test_install_agents_with_dependencies(test_registry, tmp_path):
|
||||
"""Test installing agents with dependency resolution."""
|
||||
installer = AgentInstaller(test_registry)
|
||||
project_dir = tmp_path / "test_project"
|
||||
|
||||
config = InstallationConfig(
|
||||
target_dir=project_dir,
|
||||
create_backup=False,
|
||||
update_docs=False
|
||||
)
|
||||
|
||||
# Install an agent that depends on others
|
||||
results = installer.install_agents(["setup-repository"], config)
|
||||
|
||||
assert "setup-repository" in results
|
||||
assert results["setup-repository"] == "INSTALLED"
|
||||
|
||||
|
||||
def test_list_installed_agents(test_registry, tmp_path):
|
||||
"""Test listing installed agents."""
|
||||
installer = AgentInstaller(test_registry)
|
||||
project_dir = tmp_path / "test_project"
|
||||
|
||||
# Initially no agents
|
||||
installed = installer.list_installed_agents(project_dir)
|
||||
assert installed == []
|
||||
|
||||
# Install some agents
|
||||
config = InstallationConfig(target_dir=project_dir, create_backup=False, update_docs=False)
|
||||
installer.install_agents(["base-agent", "todo-keeper"], config)
|
||||
|
||||
# Check installed agents
|
||||
installed = installer.list_installed_agents(project_dir)
|
||||
assert "base-agent" in installed
|
||||
assert "todo-keeper" in installed
|
||||
assert len(installed) == 2
|
||||
|
||||
|
||||
def test_update_agents(test_registry, tmp_path):
|
||||
"""Test updating installed agents."""
|
||||
installer = AgentInstaller(test_registry)
|
||||
project_dir = tmp_path / "test_project"
|
||||
|
||||
# Install initial agents
|
||||
config = InstallationConfig(target_dir=project_dir, create_backup=False, update_docs=False)
|
||||
installer.install_agents(["base-agent"], config)
|
||||
|
||||
# Update all agents
|
||||
results = installer.update_agents(project_dir)
|
||||
assert "base-agent" in results
|
||||
assert results["base-agent"] == "INSTALLED"
|
||||
|
||||
# Update specific agents
|
||||
results = installer.update_agents(project_dir, ["base-agent"])
|
||||
assert results["base-agent"] == "INSTALLED"
|
||||
|
||||
|
||||
def test_remove_agents(test_registry, tmp_path):
|
||||
"""Test removing agents from a project."""
|
||||
installer = AgentInstaller(test_registry)
|
||||
project_dir = tmp_path / "test_project"
|
||||
|
||||
# Install agents first
|
||||
config = InstallationConfig(target_dir=project_dir, create_backup=False, update_docs=False)
|
||||
installer.install_agents(["base-agent", "todo-keeper"], config)
|
||||
|
||||
# Remove an agent
|
||||
results = installer.remove_agents(["base-agent"], project_dir)
|
||||
assert results["base-agent"] == "REMOVED"
|
||||
assert not (project_dir / "agents" / "agent-base-agent.md").exists()
|
||||
|
||||
# Try to remove non-existent agent
|
||||
results = installer.remove_agents(["non-existent"], project_dir)
|
||||
assert results["non-existent"] == "NOT_FOUND"
|
||||
|
||||
|
||||
def test_validate_installation(test_registry, tmp_path):
|
||||
"""Test validating agent installation."""
|
||||
installer = AgentInstaller(test_registry)
|
||||
project_dir = tmp_path / "test_project"
|
||||
|
||||
# Validate empty project
|
||||
errors = installer.validate_installation(project_dir)
|
||||
assert "project" in errors
|
||||
assert "No agents directory found" in errors["project"]
|
||||
|
||||
# Install agents and validate
|
||||
config = InstallationConfig(target_dir=project_dir, create_backup=False, update_docs=False)
|
||||
installer.install_agents(["base-agent"], config)
|
||||
|
||||
errors = installer.validate_installation(project_dir)
|
||||
assert len(errors) == 0 # Should be valid
|
||||
|
||||
|
||||
def test_update_claude_config(test_registry, tmp_path):
|
||||
"""Test updating Claude configuration."""
|
||||
installer = AgentInstaller(test_registry)
|
||||
project_dir = tmp_path / "test_project"
|
||||
claude_config = project_dir / "claude_config.json"
|
||||
|
||||
config = InstallationConfig(
|
||||
target_dir=project_dir,
|
||||
claude_config_path=claude_config,
|
||||
create_backup=False,
|
||||
update_docs=False
|
||||
)
|
||||
|
||||
installer.install_agents(["base-agent"], config)
|
||||
|
||||
# Check that config was created and updated
|
||||
assert claude_config.exists()
|
||||
with open(claude_config) as f:
|
||||
config_data = json.load(f)
|
||||
|
||||
assert "agents" in config_data
|
||||
assert "base-agent" in config_data["agents"]
|
||||
assert config_data["agents"]["base-agent"]["enabled"] is True
|
||||
|
||||
|
||||
def test_project_initializer(test_registry, tmp_path):
|
||||
"""Test project initialization."""
|
||||
initializer = ProjectInitializer(test_registry)
|
||||
project_dir = tmp_path / "new_project"
|
||||
|
||||
results = initializer.init_project(
|
||||
project_dir,
|
||||
template="python-basic",
|
||||
project_name="new_project"
|
||||
)
|
||||
|
||||
# Check that project structure was created
|
||||
assert project_dir.exists()
|
||||
assert (project_dir / "src").exists()
|
||||
assert (project_dir / "tests").exists()
|
||||
assert (project_dir / "README.md").exists()
|
||||
assert (project_dir / ".gitignore").exists()
|
||||
assert (project_dir / "pyproject.toml").exists()
|
||||
|
||||
# Check that agents were installed
|
||||
assert (project_dir / "agents").exists()
|
||||
agents_installed = len([f for f in (project_dir / "agents").glob("agent-*.md")])
|
||||
assert agents_installed > 0
|
||||
|
||||
|
||||
def test_project_initializer_custom_agents(test_registry, tmp_path):
|
||||
"""Test project initialization with custom agents."""
|
||||
initializer = ProjectInitializer(test_registry)
|
||||
project_dir = tmp_path / "custom_project"
|
||||
|
||||
results = initializer.init_project(
|
||||
project_dir,
|
||||
template="python-basic",
|
||||
agent_names=["base-agent", "todo-keeper"],
|
||||
project_name="custom_project"
|
||||
)
|
||||
|
||||
# Check that specific agents were installed
|
||||
assert (project_dir / "agents" / "agent-base-agent.md").exists()
|
||||
assert (project_dir / "agents" / "agent-todo-keeper.md").exists()
|
||||
|
||||
|
||||
def test_installation_config():
|
||||
"""Test InstallationConfig creation."""
|
||||
config = InstallationConfig(
|
||||
target_dir=Path("/tmp/test"),
|
||||
claude_config_path=Path("/tmp/test/claude.json"),
|
||||
makefile_path=Path("/tmp/test/Makefile"),
|
||||
update_docs=True,
|
||||
create_backup=False
|
||||
)
|
||||
|
||||
assert config.target_dir == Path("/tmp/test")
|
||||
assert config.claude_config_path == Path("/tmp/test/claude.json")
|
||||
assert config.makefile_path == Path("/tmp/test/Makefile")
|
||||
assert config.update_docs is True
|
||||
assert config.create_backup is False
|
||||
230
tests/test_registry.py
Normal file
230
tests/test_registry.py
Normal file
@@ -0,0 +1,230 @@
|
||||
"""Tests for agent registry functionality."""
|
||||
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
from kaizen_agentic.registry import AgentRegistry, AgentDefinition, AgentCategory
|
||||
|
||||
|
||||
def test_agent_definition_from_file(tmp_path):
|
||||
"""Test creating AgentDefinition from a markdown file."""
|
||||
agent_content = """---
|
||||
name: test-agent
|
||||
description: A test agent for demonstration
|
||||
model: inherit
|
||||
---
|
||||
|
||||
# Test Agent
|
||||
|
||||
This is a test agent that demonstrates the functionality.
|
||||
|
||||
It uses todo-keeper and changelog-keeper agents for dependencies.
|
||||
"""
|
||||
|
||||
agent_file = tmp_path / "agent-test-agent.md"
|
||||
agent_file.write_text(agent_content)
|
||||
|
||||
agent_def = AgentDefinition.from_file(agent_file)
|
||||
|
||||
assert agent_def.name == "test-agent"
|
||||
assert agent_def.description == "A test agent for demonstration"
|
||||
assert agent_def.model == "inherit"
|
||||
assert agent_def.file_path == agent_file
|
||||
|
||||
|
||||
def test_agent_registry_load_agents(tmp_path):
|
||||
"""Test loading agents from directory."""
|
||||
# Create test agents
|
||||
agent1_content = """---
|
||||
name: agent-one
|
||||
description: First test agent
|
||||
---
|
||||
|
||||
# Agent One
|
||||
"""
|
||||
|
||||
agent2_content = """---
|
||||
name: agent-two
|
||||
description: Second test agent
|
||||
---
|
||||
|
||||
# Agent Two
|
||||
"""
|
||||
|
||||
(tmp_path / "agent-agent-one.md").write_text(agent1_content)
|
||||
(tmp_path / "agent-agent-two.md").write_text(agent2_content)
|
||||
|
||||
registry = AgentRegistry(tmp_path)
|
||||
|
||||
assert len(registry._agents) == 2
|
||||
assert "agent-one" in registry._agents
|
||||
assert "agent-two" in registry._agents
|
||||
|
||||
|
||||
def test_agent_registry_get_agent(tmp_path):
|
||||
"""Test getting specific agent."""
|
||||
agent_content = """---
|
||||
name: test-agent
|
||||
description: A test agent
|
||||
---
|
||||
|
||||
# Test Agent
|
||||
"""
|
||||
|
||||
(tmp_path / "agent-test-agent.md").write_text(agent_content)
|
||||
registry = AgentRegistry(tmp_path)
|
||||
|
||||
agent = registry.get_agent("test-agent")
|
||||
assert agent is not None
|
||||
assert agent.name == "test-agent"
|
||||
|
||||
missing_agent = registry.get_agent("missing-agent")
|
||||
assert missing_agent is None
|
||||
|
||||
|
||||
def test_agent_registry_list_agents(tmp_path):
|
||||
"""Test listing agents."""
|
||||
# Create test agents with different categories
|
||||
todo_agent = """---
|
||||
name: todo-keeper
|
||||
description: Manages TODO files
|
||||
---
|
||||
|
||||
# TODO Keeper
|
||||
"""
|
||||
|
||||
test_agent = """---
|
||||
name: test-runner
|
||||
description: Runs tests
|
||||
---
|
||||
|
||||
# Test Runner
|
||||
"""
|
||||
|
||||
(tmp_path / "agent-todo-keeper.md").write_text(todo_agent)
|
||||
(tmp_path / "agent-test-runner.md").write_text(test_agent)
|
||||
|
||||
registry = AgentRegistry(tmp_path)
|
||||
|
||||
all_agents = registry.list_agents()
|
||||
assert len(all_agents) == 2
|
||||
|
||||
# Test filtering by category
|
||||
project_agents = registry.list_agents(AgentCategory.PROJECT_MANAGEMENT)
|
||||
assert len(project_agents) == 1
|
||||
assert project_agents[0].name == "todo-keeper"
|
||||
|
||||
|
||||
def test_agent_registry_resolve_dependencies(tmp_path):
|
||||
"""Test dependency resolution."""
|
||||
# Create agents with dependencies
|
||||
base_agent = """---
|
||||
name: base-agent
|
||||
description: Base agent with no dependencies
|
||||
---
|
||||
|
||||
# Base Agent
|
||||
"""
|
||||
|
||||
dependent_agent = """---
|
||||
name: dependent-agent
|
||||
description: Agent that depends on base-agent
|
||||
---
|
||||
|
||||
# Dependent Agent
|
||||
|
||||
This agent uses base-agent for functionality.
|
||||
"""
|
||||
|
||||
complex_agent = """---
|
||||
name: complex-agent
|
||||
description: Agent with multiple dependencies
|
||||
---
|
||||
|
||||
# Complex Agent
|
||||
|
||||
This agent uses both base-agent and dependent-agent.
|
||||
"""
|
||||
|
||||
(tmp_path / "agent-base-agent.md").write_text(base_agent)
|
||||
(tmp_path / "agent-dependent-agent.md").write_text(dependent_agent)
|
||||
(tmp_path / "agent-complex-agent.md").write_text(complex_agent)
|
||||
|
||||
registry = AgentRegistry(tmp_path)
|
||||
|
||||
# Test simple dependency resolution
|
||||
resolved = registry.resolve_dependencies(["dependent-agent"])
|
||||
assert "base-agent" in resolved
|
||||
assert "dependent-agent" in resolved
|
||||
|
||||
# Test complex dependency resolution
|
||||
resolved = registry.resolve_dependencies(["complex-agent"])
|
||||
assert all(agent in resolved for agent in ["base-agent", "dependent-agent", "complex-agent"])
|
||||
|
||||
|
||||
def test_agent_registry_get_templates(tmp_path):
|
||||
"""Test getting predefined templates."""
|
||||
registry = AgentRegistry(tmp_path)
|
||||
templates = registry.get_agent_templates()
|
||||
|
||||
assert "python-basic" in templates
|
||||
assert "python-web" in templates
|
||||
assert "python-cli" in templates
|
||||
assert "python-data" in templates
|
||||
assert "comprehensive" in templates
|
||||
|
||||
# Check that templates contain expected agents
|
||||
assert "setup-repository" in templates["python-basic"]
|
||||
assert "todo-keeper" in templates["python-basic"]
|
||||
|
||||
|
||||
def test_agent_registry_validate_agents(tmp_path):
|
||||
"""Test agent validation."""
|
||||
# Create valid agent
|
||||
valid_agent = """---
|
||||
name: valid-agent
|
||||
description: A valid agent
|
||||
---
|
||||
|
||||
# Valid Agent
|
||||
"""
|
||||
|
||||
# Create invalid agent (missing required fields)
|
||||
invalid_agent = """---
|
||||
description: Missing name field
|
||||
---
|
||||
|
||||
# Invalid Agent
|
||||
"""
|
||||
|
||||
(tmp_path / "agent-valid-agent.md").write_text(valid_agent)
|
||||
(tmp_path / "agent-invalid-agent.md").write_text(invalid_agent)
|
||||
|
||||
registry = AgentRegistry(tmp_path)
|
||||
|
||||
errors = registry.validate_agents()
|
||||
# The invalid agent should not be loaded, so no validation errors
|
||||
# (it fails during loading)
|
||||
assert len(errors) == 0 # Because invalid agents aren't loaded
|
||||
|
||||
|
||||
def test_agent_category_determination():
|
||||
"""Test automatic category determination."""
|
||||
# Test project management category
|
||||
assert AgentDefinition._determine_category("todo-keeper", "") == AgentCategory.PROJECT_MANAGEMENT
|
||||
assert AgentDefinition._determine_category("changelog-helper", "") == AgentCategory.PROJECT_MANAGEMENT
|
||||
|
||||
# Test testing category
|
||||
assert AgentDefinition._determine_category("test-runner", "") == AgentCategory.TESTING
|
||||
assert AgentDefinition._determine_category("tdd-workflow", "") == AgentCategory.TESTING
|
||||
|
||||
# Test code quality category
|
||||
assert AgentDefinition._determine_category("code-refactoring", "") == AgentCategory.CODE_QUALITY
|
||||
assert AgentDefinition._determine_category("optimization-helper", "") == AgentCategory.CODE_QUALITY
|
||||
|
||||
# Test documentation category
|
||||
assert AgentDefinition._determine_category("documentation-generator", "") == AgentCategory.DOCUMENTATION
|
||||
assert AgentDefinition._determine_category("claude-helper", "") == AgentCategory.DOCUMENTATION
|
||||
|
||||
# Test infrastructure category
|
||||
assert AgentDefinition._determine_category("setup-repository", "") == AgentCategory.INFRASTRUCTURE
|
||||
assert AgentDefinition._determine_category("tooling-manager", "") == AgentCategory.INFRASTRUCTURE
|
||||
Reference in New Issue
Block a user