Development Guides Debugging - aku11i/phantom GitHub Wiki

Debugging Guide

This guide covers debugging techniques and tools for troubleshooting Phantom during development.

Debugging Tools

Node.js Debugger

Node.js includes a built-in debugger that works with Chrome DevTools and VS Code.

Command Line Debugging

# Start with inspector
node --inspect dist/phantom.js create test

# Start with inspector and break on first line
node --inspect-brk dist/phantom.js create test

# Specific port
node --inspect=9229 dist/phantom.js list

Chrome DevTools

  1. Start Node with --inspect
  2. Open Chrome and navigate to chrome://inspect
  3. Click "inspect" under Remote Target
  4. Use DevTools to debug

VS Code Debugging

Launch Configurations

Source: Recommended .vscode/launch.json

{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Debug Phantom CLI",
      "skipFiles": ["<node_internals>/**"],
      "program": "${workspaceFolder}/dist/phantom.js",
      "args": ["create", "test-phantom"],
      "outFiles": ["${workspaceFolder}/dist/**/*.js"],
      "sourceMaps": true,
      "console": "integratedTerminal"
    },
    {
      "type": "node",
      "request": "launch",
      "name": "Debug Current Test",
      "skipFiles": ["<node_internals>/**"],
      "program": "${file}",
      "args": ["--test"],
      "console": "integratedTerminal"
    },
    {
      "type": "node",
      "request": "attach",
      "name": "Attach to Process",
      "port": 9229,
      "skipFiles": ["<node_internals>/**"],
      "sourceMaps": true
    }
  ]
}

Debugging TypeScript Source

Since Phantom is built with esbuild, source maps enable debugging TypeScript:

# Build with source maps (default)
pnpm build

# Debug TypeScript directly
node --enable-source-maps --inspect dist/phantom.js

Common Debugging Scenarios

1. Command Not Working

// Add debug logging to handler
export async function createHandler(args: string[]): Promise<void> {
  console.log("createHandler called with:", args);
  
  const [name, branch] = args;
  console.log("Parsed:", { name, branch });
  
  // Existing code...
}

2. Git Command Failures

Debug Git operations:

// In src/core/git/executor.ts
export class GitExecutor {
  execute(args: string[]): Result<string> {
    console.log("Git command:", "git", args.join(" "));
    
    const { status, stdout, stderr } = spawnSync("git", args);
    
    console.log("Git exit code:", status);
    console.log("Git stdout:", stdout);
    console.log("Git stderr:", stderr);
    
    // Existing code...
  }
}

3. Path Resolution Issues

// Debug path construction
import { getWorktreePath } from "../paths.js";

const path = getWorktreePath(repoName, phantomName);
console.log("Resolved path:", {
  repoName,
  phantomName,
  resultPath: path,
  exists: existsSync(path)
});

4. Result Type Debugging

// Helper to log Result types
function debugResult<T>(result: Result<T>, label: string): void {
  if (result.ok) {
    console.log(`${label} - Success:`, result.value);
  } else {
    console.log(`${label} - Error:`, result.error.message);
    console.error(result.error.stack);
  }
}

// Usage
const result = await createWorktree(name);
debugResult(result, "createWorktree");

Test Debugging

Running Single Test with Debugging

# Run specific test file
node --inspect --test src/core/worktree/create.test.ts

# Run test matching pattern
node --inspect --test --test-name-pattern="validation"

Debugging Mock Behavior

import { mock } from "node:test";

// Log mock calls
const mockExecute = mock.fn((args) => {
  console.log("Mock called with:", args);
  return Result.ok("mocked response");
});

// After test
console.log("Mock call count:", mockExecute.mock.calls.length);
console.log("All calls:", mockExecute.mock.calls);

Test Timeout Issues

import { test } from "node:test";

// Increase timeout for debugging
test("slow operation", { timeout: 30000 }, async () => {
  // Set breakpoints and debug without timeout
});

Debugging Techniques

1. Binary Search Debugging

When unsure where an issue occurs:

console.log("Point A reached");
// Half of the code
console.log("Point B reached");
// Other half
console.log("Point C reached");

2. State Inspection

// Create debug helper
function inspectState(label: string, state: any): void {
  console.log(`\n=== ${label} ===`);
  console.log(JSON.stringify(state, null, 2));
  console.log("=================\n");
}

// Use throughout code
inspectState("Before validation", { name, branch });
inspectState("After Git operation", { result, worktrees });

3. Error Stack Traces

// Enhance error information
try {
  await riskyOperation();
} catch (error) {
  console.error("Operation failed");
  console.error("Error:", error.message);
  console.error("Stack:", error.stack);
  console.error("Context:", { name, branch, path });
  throw error;
}

4. Conditional Debugging

// Environment-based debugging
const DEBUG = process.env.DEBUG_PHANTOM === "true";

if (DEBUG) {
  console.log("Debug info:", data);
}

// Or use a debug function
function debug(...args: any[]): void {
  if (process.env.DEBUG_PHANTOM) {
    console.log("[DEBUG]", ...args);
  }
}

Debugging Git Worktrees

Inspect Git State

# Check worktree state
git worktree list --porcelain

# Check Git internals
ls -la .git/worktrees/

# View worktree config
cat .git/worktrees/phantom-name/gitdir

Common Git Issues

// Debug Git worktree issues
async function debugWorktreeState(name: string): Promise<void> {
  const git = new GitExecutor();
  
  // List all worktrees
  const listResult = git.execute(["worktree", "list", "--porcelain"]);
  console.log("All worktrees:", listResult);
  
  // Check specific worktree
  const worktreePath = getWorktreePath(repoName, name);
  console.log("Checking path:", worktreePath);
  console.log("Path exists:", existsSync(worktreePath));
  console.log("Is directory:", existsSync(worktreePath) && 
    statSync(worktreePath).isDirectory());
  
  // Check Git directory
  const gitDir = join(worktreePath, ".git");
  console.log("Git dir:", gitDir);
  console.log("Git file contents:", 
    existsSync(gitDir) ? readFileSync(gitDir, "utf-8") : "Not found");
}

Performance Debugging

Timing Operations

// Simple timing
console.time("createWorktree");
const result = await createWorktree(name);
console.timeEnd("createWorktree");

// Detailed timing
const start = performance.now();
const result = await operation();
const duration = performance.now() - start;
console.log(`Operation took ${duration.toFixed(2)}ms`);

Memory Profiling

// Memory usage tracking
function logMemoryUsage(label: string): void {
  const usage = process.memoryUsage();
  console.log(`Memory (${label}):`);
  console.log(`  RSS: ${(usage.rss / 1024 / 1024).toFixed(2)} MB`);
  console.log(`  Heap Used: ${(usage.heapUsed / 1024 / 1024).toFixed(2)} MB`);
  console.log(`  Heap Total: ${(usage.heapTotal / 1024 / 1024).toFixed(2)} MB`);
}

logMemoryUsage("Before operation");
await heavyOperation();
logMemoryUsage("After operation");

CPU Profiling

# Start with profiling
node --prof dist/phantom.js create test

# Process the log
node --prof-process isolate-*.log > profile.txt

# Analyze profile.txt for bottlenecks

Debugging Production Issues

Verbose Logging

// Add verbose mode
const VERBOSE = process.env.PHANTOM_VERBOSE === "true";

function verbose(message: string, data?: any): void {
  if (VERBOSE) {
    console.error(`[VERBOSE] ${message}`, data || "");
  }
}

// Use throughout
verbose("Creating worktree", { name, branch });
verbose("Git command", { cmd: args.join(" ") });

Error Context

// Enhanced error class
class PhantomError extends Error {
  constructor(
    message: string,
    public code: string,
    public context: Record<string, any>
  ) {
    super(message);
    this.name = "PhantomError";
  }
}

// Usage
throw new PhantomError(
  "Failed to create worktree",
  "WORKTREE_CREATE_FAILED",
  { name, branch, gitError: stderr }
);

Debug Utilities

Debug Module

Create a debug utility:

// src/core/debug.ts
const DEBUG = process.env.DEBUG?.includes("phantom");

export function debug(namespace: string) {
  return (...args: any[]) => {
    if (DEBUG || process.env.DEBUG?.includes(namespace)) {
      console.error(`[${namespace}]`, ...args);
    }
  };
}

// Usage
import { debug } from "../debug.js";
const log = debug("phantom:git");

log("Executing command", args);

Inspection Helpers

// Pretty print for debugging
export function inspect(obj: any, label?: string): void {
  if (label) console.log(`\n${label}:`);
  console.dir(obj, {
    depth: null,
    colors: true,
    compact: false
  });
}

// Breakpoint helper
export function breakpoint(condition: boolean = true): void {
  if (condition) {
    debugger;
  }
}

Common Debugging Patterns

1. Trace Execution Flow

class ExecutionTracer {
  private stack: string[] = [];
  
  enter(name: string): void {
    this.stack.push(name);
    console.log("→".repeat(this.stack.length), name);
  }
  
  exit(name: string): void {
    console.log("←".repeat(this.stack.length), name);
    this.stack.pop();
  }
}

const tracer = new ExecutionTracer();
tracer.enter("createWorktree");
// ... code ...
tracer.exit("createWorktree");

2. Assertion Helpers

function assertDefined<T>(
  value: T | undefined,
  message: string
): asserts value is T {
  if (value === undefined) {
    console.error("Assertion failed:", message);
    console.trace();
    throw new Error(message);
  }
}

// Usage
assertDefined(worktreeName, "Worktree name is required");

3. State Snapshots

// Capture state at different points
const stateHistory: any[] = [];

function captureState(label: string, state: any): void {
  stateHistory.push({
    timestamp: Date.now(),
    label,
    state: JSON.parse(JSON.stringify(state))
  });
}

function dumpStateHistory(): void {
  console.log("State History:");
  stateHistory.forEach(({ timestamp, label, state }) => {
    console.log(`\n[${new Date(timestamp).toISOString()}] ${label}:`);
    console.log(JSON.stringify(state, null, 2));
  });
}

Tips and Tricks

1. Use Descriptive Logs

// Bad
console.log(result);
console.log(name);

// Good
console.log("Worktree creation result:", result);
console.log("Processing phantom name:", name);

2. Guard Debug Code

// Ensure debug code doesn't reach production
if (process.env.NODE_ENV !== "production") {
  console.log("Debug:", sensitiveData);
}

3. Clean Up Debug Code

# Find remaining debug statements
grep -r "console\." src/
grep -r "debugger" src/

4. Use Source Maps

# Always enable source maps in development
node --enable-source-maps dist/phantom.js

# Build with inline source maps for debugging
# (modify build.ts temporarily)
sourcemap: "inline"

Summary

Effective debugging in Phantom involves:

  1. Right Tools - VS Code debugger, Chrome DevTools
  2. Strategic Logging - Meaningful debug output
  3. State Inspection - Understanding data flow
  4. Isolation - Debug one thing at a time
  5. Clean Up - Remove debug code before committing

Remember: Good debugging is methodical and patient. Take time to understand the issue before jumping to solutions.

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