> ## Documentation Index
> Fetch the complete documentation index at: https://factory-changelog-jun25.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Gitワークフローフック

> コミット検証、ブランチ保護、自動ワークフローのためにフックをGitと連携する

このクックブックでは、Gitワークフローを強制し、コミットを検証し、ブランチを保護し、変更ログ生成を自動化するためのフックの使用方法を説明します。

## 動作原理

Gitワークフローフックでできること：

1. **コミットを検証**: コミットメッセージが規約に従っているかチェック
2. **ブランチを保護**: main/productionへの誤ったコミットを防止
3. **変更ログを生成**: コミットから変更ログエントリを自動作成
4. **プッシュ前チェックを実行**: プッシュ前にコードを検証
5. **PR要件を強制**: ブランチ名、線形issues等をチェック

## 前提条件

基本的なGitツール：

<CodeGroup>
  ```bash Git theme={null}
  git --version
  ```

  ```bash GitHub CLI (optional) theme={null}
  brew install gh  # macOS
  # or download from https://cli.github.com
  ```

  ```bash Conventional Commits (optional) theme={null}
  npm install -g commitlint @commitlint/cli @commitlint/config-conventional
  ```
</CodeGroup>

## 基本的なGitフック

### コミットメッセージ検証

従来型コミット形式を強制します。

`.factory/hooks/validate-commit-msg.sh` を作成：

```bash theme={null}
#!/bin/bash

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

# Only validate Bash commands that look like git commit
if [ "$tool_name" != "Bash" ]; then
  exit 0
fi

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

# Check if this is a git commit command
if ! echo "$command" | grep -qE "^git commit"; then
  exit 0
fi

# Extract commit message from command
if echo "$command" | grep -qE "git commit -m"; then
  # Extract message from -m flag
  commit_msg=$(echo "$command" | sed -E 's/.*git commit.*-m[= ]*["\x27]([^"\x27]+)["\x27].*/\1/')
else
  # Allow commits without -m (will open editor)
  exit 0
fi

# Validate conventional commit format
# Format: type(scope): description
# Example: feat(auth): add login functionality

if ! echo "$commit_msg" | grep -qE "^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\(.+\))?:.+"; then
  echo "❌ Invalid commit message format" >&2
  echo "" >&2
  echo "Commit message must follow Conventional Commits format:" >&2
  echo "  type(scope): description" >&2
  echo "" >&2
  echo "Valid types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert" >&2
  echo "" >&2
  echo "Examples:" >&2
  echo "  feat(auth): add user login" >&2
  echo "  fix(api): handle null values" >&2
  echo "  docs: update README" >&2
  exit 2
fi

# Check for Linear issue reference
if ! echo "$commit_msg" | grep -qE "FAC-[0-9]+"; then
  echo "⚠️ No Linear issue reference found" >&2
  echo "Consider adding issue reference like: feat(auth): add login FAC-123" >&2
  # Warning only, don't block
fi

exit 0
```

```bash theme={null}
chmod +x .factory/hooks/validate-commit-msg.sh
```

`.factory/hooks.json` に追加：

```json theme={null}
{
  "PreToolUse": [
    {
      "matcher": "Bash",
      "hooks": [
        {
          "type": "command",
          "command": "\"$DROID_PROJECT_DIR\"/.factory/hooks/validate-commit-msg.sh",
          "timeout": 3
        }
      ]
    }
  ]
}
```

### ブランチ保護

保護されたブランチへの直接コミットを防止します。

`.factory/hooks/protect-branches.sh` を作成：

```bash theme={null}
#!/bin/bash

input=$(cat)
tool_name=$(echo "$input" | jq -r '.tool_name')
command=$(echo "$input" | jq -r '.tool_input.command // ""')

# Only check git commit commands
if [ "$tool_name" != "Bash" ] || ! echo "$command" | grep -qE "^git (commit|push)"; then
  exit 0
fi

cwd=$(echo "$input" | jq -r '.cwd')
cd "$cwd"

# Check if we're in a git repo
if [ ! -d ".git" ]; then
  exit 0
fi

current_branch=$(git branch --show-current)

# Protected branches that cannot be committed to directly
protected_branches=("main" "master" "production" "prod")

for branch in "${protected_branches[@]}"; do
  if [ "$current_branch" = "$branch" ]; then
    echo "❌ Cannot commit directly to protected branch: $branch" >&2
    echo "" >&2
    echo "Please create a feature branch instead:" >&2
    echo "  git checkout -b feature/your-feature-name" >&2
    echo "" >&2
    echo "Then create a pull request to merge your changes." >&2
    exit 2
  fi
done

exit 0
```

```bash theme={null}
chmod +x .factory/hooks/protect-branches.sh
```

### ブランチ命名の強制

フィーチャーブランチが命名規約に従うことを要求：

`.factory/hooks/validate-branch-name.sh` を作成：

```bash theme={null}
#!/bin/bash

input=$(cat)
tool_name=$(echo "$input" | jq -r '.tool_name')
command=$(echo "$input" | jq -r '.tool_input.command // ""')

# Only check git checkout -b commands
if [ "$tool_name" != "Bash" ] || ! echo "$command" | grep -qE "^git checkout -b"; then
  exit 0
fi

# Extract branch name
branch_name=$(echo "$command" | sed -E 's/.*git checkout -b[= ]*([^ ]+).*/\1/')

# Valid patterns:
# - feature/FAC-123-description
# - fix/FAC-123-description
# - hotfix/FAC-123-description

if ! echo "$branch_name" | grep -qE "^(feature|fix|hotfix|docs|refactor)/[A-Z]+-[0-9]+-[a-z0-9-]+$"; then
  echo "❌ Invalid branch name format" >&2
  echo "" >&2
  echo "Branch names must follow the pattern:" >&2
  echo "  type/ISSUE-123-description" >&2
  echo "" >&2
  echo "Examples:" >&2
  echo "  feature/FAC-123-add-user-auth" >&2
  echo "  fix/FAC-456-fix-login-bug" >&2
  echo "  hotfix/FAC-789-critical-security-fix" >&2
  exit 2
fi

exit 0
```

```bash theme={null}
chmod +x .factory/hooks/validate-branch-name.sh
```

## 高度なGit自動化

### 変更ログエントリの自動生成

コミットから変更ログエントリを自動作成：

`.factory/hooks/update-changelog.sh` を作成：

```bash theme={null}
#!/bin/bash
set -e

input=$(cat)
tool_name=$(echo "$input" | jq -r '.tool_name')
command=$(echo "$input" | jq -r '.tool_input.command // ""')

# Only run after git commit
if [ "$tool_name" != "Bash" ] || ! echo "$command" | grep -qE "^git commit"; then
  exit 0
fi

cwd=$(echo "$input" | jq -r '.cwd')
cd "$cwd"

# Get the last commit message
last_commit=$(git log -1 --pretty=format:"%s")

# Parse conventional commit
if echo "$last_commit" | grep -qE "^(feat|fix)(\(.+\))?:"; then
  commit_type=$(echo "$last_commit" | sed -E 's/^([^:(]+).*/\1/')
  commit_msg=$(echo "$last_commit" | sed -E 's/^[^:]+: (.+)/\1/')

  # Determine changelog section
  if [ "$commit_type" = "feat" ]; then
    section="### Features"
  elif [ "$commit_type" = "fix" ]; then
    section="### Bug Fixes"
  else
    exit 0
  fi

  # Create/update CHANGELOG.md
  if [ ! -f "CHANGELOG.md" ]; then
    cat > CHANGELOG.md << EOF
# Changelog

All notable changes to this project will be documented in this file.

## [Unreleased]

$section

- $commit_msg

EOF
  else
    # Insert into Unreleased section
    if grep -q "## \[Unreleased\]" CHANGELOG.md; then
      # Check if section exists
      if grep -q "^$section" CHANGELOG.md; then
        # Add to existing section
        sed -i.bak "/^$section/a\\
- $commit_msg
" CHANGELOG.md
      else
        # Create new section
        sed -i.bak "/## \[Unreleased\]/a\\
\\
$section\\
\\
- $commit_msg
" CHANGELOG.md
      fi
      rm CHANGELOG.md.bak 2>/dev/null || true
    fi
  fi

  echo "✓ Updated CHANGELOG.md"
fi

exit 0
```

```bash theme={null}
chmod +x .factory/hooks/update-changelog.sh
```

PostToolUseに追加：

```json theme={null}
{
  "PostToolUse": [
    {
      "matcher": "Bash",
      "hooks": [
        {
          "type": "command",
          "command": "\"$DROID_PROJECT_DIR\"/.factory/hooks/update-changelog.sh",
          "timeout": 5
        }
      ]
    }
  ]
}
```

### プッシュ前検証

git pushを許可する前にテストとチェックを実行：

`.factory/hooks/pre-push-check.sh` を作成：

```bash theme={null}
#!/bin/bash
set -e

input=$(cat)
tool_name=$(echo "$input" | jq -r '.tool_name')
command=$(echo "$input" | jq -r '.tool_input.command // ""')

# Only check git push commands
if [ "$tool_name" != "Bash" ] || ! echo "$command" | grep -qE "^git push"; then
  exit 0
fi

cwd=$(echo "$input" | jq -r '.cwd')
cd "$cwd"

echo "🔍 Running pre-push checks..."

# Check for uncommitted changes
if [ -n "$(git status --porcelain)" ]; then
  echo "⚠️ You have uncommitted changes" >&2
  echo "Commit or stash them before pushing" >&2
  git status --short >&2
  exit 2
fi

# Run linter
if [ -f "package.json" ] && grep -q '"lint"' package.json; then
  echo "Running linter..."
  if ! npm run lint 2>&1; then
    echo "❌ Linting failed" >&2
    echo "Fix lint errors before pushing" >&2
    exit 2
  fi
  echo "✓ Linting passed"
fi

# Run tests
if [ -f "package.json" ] && grep -q '"test"' package.json; then
  echo "Running tests..."
  if ! npm test 2>&1; then
    echo "❌ Tests failed" >&2
    echo "Fix failing tests before pushing" >&2
    exit 2
  fi
  echo "✓ Tests passed"
fi

# Check for merge conflicts markers
if git grep -qE "^(<<<<<<<|=======|>>>>>>>)" 2>/dev/null; then
  echo "❌ Merge conflict markers found in files" >&2
  git grep -l "^(<<<<<<<|=======|>>>>>>>)" >&2
  exit 2
fi

echo "✓ All pre-push checks passed"

exit 0
```

```bash theme={null}
chmod +x .factory/hooks/pre-push-check.sh
```

### 新しいブランチをプッシュ時にPRを自動作成

フィーチャーブランチをプッシュ時に自動的にPRを開く：

`.factory/hooks/auto-create-pr.sh` を作成：

```bash theme={null}
#!/bin/bash
set -e

input=$(cat)
tool_name=$(echo "$input" | jq -r '.tool_name')
command=$(echo "$input" | jq -r '.tool_input.command // ""')

# Only run after successful git push of a new branch
if [ "$tool_name" != "Bash" ] || ! echo "$command" | grep -qE "^git push.*-u origin"; then
  exit 0
fi

cwd=$(echo "$input" | jq -r '.cwd')
cd "$cwd"

# Check if gh CLI is available
if ! command -v gh &> /dev/null; then
  exit 0
fi

current_branch=$(git branch --show-current)

# Don't create PR for main/master branches
if [[ "$current_branch" =~ ^(main|master|dev|develop)$ ]]; then
  exit 0
fi

# Check if PR already exists
if gh pr view &>/dev/null; then
  echo "ℹ️ PR already exists for this branch"
  exit 0
fi

# Extract issue number from branch name
issue_number=""
if [[ $current_branch =~ ([A-Z]+-[0-9]+) ]]; then
  issue_number="${BASH_REMATCH[1]}"
fi

# Generate PR title from branch name or commits
pr_title="$current_branch"
if [ -n "$issue_number" ]; then
  pr_title="$issue_number: $(echo "$current_branch" | sed -E 's/^[^/]+\/[A-Z]+-[0-9]+-//; s/-/ /g')"
fi

# Create PR
echo "🔄 Creating pull request..."
if gh pr create --title "$pr_title" --body "Closes $issue_number" --web; then
  echo "✓ Pull request created and opened in browser"
else
  echo "⚠️ Could not create PR automatically"
fi

exit 0
```

```bash theme={null}
chmod +x .factory/hooks/auto-create-pr.sh
```

### コミットでの共同著者の強制

コミットに共同著者トレーラーを追加：

`.factory/hooks/add-coauthor.sh` を作成：

```bash theme={null}
#!/bin/bash
set -e

input=$(cat)
tool_name=$(echo "$input" | jq -r '.tool_name')
command=$(echo "$input" | jq -r '.tool_input.command // ""')

# Only modify git commit commands
if [ "$tool_name" != "Bash" ] || ! echo "$command" | grep -qE "^git commit.*-m"; then
  exit 0
fi

# Extract commit message
commit_msg=$(echo "$command" | sed -E 's/.*git commit.*-m[= ]*["\x27]([^"\x27]+)["\x27].*/\1/')

# Check if co-author is already present
if echo "$commit_msg" | grep -qE "Co-authored-by:"; then
  exit 0
fi

# Add factory droid co-author
coauthor="Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>"

# Modify command to include co-author
modified_msg="$commit_msg

$coauthor"

# Return modified command via JSON output
cat << EOF
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "allow",
    "permissionDecisionReason": "Adding co-author to commit",
    "updatedInput": {
      "command": "$(echo "$command" | sed -E "s/(git commit.*-m[= ]*)[\"']([^\"']+)[\"']/\1\"$modified_msg\"/")"
    }
  }
}
EOF

exit 0
```

```bash theme={null}
chmod +x .factory/hooks/add-coauthor.sh
```

## 実際の使用例

### 例1: モノレポコミット検証

コミットが1つのパッケージのみに触れることを確保：

`.factory/hooks/validate-monorepo-scope.sh` を作成：

```bash theme={null}
#!/bin/bash

input=$(cat)
tool_name=$(echo "$input" | jq -r '.tool_name')
command=$(echo "$input" | jq -r '.tool_input.command // ""')

if [ "$tool_name" != "Bash" ] || ! echo "$command" | grep -qE "^git commit"; then
  exit 0
fi

cwd=$(echo "$input" | jq -r '.cwd')
cd "$cwd"

# Get staged files
staged_files=$(git diff --cached --name-only)

if [ -z "$staged_files" ]; then
  exit 0
fi

# Check if changes span multiple packages
packages=$(echo "$staged_files" | grep -E "^(packages|apps)/" | cut -d/ -f1-2 | sort -u)
package_count=$(echo "$packages" | wc -l | tr -d ' ')

if [ "$package_count" -gt 1 ]; then
  echo "❌ Commit spans multiple packages" >&2
  echo "" >&2
  echo "Changed packages:" >&2
  echo "$packages" | sed 's/^/  - /' >&2
  echo "" >&2
  echo "Please commit changes to each package separately for clearer history." >&2
  exit 2
fi

exit 0
```

### 例2: リリース自動化

バージョン変更時にリリースを自動タグ付け：

`.factory/hooks/auto-tag-release.sh` を作成：

```bash theme={null}
#!/bin/bash
set -e

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

# Only run after commits
if [ "$tool_name" != "Bash" ]; then
  exit 0
fi

command=$(echo "$input" | jq -r '.tool_input.command // ""')
if ! echo "$command" | grep -qE "^git commit"; then
  exit 0
fi

cwd=$(echo "$input" | jq -r '.cwd')
cd "$cwd"

# Check if package.json version changed in last commit
if ! git diff HEAD~1 HEAD --name-only | grep -q "package.json"; then
  exit 0
fi

# Check if version field changed
if git diff HEAD~1 HEAD -- package.json | grep -q "^+.*\"version\""; then
  # Get new version
  new_version=$(jq -r '.version' package.json)

  echo "📦 Version bump detected: v$new_version"
  echo "Creating git tag..."

  # Create and push tag
  if git tag "v$new_version" && git push origin "v$new_version"; then
    echo "✓ Created and pushed tag v$new_version"
  fi
fi

exit 0
```

## ベストプラクティス

<Steps>
  <Step title="防止にはPreToolUseを使用">
    悪いコミットが行われる前にブロックします:

    ```bash theme={null}
    # In PreToolUse hook
    if invalid_commit; then
      echo "❌ Cannot proceed" >&2
      exit 2  # Blocks the git commit
    fi
    ```
  </Step>

  <Step title="自動化にはPostToolUseを使用">
    成功したコミット後のフォローアップアクションを自動化します:

    ```bash theme={null}
    # In PostToolUse hook
    if git_commit_successful; then
      update_changelog
      create_pr
    fi
    ```
  </Step>

  <Step title="明確なエラーメッセージを提供">
    何が問題でどう修正するかをユーザーに正確に伝えます:

    ```bash theme={null}
    echo "❌ Commit message must include issue reference" >&2
    echo "Example: feat(auth): add login FAC-123" >&2
    ```
  </Step>

  <Step title="フックを設定可能にする">
    チームが動作をカスタマイズできるようにします:

    ```bash theme={null}
    PROTECTED_BRANCHES="${DROID_PROTECTED_BRANCHES:-main,master,production}"
    REQUIRE_ISSUE_REF="${DROID_REQUIRE_ISSUE:-true}"
    ```
  </Step>

  <Step title="ドライランでフックをテスト">
    実際にコミットせずにテストします:

    ```bash theme={null}
    # Simulate a commit
    echo '{"tool_name":"Bash","tool_input":{"command":"git commit -m \"test\""}}' | \
      .factory/hooks/validate-commit-msg.sh
    ```
  </Step>
</Steps>

## トラブルシューティング

**問題**: 検証が厳しすぎる

**解決策**: エスケープハッチを追加：

```bash theme={null}
# Allow bypass with special prefix
if echo "$commit_msg" | grep -q "^WIP:"; then
  echo "⚠️ WIP commit allowed"
  exit 0
fi
```

**問題**: DroidフックとGit/hooksの両方が実行される

**解決策**: 調整するかどちらかを選択：

```bash theme={null}
# In .git/hooks/pre-commit
if [ -n "$DROID_SESSION" ]; then
  exit 0  # Let Droid hooks handle it
fi
```

**問題**: テストに時間がかかりすぎる

**解決策**: 高速チェックのみ実行：

```bash theme={null}
# Run fast subset of tests
npm run test:unit  # Skip slow integration tests

# Or run in parallel with push
npm test &
git push
```

## 関連項目

* [フックリファレンス](/jp/reference/hooks-reference) - 完全なフック API ドキュメント
* [Get started with hooks](/jp/cli/configuration/hooks-guide) - 基本的なフック入門
* [Code validation](/jp/guides/hooks/code-validation) - コード品質の検証
* [Testing automation](/jp/guides/hooks/testing-automation) - テストの自動化
