メインコンテンツへスキップ
This cookbook shows how to automatically format code after Droid edits files, ensuring consistent code style across your project without manual intervention.

How it works

The hook:
  1. Triggers on file edits: Runs after Write or Edit tool calls
  2. Detects file type: Checks file extension to determine formatter
  3. Runs appropriate formatter: Executes prettier, black, gofmt, rustfmt, etc.
  4. Provides feedback: Reports formatting results to the user
  5. Handles errors gracefully: Continues even if formatting fails

Prerequisites

Install formatters for your language stack:
npm install -D prettier

Basic setup

Single language project

For a JavaScript/TypeScript project, add this to your .factory/settings.json:
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.file_path' | { read file_path; if echo \"$file_path\" | grep -qE '\\.(ts|tsx|js|jsx)$'; then npx prettier --write \"$file_path\" 2>&1 && echo \"✓ Formatted $file_path\"; fi; }",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

Multi-language project

For projects with multiple languages, use a script to handle different file types. Create .factory/hooks/format.sh:
#!/bin/bash
set -e

# Read the hook input
input=$(cat)
file_path=$(echo "$input" | jq -r '.tool_input.file_path')

# Skip if file doesn't exist
if [ ! -f "$file_path" ]; then
  exit 0
fi

# Determine formatter based on file extension
case "$file_path" in
  *.ts|*.tsx|*.js|*.jsx|*.json|*.css|*.scss|*.md|*.mdx)
    if command -v prettier &> /dev/null; then
      prettier --write "$file_path" 2>&1
      echo "✓ Formatted with Prettier: $file_path"
    fi
    ;;
  *.py)
    if command -v black &> /dev/null; then
      black "$file_path" 2>&1
      echo "✓ Formatted with Black: $file_path"
    fi
    if command -v isort &> /dev/null; then
      isort "$file_path" 2>&1
      echo "✓ Sorted imports with isort: $file_path"
    fi
    ;;
  *.go)
    if command -v gofmt &> /dev/null; then
      gofmt -w "$file_path" 2>&1
      echo "✓ Formatted with gofmt: $file_path"
    fi
    ;;
  *.rs)
    if command -v rustfmt &> /dev/null; then
      rustfmt "$file_path" 2>&1
      echo "✓ Formatted with rustfmt: $file_path"
    fi
    ;;
  *.java)
    if command -v google-java-format &> /dev/null; then
      google-java-format -i "$file_path" 2>&1
      echo "✓ Formatted with google-java-format: $file_path"
    fi
    ;;
  *)
    # No formatter for this file type
    exit 0
    ;;
esac

exit 0
Make the script executable:
chmod +x .factory/hooks/format.sh
Add to .factory/settings.json:
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "\"$DROID_PROJECT_DIR\"/.factory/hooks/format.sh",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

Advanced configurations

Format with custom config

Use project-specific prettier config:
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.file_path' | { read file_path; if echo \"$file_path\" | grep -qE '\\.(ts|tsx|js|jsx)$'; then npx prettier --config \"$DROID_PROJECT_DIR\"/.prettierrc --write \"$file_path\" 2>&1; fi; }",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

Format with linting

Combine formatting with linting fixes. Create .factory/hooks/format-and-lint.sh:
#!/bin/bash
set -e

input=$(cat)
file_path=$(echo "$input" | jq -r '.tool_input.file_path')

if [ ! -f "$file_path" ]; then
  exit 0
fi

case "$file_path" in
  *.ts|*.tsx|*.js|*.jsx)
    # Format with prettier
    if command -v prettier &> /dev/null; then
      prettier --write "$file_path" 2>&1
      echo "✓ Formatted: $file_path"
    fi
    
    # Fix lint issues
    if command -v eslint &> /dev/null; then
      eslint --fix "$file_path" 2>&1 || true
      echo "✓ Linted: $file_path"
    fi
    ;;
  *.py)
    # Format with black
    if command -v black &> /dev/null; then
      black "$file_path" 2>&1
      echo "✓ Formatted with Black: $file_path"
    fi
    
    # Sort imports
    if command -v isort &> /dev/null; then
      isort "$file_path" 2>&1
      echo "✓ Sorted imports: $file_path"
    fi
    
    # Run flake8 for style issues
    if command -v flake8 &> /dev/null; then
      flake8 "$file_path" 2>&1 || true
      echo "✓ Checked with flake8: $file_path"
    fi
    ;;
esac

exit 0
Make the script executable:
chmod +x .factory/hooks/format-and-lint.sh
Add to .factory/settings.json:
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "\"$DROID_PROJECT_DIR\"/.factory/hooks/format-and-lint.sh",
            "timeout": 45
          }
        ]
      }
    ]
  }
}

Conditional formatting

Only format files in specific directories. Create .factory/hooks/format-src-only.sh:
#!/bin/bash
set -e

input=$(cat)
file_path=$(echo "$input" | jq -r '.tool_input.file_path')

# Only format files in src/ or packages/ directories
if ! echo "$file_path" | grep -qE '^(src/|packages/)'; then
  exit 0
fi

case "$file_path" in
  *.ts|*.tsx|*.js|*.jsx)
    if command -v prettier &> /dev/null; then
      prettier --write "$file_path" 2>&1
      echo "✓ Formatted: $file_path"
    fi
    ;;
esac

exit 0
Make the script executable:
chmod +x .factory/hooks/format-src-only.sh
Add to .factory/settings.json:
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "\"$DROID_PROJECT_DIR\"/.factory/hooks/format-src-only.sh",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

Real-world examples

Example 1: React component formatting

// Droid creates this file
import React from 'react';
import {useState} from 'react';

export const Button = ({onClick,label}:{onClick:()=>void;label:string})=>{
const [loading,setLoading]=useState(false);
return <button onClick={onClick} disabled={loading}>{label}</button>
}

Example 2: Python import sorting

# Droid creates this file
from typing import Optional
import sys
from django.db import models
import os
from myapp.utils import helper

def process_data(data: Optional[str]) -> None:
    if data:
        helper(data)

Best practices

Formatters can sometimes introduce subtle bugs (e.g., changing string formats, line continuations). Always review changes before committing.
1

Start with read-only mode

Test your formatter configuration manually first:
# Dry run to see what would change
prettier --check src/
black --check src/
2

Use consistent config files

Ensure formatter configs are committed:
# Add to version control
git add .prettierrc .prettierignore
git add pyproject.toml  # for black config
3

Set appropriate timeouts

Large files may need more time:
{
  "timeout": 60  // Increase for large files
}
4

Handle formatter errors gracefully

Don’t block Droid if formatting fails:
# Use || true to continue on errors
prettier --write "$file_path" 2>&1 || true
5

Consider Git hooks integration

Combine with pre-commit hooks for consistency:
# .pre-commit-config.yaml
repos:
  - repo: https://github.com/pre-commit/mirrors-prettier
    rev: v3.0.0
    hooks:
      - id: prettier

Troubleshooting

Formatter not found

Problem: command not found error Solution: Install the formatter globally or use npx/project binaries:
# Install globally
npm install -g prettier

# Or use project version
npx prettier --write "$file_path"

Formatting breaks code

Problem: Formatter introduces syntax errors Solution: Add file validation after formatting:
# For TypeScript
prettier --write "$file_path"
tsc --noEmit "$file_path" || echo "⚠️ Type errors after formatting"

Hook runs too slowly

Problem: Formatting takes too long Solution: Only format changed files, use faster formatters:
# Skip files that haven't changed
if git diff --quiet "$file_path"; then
  exit 0
fi

Conflicts with editor formatting

Problem: Editor and hook format differently Solution: Use the same config for both:
// .vscode/settings.json
{
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.formatOnSave": true,
  "prettier.configPath": ".prettierrc"
}

See also