📓 memotty

Claude CodeとGitHub Issueを使った全自動開発について

注意事項 #

現在多数の方に閲覧されていますが、こちらの記事はまだ試験段階であり、改善の余地はたくさんあると考えています。
使用する際は十分ご注意ください。
このコードを使用したことで発生する不利益については、筆者は一切責任を負いません。
ご了承ください。

準備 #

  • wikiは使用しない
    • docsディレクトリで管理する
  • README.mdにプロジェクト概要を書く
  • GitHub Issueのtemplateを作成する
    • .github/ISSUE_TEMPLATE/配下
  • 先にIssueを作成しておく
    • docs/db-schema.md から docs/er.md を作成
    • ログイン画面作成
    • User一覧作成
    • などなど
  • ghコマンドをインストールする

出先からの実行専用スクリプト #

Priority & issue番号で並び替えして最初のissueを実行 #

  1. 優先順位の検索: P0->P1->P2->P3->none labelの順で検索
  2. 番号順のソート: 同じ優先度内では番号が小さいissueを優先
  3. 自動モード: -a オプションで確認なしで実行
  4. ラベルフィルタ: 追加のラベルで フィルタリング可能
  5. 詳細な進捗表示: 各ステップで状況を表示
  6. Claude Codeの実行履歴を完全保存
#!/bin/bash

# 優先度順で最初のissueを処理し、Claude Codeの履歴も保存するスクリプト
# 使用方法: ./process_issue_with_history.sh [オプション]

set -e

# 色付き出力用の関数
print_info() {
    echo -e "\033[0;36m[INFO]\033[0m $1"
}

print_success() {
    echo -e "\033[0;32m[SUCCESS]\033[0m $1"
}

print_error() {
    echo -e "\033[0;31m[ERROR]\033[0m $1"
}

print_warning() {
    echo -e "\033[0;33m[WARNING]\033[0m $1"
}

# ヘルプメッセージ
show_help() {
    echo "使用方法: $0 [オプション]"
    echo ""
    echo "オプション:"
    echo "  -l, --label <label>     特定のラベルでフィルタリング"
    echo "  -a, --auto             確認なしで自動実行"
    echo "  -n, --no-history       履歴を保存しない"
    echo "  -d, --history-dir      履歴保存ディレクトリ(デフォルト: claude_history)"
    echo "  -h, --help             このヘルプを表示"
    echo ""
    echo "優先度ラベル:"
    echo "  P0: 最優先"
    echo "  P1: 高優先度"
    echo "  P2: 中優先度"
    echo "  P3: 低優先度"
    echo "  (ラベルなし): 最低優先度"
}

# デフォルト値
ADDITIONAL_LABEL=""
AUTO_MODE=false
SAVE_HISTORY=true
HISTORY_DIR="claude_history"

# オプション解析
while span> $; do
    case $1 in
        -l|--label)
            ADDITIONAL_LABEL="$2"
            shift 2
            ;;
        -a|--auto)
            AUTO_MODE=true
            shift
            ;;
        -n|--no-history)
            SAVE_HISTORY=false
            shift
            ;;
        -d|--history-dir)
            HISTORY_DIR="$2"
            shift 2
            ;;
        -h|--help)
            show_help
            exit 0
            ;;
        *)
            print_error "不明なオプション: $1"
            show_help
            exit 1
            ;;
    esac
done

# GitHub CLIの存在確認
if ! command -v gh &> /dev/null; then
    print_error "GitHub CLI (gh) がインストールされていません"
    echo "インストール方法: https://cli.github.com/"
    exit 1
fi

# Claude Codeの存在確認
if ! command -v claude &> /dev/null; then
    print_error "Claude Code がインストールされていません"
    exit 1
fi

# リポジトリ内かチェック
if ! git rev-parse --git-dir > /dev/null 2>&1; then
    print_error "Gitリポジトリ内で実行してください"
    exit 1
fi

# jqの存在確認
if ! command -v jq &> /dev/null; then
    print_error "jq がインストールされていません"
    echo "インストール方法: sudo apt-get install jq (Ubuntu) または brew install jq (Mac)"
    exit 1
fi

print_info "優先度順でissueを検索中..."

# 優先度ラベルの配列
PRIORITY_LABELS=("P0" "P1" "P2" "P3" "")

# 最初に見つかったissueを処理
FOUND_ISSUE=""
FOUND_PRIORITY=""

for priority in "${PRIORITY_LABELS[@]}"; do
    # クエリの構築
    QUERY="--state open"
    
    if [ -n "$priority" ]; then
        QUERY="$QUERY --label $priority"
        current_priority="$priority"
    else
        current_priority="ラベルなし"
    fi
    
    if [ -n "$ADDITIONAL_LABEL" ]; then
        QUERY="$QUERY --label $ADDITIONAL_LABEL"
    fi
    
    print_info "優先度 $current_priority のissueを検索中..."
    
    # issueを取得(番号順でソート)
    ISSUES=$(gh issue list $QUERY --json number,title,state,labels --limit 100 | jq 'sort_by(.number)')
    
    # 最初のissueを取得
    if [ -n "$ISSUES" ] && [ "$ISSUES" != "[]" ]; then
        FOUND_ISSUE=$(echo "$ISSUES" | jq -r '.[0]')
        FOUND_PRIORITY="$current_priority"
        break
    fi
done

# issueが見つからなかった場合
if [ -z "$FOUND_ISSUE" ] || [ "$FOUND_ISSUE" = "null" ]; then
    print_warning "処理可能なissueが見つかりませんでした"
    exit 0
fi

# issue情報を抽出
ISSUE_NUMBER=$(echo "$FOUND_ISSUE" | jq -r '.number')
ISSUE_TITLE=$(echo "$FOUND_ISSUE" | jq -r '.title')

print_success "処理するissueが見つかりました"
echo ""
echo "================================"
echo "優先度: $FOUND_PRIORITY"
echo "Issue #$ISSUE_NUMBER: $ISSUE_TITLE"
echo "================================"

# 詳細情報を取得
ISSUE_BODY=$(gh issue view "$ISSUE_NUMBER" --json body -q .body)
echo "$ISSUE_BODY" | head -n 10
if [ $(echo "$ISSUE_BODY" | wc -l) -gt 10 ]; then
    echo "... (以下省略)"
fi
echo "================================"
echo ""

# 自動モードでない場合は確認
if [ "$AUTO_MODE" = false ]; then
    read -p "このissueを処理しますか? (y/N): " confirm
    if [ "$confirm" != "y" ]; then
        print_info "処理をキャンセルしました"
        exit 0
    fi
fi

# 履歴ディレクトリの準備
if [ "$SAVE_HISTORY" = true ]; then
    mkdir -p "$HISTORY_DIR"
    SESSION_ID="issue_${ISSUE_NUMBER}_$(date +%Y%m%d_%H%M%S)"
    SESSION_DIR="$HISTORY_DIR/$SESSION_ID"
    mkdir -p "$SESSION_DIR"
    
    print_info "セッションID: $SESSION_ID"
    print_info "履歴保存先: $SESSION_DIR"
    
    # Issue情報を保存
    gh issue view "$ISSUE_NUMBER" > "$SESSION_DIR/issue_details.txt" 2>&1
    gh issue view "$ISSUE_NUMBER" --json number,title,body,state,labels > "$SESSION_DIR/issue_data.json"
    
    # メタデータの記録
    cat > "$SESSION_DIR/metadata.json" << EOF
{
  "session_id": "$SESSION_ID",
  "issue_number": $ISSUE_NUMBER,
  "priority": "$FOUND_PRIORITY",
  "start_time": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
  "git_branch": "$(git branch --show-current)",
  "git_commit": "$(git rev-parse HEAD)",
  "user": "$(whoami)",
  "hostname": "$(hostname)"
}
EOF
fi

# 現在のブランチを保存
CURRENT_BRANCH=$(git branch --show-current)

# 変更があるかチェック
if ! git diff --quiet || ! git diff --cached --quiet; then
    print_error "コミットされていない変更があります"
    echo "変更をコミットまたはstashしてから再実行してください"
    exit 1
fi

# ブランチ作成
BRANCH_NAME="issue-$ISSUE_NUMBER"
print_info "ブランチ '$BRANCH_NAME' を作成します"

# mainまたはmasterブランチを取得
if git show-ref --verify --quiet refs/heads/main; then
    BASE_BRANCH="main"
elif git show-ref --verify --quiet refs/heads/master; then
    BASE_BRANCH="master"
else
    print_error "mainまたはmasterブランチが見つかりません"
    exit 1
fi

# 最新の状態を取得
print_info "最新の変更を取得中..."
git checkout "$BASE_BRANCH"
git pull origin "$BASE_BRANCH"

# ブランチが既に存在するかチェック
if git show-ref --verify --quiet "refs/heads/$BRANCH_NAME"; then
    print_warning "ブランチ '$BRANCH_NAME' は既に存在します"
    if [ "$AUTO_MODE" = false ]; then
        read -p "既存のブランチを使用しますか? (y/N): " use_existing
        if [ "$use_existing" = "y" ]; then
            git checkout "$BRANCH_NAME"
        else
            exit 1
        fi
    else
        print_info "既存のブランチをスキップします"
        exit 0
    fi
else
    git checkout -b "$BRANCH_NAME"
fi

# 一時ファイルにissue情報を保存
TEMP_FILE=$(mktemp)
cat > "$TEMP_FILE" << EOF
GitHub Issue #$ISSUE_NUMBER

優先度: $FOUND_PRIORITY
タイトル: $ISSUE_TITLE

説明:
$ISSUE_BODY

---
上記のissueの内容に基づいて、必要な機能を実装してください。
実装が完了したら、変更内容の概要を教えてください。
EOF

# 履歴保存時はプロンプトも保存
if [ "$SAVE_HISTORY" = true ]; then
    cp "$TEMP_FILE" "$SESSION_DIR/claude_prompt.txt"
fi

print_info "Claude Codeで実装を開始します..."
echo ""

# Claude Codeで実装(履歴保存の有無で分岐)
if [ "$SAVE_HISTORY" = true ]; then
    # scriptコマンドを使用してセッション全体を記録
    if span> &; then
        # macOS
        script -q "$SESSION_DIR/claude_session.log" bash -c "
            claude < '$TEMP_FILE' 2>&1 | tee '$SESSION_DIR/claude_output.log'
        "
    else
        # Linux
        script -q -c "claude < '$TEMP_FILE' 2>&1 | tee '$SESSION_DIR/claude_output.log'" "$SESSION_DIR/claude_session.log"
    fi
else
    # 履歴を保存しない場合
    claude < "$TEMP_FILE"
fi

# 一時ファイルを削除
rm "$TEMP_FILE"

echo ""
print_info "実装が完了しました"

# 変更の確認
if git diff --quiet; then
    print_info "変更はありません"
    exit 0
fi

# 変更内容を表示
echo ""
print_info "変更内容:"
git status --short

# 履歴保存(実装後の状態)
if [ "$SAVE_HISTORY" = true ]; then
    git diff > "$SESSION_DIR/git_diff.patch"
    git diff --name-only > "$SESSION_DIR/changed_files.txt"
    git status --short > "$SESSION_DIR/git_status.txt"
fi

# 自動モードの場合は自動的にコミット
if [ "$AUTO_MODE" = true ]; then
    commit_confirm="y"
else
    echo ""
    read -p "変更をコミットしますか? (y/N): " commit_confirm
fi

if [ "$commit_confirm" = "y" ]; then
    git add .
    
    # コミットメッセージの作成
    COMMIT_MESSAGE="feat: implement #$ISSUE_NUMBER - $ISSUE_TITLE

Priority: $FOUND_PRIORITY"
    
    git commit -m "$COMMIT_MESSAGE"
    print_success "コミットが完了しました"
    
    # 履歴にコミット情報を追加
    if [ "$SAVE_HISTORY" = true ]; then
        git rev-parse HEAD > "$SESSION_DIR/commit_hash.txt"
        echo "$COMMIT_MESSAGE" > "$SESSION_DIR/commit_message.txt"
    fi
    
    # PRを作成するか確認
    if [ "$AUTO_MODE" = true ]; then
        pr_confirm="y"
    else
        read -p "Pull Requestを作成しますか? (y/N): " pr_confirm
    fi
    
    if [ "$pr_confirm" = "y" ]; then
        print_info "ブランチをプッシュ中..."
        git push -u origin "$BRANCH_NAME"
        
        # PR作成
        PR_BODY="Closes #$ISSUE_NUMBER

## 優先度
$FOUND_PRIORITY

## 変更内容
Claude Codeを使用してIssue #$ISSUE_NUMBER を実装しました。

## テスト
- [ ] 機能が正常に動作することを確認
- [ ] 既存のテストが通ることを確認
- [ ] 必要に応じて新しいテストを追加"
        
        PR_URL=$(gh pr create \\             --title "Fix #$ISSUE_NUMBER: $ISSUE_TITLE" \\             --body "$PR_BODY" \
            --base "$BASE_BRANCH" \\             --head "$BRANCH_NAME" \
            --web=false)
        
        print_success "Pull Requestを作成しました"
        
        # 履歴にPR情報を追加
        if [ "$SAVE_HISTORY" = true ]; then
            echo "$PR_URL" > "$SESSION_DIR/pr_url.txt"
        fi
    fi
fi

# 履歴保存の最終処理
if [ "$SAVE_HISTORY" = true ]; then
    # 終了時刻を記録
    END_TIME=$(date -u +%Y-%m-%dT%H:%M:%SZ)
    jq --arg end_time "$END_TIME" '. + {end_time: $end_time}' "$SESSION_DIR/metadata.json" > "$SESSION_DIR/metadata_tmp.json"
    mv "$SESSION_DIR/metadata_tmp.json" "$SESSION_DIR/metadata.json"
    
    # サマリーレポートの作成
    cat > "$SESSION_DIR/summary.md" << EOF
# Claude Code実装セッション サマリー

## セッション情報
- セッションID: $SESSION_ID
- Issue番号: #$ISSUE_NUMBER
- 優先度: $FOUND_PRIORITY
- タイトル: $ISSUE_TITLE
- 開始時刻: $(jq -r .start_time "$SESSION_DIR/metadata.json")
- 終了時刻: $END_TIME

## 実行結果
- ブランチ: $BRANCH_NAME
- コミット: $([ -f "$SESSION_DIR/commit_hash.txt" ] && cat "$SESSION_DIR/commit_hash.txt" || echo "未コミット")
- PR: $([ -f "$SESSION_DIR/pr_url.txt" ] && cat "$SESSION_DIR/pr_url.txt" || echo "未作成")

## 変更ファイル
$([ -f "$SESSION_DIR/changed_files.txt" ] && cat "$SESSION_DIR/changed_files.txt" | sed 's/^/- /' || echo "なし")

## Claude Codeの出力(抜粋)
\`\`\`
$([ -f "$SESSION_DIR/claude_output.log" ] && tail -n 20 "$SESSION_DIR/claude_output.log" || echo "ログなし")
\`\`\`

## 詳細ファイル
- 完全なClaude出力: claude_output.log
- セッション記録: claude_session.log
- Git差分: git_diff.patch
- Issue詳細: issue_details.txt
EOF
fi

# 成功時のサマリー
echo ""
echo "================================"
echo "処理完了サマリー"
echo "================================"
echo "優先度: $FOUND_PRIORITY"
echo "Issue: #$ISSUE_NUMBER"
echo "ブランチ: $BRANCH_NAME"
if [ "$SAVE_HISTORY" = true ]; then
    echo "セッションID: $SESSION_ID"
    echo "履歴保存先: $SESSION_DIR"
fi
echo "================================"

print_success "処理が完了しました"

実行方法 #

chmod +x process_top_priority_issue.sh

# 基本的な使用(優先度順で最初のissueを処理)
./process_top_priority_issue.sh

# bugラベルがついた中で最優先のissueを処理
./process_top_priority_issue.sh -l bug

# 自動モード(確認なしで実行)
./process_top_priority_issue.sh -a

# 複数のオプションを組み合わせ
./process_top_priority_issue.sh -l enhancement -a

カスタマイズ可能な部分 #

優先度ラベルを変更したい場合は、スクリプト内の PRIORITY_LABELS配列を編集すること。

# 例:緊急度ベースのラベル
PRIORITY_LABELS=("urgent" "high" "medium" "low" "")

# 例:重要度ベースのラベル
PRIORITY_LABELS=("critical" "important" "normal" "")

自宅で実行するバージョン #

出先と中身はほぼ同じで、コミットせずClaude Codeが実装したものを目視確認していくときようのやーつ。

#!/bin/bash

# Claude Codeの試行履歴を保存しながら実装するスクリプト
# 使用方法: ./implement_with_history.sh <issue番号>

set -e

# 色付き出力用の関数
print_info() {
    echo -e "\033[0;36m[INFO]\033[0m $1"
}

print_success() {
    echo -e "\033[0;32m[SUCCESS]\033[0m $1"
}

print_error() {
    echo -e "\033[0;31m[ERROR]\033[0m $1"
}

# 引数チェック
if [ $# -eq 0 ]; then
    print_error "Issue番号を指定してください"
    echo "使用方法: $0 <issue番号>"
    exit 1
fi

ISSUE_NUMBER=$1

# 履歴ディレクトリの作成
HISTORY_DIR="claude_history"
mkdir -p "$HISTORY_DIR"

# セッション用のディレクトリ作成
SESSION_ID="issue_${ISSUE_NUMBER}_$(date +%Y%m%d_%H%M%S)"
SESSION_DIR="$HISTORY_DIR/$SESSION_ID"
mkdir -p "$SESSION_DIR"

print_info "セッションID: $SESSION_ID"
print_info "履歴保存先: $SESSION_DIR"

# GitHub CLIとClaude Codeの存在確認
if ! command -v gh &> /dev/null; then
    print_error "GitHub CLI (gh) がインストールされていません"
    exit 1
fi

if ! command -v claude &> /dev/null; then
    print_error "Claude Code がインストールされていません"
    exit 1
fi

# Issue情報の取得と保存
print_info "Issue #$ISSUE_NUMBER の情報を取得中..."
gh issue view "$ISSUE_NUMBER" > "$SESSION_DIR/issue_details.txt" 2>&1

# Issue情報を構造化して保存
gh issue view "$ISSUE_NUMBER" --json number,title,body,state,labels > "$SESSION_DIR/issue_data.json"

# メタデータの記録
cat > "$SESSION_DIR/metadata.json" << EOF
{
  "session_id": "$SESSION_ID",
  "issue_number": $ISSUE_NUMBER,
  "start_time": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
  "git_branch": "$(git branch --show-current)",
  "git_commit": "$(git rev-parse HEAD)",
  "user": "$(whoami)",
  "hostname": "$(hostname)"
}
EOF

# Claudeへの入力を準備
ISSUE_TITLE=$(gh issue view "$ISSUE_NUMBER" --json title -q .title)
ISSUE_BODY=$(gh issue view "$ISSUE_NUMBER" --json body -q .body)

# Claude用のプロンプトを作成
cat > "$SESSION_DIR/claude_prompt.txt" << EOF
GitHub Issue #$ISSUE_NUMBER

タイトル: $ISSUE_TITLE

説明:
$ISSUE_BODY

---
上記のissueの内容に基づいて、必要な機能を実装してください。
実装が完了したら、変更内容の概要を教えてください。
EOF

print_info "Claude Codeで実装を開始します..."
echo "すべての出力は $SESSION_DIR/claude_output.log に保存されます"
echo ""

# scriptコマンドを使用してClaude Codeのセッション全体を記録
if span> &; then
    # macOS
    script -q "$SESSION_DIR/claude_session.log" bash -c "
        claude < '$SESSION_DIR/claude_prompt.txt' 2>&1 | tee '$SESSION_DIR/claude_output.log'
    "
else
    # Linux
    script -q -c "claude < '$SESSION_DIR/claude_prompt.txt' 2>&1 | tee '$SESSION_DIR/claude_output.log'" "$SESSION_DIR/claude_session.log"
fi

# 実装後の変更を記録
print_info "変更内容を記録中..."

# git diffを保存
git diff > "$SESSION_DIR/git_diff.patch"
git diff --name-only > "$SESSION_DIR/changed_files.txt"
git status --short > "$SESSION_DIR/git_status.txt"

# 終了時刻を記録
END_TIME=$(date -u +%Y-%m-%dT%H:%M:%SZ)
jq --arg end_time "$END_TIME" '. + {end_time: $end_time}' "$SESSION_DIR/metadata.json" > "$SESSION_DIR/metadata_tmp.json"
mv "$SESSION_DIR/metadata_tmp.json" "$SESSION_DIR/metadata.json"

# サマリーレポートの作成
cat > "$SESSION_DIR/summary.md" << EOF
# Claude Code実装セッション サマリー

## セッション情報
- セッションID: $SESSION_ID
- Issue番号: #$ISSUE_NUMBER
- タイトル: $ISSUE_TITLE
- 開始時刻: $(date)
- 終了時刻: $(date)

## 変更ファイル
$(cat "$SESSION_DIR/changed_files.txt" | sed 's/^/- /')

## Claude Codeの出力(抜粋)
\`\`\`
$(tail -n 20 "$SESSION_DIR/claude_output.log")
\`\`\`

## 詳細ファイル
- 完全なClaude出力: claude_output.log
- セッション記録: claude_session.log
- Git差分: git_diff.patch
- Issue詳細: issue_details.txt
EOF

print_success "実装が完了しました"

echo ""
echo "================================"
echo "セッションサマリー"
echo "================================"
echo "セッションID: $SESSION_ID"
echo "保存先: $SESSION_DIR"
echo ""
echo "主要ファイル:"
echo "  - claude_output.log: Claude Codeの出力"
echo "  - claude_session.log: 完全なセッション記録"
echo "  - git_diff.patch: 変更内容の差分"
echo "  - summary.md: セッションサマリー"
echo "================================"

# 変更がある場合は表示
if [ -s "$SESSION_DIR/git_diff.patch" ]; then
    echo ""
    print_info "変更されたファイル:"
    cat "$SESSION_DIR/changed_files.txt"
    echo ""
    echo "次のステップ:"
    echo "  1. cat $SESSION_DIR/summary.md      # サマリーを確認"
    echo "  2. git diff                          # 変更内容を確認"
    echo "  3. git add .                         # ステージング"
    echo "  4. git commit -m \"feat: implement #$ISSUE_NUMBER\""
fi

実行方法 #

chmod +x implement_with_history.sh

# 基本的な使用(実装のみ)
./implement_with_history.sh

# ブランチも作成せず、現在のブランチで実装
./implement_with_history.sh -b

# 実装内容をログファイルに保存
./implement_with_history.sh -o implementation_log.md

# 自動モード + ログ保存 + ブランチ作成なし
./implement_with_history.sh -a -b -o log.md

# 特定のラベルでフィルタリング
./implement_with_history.sh -l bug