Architecture System Design - aku11i/phantom GitHub Wiki
This document describes Phantom's overall architecture, design patterns, and architectural decisions.
Phantom follows a layered architecture with clear separation of concerns:
graph TD
subgraph "User Interface Layer"
CLI[CLI Entry Point]
Commands[Command Parser]
end
subgraph "Application Layer"
Handlers[Command Handlers]
Output[Output Formatting]
ErrorHandler[Error Handling]
end
subgraph "Domain Layer"
Worktree[Worktree Domain]
Git[Git Domain]
Process[Process Domain]
end
subgraph "Infrastructure Layer"
GitCLI[Git CLI Wrapper]
FileSystem[File System]
Shell[Shell Execution]
end
CLI --> Commands
Commands --> Handlers
Handlers --> Output
Handlers --> ErrorHandler
Handlers --> Worktree
Handlers --> Process
Worktree --> Git
Process --> Shell
Git --> GitCLI
style CLI fill:#f9f,stroke:#333
style Handlers fill:#9ff,stroke:#333
style Worktree fill:#ff9,stroke:#333
style GitCLI fill:#9f9,stroke:#333
Source: CLAUDE.md#L26-L31
Each module has exactly one reason to change:
- CLI Handlers: Only change when command interface changes
- Core Logic: Only change when business rules change
- Git Operations: Only change when Git integration changes
- Process Management: Only change when execution model changes
High-level modules don't depend on low-level modules:
CLI Layer → Core Layer → Infrastructure
↖ ↙
Interfaces
The architecture is open for extension but closed for modification:
- New commands can be added without changing existing code
- New Git operations can be added to the executor
- Output formats can be extended
Modules depend only on interfaces they use:
- CLI handlers don't know about Git internals
- Core logic doesn't know about console output
- Git operations don't know about CLI commands
Source: src/core/types/result.ts
Functional error handling without exceptions:
type Result<T, E = Error> =
| { ok: true; value: T }
| { ok: false; error: E }
Benefits:
- Explicit error handling
- Type-safe error propagation
- Composable operations
- No hidden control flow
Usage example:
function createWorktree(name: string): Result<WorktreeInfo> {
const validation = validateName(name);
if (!validation.ok) {
return validation;
}
const creation = git.addWorktree(name);
if (!creation.ok) {
return creation;
}
return Result.ok(creation.value);
}
Each CLI command is encapsulated as a handler:
interface CommandHandler {
execute(args: ParsedArgs): Promise<void>;
}
Located in: src/cli/handlers/
Benefits:
- Decoupled command logic
- Easy to test
- Simple to add new commands
- Consistent interface
Git operations are wrapped in a simplified interface:
Source: src/core/git/executor.ts
class GitExecutor {
execute(args: string[]): Result<string>;
}
This hides the complexity of:
- Process spawning
- Error handling
- Output parsing
- Environment setup
Different execution strategies for processes:
Source: src/core/process/
-
exec.ts
: One-time command execution -
shell.ts
: Interactive shell sessions -
spawn.ts
: Core spawning logic
Responsibilities:
- Parse command-line arguments
- Route to appropriate handlers
- Format output for users
- Handle process exit codes
Key Components:
- Command handlers
- Output formatter
- Error handler
Design Decisions:
- No business logic
- Thin orchestration layer
- Delegates to core
Responsibilities:
- Business logic implementation
- Domain model management
- Validation rules
- Core algorithms
Key Modules:
Source: src/core/worktree/
- Manages phantom lifecycle
- Validates phantom names
- Coordinates Git operations
Source: src/core/git/
- Wraps Git CLI commands
- Parses Git output
- Handles Git errors
Source: src/core/process/
- Spawns child processes
- Manages process lifecycle
- Handles I/O streams
Responsibilities:
- System interaction
- External tool integration
- I/O operations
Components:
- File system access
- Process spawning
- Git CLI execution
sequenceDiagram
participant User
participant CLI
participant Handler
participant Core
participant Git
participant FS
User->>CLI: phantom create feature
activate CLI
CLI->>Handler: route(command, args)
activate Handler
Handler->>Core: validateWorktreeName(name)
activate Core
Core-->>Handler: Result<void>
deactivate Core
alt Validation Failed
Handler->>CLI: formatError(error)
CLI->>User: Display error
else Validation Passed
Handler->>Core: createWorktree(name, branch)
activate Core
Core->>Git: getCurrentBranch()
activate Git
Git->>FS: git branch --show-current
FS-->>Git: branch name
Git-->>Core: Result<string>
deactivate Git
Core->>Git: addWorktree(name, branch)
activate Git
Git->>FS: git worktree add
FS-->>Git: success
Git-->>Core: Result<WorktreeInfo>
deactivate Git
Core-->>Handler: Result<WorktreeInfo>
deactivate Core
Handler->>CLI: formatSuccess(info)
CLI->>User: Display success
end
deactivate Handler
deactivate CLI
graph TB
A[Git Command Fails] --> B[Create Error Result]
B --> C[Propagate to Core]
C --> D[Transform to Domain Error]
D --> E[Return to Handler]
E --> F[Format User Message]
F --> G[Set Exit Code]
G --> H[Display to User]
style A fill:#f99
style H fill:#9f9
graph TD
subgraph "Entry Points"
bin[bin/phantom.ts]
end
subgraph "CLI Layer"
handlers[handlers/*]
output[output.ts]
errors[errors.ts]
end
subgraph "Core Layer"
worktree[worktree/*]
git[git/*]
process[process/*]
paths[paths.ts]
types[types/*]
end
bin --> handlers
handlers --> output
handlers --> errors
handlers --> worktree
handlers --> process
worktree --> git
worktree --> paths
worktree --> types
process --> types
git --> types
style bin fill:#f9f
style handlers fill:#9ff
style worktree fill:#ff9
- No Upward Dependencies: Core never imports from CLI
- Interface Boundaries: Modules communicate through defined interfaces
-
Shared Types: Common types in
types/
module - No Circular Dependencies: Enforced by module structure
Phantom is designed to be stateless:
- No daemon processes
- No persistent configuration
- No background services
- Each invocation is independent
Benefits:
- Simple mental model
- Easy to reason about
- No state corruption
- Parallel execution safe
All state is stored in Git:
- Worktree information in
.git/worktrees/
- Branch information in Git refs
- No custom state files
-
Validation Errors
- Invalid phantom names
- Missing arguments
- Invalid branches
-
Git Errors
- Repository not found
- Branch doesn't exist
- Worktree conflicts
-
System Errors
- Permission denied
- Disk full
- Process failures
graph LR
A[Operation] --> B{Recoverable?}
B -->|Yes| C[Retry/Suggest Fix]
B -->|No| D[Clean Failure]
C --> E[User Action]
D --> F[Exit with Code]
E --> A
- Zero Dependencies: Fast startup
- Bundled Executable: Single file distribution
- Lazy Loading: Load only what's needed
- Direct Git CLI: No abstraction overhead
- O(n) operations for n worktrees
- No exponential algorithms
- Efficient Git command usage
- Minimal memory footprint
All user input is validated:
function validateWorktreeName(name: string): Result<void> {
// Check for path traversal
if (name.includes('..') || name.includes('/')) {
return Result.error(new Error('Invalid characters'));
}
// Additional validation...
}
Safe command execution:
- No shell interpolation by default
- Explicit shell mode for interactive sessions
- Controlled environment variables
-
Plugin System
- Command plugins
- Output format plugins
- Git provider plugins
-
Configuration System
- User preferences
- Repository settings
- Global configuration
-
Event System
- Lifecycle hooks
- Progress reporting
- Telemetry
These must be maintained:
- Zero runtime dependencies
- Stateless operation
- Git as single source of truth
- Clean layer separation
- Functional error handling
Phantom's architecture is designed for:
- Simplicity: Easy to understand and maintain
- Performance: Fast execution with minimal overhead
- Reliability: Predictable behavior and error handling
- Extensibility: Easy to add new features
- Testability: Clear boundaries and pure functions
The layered architecture with functional patterns creates a robust, maintainable system that enhances Git workflow productivity.