Development Guides Local Development - aku11i/phantom GitHub Wiki

Local Development

This guide covers running Phantom locally during development, including setup, workflow, and common tasks.

Prerequisites

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.

Getting Started

1. Clone and Setup

# Clone the repository
git clone https://github.com/aku11i/phantom.git
cd phantom

# Install dependencies
pnpm install

# Verify setup
pnpm test

2. Running from Source

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"

3. Development Scripts

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

Development Workflow

1. Feature Development

# 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

2. Test-Driven Development

# 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

3. Debugging Workflow

# 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)

Project Structure

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

Common Development Tasks

Adding a New Command

  1. 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}`);
}
  1. 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" });
}
  1. 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;
  1. 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);
  });
});

Modifying Existing Commands

  1. 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
  1. 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 });
  // ...
}
  1. 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");
  }
});

Working with Git Operations

  1. 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
  }
}
  1. 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);
}

Local Testing Strategies

1. Manual Testing

# 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

2. Integration Testing

# 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

3. Performance Testing

// 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);

IDE Setup

Visual Studio Code

  1. 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"
    }
  ]
}
  1. 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
      }
    }
  ]
}

Debugging Tips

  1. Add Breakpoints

    • Click in the gutter next to line numbers
    • Use debugger; statement in code
  2. Inspect Variables

    • Hover over variables during debugging
    • Use Debug Console for evaluation
  3. Step Through Code

    • F10: Step over
    • F11: Step into
    • Shift+F11: Step out

Environment Variables

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

Code Quality

Before Committing

Always run:

pnpm ready

This runs:

  1. Code formatting (pnpm fix)
  2. Type checking (pnpm typecheck)
  3. Unit tests (pnpm test)

Manual Checks

# 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

Troubleshooting

Common Issues

  1. 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";     #
  1. Type Errors
# Run type check for details
pnpm typecheck

# Check TypeScript version
npx tsc --version
  1. Test Failures
# Run single test for debugging
node --test src/core/worktree/create.test.ts

# Run with more output
node --test --test-reporter=spec

Debug Output

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));

Best Practices

1. Use Type Safety

// Good: Strong typing
function createWorktree(
  name: string,
  branch?: string
): Promise<Result<WorktreeInfo>>

// Bad: Any types
function createWorktree(name: any, options: any): any

2. Follow Patterns

// Follow Result pattern for errors
return Result.ok(value);
return Result.error(new Error("Failed"));

// Use existing helpers
import { validateWorktreeName } from "./validate.js";

3. Write Tests First

# TDD workflow
1. Write failing test
2. Write minimal code to pass
3. Refactor
4. Repeat

4. Keep It Simple

  • Don't over-engineer
  • Follow existing patterns
  • Ask if unsure

Next Steps

Happy coding! 🚀

⚠️ **GitHub.com Fallback** ⚠️