scripts-test-coverage-report
#!/usr/bin/env python3 """
title: "Test Coverage Report" component_type: script version: "1.0.0" audience: contributor status: stable summary: "Test Coverage Report for CODITECT Framework" keywords: ['coverage', 'report', 'test'] tokens: ~500 created: 2025-12-22 updated: 2025-12-22 script_name: "test-coverage-report.py" language: python executable: true usage: "python3 scripts/test-coverage-report.py [options]" python_version: "3.10+" dependencies: [] modifies_files: false network_access: false requires_auth: false
Test Coverage Report for CODITECT Framework
Analyzes test coverage gaps between component inventory and test suite. Generates actionable reports for improving test coverage.
Usage: python3 scripts/test-coverage-report.py # Full report python3 scripts/test-coverage-report.py --json # JSON output python3 scripts/test-coverage-report.py --category scripts # Single category """
import json import argparse from pathlib import Path from datetime import datetime, timezone from typing import Dict, List, Set, Tuple
def get_repo_root() -> Path: """Get repository root.""" script_dir = Path(file).parent return script_dir.parent
def get_inventory(repo_root: Path) -> Dict[str, List[Path]]: """Get all components by category.""" inventory = {}
# Agents
agents_dir = repo_root / "agents"
if agents_dir.exists():
agents = [a for a in agents_dir.glob("*.md")
if a.stem not in ["README", "INDEX"]]
inventory["agents"] = agents
# Commands
commands_dir = repo_root / "commands"
if commands_dir.exists():
commands = [c for c in commands_dir.glob("*.md")
if c.stem not in ["README", "INDEX", "GUIDE"]]
inventory["commands"] = commands
# Skills
skills_dir = repo_root / "skills"
if skills_dir.exists():
skills = list(skills_dir.rglob("SKILL.md"))
inventory["skills"] = skills
# Scripts
scripts_dir = repo_root / "scripts"
if scripts_dir.exists():
scripts_py = list(scripts_dir.glob("*.py"))
scripts_sh = list(scripts_dir.glob("*.sh"))
core_dir = scripts_dir / "core"
scripts_core = list(core_dir.glob("*.py")) if core_dir.exists() else []
# Exclude test files
all_scripts = [s for s in (scripts_py + scripts_sh + scripts_core)
if not s.stem.startswith("test_")
and not s.name.startswith(".")
and "generated_task" not in str(s)]
inventory["scripts"] = all_scripts
# Hooks
hooks_dir = repo_root / "hooks"
if hooks_dir.exists():
hooks = list(hooks_dir.glob("*.md")) + \
list(hooks_dir.glob("*.sh")) + \
list(hooks_dir.glob("*.py"))
inventory["hooks"] = hooks
return inventory
def get_scripts_with_cli(scripts: List[Path]) -> List[Path]: """Find scripts that use argparse (have CLI).""" cli_scripts = [] for script in scripts: if script.suffix != ".py": continue try: content = script.read_text() if "argparse" in content or "ArgumentParser" in content: cli_scripts.append(script) except Exception: pass return cli_scripts
def get_tested_cli_scripts(repo_root: Path) -> Set[str]: """Get scripts that are in the cli_scripts test list.""" test_suite = repo_root / "scripts" / "test-suite.py" if not test_suite.exists(): return set()
content = test_suite.read_text()
# Find cli_scripts list
tested = set()
in_list = False
for line in content.split("\n"):
if "cli_scripts = [" in line:
in_list = True
continue
if in_list:
if "]" in line:
break
# Extract script name from line like ' "test-suite.py",'
line = line.strip().strip('"').strip("'").strip(",")
if line and not line.startswith("#"):
tested.add(line)
return tested
def get_functional_tests(repo_root: Path) -> Dict[str, List[str]]: """Get scripts with functional tests.""" # These are hardcoded based on what we know is tested return { "work_items.py": ["next", "dashboard", "search"], }
def analyze_coverage(repo_root: Path) -> Dict: """Analyze test coverage and return report data.""" inventory = get_inventory(repo_root) cli_scripts = get_scripts_with_cli(inventory.get("scripts", [])) tested_cli = get_tested_cli_scripts(repo_root) functional_tests = get_functional_tests(repo_root)
# CLI coverage
cli_coverage = []
for script in cli_scripts:
cli_coverage.append({
"name": script.name,
"path": str(script.relative_to(repo_root)),
"tested": script.name in tested_cli,
"functional_tests": functional_tests.get(script.name, [])
})
# Sort: untested first, then alphabetical
cli_coverage.sort(key=lambda x: (x["tested"], x["name"]))
# Summary stats
total_components = sum(len(v) for v in inventory.values())
tested_cli_count = len([c for c in cli_coverage if c["tested"]])
functional_count = len([c for c in cli_coverage if c["functional_tests"]])
report = {
"generated": datetime.now(timezone.utc).isoformat(),
"inventory": {
"agents": len(inventory.get("agents", [])),
"commands": len(inventory.get("commands", [])),
"skills": len(inventory.get("skills", [])),
"scripts": len(inventory.get("scripts", [])),
"hooks": len(inventory.get("hooks", [])),
"total": total_components
},
"cli_coverage": {
"scripts_with_cli": len(cli_scripts),
"cli_tested": tested_cli_count,
"cli_untested": len(cli_scripts) - tested_cli_count,
"functional_tested": functional_count,
"coverage_percent": round(tested_cli_count / len(cli_scripts) * 100, 1) if cli_scripts else 0
},
"untested_cli_scripts": [c["name"] for c in cli_coverage if not c["tested"]],
"scripts_needing_functional_tests": [
c["name"] for c in cli_coverage
if c["tested"] and not c["functional_tests"]
],
"recommendations": []
}
# Add recommendations
if report["cli_coverage"]["cli_untested"] > 0:
report["recommendations"].append({
"priority": "high",
"category": "cli_tests",
"description": f"Add --help tests for {report['cli_coverage']['cli_untested']} scripts",
"fix": "Update cli_scripts list in test-suite.py or use dynamic discovery"
})
if len(report["scripts_needing_functional_tests"]) > 0:
report["recommendations"].append({
"priority": "medium",
"category": "functional_tests",
"description": f"Add functional tests for {len(report['scripts_needing_functional_tests'])} scripts",
"scripts": report["scripts_needing_functional_tests"][:10]
})
return report
def print_report(report: Dict, category: str = None): """Print human-readable report.""" print("=" * 70) print("CODITECT TEST COVERAGE REPORT") print(f"Generated: {report['generated']}") print("=" * 70) print()
# Inventory
print("COMPONENT INVENTORY:")
inv = report["inventory"]
print(f" Agents: {inv['agents']}")
print(f" Commands: {inv['commands']}")
print(f" Skills: {inv['skills']}")
print(f" Scripts: {inv['scripts']}")
print(f" Hooks: {inv['hooks']}")
print(f" Total: {inv['total']}")
print()
# CLI Coverage
cli = report["cli_coverage"]
print("CLI TEST COVERAGE:")
print(f" Scripts with CLI: {cli['scripts_with_cli']}")
print(f" CLI tested (--help): {cli['cli_tested']}")
print(f" CLI untested: {cli['cli_untested']}")
print(f" Functional tests: {cli['functional_tested']}")
print(f" Coverage: {cli['coverage_percent']}%")
print()
# Untested scripts
if report["untested_cli_scripts"]:
print("UNTESTED CLI SCRIPTS (need --help test):")
for i, script in enumerate(report["untested_cli_scripts"][:20]):
print(f" {i+1:3}. {script}")
if len(report["untested_cli_scripts"]) > 20:
print(f" ... and {len(report['untested_cli_scripts']) - 20} more")
print()
# Scripts needing functional tests
if report["scripts_needing_functional_tests"]:
print("SCRIPTS NEEDING FUNCTIONAL TESTS:")
for script in report["scripts_needing_functional_tests"]:
print(f" - {script}")
print()
# Recommendations
if report["recommendations"]:
print("=" * 70)
print("RECOMMENDATIONS:")
print("=" * 70)
for i, rec in enumerate(report["recommendations"], 1):
print(f"\n{i}. [{rec['priority'].upper()}] {rec['category']}")
print(f" {rec['description']}")
if "fix" in rec:
print(f" Fix: {rec['fix']}")
print()
def main(): parser = argparse.ArgumentParser( description="Generate test coverage report for CODITECT framework", formatter_class=argparse.RawDescriptionHelpFormatter ) parser.add_argument("--json", action="store_true", help="Output as JSON") parser.add_argument("--category", choices=["agents", "commands", "skills", "scripts", "hooks"], help="Report on single category") parser.add_argument("--save", metavar="PATH", help="Save report to file")
args = parser.parse_args()
repo_root = get_repo_root()
report = analyze_coverage(repo_root)
if args.json:
print(json.dumps(report, indent=2))
else:
print_report(report, args.category)
if args.save:
with open(args.save, "w") as f:
json.dump(report, f, indent=2)
print(f"Report saved to: {args.save}")
# Exit with code based on coverage
if report["cli_coverage"]["coverage_percent"] < 50:
return 1 # Poor coverage
return 0
if name == "main": exit(main())