Project Manager
The Project Manager service orchestrates the complete project initialization workflow for SpecifyX. It coordinates multiple services to create, configure, and set up projects with dynamic template rendering, AI assistant integration, and comprehensive validation.
Overview
The service acts as the primary orchestrator for project operations, integrating:
- Template Service: For dynamic Jinja2 template rendering
- Config Service: For TOML-based configuration management
- Git Service: For repository initialization and branch management
- Validation: For project name and directory validation
Key Features
Project Initialization Orchestration
- Complete project setup workflow with error handling and rollback
- Dynamic template rendering based on AI assistant selection
- Flexible project structure creation with platform-specific paths
- Git repository initialization with branch creation
- Configuration management with user preferences
Service Coordination
- Dependency injection with configurable service instances
- Graceful fallbacks to default service implementations
- Coordinated error handling across all services
- Transaction-like behavior with cleanup on failure
Template and AI Integration
- AI-aware template folder mappings (claude, gemini, copilot, etc.)
- Dynamic template selection based on assistant choice
- Cross-platform script generation with proper permissions
- Template context enrichment with project metadata
Core Class
ProjectManager
class ProjectManager:
def __init__(
self,
config_service: Optional[ConfigService] = None,
git_service: Optional[GitService] = None,
folder_mappings: Optional[List[TemplateFolderMapping]] = None,
template_service: Optional["JinjaTemplateService"] = None,
)
The ProjectManager accepts optional service dependencies, using defaults if not provided:
- Creates
TomlConfigService
for configuration management - Creates
CommandLineGitService
for git operations - Creates
JinjaTemplateService
for template rendering
Project Initialization
Primary Workflow
options = ProjectInitOptions(
project_name="my-project",
ai_assistant="claude",
use_current_dir=False,
skip_git=False,
branch_naming_config=BranchNamingConfig()
)
result = project_manager.initialize_project(options)
if result.success:
print(f"Project created at: {result.project_path}")
print(f"Completed steps: {result.completed_steps}")
else:
print(f"Error: {result.error_message}")
Initialization Steps
The initialization process follows these ordered steps:
- VALIDATION - Validate project name and directory
- DIRECTORY_CREATION - Create project directory (if needed)
- STRUCTURE_SETUP - Create basic project structure
- GIT_INIT - Initialize git repository (unless skipped)
- CONFIG_SAVE - Save project configuration
- TEMPLATE_RENDER - Render all templates with dynamic mappings
- BRANCH_CREATION - Create initial git branch
- FINALIZATION - Complete initialization
Project Structure Creation
# Dynamic structure based on AI assistant
basic_dirs = PATH_DEFAULTS.get_project_structure_paths(ai_assistant)
# AI-specific examples:
# claude: [".claude", ".claude/commands", ".specify", ".specify/scripts", ".specify/templates"]
# gemini: [".gemini", ".gemini/commands", ".specify", ".specify/scripts", ".specify/templates"]
Template Folder Mappings
Dynamic Mapping Generation
def _get_default_folder_mappings(self, ai_assistant: str = "claude") -> List[TemplateFolderMapping]:
"""Get default folder mappings for the specified AI assistant"""
results = PATH_DEFAULTS.get_folder_mappings(ai_assistant)
mappings = []
for result in results:
exec_extensions = (
PATH_DEFAULTS.EXECUTABLE_EXTENSIONS
if result.category == "scripts"
else []
)
mappings.append(TemplateFolderMapping(
source=result.source_path,
target_pattern=result.target_path,
render=result.should_render,
executable_extensions=exec_extensions,
))
return mappings
Folder Categories
- Commands: AI-specific command templates (
.claude/commands/
,.gemini/commands/
) - Scripts: Cross-platform Python scripts (
.specify/scripts/
) - Memory: AI context and memory templates
- Runtime Templates: Templates for runtime use (
.specify/templates/
)
Template Context Enrichment
Context Creation
context = TemplateContext(
project_name=options.project_name or project_path.name,
ai_assistant=options.ai_assistant,
project_path=project_path,
branch_naming_config=options.branch_naming_config or BranchNamingConfig(),
)
render_result = self._render_all_templates(context)
The ProjectManager enriches template context with:
- Project metadata (name, path, creation date)
- AI assistant configuration
- Branch naming patterns and validation rules
- Platform-specific variables
- Git repository information
Validation and Error Handling
Project Validation
def validate_project_name(self, name: str) -> tuple[bool, Optional[str]]:
"""Validate project name using Validators infrastructure"""
try:
Validators.project_name(name)
return True, None
except ValidationError as e:
return False, str(e)
def validate_project_directory(self, project_path: Path, use_current_dir: bool) -> tuple[bool, Optional[str]]:
"""Validate project directory with comprehensive checks"""
# Handles existing projects, empty directories, permission checks
Cleanup on Failure
def cleanup_failed_init(self, project_path: Path, completed_steps: List[ProjectInitStep]) -> bool:
"""Clean up after failed initialization"""
# Removes created directories, files, and configurations
# Only removes what was created during this initialization
Project Management Operations
Project Information
def get_project_info(self, project_path: Path) -> Optional[Dict[str, Any]]:
"""Get comprehensive project information"""
return {
"name": config.name,
"ai_assistant": config.template_settings.ai_assistant,
"branch_naming": config.branch_naming.to_dict(),
"template_settings": config.template_settings.to_dict(),
"path": str(project_path),
"initialized": (project_path / ".specify").exists(),
}
Project Detection
def is_project_initialized(self, project_path: Path) -> bool:
"""Check if directory is already initialized as SpecifyX project"""
return (project_path / ".specify").exists()
Configuration Management
def configure_branch_naming(self, project_path: Path, interactive: bool = False) -> bool:
"""Configure branch naming patterns for existing projects"""
# Loads existing config or creates default
# Supports both interactive and automated configuration
Cross-Platform Support
Cross-Platform Initialization
def initialize_cross_platform_project(self, options: ProjectInitOptions) -> bool:
"""Initialize project with cross-platform compatibility"""
# Handles platform-specific path separators
# Generates appropriate executable permissions
# Creates platform-aware directory structures
Platform Considerations
- Windows executable extensions (
.bat
,.cmd
,.ps1
) - Unix executable permissions (
chmod +x
) - Path separator normalization
- Cross-platform script generation
Integration Examples
Basic Project Creation
from specify_cli.services.project_manager import ProjectManager
from specify_cli.models.project import ProjectInitOptions
manager = ProjectManager()
options = ProjectInitOptions(
project_name="web-app",
ai_assistant="claude",
use_current_dir=False,
skip_git=False
)
result = manager.initialize_project(options)
if result.success:
print(f"Project created: {result.project_path}")
for step in result.completed_steps:
print(f"✓ {step.value}")
else:
print(f"Failed: {result.error_message}")
Custom Service Configuration
from specify_cli.services.config_service import TomlConfigService
from specify_cli.services.git_service import CommandLineGitService
from specify_cli.services.template_service import JinjaTemplateService
# Custom service instances
config_service = TomlConfigService()
git_service = CommandLineGitService()
template_service = JinjaTemplateService()
manager = ProjectManager(
config_service=config_service,
git_service=git_service,
template_service=template_service
)
Existing Project Migration
def migrate_existing_project(self, project_path: Path) -> bool:
"""Migrate existing project to SpecifyX structure"""
# Checks if already migrated
# Creates basic structure with default AI assistant
# Saves default configuration
Error Scenarios and Recovery
Common Error Conditions
- Directory not empty: When creating new project in existing directory
- Already initialized: When project already has
.specify
directory - Git initialization failed: When git is not available or repository exists
- Template rendering failed: When template syntax errors or missing variables
- Permission denied: When insufficient permissions for directory/file creation
Recovery Mechanisms
- Atomic operations: Either complete success or full rollback
- Step tracking: Precise tracking of completed steps for cleanup
- Warning collection: Non-fatal issues collected as warnings
- Graceful fallbacks: Continue with warnings when possible
Integration Points
The Project Manager integrates with:
- CLI Commands: Primary interface for
specify init
command - Template Service: For dynamic template rendering and file generation
- Config Service: For project and global configuration management
- Git Service: For repository initialization and branch management
- Validation System: For comprehensive input validation
Factory Pattern
# Use existing service instances or create defaults
def create_project_manager() -> ProjectManager:
"""Factory function for creating ProjectManager with defaults"""
return ProjectManager()
# Or with custom services
def create_custom_project_manager(services: dict) -> ProjectManager:
"""Factory function with custom service configuration"""
return ProjectManager(**services)
Performance Considerations
- Lazy service initialization: Services created only when needed
- Template caching: Templates loaded once and cached by service
- Efficient file operations: Batch operations where possible
- Resource cleanup: Proper cleanup of temporary resources
- Validation optimization: Fail-fast validation before expensive operations
The Project Manager provides a robust, extensible foundation for SpecifyX project initialization with comprehensive error handling, cross-platform compatibility, and seamless service integration.