Afresto Next のデプロイ
スタック: バックエンド Go (push → GHCR → Watchtower 約5分) · Web React (Vite → Cloudflare Worker、手動 wrangler deploy) · モバイル RN/Expo は別リポジトリ <repo-mobile> (EAS OTA)。本番デプロイは常にユーザーの許可を得てから(毎回承認を得ること)。
🔴 ルール #1 — このリポジトリは多数のエージェントタブで頻繁に使用されます
git status には他のセッションの変更ファイルが頻繁に表示されます。そのため:
git add -a/git commit -a/git add .は禁止 — 他のセッションの途中作業をコミットに巻き込んでしまいます。- 選択的にステージング し、確認:
git diff --cached --name-onlyで自分のファイルだけが含まれていることを確認してください(よく間違って含まれるファイル:web/src/pages/ChatPage.tsx、backend/internal/db/dbgen/models.go(自分で再生成したものでない場合))。 - 2つのセッションで共有されているファイル(例: ExamPage)の一部だけをステージしたい場合:
stash/resetは避けてください(他のエージェントの作業領域を奪います)。代わりに Git インデックスを blob 経由で編集:git show :path > tmp→tmpから相手の hunk を削除 →SHA=$(git hash-object -w tmp)→git update-index --cacheinfo 100644,$SHA,path。ディスク上のファイルは変更されません。 - 🚨 Bash の cwd ツールは前回の呼び出しから
cd .../web(または worktree)に固定されたままになることがあります。その後git add backend/...を実行するとpathspec did not matchで失敗するか(または間違ったツリーをステージしてしまう)。選択的ステージの前にcd <repo-root> &&を実行してください(または絶対パスを使用)。git diff --cached --name-onlyが正しいルート相対パス(../backendやsrc/...ではない)を表示していることを確認してください。 - デプロイ後:
git statusで他のセッションの作業ファイルが無事であることを確認してください。
Go 品質ゲート(バックエンドコミット前)
Laravel の Pint + PHPStan に相当する Go 用のチェック。バックエンドファイルをステージする前にローカルで実行:
- フォーマット:
cd backend && gofmt -l .→ 空であること。出力がある場合 →gofmt -w .(自動修正)、結果をステージ。(pint --testに相当) - ビルド + vet:
go build ./... && go vet ./...が正常終了すること。 - 静的解析(ターゲット):
golangci-lint run ./...(gofmt/govet/staticcheck/errcheck/ineffassign/unused/misspell をラップ)。phpstan analyseに相当。 - ⚠️ ステータス: CIゲートはまだ有効ではありません。
backend/.golangci.ymlも.github/workflows/ci.ymlも未作成で、staticcheckは古いままです。したがって、フォーマット+ビルド+vetは現時点では手動で行います。CIでの適用と完全な設定はdocs/plan/00-tooling-quality-gate.mdで計画中。生成コードinternal/db/dbgenは lint 対象から除外します。 - テストは変更に伴って実行 → afresto-testing スキル(
go test ./...;TEST_DATABASE_URLがない場合はインテグレーションテストはスキップ)。
🔴 ルール #00 — 意味のある作業はまず GitHub Issue に(2026年9月5日以降)
ルール #0(ブランチとPR)の前に適用: コードに一切触れる前に。
目的は単なる形式的なものではありません。チャットで来たリクエストは二者だけが知る情報です — チームはそれを読めず、オーナーはアサインする場所がなく、モジュールは誰が何をやっているかの 記録を残しません。
Issue が必須のケース
新機能 · 学校に見える変更 · DBスキーマの変更 · プロダクト判断が必要なもの · 複数のPRにまたがる作業。
Issue が不要なケース
明白で狭いバグ · テキスト/ラベルの修正 · ドキュメント · PRレビューコメントからの直接のフォローアップ。
迷ったら Issue を作成してください。コストは低く、クローズもできます。この閾値がルールを生かします: 些細なリクエストがすべて Issue になれば、人々は黙ってそれを無視し始め、一度無視しても影響がなければルールは死にます。
エージェントの行動
- コードを十分に調査し、「初期調査結果」を埋め、ブロッカーを見つける — 推測しないこと。
- Issue を作成
.github/ISSUE_TEMPLATE/pekerjaan.mdテンプレートを使用し、ラベルmodul:*を付ける(これがモジュールごとの記録を形成します)。未決定事項がある場合はbutuh-keputusanも追加。 - ストップ。 コーディングしない、ブランチを作成しない、自分自身をアサインしない。 アサインはオーナーの行為です。チームにもフィードバックやアイデアの修正を行う余地が必要です。
- Issue 番号をオーナーに報告し、未決定の判断事項があればそれも報告する。
🔑 オーナーはこれをスキップできます。「今すぐやれ」と言うことで — Issue は記録として作成され、作業は続行されます。このルールはデフォルトを規定するものであり、拘束するものではありません。
作業中に新たに発見されたブロッカー
続行前に知らせる: Issue にコメント(チームも見られるように)かつ オーナーに伝える。判断によって作業が停止する場合 → butuh-keputusan ラベルを追加。黙って自分で解釈して続行しないこと — それは「完了」していても間違っている作業を生み出します。
ループを閉じる
PR は本文に Closes #<番号> を含める必要があります。これにより Issue → PR → 人が結びつき、gh issue list --label modul:ujian --state all がそのモジュールで誰が何をやったかの記録となります。この行がないと、証跡が途切れます。
🔴 ルール #0 — main に直接コミット/プッシュしないこと(4名チーム、2026年7月25日以降)
すべての作業はブランチ + プルリクエスト経由。レビュアーのみが main にマージします(マージ = デプロイのトリガー)。
- 開始前:
git checkout main && git pull— 新しいマイグレーションがある場合、まずマイグレーションを実行します。 - テーマ別ブランチを作成/チェックアウトしてからコードに触れる:
- 機能 →
feat/<名前>· バグ →bugfix/<名前>· Issue のあるバグ → 新しいブランチ + Issue の調査。
- 機能 →
- 選択的コミット →
git push -u origin <ブランチ>(main にはしない)。 mainへの PR を開く:ghにログインしている場合はgh pr create。未ログインの場合はユーザーに URL を提供https://github.com/Afresto-Next/next/compare/main...<ブランチ>?expand=1。- レビュアーが承認 & マージ → デプロイ。PR を自分でマージしないこと。
- 以下の「バックエンドプッシュ / Webデプロイ」の部分は、PR が main にマージされた後に実行されるメカニズム(通常はデプロイ担当者による)であり、main への直接プッシュを許可するものではありません。
- スキルの変更も PR 経由(CODEOWNERS)。
手順の順序
1. コミット(Bash ツール = Git Bash / sh。PowerShell は不可)
- 複数行メッセージ: ヒアドキュメント
git commit -q -F - <<'EOF' … EOF。@'…'@は使用しないこと(PowerShell のヒアストリングで、件名が@ ...に汚染されます)。 - メッセージの末尾:
Co-Authored-By: Claude Opus 4.8 <[email protected]>。 - 警告 「CRLF will be replaced by LF」 = 正常。無視してください。
- 自分のタスクブランチ(
feat/*·bugfix/*)にコミット。mainにはしない(ルール #0 参照)。ユーザーが要求したときにコミットします。
🔴 git add -A / git add <dir>/ は禁止 — すでに3回ユーザーファイルを巻き込んでいます
このリポジトリには多数の未追跡のユーザー作業ファイルが保存されています: docs/bskap/(画像+PDF)、
docs/analisis butir soal/、elibrary/、cloudflare/**/.wrangler/(アカウントキャッシュ)。一度 -A を実行すると、
すべてが含まれます。
実際の事例: git add docs/ で BSKAP の PDF/xls/画像が含まれた(7月25日 & 8月2日)。git add -A で 446ファイル(wrangler-account.json を含む)が含まれた(8月9日)。
git add path/ke/berkas1 path/ke/berkas2 # 常に1つずつ
git commit -q -F - <<'EOF'
...
EOF
git show --name-only --format="" HEAD | wc -l # 意図した数と一致するか?
数がずれている場合: git reset --soft HEAD~1 && git reset、正しくステージし直す。すでに自分のブランチにプッシュ済みの場合、git push --force-with-lease は安全です。
2. DB マイグレーション — TIP コミットに必須
.github/workflows/migrate.yml は git diff --name-only HEAD~1 HEAD(最新コミットのみ)でマイグレーションを検出し、「Build & Push Docker Images」の成功後に実行されます(workflow_run)。
- プッシュにマイグレーションが含まれる場合 → マイグレーションはTIP コミットになければならない(マイグレーションとコードが1つのコミットにあれば安全)。
- マイグレーションはセルフホストランナーVMでビルド後に自動実行。
run --rm migrateで同期 → エラー時はジョブが完全に失敗(静かに失敗しない)。 - マイグレーションの確認: そのコミットの Actions → 「Migrate DB」が緑色になっていることを確認(
ghCLI 利用可能:gh run list --workflow="Migrate DB (self-hosted)" --limit 1— エージェントは自分で確認でき、ユーザーに依頼する必要なし)。Watchtower は次のポーリング(約5分)でapiイメージを交換。ほとんどの場合、マイグレーション完了直後に行われます。 - マイグレーションをプッシュする前に、ローカルDB(v102 = 本番)で psql を使用して
BEGIN; … ROLLBACK;を実行し SQL を確認してください。afresto-db-change スキル / エンジニアリングトラップ §4 を参照。
🔴 マイグレーション番号: ブランチ作成時ではなく、マージ直前に再確認
チームメンバーがブランチが開いている間に別のマイグレーションシリーズをマージする可能性があります。番号が重複すると
goose が sortAndConnectMigrations でパニックを起こします — SQLエラーではないため、誤った判断をしやすいです。
✅ データ破損はありません: パニックは収集時に発生し、一つも実行される前です。
実際の事例(2026年8月9日): ブランチ作成時に 00138 を選択。マージ時にはチームはすでに 00138–00140 を使用していました。最初の修正では 00140 と名前を付け、まだ衝突 — 再び推測したためです。
推測せず、数えましょう:
git pull # まず必須。チームのマイグレーションを確認するため
MAX=$(ls backend/migrations/ | sed 's/_.*//' | sort -n | tail -1)
NEXT=$(printf "%05d" $((10#$MAX + 1)))
ls backend/migrations/ | sed 's/_.*//' | sort | uniq -d # 空であること
すでにコミットしてしまった場合: git mv で新しい番号にリネームし、小さなPRを作成、マージ、そして Actions タブから 「Migrate DB (self-hosted)」 ワークフローを再実行します。
3. バックエンドプッシュ(PR が main にマージされた後 — ルール #0 参照)
PR マージ → main が更新される → GHCR → Watchtower 約5分でイメージ交換。バックエンドに変更がない場合、Watchtower を待つ必要はありません。(開発時: git push -u origin <ブランチ> して PR — main にはプッシュしないこと)。
⚠️ バックエンドマージ後: 502 エラーを確認(すでに6回発生)
api/worker コンテナの再作成は、しばしば全停止を引き起こします — すべてのコンテナが Up 状態でも、すべてのルートが 502。実質的には、バックエンドのマージごとにサイコロを振るようなものです。
curl -s -o /dev/null -w "%{http_code}\n" --max-time 15 https://next.afresto.co/api/v1/auth/me
# 401 = 正常(認証要求) · 502 = 停止
502 の場合 → Actions → 「Ops — 復旧 API」 → restart-docker(SSH不要、1分未満で復旧)。新しくデプロイされた機能を調査しないこと: 502 + コンテナ Up = Dockerデーモンの再起動。
詳細: docs/ops-auto-pulih.md。
4. Webデプロイ — クリーンなワークツリーから(多数のエージェントタブがあるため)
npm run build はワーキングツリー全体を読み取ります → 他のセッションの途中作業も含まれて公開されます。したがって、自分のコミットからワークツリーでビルド&デプロイ:
rm -rf /c/wtd 2>/dev/null; git worktree add --detach /c/wtd <SHA-コミット>
cd /c/wtd && git status --short # 空であること(クリーン)
cd /c/wtd/web && npm ci && npm run build
cd /c/wtd/cloudflare/web-worker && npx wrangler deploy
cd <repo-projectTwo> && git worktree remove --force /c/wtd && git worktree prune
- 🔥 ワークツリーのパスは短く(
/c/wtd) — スクラッチパッド内の長いDKIカレンダーPDFファイル名と組み合わさると、チェックアウト時に Windows エラー 「Filename too long」 が発生します。 - 必要に応じてバンドルを確認:
grep -rl "<フィーチャーの目印>" /c/wtd/web/dist/assets/*.js— ただし偽陽性に注意(一般的な目印は他のフィーチャーにも存在する可能性があります)。 - Cloudflare はアセットを約20秒キャッシュ → Ctrl+F5 でテスト。(200だがサイズ約397バイト = フォールバック。実際のアセットではありません)。
5. モバイル OTA — 別リポジトリ、--environment 必須
cd "<repo-mobile>"
npx eas update --branch preview --environment preview --message "…" --non-interactive
- 🔥
--environment必須(preview/production)— これがないと間違った環境に更新されるか、失敗します。 - モバイルリポジトリにはGitHubリモートがあります(
Afresto-Next/afresto-next-mobile)→ ルール #0 が適用: ブランチ → PR → マージ。mainに直接プッシュしないこと。 - OTA は JS/アセットのみ。ネイティブモジュールの変更 → OTA 経由ではクラッシュ → 再ビルド必須。
🔥 eas update はコミットではなくワーキングツリーを送信
手順4の npm run build とまったく同じ — ディレクトリ全体がバンドルされ、
他のセッションの途中作業も含まれます。公開結果の行 Commit <hash>* の * マーク = ダーティツリー。
公開直前に必須 — 出力し、結果の Commit 行と照合:
echo "publish from: $(git branch --show-current) @ $(git log --oneline -1 --format=%h)"
git log --oneline HEAD..origin/main # 空 = 他の誰かの作業が取り残されていない
mainから公開すること。フィーチャーブランチからではない。ブランチから公開すると、すでにマージされた他の人の作業がチャンネルから失われます — OTA はバンドル全体を置き換え、パッチ適用はしません。- 本番環境: まず他のセッションの未コミット変更を退避する(
git stash push -- <ファイル>…git stash pop)。本番環境はレビューされていないコードを含んではいけません。
🚨 作業中にブランチが移動する可能性があります(多数のエージェントタブ、1つのクローン)
2026年8月20日の事例: preview公開は明らかに main @ d0938fa から。次のコマンドで HEAD が別のブランチ @ 8126a50 にあることが判明 — 別のセッションが同じクローンでブランチを移動しました。本番環境が3PR分古いコードを実行し、エラーは一つもありませんでした。
➡️ ブランチがコマンド間で同じであると想定しないこと。 毎回出力して確認(上記のコマンド)。 ➡️ 真の防止策: ワークツリーからの OTA。手順4のWebデプロイと同様に — 他のセッションはブランチを移動できません:
rm -rf /c/wtm 2>/dev/null; git worktree add --detach /c/wtm <SHA-コミット>
cd /c/wtm && git status --short # 空であること
npm ci && npx eas update --branch <ch> --environment <env> --message "…" --non-interactive
cd <repo-mobile> && git worktree remove --force /c/wtm && git worktree prune
🪤 ワークツリーのパスは短く(/c/wtm)— 手順4と同じ理由。
🪤 ワークツリーでの npm ci は時間がかかります。小さなOTAの場合、メインクローンでそのまま実行しても構いませんただし、上記のブランチ+コミットの確認を実行し、結果が一致することを確認してください。
🏢 GHCR & GitHub 組織 — リポジトリは現在 Afresto-Next 組織にあります
リポジトリ(next、afresto-next-mobile、afresto-claude-skills)は ristology/* から Afresto-Next 組織に移行しました(2026年7月20日)。完全な移行手順書: docs/runbook-transfer-next-org.md。
- 🚨 リポジトリの組織への移動 = GHCRパスが所有者を変更 → Watchtower が静かに停止**。CI はイメージタグ
ghcr.io/${GITHUB_REPOSITORY_OWNER}/afresto-next-{api,web,migrate}を使用 — 所有者はリポジトリ所有者に従います…(以下略)