Skip to main content

#!/usr/bin/env python3 """ Test invoke-agent.py Universal Invoker

Verifies that invoke-agent.py can correctly:

  1. Load all 130+ agents
  2. Parse their frontmatter
  3. Generate valid Task() calls

Run: python3 scripts/tests/test_invoke_agent.py python3 scripts/tests/test_invoke_agent.py -v # Verbose

Author: CODITECT Team Version: 1.0.0 Created: 2025-12-22 """

import sys import json import subprocess from pathlib import Path from typing import List, Tuple

Shared Colors module (consolidates 36 duplicate definitions)

_script_dir = Path(file).parent.parent # tests/ -> scripts/ sys.path.insert(0, str(_script_dir / "core")) from colors import Colors

def get_framework_root() -> Path: """Get framework root directory""" script_path = Path(file).resolve() return script_path.parent.parent.parent # scripts/tests -> scripts -> coditect-core

def get_all_agents(framework_root: Path) -> List[str]: """Get list of all agent names""" agents_dir = framework_root / "agents" agents = [] for f in sorted(agents_dir.glob("*.md")): if f.name != "README.md": agents.append(f.stem) return agents

def test_invoke_agent_list(framework_root: Path) -> Tuple[bool, str]: """Test --list command""" invoke_script = framework_root / "scripts" / "core" / "invoke-agent.py"

result = subprocess.run(
["python3", str(invoke_script), "--list"],
capture_output=True,
text=True,
cwd=str(framework_root)
)

if result.returncode != 0:
return False, f"--list failed: {result.stderr}"

# Check output contains expected agent count
if "Available CODITECT agents" not in result.stdout:
return False, "Missing 'Available CODITECT agents' header"

# Count listed agents
lines = [l for l in result.stdout.split('\n') if l.strip().startswith(('1.', '2.', '3.'))]
# Actually count all numbered lines
import re
agent_lines = re.findall(r'^\s+\d+\.\s+\w', result.stdout, re.MULTILINE)

if len(agent_lines) < 100:
return False, f"Expected 100+ agents listed, got {len(agent_lines)}"

return True, f"Listed {len(agent_lines)} agents"

def test_invoke_agent_info(framework_root: Path, agent_name: str) -> Tuple[bool, str]: """Test --info command for a specific agent""" invoke_script = framework_root / "scripts" / "core" / "invoke-agent.py"

result = subprocess.run(
["python3", str(invoke_script), "--info", agent_name],
capture_output=True,
text=True,
cwd=str(framework_root)
)

if result.returncode != 0:
return False, f"--info {agent_name} failed: {result.stderr}"

# Check output contains expected fields
required = ["Agent:", "Title:", "Model:", "Path:"]
for field in required:
if field not in result.stdout:
return False, f"Missing field '{field}' in output"

return True, f"Info loaded for {agent_name}"

def test_invoke_agent_task_generation(framework_root: Path, agent_name: str) -> Tuple[bool, str]: """Test Task() call generation for a specific agent""" invoke_script = framework_root / "scripts" / "core" / "invoke-agent.py"

result = subprocess.run(
["python3", str(invoke_script), agent_name, "test task"],
capture_output=True,
text=True,
cwd=str(framework_root)
)

if result.returncode != 0:
return False, f"Task generation failed: {result.stderr}"

# Check output contains valid Task() call
if 'Task(' not in result.stdout:
return False, "Missing Task() in output"

if 'subagent_type="general-purpose"' not in result.stdout:
return False, "Missing subagent_type='general-purpose' in Task()"

if agent_name not in result.stdout.lower():
return False, f"Agent name '{agent_name}' not found in output"

return True, f"Task() generated for {agent_name}"

def test_invoke_agent_json_output(framework_root: Path, agent_name: str) -> Tuple[bool, str]: """Test JSON output format""" invoke_script = framework_root / "scripts" / "core" / "invoke-agent.py"

result = subprocess.run(
["python3", str(invoke_script), "--json", agent_name, "test task"],
capture_output=True,
text=True,
cwd=str(framework_root)
)

if result.returncode != 0:
return False, f"JSON output failed: {result.stderr}"

try:
data = json.loads(result.stdout)
if "subagent_type" not in data:
return False, "Missing 'subagent_type' in JSON"
if data["subagent_type"] != "general-purpose":
return False, f"Wrong subagent_type: {data['subagent_type']}"
return True, "JSON output valid"
except json.JSONDecodeError as e:
return False, f"Invalid JSON: {e}"

def test_all_agents_loadable(framework_root: Path, verbose: bool = False) -> Tuple[int, int, List[str]]: """Test that all agents can be loaded by invoke-agent.py""" agents = get_all_agents(framework_root) passed = 0 failed = 0 failures = []

print(f"\nTesting {len(agents)} agents are loadable...")

for agent in agents:
success, message = test_invoke_agent_info(framework_root, agent)
if success:
passed += 1
if verbose:
print(f" {Colors.GREEN}✓{Colors.RESET} {agent}")
else:
failed += 1
failures.append(f"{agent}: {message}")
print(f" {Colors.RED}✗{Colors.RESET} {agent}: {message}")

return passed, failed, failures

def test_sample_task_generation(framework_root: Path, verbose: bool = False) -> Tuple[int, int, List[str]]: """Test Task() generation for a sample of agents""" # Test a representative sample sample_agents = [ "git-workflow-orchestrator", "orchestrator", "codi-documentation-writer", "devops-engineer", "security-specialist", "code-reviewer", "project-organizer", "database-architect", "frontend-react-typescript-expert", "cloud-architect" ]

passed = 0
failed = 0
failures = []

print(f"\nTesting Task() generation for {len(sample_agents)} sample agents...")

for agent in sample_agents:
success, message = test_invoke_agent_task_generation(framework_root, agent)
if success:
passed += 1
if verbose:
print(f" {Colors.GREEN}✓{Colors.RESET} {agent}")
else:
failed += 1
failures.append(f"{agent}: {message}")
print(f" {Colors.RED}✗{Colors.RESET} {agent}: {message}")

return passed, failed, failures

def main(): import argparse parser = argparse.ArgumentParser(description="Test invoke-agent.py") parser.add_argument('-v', '--verbose', action='store_true') args = parser.parse_args()

print(f"{Colors.BOLD}invoke-agent.py Test Suite{Colors.RESET}")
print("=" * 50)

framework_root = get_framework_root()
total_passed = 0
total_failed = 0
all_failures = []

# Test 1: --list command
print("\n1. Testing --list command...")
success, message = test_invoke_agent_list(framework_root)
if success:
total_passed += 1
print(f" {Colors.GREEN}✓{Colors.RESET} {message}")
else:
total_failed += 1
all_failures.append(f"--list: {message}")
print(f" {Colors.RED}✗{Colors.RESET} {message}")

# Test 2: All agents loadable
passed, failed, failures = test_all_agents_loadable(framework_root, args.verbose)
total_passed += passed
total_failed += failed
all_failures.extend(failures)

# Test 3: Sample Task() generation
passed, failed, failures = test_sample_task_generation(framework_root, args.verbose)
total_passed += passed
total_failed += failed
all_failures.extend(failures)

# Test 4: JSON output
print("\n4. Testing JSON output...")
success, message = test_invoke_agent_json_output(framework_root, "orchestrator")
if success:
total_passed += 1
print(f" {Colors.GREEN}✓{Colors.RESET} {message}")
else:
total_failed += 1
all_failures.append(f"JSON output: {message}")
print(f" {Colors.RED}✗{Colors.RESET} {message}")

# Summary
print("\n" + "=" * 50)
print(f"{Colors.BOLD}Summary{Colors.RESET}")
print("=" * 50)
print(f"\nTests: {total_passed}/{total_passed + total_failed} passed")

if total_failed > 0:
print(f"\n{Colors.RED}Failures:{Colors.RESET}")
for f in all_failures:
print(f" - {f}")
print(f"\n{Colors.RED}{Colors.BOLD}{total_failed} test(s) failed{Colors.RESET}")
sys.exit(1)
else:
print(f"\n{Colors.GREEN}{Colors.BOLD}All tests passed!{Colors.RESET}")
sys.exit(0)

if name == "main": main()