Skip to main content

scripts-update-component-counts

#!/usr/bin/env python3 """

title: "Update Component Counts" component_type: script version: "1.0.0" audience: contributor status: stable summary: "CODITECT Component Counter - Single Source of Truth Generator" keywords: ['component', 'counts', 'update', 'validation'] tokens: ~500 created: 2025-12-22 updated: 2025-12-22 script_name: "update-component-counts.py" language: python executable: true usage: "python3 scripts/update-component-counts.py [options]" python_version: "3.10+" dependencies: [] modifies_files: false network_access: false requires_auth: false

CODITECT Component Counter - Single Source of Truth Generator

Scans component directories and generates config/component-counts.json as the authoritative source for all component counts.

Usage: python3 scripts/update-component-counts.py [--check]

Options: --check Verify counts match without updating (for CI)

When to run: - Automatically called by update-component-activation.py - Pre-commit hook (recommended) - After adding/removing components - CI pipeline validation

Author: AZ1.AI INC """

import argparse import json import sys from datetime import datetime, timezone from pathlib import Path

def count_components(repo_root: Path) -> dict: """Count components in each directory.""" counts = {}

# Agents: agents/*.md excluding README, INDEX
agents_dir = repo_root / "agents"
if agents_dir.exists():
agent_files = [f for f in agents_dir.glob("*.md")
if f.stem.lower() not in ("readme", "index", "agent-index")]
counts["agents"] = len(agent_files)
else:
counts["agents"] = 0

# Commands: commands/*.md excluding README, INDEX, COMMAND-GUIDE
commands_dir = repo_root / "commands"
if commands_dir.exists():
command_files = [f for f in commands_dir.glob("*.md")
if f.stem.lower() not in ("readme", "index", "command-guide")]
counts["commands"] = len(command_files)
else:
counts["commands"] = 0

# Skills: skills/**/SKILL.md (recursive to catch nested skills like document-skills/xlsx/)
skills_dir = repo_root / "skills"
if skills_dir.exists():
skill_files = list(skills_dir.glob("**/SKILL.md"))
counts["skills"] = len(skill_files)
else:
counts["skills"] = 0

# Scripts: scripts/**/*.py + scripts/**/*.sh (recursive, excluding tests)
scripts_dir = repo_root / "scripts"
if scripts_dir.exists():
exclude_dirs = {"__pycache__", "tests", ".git", "venv", ".venv"}
py_files = []
sh_files = []
for f in scripts_dir.glob("**/*.py"):
# Skip excluded directories and test files
if any(p in exclude_dirs for p in f.parts):
continue
if f.stem.startswith("test_") or "/tests/" in str(f):
continue
if f.name == "__init__.py":
continue
py_files.append(f)
for f in scripts_dir.glob("**/*.sh"):
if any(p in exclude_dirs for p in f.parts):
continue
sh_files.append(f)
counts["scripts"] = len(py_files) + len(sh_files)
else:
counts["scripts"] = 0

# Hooks: hooks/*.md + hooks/*.sh + hooks/*.py excluding README, INDEX docs
hooks_dir = repo_root / "hooks"
if hooks_dir.exists():
hook_md = [f for f in hooks_dir.glob("*.md")
if f.stem.lower() not in ("readme", "index", "hooks-index", "phase2-3-advanced-hooks")]
hook_sh = list(hooks_dir.glob("*.sh"))
hook_py = list(hooks_dir.glob("*.py"))
counts["hooks"] = len(hook_md) + len(hook_sh) + len(hook_py)
else:
counts["hooks"] = 0

# Prompts: prompts/*.md excluding README
prompts_dir = repo_root / "prompts"
if prompts_dir.exists():
prompt_files = [f for f in prompts_dir.glob("*.md")
if f.stem.lower() not in ("readme", "index")]
counts["prompts"] = len(prompt_files)
else:
counts["prompts"] = 0

# Workflows: workflows/**/*.yaml + workflows/**/*.yml + workflows/**/*.json (all executable workflow definitions)
# Includes both native CODITECT YAML and n8n-format JSON (1,149+ total)
workflows_dir = repo_root / "workflows"
if workflows_dir.exists():
# Recursive glob for all workflow files in subdirectories
yaml_files = list(workflows_dir.glob("**/*.yaml"))
yml_files = list(workflows_dir.glob("**/*.yml"))
json_files = [f for f in workflows_dir.glob("**/*.json")
if f.stem.lower() not in ("readme", "index", "workflow-index")]
counts["workflows"] = len(yaml_files) + len(yml_files) + len(json_files)
else:
counts["workflows"] = 0

# Total
counts["total"] = sum(counts.values())

return counts

def load_current_counts(config_path: Path) -> dict: """Load current counts from config file.""" if config_path.exists(): with open(config_path, "r") as f: return json.load(f) return {}

def save_counts(config_path: Path, counts: dict, version: str = "1.2.1"): """Save counts to config file.""" data = { "_comment": "AUTO-GENERATED - Do not edit manually. Run: python3 scripts/update-component-counts.py", "generated": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), "version": version, "counts": counts, "directories": { "agents": "agents/.md (excluding README)", "commands": "commands/.md (excluding README, INDEX, GUIDE)", "skills": "skills//SKILL.md (recursive, includes nested skills)", "scripts": "scripts//.py + scripts/**/.sh (recursive, excluding tests)", "hooks": "hooks/.md + hooks/.sh + hooks/.py", "prompts": "prompts/.md (excluding README)", "workflows": "workflows//*.yaml + workflows//.yml + workflows/**/.json (recursive, executable)" } }

with open(config_path, "w") as f:
json.dump(data, f, indent=2)
f.write("\n")

return data

def main(): # Find repo root (where this script lives in scripts/) script_path = Path(file).resolve() repo_root = script_path.parent.parent config_path = repo_root / "config" / "component-counts.json"

# Parse arguments
parser = argparse.ArgumentParser(
description="CODITECT Component Counter - Single Source of Truth Generator",
epilog="Scans component directories and generates config/component-counts.json"
)
parser.add_argument(
"--check",
action="store_true",
help="Verify counts match without updating (for CI)"
)
args = parser.parse_args()

# Check mode
check_only = args.check

# Count components
counts = count_components(repo_root)

if check_only:
# Verify mode for CI
current = load_current_counts(config_path)
current_counts = current.get("counts", {})

if counts != current_counts:
print("ERROR: Component counts are out of sync!")
print(f" Expected: {current_counts}")
print(f" Actual: {counts}")
print("\nRun: python3 scripts/update-component-counts.py")
sys.exit(1)
else:
print("OK: Component counts are in sync")
print(f" {counts}")
sys.exit(0)

# Update mode
data = save_counts(config_path, counts)

print("Component counts updated:")
print(f" Agents: {counts['agents']}")
print(f" Commands: {counts['commands']}")
print(f" Skills: {counts['skills']}")
print(f" Scripts: {counts['scripts']}")
print(f" Hooks: {counts['hooks']}")
print(f" Prompts: {counts['prompts']}")
print(f" Workflows: {counts['workflows']}")
print(f" ─────────────────────")
print(f" Total: {counts['total']}")
print(f"\nSaved to: {config_path}")

if name == "main": main()