Development Guides Local Development - aku11i/phantom GitHub Wiki
This guide covers running Phantom locally during development, including setup, workflow, and common tasks.
Before starting local development, ensure you have:
- Node.js v22.0.0 or higher
- pnpm v10.8.1 or higher
- Git (with worktree support)
- A code editor (VS Code recommended)
See Development Setup for detailed installation instructions.
# Clone the repository
git clone https://github.com/aku11i/phantom.git
cd phantom
# Install dependencies
pnpm install
# Verify setup
pnpm test
Source: package.json#L12
# Run the CLI directly from source
pnpm start
# With arguments
pnpm start create my-feature
pnpm start list
pnpm start exec my-feature "npm test"
Command | Description | Use Case |
---|---|---|
pnpm start |
Run CLI from source | Testing CLI commands |
pnpm build |
Build the project | Create distribution |
pnpm test |
Run unit tests | Verify changes |
pnpm lint |
Check code quality | Find issues |
pnpm fix |
Auto-fix issues | Clean up code |
pnpm typecheck |
Check types | Catch type errors |
pnpm ready |
All checks | Pre-commit verification |
# Create a new branch
git checkout -b feature/new-command
# Make changes
code src/cli/handlers/new-command.ts
# Test your changes
pnpm start new-command
# Run tests
pnpm test
# Check everything
pnpm ready
# 1. Write test first
code src/core/feature/new-feature.test.ts
# 2. Run test (should fail)
node --test src/core/feature/new-feature.test.ts
# 3. Implement feature
code src/core/feature/new-feature.ts
# 4. Run test again (should pass)
node --test src/core/feature/new-feature.test.ts
# 5. Run all tests
pnpm test
# Run with Node.js inspector
node --inspect-brk dist/phantom.js create test
# Run tests with debugging
node --inspect --test src/core/worktree/create.test.ts
# Use VS Code debugger (see IDE Setup below)
Understanding the codebase layout:
src/
├── bin/
│ └── phantom.ts # Entry point
├── cli/
│ ├── handlers/ # Command implementations
│ │ ├── create.ts # Create command
│ │ ├── delete.ts # Delete command
│ │ └── ... # Other commands
│ ├── output.ts # Output formatting
│ └── errors.ts # Error handling
└── core/
├── worktree/ # Worktree logic
├── git/ # Git operations
├── process/ # Process management
└── types/ # Type definitions
- Create Handler File
// src/cli/handlers/status.ts
import { Result } from "../../core/types/result.js";
import { getWorktreeStatus } from "../../core/worktree/status.js";
import { output } from "../output.js";
export async function statusHandler(args: string[]): Promise<void> {
const name = args[0];
if (!name) {
output.error("Usage: phantom status <name>");
process.exitCode = 1;
return;
}
const result = await getWorktreeStatus(name);
if (!result.ok) {
output.error(result.error.message);
process.exitCode = 1;
return;
}
output.success(`Status: ${result.value.status}`);
}
- Add Core Logic
// src/core/worktree/status.ts
import { Result } from "../types/result.js";
import { GitExecutor } from "../git/executor.js";
export async function getWorktreeStatus(
name: string
): Promise<Result<WorktreeStatus>> {
const git = new GitExecutor();
// Implementation
return Result.ok({ status: "clean" });
}
- Wire Up Command
// src/bin/phantom.ts
import { statusHandler } from "../cli/handlers/status.js";
// In command switch
case "status":
await statusHandler(args.slice(1));
break;
- Add Tests
// src/core/worktree/status.test.ts
import { describe, it } from "node:test";
import assert from "node:assert";
import { getWorktreeStatus } from "./status.js";
describe("getWorktreeStatus", () => {
it("should return clean status", async () => {
const result = await getWorktreeStatus("test");
assert.strictEqual(result.ok, true);
});
});
- Understand Current Behavior
# Read the existing code
code src/cli/handlers/create.ts
code src/core/worktree/create.ts
# Check existing tests
code src/core/worktree/create.test.ts
# Run current implementation
pnpm start create test-phantom
- Make Changes
// Add new option to create command
export async function createHandler(args: string[]): Promise<void> {
const [name, branch, customPath] = args;
// Add custom path support
const result = await createWorktree(name, branch, { customPath });
// ...
}
- Update Tests
it("should support custom path option", async () => {
const result = await createWorktree("test", "main", {
customPath: "/custom/location"
});
assert.strictEqual(result.ok, true);
if (result.ok) {
assert.strictEqual(result.value.path, "/custom/location/test");
}
});
- Understanding Git Module
// src/core/git/executor.ts
import { spawn } from "node:child_process";
import { Result } from "../types/result.js";
export class GitExecutor {
execute(args: string[]): Result<string> {
// Executes git command and returns output
}
}
- Adding New Git Operation
// src/core/git/libs/stash-list.ts
import { GitExecutor } from "../executor.js";
import { Result } from "../../types/result.js";
export function listStashes(): Result<string[]> {
const git = new GitExecutor();
const result = git.execute(["stash", "list"]);
if (!result.ok) {
return result;
}
const stashes = result.value
.split("\n")
.filter(line => line.trim());
return Result.ok(stashes);
}
# Create test repository
mkdir test-repo && cd test-repo
git init
# Test phantom commands
pnpm start create feature-1
pnpm start list
pnpm start exec feature-1 "pwd"
pnpm start shell feature-1
pnpm start delete feature-1
# Create test script
cat > test-integration.sh << 'EOF'
#!/bin/bash
set -e
# Setup
TEMP_DIR=$(mktemp -d)
cd $TEMP_DIR
git init
# Test create
node /path/to/phantom/dist/phantom.js create test1
[ -d ".git/worktrees/test1" ] || exit 1
# Test list
node /path/to/phantom/dist/phantom.js list | grep test1 || exit 1
# Cleanup
cd ..
rm -rf $TEMP_DIR
echo "Integration tests passed!"
EOF
chmod +x test-integration.sh
./test-integration.sh
// Simple performance test
console.time("create-worktree");
await createWorktree("perf-test");
console.timeEnd("create-worktree");
// Memory usage
const before = process.memoryUsage();
await operation();
const after = process.memoryUsage();
console.log("Memory delta:", after.heapUsed - before.heapUsed);
- Launch Configuration
.vscode/launch.json
:
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug CLI",
"skipFiles": ["<node_internals>/**"],
"program": "${workspaceFolder}/src/bin/phantom.ts",
"args": ["create", "test"],
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
"sourceMaps": true,
"runtimeArgs": ["--loader", "tsx"]
},
{
"type": "node",
"request": "launch",
"name": "Debug Current Test",
"skipFiles": ["<node_internals>/**"],
"program": "${file}",
"args": ["--test"],
"console": "integratedTerminal"
}
]
}
- Tasks Configuration
.vscode/tasks.json
:
{
"version": "2.0.0",
"tasks": [
{
"label": "Run Tests",
"type": "npm",
"script": "test",
"problemMatcher": [],
"group": {
"kind": "test",
"isDefault": true
}
},
{
"label": "Build",
"type": "npm",
"script": "build",
"group": {
"kind": "build",
"isDefault": true
}
}
]
}
-
Add Breakpoints
- Click in the gutter next to line numbers
- Use
debugger;
statement in code
-
Inspect Variables
- Hover over variables during debugging
- Use Debug Console for evaluation
-
Step Through Code
- F10: Step over
- F11: Step into
- Shift+F11: Step out
Useful for development:
# Enable debug output
DEBUG=phantom:* pnpm start create test
# Use custom phantom directory
PHANTOM_BASE_DIR=/custom/path pnpm start create test
# Node.js flags
NODE_OPTIONS="--trace-warnings" pnpm start
Always run:
pnpm ready
This runs:
- Code formatting (
pnpm fix
) - Type checking (
pnpm typecheck
) - Unit tests (
pnpm test
)
# Check specific file
npx biome check src/cli/handlers/create.ts
# Fix specific file
npx biome check --apply src/cli/handlers/create.ts
# Type check specific file
npx tsc --noEmit src/core/worktree/create.ts
- Module Not Found Errors
# Ensure you've built the project
pnpm build
# Check import paths (must include .js extension)
import { something } from "./module.js"; # ✓
import { something } from "./module"; # ✗
- Type Errors
# Run type check for details
pnpm typecheck
# Check TypeScript version
npx tsc --version
- Test Failures
# Run single test for debugging
node --test src/core/worktree/create.test.ts
# Run with more output
node --test --test-reporter=spec
Add debug logging:
// Temporary debug logging
console.log("Debug:", { name, branch, path });
// Conditional debug
if (process.env.DEBUG) {
console.log("Git args:", args);
}
// Pretty print objects
console.log(JSON.stringify(result, null, 2));
// Good: Strong typing
function createWorktree(
name: string,
branch?: string
): Promise<Result<WorktreeInfo>>
// Bad: Any types
function createWorktree(name: any, options: any): any
// Follow Result pattern for errors
return Result.ok(value);
return Result.error(new Error("Failed"));
// Use existing helpers
import { validateWorktreeName } from "./validate.js";
# TDD workflow
1. Write failing test
2. Write minimal code to pass
3. Refactor
4. Repeat
- Don't over-engineer
- Follow existing patterns
- Ask if unsure
- Read Debugging Guide for advanced debugging
- Check Code Styles for conventions
- Review Testing Strategy for test guidelines
Happy coding! 🚀