Architecture Module Structure - aku11i/phantom GitHub Wiki
This document details Phantom's code organization, module dependencies, and architectural boundaries.
Source: CLAUDE.md#L17-L42
phantom/
├── src/ # Source code root
│ ├── bin/ # Entry points
│ │ └── phantom.ts # Main CLI executable
│ ├── cli/ # CLI-specific layer
│ │ ├── handlers/ # Command implementations
│ │ ├── output.ts # Console formatting
│ │ └── errors.ts # Error handling
│ └── core/ # Business logic layer
│ ├── worktree/ # Worktree operations
│ ├── git/ # Git abstractions
│ ├── process/ # Process execution
│ ├── paths.ts # Path management
│ ├── version.ts # Version info
│ └── types/ # Type definitions
├── dist/ # Build output
├── docs/ # Documentation
└── wiki/ # GitHub Wiki content
graph TB
subgraph "Entry Layer"
bin[bin/phantom.ts]
end
subgraph "CLI Layer"
handlers[cli/handlers/*]
output[cli/output.ts]
errors[cli/errors.ts]
end
subgraph "Core Layer - Domain"
worktree[core/worktree/*]
version[core/version.ts]
paths[core/paths.ts]
end
subgraph "Core Layer - Infrastructure"
git[core/git/*]
process[core/process/*]
types[core/types/*]
end
bin --> handlers
handlers --> output
handlers --> errors
handlers --> worktree
handlers --> process
handlers --> version
worktree --> git
worktree --> paths
worktree --> types
process --> types
git --> types
paths --> types
classDef entry fill:#f9f,stroke:#333,stroke-width:2px
classDef cli fill:#9ff,stroke:#333,stroke-width:2px
classDef domain fill:#ff9,stroke:#333,stroke-width:2px
classDef infra fill:#9f9,stroke:#333,stroke-width:2px
class bin entry
class handlers,output,errors cli
class worktree,version,paths domain
class git,process,types infra
Purpose: Application entry points
Files:
-
phantom.ts
- Main CLI entry point
Responsibilities:
- Bootstrap the application
- Parse command-line arguments
- Route to appropriate handlers
- Handle uncaught errors
Dependencies:
- Imports from CLI layer only
- No direct core imports
Purpose: User interface and interaction
Source: src/cli/handlers/
Files:
-
create.ts
- Create phantom command -
delete.ts
- Delete phantom command -
list.ts
- List phantoms command -
exec.ts
- Execute command in phantom -
shell.ts
- Interactive shell command -
where.ts
- Get phantom path command -
version.ts
- Show version command -
attach.ts
- Attach existing worktree
Responsibilities:
- Orchestrate core operations
- Handle user input validation
- Format output messages
- Manage exit codes
Source: src/cli/output.ts
Responsibilities:
- Console output formatting
- Table rendering
- Color management
- Progress indicators
Key Functions:
-
formatTable()
- Render data as table -
formatError()
- Format error messages -
formatSuccess()
- Format success messages
Source: src/cli/errors.ts
Responsibilities:
- Map errors to exit codes
- Format error messages
- Handle unexpected errors
Purpose: Business logic and domain operations
Source: src/core/worktree/
Files:
-
create.ts
- Worktree creation logic -
delete.ts
- Worktree deletion logic -
list.ts
- List worktrees logic -
where.ts
- Locate worktree logic -
attach.ts
- Attach existing worktree -
validate.ts
- Name validation -
errors.ts
- Domain-specific errors
Core Types:
interface WorktreeInfo {
name: string;
path: string;
branch: string;
isMain: boolean;
}
Dependencies:
- Uses Git module for operations
- Uses Paths module for locations
- Returns Result types
Source: src/core/git/
Structure:
git/
├── executor.ts # Main Git command executor
└── libs/ # Git operation wrappers
├── add-worktree.ts
├── attach-worktree.ts
├── branch-exists.ts
├── get-current-branch.ts
├── get-git-root.ts
└── list-worktrees.ts
Git Executor:
class GitExecutor {
execute(args: string[]): Result<string>;
executeJson<T>(args: string[]): Result<T>;
}
Git Libraries: Each library wraps specific Git operations:
-
addWorktree()
- Create new worktree -
getCurrentBranch()
- Get active branch -
getGitRoot()
- Find repository root -
listWorktrees()
- List all worktrees
Source: src/core/process/
Files:
-
spawn.ts
- Core process spawning -
exec.ts
- Command execution -
shell.ts
- Interactive shells -
errors.ts
- Process errors
Key Types:
interface SpawnOptions {
cwd?: string;
env?: Record<string, string>;
stdio?: StdioOptions;
}
Responsibilities:
- Safe process spawning
- I/O stream management
- Signal handling
- Error propagation
Source: src/core/paths.ts
Functions:
-
getWorktreeBasePath()
- Base directory for phantoms -
getWorktreePath()
- Full path to phantom -
ensureWorktreeDirectory()
- Create directories
Path Structure:
/tmp/phantom/
└── <repo-name>/
├── <phantom-1>/
├── <phantom-2>/
└── <phantom-3>/
Source: src/core/types/
Files:
-
result.ts
- Result type implementation
Result Type:
type Result<T, E = Error> =
| { ok: true; value: T }
| { ok: false; error: E }
namespace Result {
function ok<T>(value: T): Result<T>;
function error<E>(error: E): Result<never, E>;
}
sequenceDiagram
participant CLI
participant Handler
participant Core
participant Git
CLI->>Handler: Command + Args
Handler->>Core: Business Operation
Core->>Git: Git Command
Git-->>Core: Result<Data>
Core-->>Handler: Result<DomainObject>
Handler->>CLI: Format Output
graph LR
A[Git Error] --> B[Result.error]
B --> C[Core Layer]
C --> D[Domain Error]
D --> E[Handler]
E --> F[User Message]
style A fill:#f99
style F fill:#9f9
Each layer transforms data appropriately:
- Git Layer: Raw strings → Parsed data
- Core Layer: Parsed data → Domain objects
- CLI Layer: Domain objects → User output
- Downward Only: Higher layers depend on lower layers
- No Upward: Core never imports from CLI
- No Circular: Enforced by structure
Each module exposes a clear interface:
// Good: Clear module interface
export function createWorktree(
name: string,
branch?: string
): Result<WorktreeInfo>
// Bad: Exposing internals
export function executeGitCommand(
args: string[]
): ChildProcess // Don't expose Node.js types
- Domain types in core layer
- CLI types stay in CLI layer
- Shared types in
types/
module
Test files are co-located with source:
src/
├── core/
│ ├── worktree/
│ │ ├── create.ts
│ │ ├── create.test.ts # Unit tests
│ │ ├── delete.ts
│ │ └── delete.test.ts
│ └── git/
│ ├── executor.ts
│ └── executor.test.ts
Test Patterns:
- Mock external dependencies
- Test pure functions directly
- Integration tests for workflows
Module | Responsible For | Not Responsible For |
---|---|---|
CLI Handlers | User interaction | Business logic |
Core Worktree | Phantom lifecycle | Git commands |
Git Module | Git execution | Business rules |
Process Module | Process spawning | Command logic |
Types Module | Shared types | Implementation |
Good - Single Responsibility:
// validate.ts - Only validation
export function validateWorktreeName(name: string): Result<void>
// create.ts - Only creation
export function createWorktree(name: string): Result<WorktreeInfo>
Bad - Multiple Responsibilities:
// Don't mix concerns
export function createAndValidateWorktree(
name: string,
showOutput: boolean // UI concern in core!
): Result<WorktreeInfo>
// 1. Node.js built-ins
import { spawn } from "node:child_process";
import { existsSync } from "node:fs";
// 2. External dependencies (none in runtime)
// 3. Internal - absolute paths
import { Result } from "../types/result.js";
// 4. Internal - relative paths
import { validateWorktreeName } from "./validate.js";
Avoid barrel exports (index.ts) to:
- Keep imports explicit
- Improve tree shaking
- Reduce circular dependencies
Always include .js
extension:
// Correct - ESM requires extensions
import { createWorktree } from "./create.js";
// Wrong - Will fail at runtime
import { createWorktree } from "./create";
-
Config Module
- User preferences
- Repository settings
- Default values
-
Plugin Module
- Plugin loading
- Hook management
- API surface
-
Telemetry Module
- Usage metrics
- Performance tracking
- Error reporting
When adding new modules:
- Single Purpose: One clear responsibility
- Clear Interface: Well-defined public API
- Testable: Pure functions where possible
- Documented: Clear module documentation
- Consistent: Follow existing patterns
Phantom's module structure provides:
- Clear Boundaries: Well-defined layers and responsibilities
- Maintainability: Easy to find and modify code
- Testability: Isolated modules with clear interfaces
- Scalability: New features fit naturally
- Type Safety: Strong typing throughout
The modular architecture ensures the codebase remains clean, understandable, and extensible as the project grows.