Mac miniとcodexで自動リファクタリングPRを量産する仕組み
はじめに
日々のコード管理において、リファクタリングは重要な作業ですが、手動で行うには時間がかかります。そこで、Mac miniとChatGPT Plusを組み合わせて、自動でリファクタリングPRを生成する仕組みを構築したので紹介します。
システム構成
必要な環境
- ChatGPT Plus:codexとcodex cliが使用可能
- Mac mini M1 16GB:自宅サーバーとして24時間稼働
基本的な流れ
- Mac miniでcronを使ってスクリプトを定期実行
- 最新のリリースブランチを自動検出
- codex cliを実行してリファクタリング
- PRを自動作成
- セルフホストランナーでビルド確認
運用の特徴
効率的なフィードバックループ
PRの品質が微妙な場合は、深追いせずにすぐにPRをクローズします。その代わり、iPhoneのChatGPTアプリからcodexに対してAGENTS.mdの更新指示を出すことで、次回のリファクタリング精度を向上させています。
GitHubの公式アプリとChatGPTアプリの組み合わせで、提案のPRのマージ・クローズ・AGENTS.mdの改修のPR作成・マージもiPhoneで完結します。
実際の運用では、1日2〜3のPRがマージされており、継続的なコード改善が行われています。また、失敗したPRから学習してAGENTS.mdを改善することで、普段のcodex cli使用時の精度も向上しました。リファクタリング自体はおまけ程度の位置づけですが、コードの複雑さに目を向けるきっかけとして有効に機能しています。
Swift特有の課題
実際の運用では、ネイティブのiOSアプリの開発に利用しています。
SwiftのConcurrency周りでビルドできないケースが多いため、Mac miniのセルフホストランナーでビルド可能性を確認できる体制を整えています。
メリット
- ネイティブアプリの利便性:codexはネイティブアプリが提供されており使いやすい
- 自然な学習システム:AGENTS.mdが自然に育ち、リファクタリング品質も向上
- 継続的改善:失敗から学習する仕組みが組み込まれている
今後の展望
OpenAI Codex Cloudによると、今後はCLIからクラウドのcodexを実行できるようになる予定のようです。これが実現すれば、cronでAPIを叩くだけで済むようになり、さらにシンプルな構成になると期待しています。
運用上の注意点
cronは失敗しても気づきにくいため、ログ出力を設定しておくことを強く推奨します。
自動化スクリプト
以下が実際に使用しているスクリプトです:
#!/usr/bin/env bash
set -euo pipefail
# Ensure PATH when running under cron (minimal env)
export PATH="/opt/homebrew/bin:/usr/local/bin:${PATH:-/usr/bin:/bin:/usr/sbin:/sbin}"
# ==== 設定 ====
REPO_SLUG="${REPO_SLUG:-noppefoxwolf/repo-name}" # "owner/repo" 形式
REPO_NAME=$(basename "${REPO_SLUG}")
CLONE_DIR="${CLONE_DIR:-}" # 空なら一時ディレクトリにクローン
CODEX_PROMPT=${CODEX_PROMPT:-"可読性を上げるための小さなリファクタリングをしてください"}
CODEX_CMD="codex exec --full-auto \"$CODEX_PROMPT\""
PR_TITLE_PREFIX="${PR_TITLE_PREFIX:-refactor: codex automated changes}"
PR_BODY_EXTRA="${PR_BODY_EXTRA:-This PR was generated by an automated script running Codex refactoring.}"
# ==== 事前チェック ====
command -v git >/dev/null 2>&1 || { echo "git が見つかりません"; exit 1; }
command -v gh >/dev/null 2>&1 || { echo "GitHub CLI(gh) が見つかりません: https://cli.github.com/"; exit 1; }
command -v jq >/dev/null 2>&1 || { echo "jq が見つかりません: brew install jq などで導入してください"; exit 1; }
command -v codex >/dev/null 2>&1 || { echo "codex が見つかりません"; exit 1; }
if ! gh auth status >/dev/null 2>&1; then
echo "gh が未ログインです: gh auth login を実行してください"
exit 1
fi
# ==== クローン ====
if [[ -z "${CLONE_DIR}" ]]; then
WORKDIR="$(mktemp -d)"
else
WORKDIR="${CLONE_DIR%/}"
fi
echo "Working directory: ${WORKDIR}"
if [[ -d "${WORKDIR}/${REPO_NAME}" ]]; then
echo "既存ディレクトリがあるため再利用します"
else
gh repo clone "${REPO_SLUG}" "${WORKDIR}/${REPO_NAME}"
fi
cd "${WORKDIR}/${REPO_NAME}"
# ==== 最新の release ブランチを特定 ====
git fetch --prune origin
# origin にある "release*" のブランチ一覧取得
RELEASE_BRANCHES=()
while IFS= read -r branch; do
RELEASE_BRANCHES+=("$branch")
done < <(git ls-remote --heads origin 'release*' | awk '{print $2}' | sed 's#refs/heads/##g')
if [[ ${#RELEASE_BRANCHES[@]} -eq 0 ]]; then
echo "release ブランチが見つかりませんでした"
exit 1
fi
# "release/*" の */以降をバージョン文字列として自然順ソートして最大を選ぶ
LATEST_RELEASE_VERSION="$(printf "%s\n" "${RELEASE_BRANCHES[@]}" \
| sed -E 's#^release/##' \
| sort -V \
| tail -n1)"
LATEST_RELEASE_BRANCH="release/${LATEST_RELEASE_VERSION}"
echo "Latest release branch: ${LATEST_RELEASE_BRANCH}"
# ==== 派生ブランチ作成 ====
TS="$(date +%Y%m%d-%H%M%S)"
NEW_BRANCH="chore/codex-refactor-${LATEST_RELEASE_VERSION//\//-}-${TS}"
# origin/release/* から新規ブランチを切る
git checkout -b "${NEW_BRANCH}" "origin/${LATEST_RELEASE_BRANCH}"
# ==== codex リファクタ実行 ====
echo "Running Codex: ${CODEX_CMD}"
eval "${CODEX_CMD}"
# ==== 変更があればコミット ====
if git status --porcelain | grep -q .; then
git add -A
COMMIT_MSG="${PR_TITLE_PREFIX} (base: ${LATEST_RELEASE_BRANCH})"
git commit -m "${COMMIT_MSG}"
else
echo "codex による差分はありませんでした。PR 作成をスキップします。"
exit 0
fi
# ==== Push & PR 作成 ====
git push -u origin "${NEW_BRANCH}"
PR_TITLE="${PR_TITLE_PREFIX} (${LATEST_RELEASE_BRANCH})"
PR_BODY=$(cat <<EOF
This PR targets \`${LATEST_RELEASE_BRANCH}\`.
- Source branch: \`${NEW_BRANCH}\`
- Generated at: $(date -u +"%Y-%m-%d %H:%M:%S UTC")
${PR_BODY_EXTRA}
EOF
)
gh pr create \
--title "${PR_TITLE}" \
--body "${PR_BODY}" \
--base "${LATEST_RELEASE_BRANCH}" \
--head "${NEW_BRANCH}" \
--label "refactor" \
--label "automated"
gh pr view --web || true
echo "Done."
cron設定
スクリプトを5時間ごとに実行するcron設定:
% crontab -e
0 */5 * * * (date; /path/make_refactoring_pr.sh) >> /logs/make_refactoring_pr.log 2>&1
この設定により、5時間ごとに自動でリファクタリングPRが生成され、ログファイルに実行結果が記録されます。
Discussion