Development Guides Code Styles - aku11i/phantom GitHub Wiki

Code Styles

This guide documents Phantom's coding conventions and style guidelines. Following these ensures consistency across the codebase.

General Principles

Source: CLAUDE.md#L10

  1. No Code Comments - Write self-documenting code
  2. Follow Existing Patterns - Consistency over personal preference
  3. English Only - All code, commits, and documentation in English
  4. Type Safety - Leverage TypeScript fully

Code Formatting

Biome Configuration

Source: biome.json

Phantom uses Biome for formatting and linting:

{
  "formatter": {
    "enabled": true,
    "indentStyle": "tab",
    "indentWidth": 2,
    "lineWidth": 100,
    "lineEnding": "lf",
    "quoteStyle": "double",
    "trailingComma": "all",
    "semicolons": "always"
  }
}

Key Formatting Rules

Indentation

  • Use tabs for indentation
  • Tab width displays as 2 spaces
// ✓ Good
function example() {
	if (condition) {
		doSomething();
	}
}

// ✗ Bad - spaces
function example() {
  if (condition) {
    doSomething();
  }
}

Quotes

  • Use double quotes for strings
// ✓ Good
const message = "Hello, world!";
const html = `<div class="container">`;

// ✗ Bad
const message = 'Hello, world!';

Semicolons

  • Always use semicolons
// ✓ Good
const name = "phantom";
import { Result } from "./types.js";

// ✗ Bad
const name = "phantom"
import { Result } from "./types.js"

Trailing Commas

  • Use trailing commas in multi-line structures
// ✓ Good
const config = {
	name: "phantom",
	version: "1.0.0",
	author: "aku11i",
};

// ✗ Bad
const config = {
	name: "phantom",
	version: "1.0.0",
	author: "aku11i"
};

TypeScript Conventions

Type Annotations

Always use explicit types for function parameters and return values:

// ✓ Good
function createWorktree(name: string, branch?: string): Promise<Result<WorktreeInfo>> {
	// Implementation
}

// ✗ Bad - missing types
function createWorktree(name, branch) {
	// Implementation
}

Interface vs Type

Use interface for object shapes, type for unions and primitives:

// ✓ Good - interface for objects
interface WorktreeInfo {
	name: string;
	path: string;
	branch: string;
	isMain: boolean;
}

// ✓ Good - type for unions
type Result<T, E = Error> = 
	| { ok: true; value: T }
	| { ok: false; error: E };

// ✗ Bad - type for simple objects
type WorktreeInfo = {
	name: string;
	// ...
};

Const Assertions

Use as const for literal types:

// ✓ Good
const COMMANDS = ["create", "delete", "list", "exec", "shell"] as const;
type Command = typeof COMMANDS[number];

// ✗ Bad
const COMMANDS = ["create", "delete", "list", "exec", "shell"];

Module Structure

Import Organization

Source: src/cli/handlers/create.ts

Order imports consistently:

// 1. Node.js built-ins
import { existsSync } from "node:fs";
import { join } from "node:path";

// 2. External dependencies (none in runtime)

// 3. Internal - absolute paths
import { Result } from "../../core/types/result.js";
import { createWorktree } from "../../core/worktree/create.js";

// 4. Internal - relative paths
import { output } from "../output.js";

File Extensions

Always include .js extension in imports:

// ✓ Good - ESM requires extensions
import { createWorktree } from "./create.js";

// ✗ Bad - will fail at runtime
import { createWorktree } from "./create";

Export Style

Use named exports, avoid default exports:

// ✓ Good - named exports
export function createWorktree() { }
export function deleteWorktree() { }

// ✗ Bad - default export
export default function createWorktree() { }

Naming Conventions

Variables and Functions

Use camelCase:

// ✓ Good
const worktreeName = "feature";
function validateWorktreeName(name: string) { }

// ✗ Bad
const worktree_name = "feature";
function ValidateWorktreeName(name: string) { }

Constants

Use UPPER_SNAKE_CASE for true constants:

// ✓ Good
const MAX_NAME_LENGTH = 50;
const DEFAULT_BRANCH = "main";

// ✗ Bad
const maxNameLength = 50;
const default_branch = "main";

Types and Interfaces

Use PascalCase:

// ✓ Good
interface WorktreeInfo { }
type CommandHandler = () => void;

// ✗ Bad
interface worktreeInfo { }
type commandHandler = () => void;

File Names

Use kebab-case for file names:

✓ Good:
- create-worktree.ts
- git-executor.ts
- list-worktrees.test.ts

✗ Bad:
- createWorktree.ts
- GitExecutor.ts
- listWorktrees.test.ts

Function Guidelines

Function Length

Keep functions short and focused:

// ✓ Good - single responsibility
function validateWorktreeName(name: string): Result<void> {
	if (!name) {
		return Result.error(new Error("Name is required"));
	}
	
	if (name.includes("/") || name.includes("..")) {
		return Result.error(new Error("Invalid characters in name"));
	}
	
	return Result.ok();
}

// ✗ Bad - doing too much
function createAndValidateWorktree(name: string, showOutput: boolean) {
	// Validation logic
	// Creation logic  
	// Output logic
	// Too many responsibilities
}

Early Returns

Use early returns to reduce nesting:

// ✓ Good - early returns
function processWorktree(name: string): Result<void> {
	if (!name) {
		return Result.error(new Error("Name required"));
	}
	
	if (invalidCharacters(name)) {
		return Result.error(new Error("Invalid name"));
	}
	
	// Main logic
	return Result.ok();
}

// ✗ Bad - nested conditions
function processWorktree(name: string): Result<void> {
	if (name) {
		if (!invalidCharacters(name)) {
			// Main logic
			return Result.ok();
		} else {
			return Result.error(new Error("Invalid name"));
		}
	} else {
		return Result.error(new Error("Name required"));
	}
}

Pure Functions

Prefer pure functions without side effects:

// ✓ Good - pure function
function calculatePath(basePath: string, name: string): string {
	return join(basePath, name);
}

// ✗ Bad - side effects
let globalPath: string;
function calculatePath(basePath: string, name: string): void {
	globalPath = join(basePath, name);  // Side effect
}

Error Handling

Result Type Pattern

Always use Result type for operations that can fail:

// ✓ Good - explicit error handling
function parseConfig(content: string): Result<Config> {
	try {
		const config = JSON.parse(content);
		return Result.ok(config);
	} catch (error) {
		return Result.error(new Error(`Invalid config: ${error.message}`));
	}
}

// ✗ Bad - throwing exceptions
function parseConfig(content: string): Config {
	return JSON.parse(content);  // Throws on error
}

Error Messages

Make error messages helpful:

// ✓ Good - descriptive error
return Result.error(
	new Error(`Worktree '${name}' already exists at ${path}`)
);

// ✗ Bad - vague error
return Result.error(new Error("Error"));

Async/Await

Always Use Async/Await

Prefer async/await over promises:

// ✓ Good
async function loadConfig(): Promise<Result<Config>> {
	const result = await readFile("config.json");
	if (!result.ok) {
		return result;
	}
	
	return parseConfig(result.value);
}

// ✗ Bad - promise chains
function loadConfig(): Promise<Result<Config>> {
	return readFile("config.json")
		.then(result => {
			if (!result.ok) {
				return result;
			}
			return parseConfig(result.value);
		});
}

Error Handling in Async

Handle errors properly in async functions:

// ✓ Good
async function execute(): Promise<Result<void>> {
	try {
		await riskyOperation();
		return Result.ok();
	} catch (error) {
		return Result.error(error as Error);
	}
}

// ✗ Bad - unhandled rejection
async function execute(): Promise<void> {
	await riskyOperation();  // Can throw
}

Code Organization

Single Responsibility

Each module should have one clear purpose:

// ✓ Good - focused modules
// validate.ts - only validation
export function validateWorktreeName(name: string): Result<void> { }

// create.ts - only creation
export function createWorktree(name: string): Result<WorktreeInfo> { }

// ✗ Bad - mixed responsibilities
// worktree.ts - doing everything
export function validateAndCreateWorktree() { }
export function formatWorktreeOutput() { }
export function deleteWorktreeWithLogging() { }

Dependency Direction

Dependencies should flow downward:

✓ Good:
CLI → Core → Infrastructure

✗ Bad:
Core → CLI (upward dependency)

Testing Conventions

Test Structure

Follow the Arrange-Act-Assert pattern:

it("should create worktree with branch", async () => {
	// Arrange
	const name = "feature";
	const branch = "develop";
	mockGit.execute.mockReturnValue(Result.ok(""));
	
	// Act
	const result = await createWorktree(name, branch);
	
	// Assert
	assert.strictEqual(result.ok, true);
	if (result.ok) {
		assert.strictEqual(result.value.branch, branch);
	}
});

Test Naming

Use descriptive test names:

// ✓ Good
describe("validateWorktreeName", () => {
	it("should accept alphanumeric names with hyphens");
	it("should reject names with path separators");
	it("should reject empty names");
});

// ✗ Bad
describe("validate", () => {
	it("works");
	it("fails");
});

Documentation

No Code Comments

Write self-documenting code instead of comments:

// ✗ Bad - needs comment
// Check if name contains invalid characters
if (n.indexOf("/") >= 0 || n.indexOf("..") >= 0) {
	return false;
}

// ✓ Good - self-documenting
const containsPathSeparator = name.includes("/");
const containsParentReference = name.includes("..");

if (containsPathSeparator || containsParentReference) {
	return Result.error(new Error("Name cannot contain path separators"));
}

Function Names

Use descriptive function names:

// ✓ Good
function isValidWorktreeName(name: string): boolean { }
function getWorktreeByName(name: string): Result<WorktreeInfo> { }

// ✗ Bad
function check(n: string): boolean { }
function get(n: string): Result<WorktreeInfo> { }

Git Commit Style

Commit Messages

Follow conventional commits:

✓ Good:
feat: add support for custom worktree paths
fix: handle spaces in repository names
docs: update README with new commands
test: add tests for error scenarios
refactor: extract git operations to separate module

✗ Bad:
Updated stuff
Fix
Changes
WIP

Commit Scope

Keep commits focused:

✓ Good:
- One logical change per commit
- All tests pass
- No unrelated changes

✗ Bad:
- Multiple unrelated changes
- Broken tests
- Debug code included

Linting Rules

Biome Linter Configuration

Source: biome.json

Key linting rules:

{
  "linter": {
    "rules": {
      "recommended": true,
      "style": {
        "noParameterAssign": "error",
        "noVar": "error",
        "useConst": "error",
        "useTemplate": "warn"
      },
      "complexity": {
        "noForEach": "off",
        "useOptionalChain": "warn"
      },
      "correctness": {
        "noUnusedVariables": "error",
        "noUnusedImports": "error"
      }
    }
  }
}

Running Linters

# Check code
pnpm lint

# Auto-fix issues
pnpm fix

# Check specific file
npx biome check src/core/worktree/create.ts

Best Practices Summary

  1. Consistency - Follow existing patterns
  2. Clarity - Write readable, self-documenting code
  3. Simplicity - Avoid clever code, prefer obvious solutions
  4. Type Safety - Use TypeScript features fully
  5. Error Handling - Use Result types consistently
  6. Testing - Write tests for all new code
  7. No Comments - If code needs comments, refactor it

Remember: Code is read far more often than it's written. Optimize for readability.

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