scripts-coditect-bootstrap-projects
#!/usr/bin/env python3 """
title: "============================================================================" component_type: script version: "1.0.0" audience: contributor status: stable summary: "AZ1.AI CODITECT Project Bootstrap Script" keywords: ['analysis', 'api', 'automation', 'backend', 'bootstrap'] tokens: ~500 created: 2025-12-22 updated: 2025-12-22 script_name: "coditect-bootstrap-projects.py" language: python executable: true usage: "python3 scripts/coditect-bootstrap-projects.py [options]" python_version: "3.10+" dependencies: [] modifies_files: false network_access: false requires_auth: false
AZ1.AI CODITECT Project Bootstrap Script
Copyright © 2025 AZ1.AI INC. All Rights Reserved. Developed by Hal Casteel, Founder/CEO/CTO, AZ1.AI INC.
Purpose: Automated setup of all CODITECT rollout projects
- Creates project directories
- Initializes Git repositories
- Creates GitHub repositories
- Applies CODITECT methodology (PROJECT-PLAN.md, TASKLIST.md, etc.)
- Sets up standard structure """
import os import sys import subprocess from pathlib import Path from typing import List, Dict, Any, Optional import json import logging import shutil from datetime import datetime import time
============================================================================
EXCEPTION HIERARCHY
============================================================================
class BootstrapError(Exception): """Base exception for bootstrap errors.""" pass
class PrerequisiteError(BootstrapError): """Raised when prerequisites are not met.""" pass
class GitOperationError(BootstrapError): """Raised when git operation fails.""" pass
class GitHubOperationError(BootstrapError): """Raised when GitHub operation fails.""" pass
class TemplateGenerationError(BootstrapError): """Raised when template generation fails.""" pass
class DirectoryCreationError(BootstrapError): """Raised when directory creation fails.""" pass
============================================================================
LOGGING CONFIGURATION
============================================================================
def setup_logging(log_file: Path = None, verbose: bool = False) -> logging.Logger: """ Configure dual logging (file + stdout) with production formatting.
Args:
log_file: Path to log file (default: logs/bootstrap_TIMESTAMP.log)
verbose: Enable debug-level logging
Returns:
Configured logger instance
"""
# Create logs directory if needed
if log_file is None:
log_dir = Path.cwd() / "logs"
log_dir.mkdir(exist_ok=True)
log_file = log_dir / f"bootstrap_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log"
# Configure root logger
logger = logging.getLogger("coditect_bootstrap")
logger.setLevel(logging.DEBUG if verbose else logging.INFO)
# File handler - detailed logs
file_handler = logging.FileHandler(log_file, mode='w', encoding='utf-8')
file_handler.setLevel(logging.DEBUG)
file_formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
file_handler.setFormatter(file_formatter)
# Console handler - user-friendly output
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setLevel(logging.INFO)
console_formatter = logging.Formatter('%(levelname)s: %(message)s')
console_handler.setFormatter(console_formatter)
logger.addHandler(file_handler)
logger.addHandler(console_handler)
logger.info(f"Logging initialized: {log_file}")
return logger
============================================================================
TERMINAL COLORS
============================================================================
Shared Colors module (consolidates 36 duplicate definitions)
_script_dir = Path(file).parent sys.path.insert(0, str(_script_dir / "core")) from colors import Colors
============================================================================
EXIT CODES
============================================================================
class ExitCode: """Standardized exit codes.""" SUCCESS = 0 GENERAL_ERROR = 1 PREREQUISITE_ERROR = 2 GIT_ERROR = 3 GITHUB_ERROR = 4 TEMPLATE_ERROR = 5
============================================================================
PROJECT DEFINITIONS
============================================================================
PROJECTS = [ { "name": "coditect-cloud-backend", "description": "FastAPI backend for CODITECT Cloud Platform - user lifecycle, licensing, and API services", "type": "backend", "tech_stack": ["Python", "FastAPI", "PostgreSQL", "Redis", "Celery"], "github_org": "coditect-ai", "private": True, "topics": ["fastapi", "python", "saas", "user-management", "licensing"] }, { "name": "coditect-cloud-frontend", "description": "React frontend for CODITECT Cloud Platform - user portal and admin dashboard", "type": "frontend", "tech_stack": ["React", "TypeScript", "TailwindCSS", "Vite"], "github_org": "coditect-ai", "private": True, "topics": ["react", "typescript", "tailwindcss", "admin-dashboard", "user-portal"] }, { "name": "coditect-cli", "description": "Command-line tools for CODITECT - setup, Git automation, and project management", "type": "cli", "tech_stack": ["Python", "Click", "Rich", "GitPython"], "github_org": "coditect-ai", "private": False, "topics": ["cli", "python", "git-automation", "developer-tools"] }, { "name": "coditect-docs", "description": "Official CODITECT documentation site - guides, tutorials, and API reference", "type": "docs", "tech_stack": ["Docusaurus", "Markdown", "Mermaid"], "github_org": "coditect-ai", "private": False, "topics": ["documentation", "docusaurus", "guides", "tutorials"] }, { "name": "coditect-agent-marketplace", "description": "Marketplace for discovering and sharing specialized AI agents", "type": "fullstack", "tech_stack": ["Next.js", "TypeScript", "PostgreSQL", "Algolia"], "github_org": "coditect-ai", "private": True, "topics": ["nextjs", "marketplace", "ai-agents", "discovery"] }, { "name": "coditect-analytics", "description": "Analytics and monitoring platform - usage tracking and business intelligence", "type": "backend", "tech_stack": ["Python", "ClickHouse", "Grafana", "Prometheus"], "github_org": "coditect-ai", "private": True, "topics": ["analytics", "monitoring", "grafana", "prometheus", "clickhouse"] }, { "name": "coditect-infrastructure", "description": "Infrastructure as Code - Terraform, CI/CD, and deployment automation", "type": "infrastructure", "tech_stack": ["Terraform", "Docker", "GitHub Actions", "GCP"], "github_org": "coditect-ai", "private": True, "topics": ["terraform", "devops", "gcp", "infrastructure-as-code", "ci-cd"] }, { "name": "coditect-legal", "description": "Legal documents and compliance tracking - EULA, NDA, privacy policy, etc.", "type": "docs", "tech_stack": ["Markdown", "PDF"], "github_org": "coditect-ai", "private": True, "topics": ["legal", "compliance", "eula", "nda", "privacy-policy"] } ]
============================================================================
MAIN CLASS
============================================================================
class ProjectBootstrapper: """Bootstrap all CODITECT rollout projects with production-grade error handling."""
def __init__(self, workspace_dir: Path = None, logger: logging.Logger = None):
"""
Initialize bootstrapper.
Args:
workspace_dir: Root workspace directory (default: ~/PROJECTS)
logger: Logger instance (will create if not provided)
"""
self.workspace_dir = workspace_dir or Path.home() / "PROJECTS"
self.coditect_dir = self.workspace_dir / ".coditect"
self.logger = logger or setup_logging()
self.created_projects: List[str] = []
self.failed_projects: List[tuple] = [] # (name, error)
def check_prerequisites(self) -> None:
"""
Verify all prerequisites are met.
Raises:
PrerequisiteError: If prerequisites not met
"""
self.logger.info("Checking prerequisites...")
# Check workspace directory exists
if not self.workspace_dir.exists():
raise PrerequisiteError(f"Workspace directory does not exist: {self.workspace_dir}")
# Check .coditect directory exists
if not self.coditect_dir.exists():
self.logger.warning(f".coditect directory not found at {self.coditect_dir}")
self.logger.info("Continuing without CODITECT templates")
# Check git installed
try:
result = self.run_command(['git', '--version'], check_output=False)
self.logger.debug(f"Git version: {result.stdout.strip()}")
except (subprocess.CalledProcessError, FileNotFoundError):
raise PrerequisiteError("Git is not installed or not in PATH")
# Check git configured
try:
self.run_command(['git', 'config', 'user.name'], check_output=False)
self.run_command(['git', 'config', 'user.email'], check_output=False)
except subprocess.CalledProcessError:
raise PrerequisiteError(
"Git user.name and user.email not configured. "
"Run: git config --global user.name 'Your Name' && "
"git config --global user.email 'you@example.com'"
)
# Check GitHub CLI (optional but recommended)
try:
result = self.run_command(['gh', '--version'], check_output=False)
self.logger.debug(f"GitHub CLI version: {result.stdout.strip()}")
except (subprocess.CalledProcessError, FileNotFoundError):
self.logger.warning("GitHub CLI (gh) not installed - repository creation will be skipped")
self.logger.info("Install with: brew install gh")
self.logger.info(f"{Colors.GREEN}✓ Prerequisites verified{Colors.END}")
def run_command(
self,
cmd: List[str],
cwd: Path = None,
check: bool = True,
check_output: bool = True,
timeout: int = 30,
retry_count: int = 3,
retry_delay: int = 2
) -> subprocess.CompletedProcess:
"""
Execute shell command with retry logic.
Args:
cmd: Command and arguments as list
cwd: Working directory (default: workspace_dir)
check: Raise exception on non-zero exit
check_output: Capture stdout/stderr
timeout: Command timeout in seconds
retry_count: Number of retry attempts for network operations
retry_delay: Delay between retries in seconds
Returns:
CompletedProcess instance
Raises:
subprocess.CalledProcessError: If check=True and command fails
subprocess.TimeoutExpired: If command exceeds timeout
"""
for attempt in range(1, retry_count + 1):
try:
self.logger.debug(f"Running command (attempt {attempt}/{retry_count}): {' '.join(cmd)}")
result = subprocess.run(
cmd,
cwd=cwd or self.workspace_dir,
check=check,
capture_output=check_output,
text=True,
timeout=timeout
)
if check_output and result.stdout:
self.logger.debug(f"Command output: {result.stdout.strip()}")
return result
except subprocess.TimeoutExpired as e:
self.logger.error(f"Command timed out after {timeout}s: {' '.join(cmd)}")
if attempt >= retry_count:
raise
self.logger.info(f"Retrying in {retry_delay}s...")
time.sleep(retry_delay)
except subprocess.CalledProcessError as e:
self.logger.error(f"Command failed: {' '.join(cmd)}")
self.logger.error(f"Exit code: {e.returncode}")
if e.stderr:
self.logger.error(f"Error output: {e.stderr.strip()}")
# Retry for network-related operations
if any(term in ' '.join(cmd) for term in ['clone', 'push', 'pull', 'fetch']) and attempt < retry_count:
self.logger.info(f"Network operation failed, retrying in {retry_delay}s...")
time.sleep(retry_delay)
continue
if check:
raise
return e
# Should not reach here, but just in case
raise subprocess.CalledProcessError(1, cmd, "Max retries exceeded")
def create_project_directory(self, project: Dict[str, Any]) -> Path:
"""
Create project directory.
Args:
project: Project configuration dictionary
Returns:
Path to created project directory
Raises:
DirectoryCreationError: If directory creation fails
"""
try:
project_dir = self.workspace_dir / project["name"]
if project_dir.exists():
self.logger.warning(f"Directory already exists: {project_dir}")
return project_dir
project_dir.mkdir(parents=True, exist_ok=True)
self.logger.info(f"{Colors.GREEN}✓ Created directory: {project_dir}{Colors.END}")
return project_dir
except Exception as e:
raise DirectoryCreationError(f"Failed to create directory {project['name']}: {e}")
def initialize_git(self, project_dir: Path, project: Dict[str, Any]) -> None:
"""
Initialize Git repository.
Args:
project_dir: Project directory path
project: Project configuration
Raises:
GitOperationError: If git initialization fails
"""
try:
if (project_dir / ".git").exists():
self.logger.info("Git repository already initialized")
return
self.run_command(['git', 'init'], cwd=project_dir)
self.run_command(['git', 'branch', '-m', 'main'], cwd=project_dir)
self.logger.info(f"{Colors.GREEN}✓ Git initialized{Colors.END}")
except subprocess.CalledProcessError as e:
raise GitOperationError(f"Git initialization failed: {e}")
def create_gitignore(self, project_dir: Path, project: Dict[str, Any]) -> None:
"""
Create .gitignore from CODITECT template.
Args:
project_dir: Project directory path
project: Project configuration
Raises:
TemplateGenerationError: If gitignore creation fails
"""
try:
template_path = self.coditect_dir / "templates" / "gitignore-universal-template"
gitignore_path = project_dir / ".gitignore"
if gitignore_path.exists():
self.logger.info(".gitignore already exists")
return
if template_path.exists():
shutil.copy(template_path, gitignore_path)
self.logger.info(f"{Colors.GREEN}✓ Created .gitignore from template{Colors.END}")
else:
self.logger.warning("Template not found, creating basic .gitignore")
self._create_basic_gitignore(gitignore_path, project)
except Exception as e:
raise TemplateGenerationError(f".gitignore creation failed: {e}")
def _create_basic_gitignore(self, gitignore_path: Path, project: Dict[str, Any]) -> None:
"""Create basic .gitignore when template not available."""
content = """# Python
pycache/ *.py[cod] *$py.class *.so .Python venv/ env/
Node
node_modules/ npm-debug.log*
IDEs
.vscode/ .idea/ *.swp
OS
.DS_Store Thumbs.db
Build
dist/ build/
Logs
*.log
Environment
.env .env.local """ gitignore_path.write_text(content) self.logger.info(f"{Colors.GREEN}✓ Created basic .gitignore{Colors.END}")
def create_project_plan(self, project_dir: Path, project: Dict[str, Any]) -> None:
"""
Create PROJECT-PLAN.md.
Args:
project_dir: Project directory path
project: Project configuration
Raises:
TemplateGenerationError: If creation fails
"""
try:
plan_path = project_dir / "PROJECT-PLAN.md"
if plan_path.exists():
self.logger.info("PROJECT-PLAN.md already exists")
return
content = f"""# {project['name']} - Project Plan
Copyright © 2025 AZ1.AI INC. All Rights Reserved. Developed by Hal Casteel, Founder/CEO/CTO, AZ1.AI INC.
Project Type: {project['type'].title()} Status: Phase 1 - Discovery & Planning Last Updated: {datetime.now().strftime('%Y-%m-%d')}
Executive Summary
Problem: [Define the problem this project solves]
Solution: {project['description']}
Tech Stack: {', '.join(project['tech_stack'])}
Phase 1: Discovery & Validation
Value Proposition
[Define the value proposition for this component]
Ideal Customer Profile (ICP)
Internal Users:
- AZ1.AI development team
- Platform administrators
- Support team
External Users (if applicable):
- CODITECT platform users
- Third-party developers
- Integration partners
Competitive Analysis
[Analyze similar tools/platforms if applicable]
Phase 2: Strategy & Planning
Technical Architecture
[C4 diagrams go here]
Technology Stack
{chr(10).join(f"- {tech}: [Purpose/justification]" for tech in project['tech_stack'])}
Architecture Decision Records (ADRs)
- [Create ADRs for key decisions]
Phase 3: Execution & Delivery
Development Roadmap
Sprint 1 (Weeks 1-2):
- Project setup and infrastructure
- [Additional tasks]
Sprint 2 (Weeks 3-4):
- Core functionality
- [Additional tasks]
[Add more sprints as needed]
Success Metrics
Technical Metrics:
- [Define technical success criteria]
Business Metrics:
- [Define business success criteria]
Next Steps
Immediate Actions
- Complete this project plan
- Create TASKLIST.md
- Create ADRs for key decisions
- Design architecture (C4 diagrams)
Copyright © 2025 AZ1.AI INC. All Rights Reserved. """
plan_path.write_text(content)
self.logger.info(f"{Colors.GREEN}✓ Created PROJECT-PLAN.md{Colors.END}")
except Exception as e:
raise TemplateGenerationError(f"PROJECT-PLAN.md creation failed: {e}")
def create_tasklist(self, project_dir: Path, project: Dict[str, Any]) -> None:
"""
Create TASKLIST.md.
Args:
project_dir: Project directory path
project: Project configuration
Raises:
TemplateGenerationError: If creation fails
"""
try:
tasklist_path = project_dir / "TASKLIST.md"
if tasklist_path.exists():
self.logger.info("TASKLIST.md already exists")
return
content = f"""# {project['name']} - Task List
Copyright © 2025 AZ1.AI INC. All Rights Reserved. Status: Planning Last Updated: {datetime.now().strftime('%Y-%m-%d')}
Phase 1: Setup & Planning ⏳
- Create project directory
- Initialize Git repository
- Create PROJECT-PLAN.md
- Create TASKLIST.md
- Complete project plan (fill in details)
- Create C4 architecture diagrams
- Write ADRs for key decisions
- Setup CI/CD pipeline
- Create README.md
Status: IN PROGRESS (20% complete)
Phase 2: Development 📝
Sprint 1 (Weeks 1-2)
- [Task 1]
- [Task 2]
- [Task 3]
Sprint 2 (Weeks 3-4)
- [Task 1]
- [Task 2]
- [Task 3]
Status: PENDING
Phase 3: Testing & Deployment 🚀
- Unit tests
- Integration tests
- Documentation
- Deployment to staging
- Beta testing
- Deployment to production
Status: PENDING
Blockers & Risks
Current Blockers:
- None
Identified Risks:
- [Risk 1]
- Mitigation: [Strategy]
- Status: [Monitoring/Resolved]
Last Review: {datetime.now().strftime('%Y-%m-%d')} Next Review: [Date] Project Lead: Hal Casteel (CEO/CTO) """
tasklist_path.write_text(content)
self.logger.info(f"{Colors.GREEN}✓ Created TASKLIST.md{Colors.END}")
except Exception as e:
raise TemplateGenerationError(f"TASKLIST.md creation failed: {e}")
def create_readme(self, project_dir: Path, project: Dict[str, Any]) -> None:
"""
Create README.md.
Args:
project_dir: Project directory path
project: Project configuration
Raises:
TemplateGenerationError: If creation fails
"""
try:
readme_path = project_dir / "README.md"
if readme_path.exists():
self.logger.info("README.md already exists")
return
visibility = "🔒 Private" if project['private'] else "🌐 Public"
content = f"""# {project['name']}
{visibility}
{project['description']}
Copyright © 2025 AZ1.AI INC. All Rights Reserved. Developed by Hal Casteel, Founder/CEO/CTO, AZ1.AI INC.
Overview
[Project overview goes here]
Tech Stack
{chr(10).join(f"- {tech}" for tech in project['tech_stack'])}
Getting Started
Prerequisites
[List prerequisites]
Installation
# Installation steps
Configuration
[Configuration instructions]
Development
Setup Development Environment
# Development setup
Running Locally
# Run commands
Running Tests
# Test commands
Deployment
[Deployment instructions]
Documentation
Contributing
This is a private repository for AZ1.AI INC. internal development.
License
Copyright © 2025 AZ1.AI INC. All Rights Reserved.
Proprietary and confidential. Unauthorized copying, distribution, or use is strictly prohibited.
Built with Excellence by AZ1.AI CODITECT
Systematic Development. Continuous Context. Exceptional Results. """
readme_path.write_text(content)
self.logger.info(f"{Colors.GREEN}✓ Created README.md{Colors.END}")
except Exception as e:
raise TemplateGenerationError(f"README.md creation failed: {e}")
def create_github_repo(self, project: Dict[str, Any]) -> bool:
"""
Create GitHub repository using gh CLI.
Args:
project: Project configuration
Returns:
True if successful, False otherwise
Raises:
GitHubOperationError: If creation fails critically
"""
repo_name = f"{project['github_org']}/{project['name']}"
visibility = "--private" if project['private'] else "--public"
self.logger.info(f"Creating GitHub repository: {repo_name}")
try:
# Check if gh CLI is installed
self.run_command(['gh', '--version'], check_output=False)
except (subprocess.CalledProcessError, FileNotFoundError):
self.logger.warning("GitHub CLI (gh) not installed - skipping repository creation")
return False
# Create repository
try:
self.run_command(
['gh', 'repo', 'create', repo_name,
visibility,
'--description', project['description'],
'--confirm'],
retry_count=2
)
self.logger.info(f"{Colors.GREEN}✓ Created GitHub repository: {repo_name}{Colors.END}")
# Add topics
if project.get('topics'):
topics_str = ' '.join(project['topics'])
try:
self.run_command(
['gh', 'repo', 'edit', repo_name, '--add-topic', topics_str],
check=False
)
self.logger.info(f"{Colors.GREEN}✓ Added topics: {topics_str}{Colors.END}")
except subprocess.CalledProcessError:
self.logger.warning("Failed to add topics (non-critical)")
return True
except subprocess.CalledProcessError as e:
if e.stderr and "already exists" in e.stderr:
self.logger.warning(f"Repository already exists: {repo_name}")
return True
else:
self.logger.error(f"Failed to create repository: {e.stderr if e.stderr else str(e)}")
return False
def setup_remote(self, project_dir: Path, project: Dict[str, Any]) -> None:
"""
Setup GitHub remote.
Args:
project_dir: Project directory path
project: Project configuration
Raises:
GitOperationError: If setup fails
"""
try:
remote_url = f"https://github.com/{project['github_org']}/{project['name']}.git"
# Check if remote exists
result = self.run_command(
['git', 'remote', 'get-url', 'origin'],
cwd=project_dir,
check=False
)
if result.returncode != 0:
# Add remote
self.run_command(['git', 'remote', 'add', 'origin', remote_url], cwd=project_dir)
self.logger.info(f"{Colors.GREEN}✓ Added remote: origin{Colors.END}")
else:
self.logger.info("Remote already configured")
except subprocess.CalledProcessError as e:
raise GitOperationError(f"Remote setup failed: {e}")
def initial_commit(self, project_dir: Path, project: Dict[str, Any]) -> None:
"""
Create initial commit.
Args:
project_dir: Project directory path
project: Project configuration
Raises:
GitOperationError: If commit fails
"""
try:
# Check if there are changes to commit
status_result = self.run_command(
['git', 'status', '--porcelain'],
cwd=project_dir
)
if not status_result.stdout.strip():
self.logger.info("No changes to commit")
return
# Add all files
self.run_command(['git', 'add', '.'], cwd=project_dir)
# Create commit
commit_message = f"""Initialize {project['name']}
- Add PROJECT-PLAN.md
- Add TASKLIST.md
- Add README.md
- Configure .gitignore
Copyright © 2025 AZ1.AI INC. All Rights Reserved."""
self.run_command(['git', 'commit', '-m', commit_message], cwd=project_dir)
self.logger.info(f"{Colors.GREEN}✓ Created initial commit{Colors.END}")
except subprocess.CalledProcessError as e:
# Empty commit is not an error
if "nothing to commit" in (e.stderr or ""):
self.logger.info("Nothing to commit")
else:
raise GitOperationError(f"Initial commit failed: {e}")
def push_to_github(self, project_dir: Path, project: Dict[str, Any]) -> None:
"""
Push to GitHub.
Args:
project_dir: Project directory path
project: Project configuration
Raises:
GitOperationError: If push fails
"""
try:
self.run_command(
['git', 'push', '-u', 'origin', 'main'],
cwd=project_dir,
retry_count=2
)
self.logger.info(f"{Colors.GREEN}✓ Pushed to GitHub{Colors.END}")
except subprocess.CalledProcessError as e:
error_msg = e.stderr if e.stderr else str(e)
self.logger.warning(f"Push failed: {error_msg}")
self.logger.info("You may need to authenticate with: gh auth login")
def bootstrap_project(self, project: Dict[str, Any]) -> None:
"""
Bootstrap single project.
Args:
project: Project configuration
Raises:
BootstrapError: If bootstrap fails
"""
print(f"\n{Colors.CYAN}{Colors.BOLD}{'='*70}{Colors.END}")
print(f"{Colors.CYAN}{Colors.BOLD}Bootstrapping: {project['name']}{Colors.END}")
print(f"{Colors.CYAN}{Colors.BOLD}{'='*70}{Colors.END}\n")
try:
# Create directory
project_dir = self.create_project_directory(project)
# Initialize Git
self.initialize_git(project_dir, project)
# Create standard files
self.create_gitignore(project_dir, project)
self.create_project_plan(project_dir, project)
self.create_tasklist(project_dir, project)
self.create_readme(project_dir, project)
# Create GitHub repository
github_created = self.create_github_repo(project)
# Setup remote
if github_created:
self.setup_remote(project_dir, project)
# Initial commit
self.initial_commit(project_dir, project)
# Push to GitHub
if github_created:
self.push_to_github(project_dir, project)
self.created_projects.append(project['name'])
print(f"\n{Colors.GREEN}{Colors.BOLD}✓ Project bootstrap complete: {project['name']}{Colors.END}\n")
except Exception as e:
self.logger.error(f"Failed to bootstrap {project['name']}: {e}", exc_info=True)
self.failed_projects.append((project['name'], str(e)))
raise
def bootstrap_all(self, skip_confirmation: bool = False) -> None:
"""
Bootstrap all projects.
Args:
skip_confirmation: Skip user confirmation prompt
Raises:
BootstrapError: If critical error occurs
"""
print(f"\n{Colors.CYAN}{Colors.BOLD}╔════════════════════════════════════════════════════════════════╗{Colors.END}")
print(f"{Colors.CYAN}{Colors.BOLD}║ ║{Colors.END}")
print(f"{Colors.CYAN}{Colors.BOLD}║ AZ1.AI CODITECT Project Bootstrap v1.0 ║{Colors.END}")
print(f"{Colors.CYAN}{Colors.BOLD}║ ║{Colors.END}")
print(f"{Colors.CYAN}{Colors.BOLD}║ Setting up {len(PROJECTS)} projects for CODITECT rollout ║{Colors.END}")
print(f"{Colors.CYAN}{Colors.BOLD}║ ║{Colors.END}")
print(f"{Colors.CYAN}{Colors.BOLD}╚════════════════════════════════════════════════════════════════╝{Colors.END}\n")
print(f"{Colors.BOLD}Projects to create:{Colors.END}")
for i, project in enumerate(PROJECTS, 1):
visibility = "🔒" if project['private'] else "🌐"
print(f" {i}. {visibility} {project['name']}")
print()
if not skip_confirmation:
response = input(f"{Colors.YELLOW}Proceed with bootstrap? (y/n): {Colors.END}").strip().lower()
if response != 'y':
print(f"{Colors.YELLOW}Bootstrap cancelled{Colors.END}")
return
# Bootstrap each project
for project in PROJECTS:
try:
self.bootstrap_project(project)
except Exception as e:
self.logger.error(f"Error bootstrapping {project['name']}: {e}")
# Continue with next project
continue
# Summary
self._print_summary()
def _print_summary(self) -> None:
"""Print bootstrap summary."""
print(f"\n{Colors.GREEN}{Colors.BOLD}╔════════════════════════════════════════════════════════════════╗{Colors.END}")
print(f"{Colors.GREEN}{Colors.BOLD}║ ║{Colors.END}")
print(f"{Colors.GREEN}{Colors.BOLD}║ BOOTSTRAP COMPLETE! 🎉 ║{Colors.END}")
print(f"{Colors.GREEN}{Colors.BOLD}║ ║{Colors.END}")
print(f"{Colors.GREEN}{Colors.BOLD}╚════════════════════════════════════════════════════════════════╝{Colors.END}\n")
print(f"{Colors.BOLD}Results:{Colors.END}")
print(f" Successful: {len(self.created_projects)}/{len(PROJECTS)}")
if self.failed_projects:
print(f" {Colors.RED}Failed: {len(self.failed_projects)}{Colors.END}")
for name, error in self.failed_projects:
print(f" - {name}: {error}")
print(f"\n{Colors.BOLD}Projects created in: {self.workspace_dir}{Colors.END}\n")
if self.created_projects:
print(f"{Colors.BOLD}Next steps:{Colors.END}")
print(f" 1. Review each PROJECT-PLAN.md and fill in details")
print(f" 2. Create C4 architecture diagrams")
print(f" 3. Write ADRs for key technology decisions")
print(f" 4. Begin Sprint 1 development")
print()
============================================================================
MAIN ENTRY POINT
============================================================================
def main() -> int: """ Main entry point.
Returns:
Exit code
"""
import argparse
parser = argparse.ArgumentParser(description='CODITECT Project Bootstrap')
parser.add_argument('--workspace', '-w', type=Path, help='Workspace directory (default: ~/PROJECTS)')
parser.add_argument('--verbose', '-v', action='store_true', help='Verbose logging')
parser.add_argument('--yes', '-y', action='store_true', help='Skip confirmation prompt')
parser.add_argument('--log-file', type=Path, help='Log file path')
args = parser.parse_args()
try:
# Setup logging
logger = setup_logging(log_file=args.log_file, verbose=args.verbose)
# Create bootstrapper
bootstrapper = ProjectBootstrapper(
workspace_dir=args.workspace,
logger=logger
)
# Check prerequisites
bootstrapper.check_prerequisites()
# Bootstrap all projects
bootstrapper.bootstrap_all(skip_confirmation=args.yes)
# Return success if at least some projects succeeded
if bootstrapper.created_projects:
return ExitCode.SUCCESS
else:
return ExitCode.GENERAL_ERROR
except PrerequisiteError as e:
print(f"{Colors.RED}✗ Prerequisites not met: {e}{Colors.END}")
return ExitCode.PREREQUISITE_ERROR
except GitOperationError as e:
print(f"{Colors.RED}✗ Git operation failed: {e}{Colors.END}")
return ExitCode.GIT_ERROR
except GitHubOperationError as e:
print(f"{Colors.RED}✗ GitHub operation failed: {e}{Colors.END}")
return ExitCode.GITHUB_ERROR
except TemplateGenerationError as e:
print(f"{Colors.RED}✗ Template generation failed: {e}{Colors.END}")
return ExitCode.TEMPLATE_ERROR
except Exception as e:
print(f"{Colors.RED}✗ Unexpected error: {e}{Colors.END}")
logging.exception("Unexpected error during bootstrap")
return ExitCode.GENERAL_ERROR
if name == 'main': sys.exit(main())