| name | automation-release |
| purpose | 準備する |
| description | Use when 本リポジトリの新規リリースを作成するとき。「リリースして」「/automation-release」「suno-helper をリリースしたい」「ext-v0.2.2 を出したい」で発動。Python 本体(vX.Y.Z)と Chrome 拡張(ext-vX.Y.Z)を判定し prepare / publish に自動分岐。グローバル /release は使わない。下流追従は /automation の update mode、拡張のインストールは /extension |
前後工程
前工程: なし
後工程: /automation --update
委譲先: なし
成果物
書き込む: pyproject.toml, uv.lock, CHANGELOG.md, docs/release-notes/v<version>.md, docs/release-notes/ext-v<version>.md, extensions/<name>/package.json
読み込む: CHANGELOG.md, pyproject.toml, .github/workflows/release-extensions.yml
Hard Gates / 完了条件
- Phase R / Phase 0 / Phase E0 の状態判定を表示し、
AskUserQuestion の「進行 / 中止」2択で承認されるまで branch作成・version変更・tag pushへ進まない。
- 「前提」のいずれかが不成立なら、記載した復旧手順を案内して停止する。前提が満たされるまで後続Phaseへ進まない。
- extension verify は repository root で
bash .claude/skills/automation-release/references/verify-extensions.sh [<name>] を実行し、exit 0を必須とする。non-zeroならreleaseを停止する。
- tag push前に tag名・対象commit SHA・対象版数を表示し、取消不能な外部反映操作であることを警告して
AskUserQuestion の「実行 / 中止」2択で承認を得る。承認前にpushしない。
- Python prepare完了 = version / CHANGELOG / uv.lockが同期したPRを作成済み。Python publish完了 = tagとGitHub Releaseを作成済み。post-release note の PR は別の完了状態として扱い、skip や PR pending を release publish の失敗へ巻き戻さない。
- extension prepare完了 = package.jsonのversion差分だけを含むPRを作成し、verifyと差分ガードがPASS。extension publish完了 = merge commitへのtag、workflow成功、Releaseのzip asset 3件を確認済み。post-release note の PR は別の完了状態として扱い、skip や PR pending を extension publish の失敗へ巻き戻さない。
Overview
まず依頼内容から Python 本体 release(vX.Y.Z)と Chrome 拡張 release(ext-vX.Y.Z)のどちらかを判定し(Phase R)、次にリポジトリ状態で prepare / publish に自動分岐する:
Python 本体(pyproject.toml::version が対象):
- prepare:
main の [Unreleased] を吸い上げて release/vX.Y.Z ブランチを切り、pyproject.toml::version を bump し、CHANGELOG.md を昇格し、リリース PR を作成する
- publish: マージ済みリリース PR を tag push + GitHub Release 化し、リリースブランチを削除する
Chrome 拡張(extensions/<name>/package.json::version が対象。対象拡張: suno-helper / distrokid-helper / community-helper):
- extension prepare:
release/ext-v<VER> ブランチで対象拡張の package.json::version のみ bump し、release-extensions.yml と同一契約の local verify(install / build / zip)を通してリリース PR を作成する
- extension publish: マージ済み PR の merge commit に
ext-v<VER> tag を push し、Release Extensions workflow の成功と Release asset(<name>-<VER>-chrome.zip)を確認する
責務分離:
- 本スキル = リリース実施(prepare + publish、Python 本体 / 拡張の両系列)
- 下流追従 = 各チャンネルリポジトリで
/automation --update スキル(本リポジトリで配布)が CHANGELOG.md / GitHub Release 本文を読み取って実施
- 拡張の配布・インストール側は
/extension(Release asset を読む消費側。tag ext-v* / asset <name>-<version>-chrome.zip の命名契約を本スキルから変えない)
- グローバル
/release スキルは廃止済みで存在しない。本リポジトリのリリースは常に本スキルを使う
前提
以下を確認し、満たさなければ案内して停止する:
- 実行場所が youtube-automation リポジトリ本体(
pyproject.toml::[project].name が youtube-channels-automation)であること。下流チャンネルリポジトリでの追従は /automation --update を使う
gh CLI がインストール済みで認証済み(gh auth status が green)であること。未認証なら gh auth login を依頼して停止する
- prepare(Python 本体)の場合、
CHANGELOG.md の [Unreleased] セクションに内容が書き溜められていること。空の場合は prepare を中止する(各 PR 時点で書き溜める運用が前提)
- Python 本体のバージョン管理は
pyproject.toml::version を 唯一のソース とする(src/youtube_automation/__init__.py は importlib.metadata 経由で自動追従)。配布は git+https + tag pin(PyPI 公開しない)
- extension release のバージョン管理は
extensions/<name>/package.json::version を 唯一のソース とし、Python 本体とは完全独立(契約: references/release-contracts.md)。extension release では pyproject.toml / uv.lock / CHANGELOG.md 昇格に一切触らない
- extension release の場合、
references/verify-extensions.sh <name> がexit 0を返すこと。non-zeroなら出力された原因を解消するまで停止する
- extension release の install / build / zip は Nix extensions shell(Node 24 / pnpm 11.15.1)経由で実行する。ambient
node / pnpm は使わず、extensions/<name>/pnpm-workspace.yaml::allowBuilds を有効に保つため --ignore-workspace も使わない
Instructions
実行場所: youtube-automation リポジトリのルート(/Users/mba/02-yt/00-automation)
Phase R: リリース種別判定
依頼文から Python 本体 release か extension release かを最初に判定する。extension release と判定した場合、Python 本体の pyproject.toml bump flow(Phase 0〜2)には進まない。
| 依頼の形 | 種別 | 進み先 |
|---|
拡張名 + バージョン(suno-helper をリリースしたい v0.2.2 / community-helper v0.1.1) | extension | Phase E0 へ |
ext-v プレフィックス(ext-v0.2.2 を出したい) | extension | Phase E0 へ |
上記以外(リリースして / v5.6.0 を出して 等、拡張名も ext-v も含まない) | Python 本体 | Phase 0 へ |
判定基準: 依頼文に suno-helper / distrokid-helper / community-helper(extensions/ 配下の拡張ディレクトリ名)または ext-v が含まれれば extension release。どちらの手掛かりも無ければ Python 本体 release。判定に迷う依頼(拡張名なしで 0.x 系の版数だけ指定された等)は AskUserQuestion で種別を確認してから進む。
Phase 0: 状態判定(Python 本体)
以下のコマンドでリポジトリ状態を取得し、prepare / publish / no-op を判定する:
git fetch origin --tags --prune
latest_tag=$(git tag --sort=-v:refname | head -1)
main_sha=$(git rev-parse origin/main)
tag_sha=$(git rev-parse "${latest_tag}^{commit}" 2>/dev/null || echo "")
open_release_branch=$(git ls-remote --heads origin "release/v*" | head -1)
判定ルール:
| 状態 | 条件 | フェーズ |
|---|
| prepare | open_release_branch 無し かつ main_sha != tag_sha かつ publish 条件に該当しない(main HEAD が bump コミットでない) | Phase 1 へ |
| publish | リモートに release/v<X.Y.Z> ブランチ無し かつ main に bump コミットが含まれる かつ tag 未作成 | Phase 2 へ |
| publish (alt) | リモートに release/v<X.Y.Z> ブランチ有り かつ PR が merged 済み | Phase 2 へ(マージ済みブランチが削除前のケース) |
| no-op | main_sha == tag_sha(既にリリース済み) | 終了 |
| abort | open release branch 有りで PR が未マージ | 「リリース PR がまだマージされていません」と案内して終了 |
判定結果をユーザーに伝え、AskUserQuestion で進行確認する(誤判定時の脱出口を残す)。
Phase 1: prepare
1-1. バージョン判定
CHANGELOG.md::[Unreleased] の内容を読み、semver bump 種別を提案する:
### Removed 有り、または本文中に BREAKING / 破壊的変更 記述 → major(検索は 大小文字を問わない。**Breaking:** / breaking(scope): 表記を取りこぼさないこと)
### Added 有り → minor
### Fixed のみ(または ### Changed のみで挙動変更が patch レベル)→ patch
参照: references/version-rules.md
AskUserQuestion で提案版数を表示し、ユーザーが上書き可能にする。
Unreleased が空の場合は abort:
awk '/^## \[Unreleased\]/{flag=1; next} /^## \[/{flag=0} flag' CHANGELOG.md
出力が実質空(空行のみ)なら「Unreleased に内容がありません。リリースする変更がないようです」と案内して中止。
1-2. release ブランチ作成
git checkout main
git pull origin main
git checkout -b "release/v${VER}"
事前に git status --porcelain で working tree がクリーンであることを確認。dirty なら abort。
1-3. pyproject.toml::version の bump
Edit ツールで pyproject.toml の version = "X.Y.Z" 行のみ差し替える。
他のフィールドや CLI 一覧には触らない。
1-3a. Python module 移動監査
-
previous tag から HEAD までの production module 差分を rename 検出付きで取得する。
previous_tag=$(git tag --list 'v[0-9]*' --sort=-v:refname | head -1)
test -n "${previous_tag}" || { echo "ERROR: previous Python tag が見つかりません"; exit 1; }
git diff --find-renames --name-status "${previous_tag}..HEAD" -- src/youtube_automation
-
rename として検出されなかった deleted / added module は、内容・責務・git history を確認して 手動 D/A pairing する。package marker を含む各候補について、旧 module が HEAD に残る互換 facade か、facade 無し移動かを分類する。
-
facade 無し移動のうち、旧 path が documented / exported / known-downstream-use のいずれかなら下流影響対象とする。判断根拠と old/new path を監査結果に残す。
-
対象 move ごとに [Unreleased] の ### Migration を照合する。Python module 移動: あり、互換 facade: なし、旧→新の fully-qualified youtube_automation.* row が全件揃わなければならない。1件でも未記載なら release prepare を停止し、CHANGELOG を補完してから再監査する。
-
対象となる facade 無し移動が 0 件なら、監査結果と ### Migration に Python module 移動: なし を明示する。
1-4. CHANGELOG.md の昇格
references/changelog-promotion.md の 3 段階手順をそのまま実行する。
日付は date +%Y-%m-%d で取得して [VER] - YYYY-MM-DD のフォーマットに埋める。
Migration セクション存在チェック: [Unreleased] 配下に ### Migration セクションが無い場合は warning を出し、AskUserQuestion で「Migration セクション無しで続行するか」を確認する。Migration セクションは下流の /automation --update が 所要時間の目安 / local fix 衝突注意 を抽出する契約上の入力源(詳細: references/release-contracts.md)。
awk '/^## \[Unreleased\]/{flag=1; next} /^## \[/{flag=0} flag' CHANGELOG.md \
| grep -q '^### Migration' || echo "WARNING: Unreleased に Migration セクションがありません"
1-5. uv.lock の同期
pyproject.toml::version を bump した後、uv.lock::youtube-channels-automation.version が古い値のまま残らないよう 必ず uv lock を実行して lock も同 commit に含める。
uv lock
pyproject_ver=$(grep -E '^version = ' pyproject.toml | head -1 | sed -E 's/version = "(.+)"/\1/')
lock_ver=$(grep -A1 'name = "youtube-channels-automation"' uv.lock | grep '^version' | head -1 | sed -E 's/version = "(.+)"/\1/')
if [ "${pyproject_ver}" != "${lock_ver}" ]; then
echo "ERROR: pyproject.toml (${pyproject_ver}) と uv.lock (${lock_ver}) が一致しません"
exit 1
fi
これを省くと、後続の uv sync を叩いた別 PR で uv.lock の 1 行差分(version)が機械的に発生し、無関係な PR に混入する(#515)。uv が未導入の環境では nix develop --command uv lock または direnv exec . uv lock で呼び出す。
1-6. Chrome 拡張の release 前検証
3拡張を単一ソースの検証スクリプトで検証し、exit 0を確認する:
bash .claude/skills/automation-release/references/verify-extensions.sh
検証ロジックとPASS/FAIL条件は references/verify-extensions.sh が単一ソース。non-zeroならreleaseを中止し、原因を解消してから再実行する。
1-7. commit
git add pyproject.toml uv.lock CHANGELOG.md
git commit -m "chore(release): v${VER} リリース PR"
commit メッセージは日本語 Conventional Commits 規約(CLAUDE.md「開発ワークフロー」参照)に準拠(chore(release): プレフィックス + 日本語)。uv.lock を必ず同 commit に含めること(1-5 のドリフト再発防止策)。
1-8. push + PR 作成
git push -u origin "release/v${VER}"
PR 作成は gh pr create を直接呼ぶ(リリース PR は機械的な昇格 diff のため self-review 付きの通常 PR フローは不要)。以下は quoted heredoc(<<'EOF')のため本文中の ${VER} / $(date +%Y-%m-%d) はシェル展開されない。実行前に本文のプレースホルダを実値へ置換すること:
gh pr create --base main --title "chore(release): v${VER}" --body "$(cat <<'EOF'
## Summary
v${VER} のリリース PR。
- `pyproject.toml::version` を v${VER} に bump
- `CHANGELOG.md` の `[Unreleased]` を `[${VER}] - $(date +%Y-%m-%d)` に昇格
## Release notes preview
(CHANGELOG.md の [${VER}] セクションをここに貼り付け)
## Next steps
1. このリリース PR をレビュー → マージ
2. マージ後、`/automation-release` を再実行して publish フェーズに進む(tag + GitHub Release 自動作成)
3. publish 後、各チャンネルリポジトリで `/automation --update` を実行すると CHANGELOG.md / Release 本文を読み取って追従できる
EOF
)"
PR 番号を控え、ユーザーに「リリース PR を作成しました。レビュー後にマージ → 再度 /automation-release を実行してください」と案内して prepare 終了。
Phase 2: publish
2-1. 前提検証
git checkout main
git pull origin main
VER=$(grep -E '^version = ' pyproject.toml | head -1 | sed -E 's/version = "(.+)"/\1/')
if git ls-remote --tags origin "v${VER}" | grep -q "v${VER}"; then
echo "Tag v${VER} already exists on origin. Aborting."
exit 1
fi
Phase 0 で git fetch origin --tags --prune 済みなので再 fetch は省略(Phase 2 のみで呼ばれた場合は別途 fetch する)。main の HEAD commit が chore(release): v<VER> であることも確認。
2-2. tag push
git tag "v${VER}"
if ! git push origin "v${VER}"; then
echo "Tag push rejected. Likely already exists upstream. Skipping to Release creation."
fi
2-3. GitHub Release 作成
gh release create "v${VER}" --generate-notes --title "v${VER}"
--generate-notes で PR 一覧が自動生成される。これだけで運用上は問題ない(下流の /automation --update 側が CHANGELOG.md fallback で ### Migration を抽出するため)。
リリース本文の先頭に CHANGELOG.md::[VER] セクションも含めたい場合は publish 後に gh release edit で追記する:
section=$(awk -v ver="${VER}" '
$0 ~ "^## \\[" ver "\\]" { flag = 1; next }
/^## \[/ { flag = 0 }
flag
' CHANGELOG.md)
auto=$(gh release view "v${VER}" --json body --jq .body)
gh release edit "v${VER}" --notes "${section}
---
${auto}"
2-4. 公開リリースノート案の生成と内容確認
gh release create の成功を確認してから、canonical contract の references/release-notes-authoring.md を読み、対象 version の CHANGELOG section を公開向けに変換する。
TAG="v${VER}"
NOTE_PATH="docs/release-notes/v${VER}.md"
POST_RELEASE_BRANCH="docs/release-notes-v${VER}"
section=$(awk -v ver="${VER}" '
$0 ~ "^## \\[" ver "\\]" { flag = 1; next }
/^## \\[/ { flag = 0 }
flag
' CHANGELOG.md)
test -n "${section}" || { echo "ERROR: CHANGELOG.md に ${TAG} section がありません"; exit 1; }
section の運営者影響を全件保持し、docs/release-notes/v${VER}.md を作る。canonical reference の frontmatter、見出し順、同一 tag link、内部実装・issue・PR 番号の除外を満たすこと。既存ファイルがある場合は上書きせず停止し、差分を確認して再開方法をユーザーへ提示する。
生成後、ファイル全文と git diff -- "${NOTE_PATH}" を確認し、生成内容・対象 tag・post-release branch・変更 path をユーザーへ提示する。期待する変更 path は ${NOTE_PATH} だけとし、別 path の差分があれば承認へ進まず原因を解消する。
2-5. post-release PR の承認
AskUserQuestion で「PR 作成 / 非承認 / skip」の選択を得る。承認前に commit / push / pull request 作成を行わない。main へ直接 push しない。
- 内容修正を求められたら生成内容を修正して再提示し、同じ承認 gate に戻る。
- 非承認 / skip ではノートを commit せず、GitHub Release publish は完了扱いとする。
references/release-notes-authoring.md と ${NOTE_PATH} を使う手動作成手順を報告し、Phase 2-7 の release branch cleanup へ進む。
- 承認された場合だけ Phase 2-6 へ進む。
2-6. post-release 専用 branch と pull request
既存 branch / PR を破壊・重複作成しないため、最初に local・remote・GitHub の状態を確認する。
if git show-ref --verify --quiet "refs/heads/${POST_RELEASE_BRANCH}" \
|| git ls-remote --exit-code --heads origin "${POST_RELEASE_BRANCH}" >/dev/null 2>&1; then
echo "ERROR: 既存 post-release branch: ${POST_RELEASE_BRANCH}"
gh pr list --state all --head "${POST_RELEASE_BRANCH}" --json number,state,url
echo "既存 branch / pull request を削除・上書きせず、内容を照合して retry してください"
else
git checkout -b "${POST_RELEASE_BRANCH}" origin/main
fi
既存 post-release branch を検出した場合は自動処理を停止し、既存 pull request の URL または branch の照合・retry 手順を報告する。GitHub Release publish は完了したまま、Phase 2-7 の release branch cleanup は必ず続行する。
新規 branch を作れた場合は references/publish-checklist.md の「post-release note の local gates」を記載順にそのまま実行する。non-zero なら commit / push せず、失敗した gate と retry 手順を報告する。
全 gate が green の場合だけ commit、push、main 向け PR 作成へ進む。
git add "${NOTE_PATH}"
git commit -m "docs(release-notes): ${TAG} の公開ノートを追加する"
git push -u origin "${POST_RELEASE_BRANCH}"
PR_URL=$(gh pr create --base main --head "${POST_RELEASE_BRANCH}" \
--title "docs(release-notes): ${TAG}" \
--body "${TAG} の公開リリースノート。Cloudflare Pages preview と required checks を確認後に merge する。")
pull request では Cloudflare Pages preview と branch protection の required checks の lint / test を通す。production site は PR merge 後に更新されるため、この時点では site は PR pending として報告する。
2-7. リリースブランチのクリーンアップ
git push origin --delete "release/v${VER}" 2>/dev/null || true
git branch -D "release/v${VER}" 2>/dev/null || true
PR マージ時に GitHub 側で自動削除されているケースもあるため、エラーは無視。
2-8. 完了報告
✅ v${VER} のリリースが完了しました。
GitHub Release: https://github.com/daiki-beppu/youtube-automation/releases/tag/v${VER}
生成 path: docs/release-notes/v${VER}.md
post-release PR: ${PR_URL または skip / retry 状態}
site は PR pending: Cloudflare Pages preview と required checks の確認待ち
merge 後の公開 URL: https://youtube-automation-release-notes.pages.dev/v${VER}/
次の選択肢:
- 各チャンネルリポジトリで `/automation --update` を実行すれば CHANGELOG.md / Release 本文から累積影響を要約して追従可能
非承認 / skip、生成失敗、local gate 失敗、既存 branch 検出でも GitHub Release publish 自体は完了として同じ URL を報告する。PR が無い場合は理由と手動作成手順を、PR 作成途中の失敗では重複作成しない retry 手順を併記する。
Phase E0: 状態判定(extension)
git fetch origin --tags --prune
latest_ext_tag=$(git tag --list 'ext-v*' --sort=-v:refname | head -1)
open_ext_branch=$(git ls-remote --heads origin "release/ext-v*" | head -1)
判定ルール:
| 状態 | 条件 | フェーズ |
|---|
| extension prepare | open_ext_branch 無し かつ ext-v<VER> tag 未作成 | Phase E1 へ |
| extension publish | release/ext-v<VER> の PR が merged 済み かつ ext-v<VER> tag 未作成 | Phase E2 へ |
| no-op | ext-v<VER> tag が origin に既に存在 | 終了(Release asset の確認だけなら E2-4 を単独再実行してよい) |
| abort | open な release/ext-v* ブランチ有りで PR が未マージ | 「拡張リリース PR がまだマージされていません」と案内して終了 |
判定結果をユーザーに伝え、AskUserQuestion で進行確認する(誤判定時の脱出口を残す)。
tag 版数の決定: ext-v* は3拡張共通の単一系列(references/release-contracts.md)。原則、bump する拡張の新バージョンをそのまま tag 版数に使う。ただし要求版数が latest_ext_tag の版数以下になる場合は tag だけ系列の次番号へ進め、AskUserQuestion で tag 版数を確認する(前例: ext-v0.2.3 で distrokid-helper を 0.2.1 に bump)。この場合 Release asset 名は tag 版数ではなく package.json 版数(例: distrokid-helper-0.2.1-chrome.zip)になる。
Phase E1: extension prepare
E1-1. release ブランチ作成
git checkout main
git pull origin main
git status --porcelain
git checkout -b "release/ext-v${VER}"
E1-2. package.json::version の bump
Edit ツールで extensions/<name>/package.json の "version": "X.Y.Z" 行のみ差し替える。他のフィールド(packageManager / dependencies / scripts)、他の拡張、pyproject.toml / uv.lock / CHANGELOG.md には触らない。
E1-3. local verify(release-extensions.yml と同一契約)
対象拡張を単一ソースの検証スクリプトで検証し、exit 0を確認する:
bash .claude/skills/automation-release/references/verify-extensions.sh <name>
このスクリプトは .github/workflows/release-extensions.yml と同じ Nix extensions shell(Node 24 / pnpm 11.15.1)を使い、pnpm install --frozen-lockfile → pnpm build → pnpm zip を実行する。ambient node / pnpm や --ignore-workspace を使わない。
検証ロジックとPASS/FAIL条件は references/verify-extensions.sh が単一ソース。対象拡張の期待名 zip が唯一の1件であることと、対象 lockfile に差分がないことまで確認し、non-zeroならreleaseを中止する。
差分ガード(PASS/FAIL): verify 完了後、version 以外の意図しない差分が無いことを確認する:
git status --porcelain
git diff -- "extensions/<name>/package.json"
FAIL(それ以外の差分が出た)場合は 停止し、原因と復旧手順をユーザーに表示する:
- 原因と復旧手順は
references/extension-release-checklist.md のケースA/Bに従う。
E1-4. commit + push + PR 作成
git add "extensions/<name>/package.json"
git commit -m "chore(<name>): ext-v${VER}"
git push -u origin "release/ext-v${VER}"
gh pr create --base main --title "chore(<name>): ext-v${VER}" --body "(bump 内容 X.Y.Z → ${VER} と local verify 結果を記載)"
PR 本文には bump 内容(旧版数 → ${VER})と local verify 結果(zip 生成確認・差分ガード PASS)を記載する。CHANGELOG.md の昇格は行わない(extensions/ は CHANGELOG ゲート対象外。拡張の変更履歴は Release notes が担う)。
「拡張リリース PR を作成しました。CI green を確認してマージ → 再度 /automation-release を実行してください」と案内して extension prepare 終了。
Phase E2: extension publish
E2-1. merge 状態の確認(worktree footgun 対応)
リリース PR のマージに gh pr merge <N> --merge --delete-branch を使った場合、worktree 環境では remote merge 成功後の local checkout 後処理(git checkout main)が fatal: 'main' is already used by worktree ... で失敗し、コマンド全体が non-zero を返す。これは remote merge の失敗ではない。exit code で成否を判断せず、必ず remote の PR state を確認する:
gh pr view <N> --json state,mergeCommit,mergedAt
state == "MERGED" → remote merge は成功している。mergeCommit.oid を控えて E2-2 へ進む(gh pr merge を再実行しない)
state == "OPEN" → 本当にマージされていない。失敗理由(CI 未 pass / conflict / レビュー未承認)を確認・解消してから再実行
- local 側の checkout 後処理の失敗は無視してよい(worktree では main を checkout できないのが正常。remote branch 削除だけ E2-5 で補完する)
E2-2. merge commit へ tag push
tag は origin/main の HEAD ではなく PR の merge commit に打つ(マージ後に main が進んでいても正しい commit を指すため):
merge_sha=$(gh pr view <N> --json mergeCommit -q .mergeCommit.oid)
git fetch origin --tags
if git ls-remote --tags origin "ext-v${VER}" | grep -q "ext-v${VER}"; then
echo "Tag ext-v${VER} already exists on origin. Skipping to E2-3."
else
git tag "ext-v${VER}" "${merge_sha}"
git push origin "ext-v${VER}"
fi
tag push の実行前に、tag 名・対象 commit SHA・対象拡張と版数を表示し、AskUserQuestion で実行 / 中止の 2 択確認を取る(tag push は Release Extensions workflow を起動する外部反映操作。承認されるまで push しない)。
E2-3. Release Extensions workflow の成功確認
tag push で .github/workflows/release-extensions.yml が起動する。成功するまで監視する:
run_id=$(gh run list --workflow release-extensions.yml --limit 1 --json databaseId -q '.[0].databaseId')
gh run watch "${run_id}" --exit-status
失敗した場合は gh run view "${run_id}" --log-failed でログを確認する。ビルド失敗なら修正 PR を main にマージ後、tag を打ち直す(git push origin :refs/tags/ext-v${VER} で remote tag 削除 → git tag -d ext-v${VER} → 新しい merge commit へ再 tag)。
E2-4. Release asset の確認
assets=$(gh release view "ext-v${VER}" --json assets -q '.assets[].name')
zip_count=$(printf '%s\n' "${assets}" | awk '/\.zip$/{count++} END{print count+0}')
test "${zip_count}" -eq 3
test "$(printf '%s\n' "${assets}" | grep -Ec '^suno-helper-[0-9]+\.[0-9]+\.[0-9]+-chrome\.zip$')" -eq 1
test "$(printf '%s\n' "${assets}" | grep -Ec '^distrokid-helper-[0-9]+\.[0-9]+\.[0-9]+-chrome\.zip$')" -eq 1
test "$(printf '%s\n' "${assets}" | grep -Ec '^community-helper-[0-9]+\.[0-9]+\.[0-9]+-chrome\.zip$')" -eq 1
PASSはzip assetが合計3件で、suno-helper-<version>-chrome.zip、distrokid-helper-<version>-chrome.zip、community-helper-<version>-chrome.zip が各1件の場合のみ。件数過不足・重複・別名zipがあれば停止する。
E2-5. extension 公開リリースノート案の生成と内容確認
E2-4 が PASS した後だけ、同一 tag の GitHub Release body を取得する。Python 本体の CHANGELOG section を入力に使わない。
EXT_TAG="ext-v${VER}"
release_body=$(gh release view "ext-v${VER}" --json body --jq .body)
test -n "$(printf '%s' "${release_body}" | tr -d '[:space:]')" \
|| { echo "ERROR: ${EXT_TAG} の Release body が空です"; exit 1; }
EXT_NOTE_PATH="docs/release-notes/ext-v${VER}.md"
EXT_POST_RELEASE_BRANCH="docs/release-notes-ext-v${VER}"
Release body が空なら公開ノート生成へ進まず、references/extension-release-checklist.md の retry 手順を報告する。body が取得できたら canonical contract の references/release-notes-authoring.md に従い、運営者影響を全件保持して docs/release-notes/ext-v${VER}.md へ変換する。既存ファイルがあれば上書きせず停止する。
生成後、ファイル全文と git diff -- "${EXT_NOTE_PATH}" を確認し、生成内容・対象 tag・post-release branch・変更 path をユーザーへ提示する。期待する変更 path は ${EXT_NOTE_PATH} だけとし、別 path の差分があれば承認へ進まない。
E2-6. extension post-release PR の承認
AskUserQuestion で「PR 作成 / 非承認 / skip」の選択を得る。承認前に commit / push / pull request 作成を行わない。main へ直接 push しない。
- 修正依頼では内容を修正して再提示し、同じ承認 gate に戻る。
- 非承認 / skip ではノートを commit せず、extension publish は完了扱いとする。同一 tag の Release body、canonical authoring reference、
${EXT_NOTE_PATH} を使う手動作成手順を報告し、E2-8 の cleanup へ進む。
- 承認された場合だけ E2-7 へ進む。
E2-7. extension post-release 専用 branch と pull request
if git show-ref --verify --quiet "refs/heads/${EXT_POST_RELEASE_BRANCH}" \
|| git ls-remote --exit-code --heads origin "${EXT_POST_RELEASE_BRANCH}" >/dev/null 2>&1; then
echo "ERROR: 既存 extension post-release branch: ${EXT_POST_RELEASE_BRANCH}"
gh pr list --state all --head "${EXT_POST_RELEASE_BRANCH}" --json number,state,url
echo "既存 branch / pull request を削除・上書きせず、内容を照合して retry してください"
else
git checkout -b "${EXT_POST_RELEASE_BRANCH}" origin/main
fi
既存 extension post-release branch を検出した場合は自動処理を停止する。既存 pull request があれば URL と state を報告して重複作成せず、無ければ tag・生成 path・diff を照合する retry 手順を案内する。extension publish は完了したまま、E2-8 の extension release branch cleanup は必ず続行する。
新規 branch では references/publish-checklist.md の「post-release note の local gates」を記載順に実行する。non-zero なら commit / push せず、失敗した gate と retry 手順を報告する。全 gate が green の場合だけ次へ進む。
git add "${EXT_NOTE_PATH}"
git commit -m "docs(release-notes): ${EXT_TAG} の公開ノートを追加する"
git push -u origin "${EXT_POST_RELEASE_BRANCH}"
EXT_PR_URL=$(gh pr create --base main --head "${EXT_POST_RELEASE_BRANCH}" \
--title "docs(release-notes): ${EXT_TAG}" \
--body "${EXT_TAG} の公開リリースノート。Cloudflare Pages preview と required checks を確認後に merge する。")
pull request では Cloudflare Pages preview と branch protection の required checks の lint / test を通す。production site は PR merge 後に更新されるため、この時点では site は PR pending として報告する。
E2-8. クリーンアップと完了報告
git push origin --delete "release/ext-v${VER}" 2>/dev/null || true
git branch -D "release/ext-v${VER}" 2>/dev/null || true
✅ ext-v${VER} のリリースが完了しました。
Tag: ext-v${VER}(merge commit に push 済み)
GitHub Release: https://github.com/daiki-beppu/youtube-automation/releases/tag/ext-v${VER}
Assets: ${assets}(3拡張の asset)
統一 `ext-v*` 系列: ext-v${VER}
merge commit tag: ${merge_sha}
生成 path: docs/release-notes/ext-v${VER}.md
post-release PR: ${EXT_PR_URL または skip / retry 状態}
site は PR pending: Cloudflare Pages preview と required checks の確認待ち
merge 後の公開 URL: https://youtube-automation-release-notes.pages.dev/ext-v${VER}/
次のステップ:
- 利用者への告知はチャットで Release URL を共有(ADR 0011。自動アップデート通知は無し)
- 手元 Chrome の拡張更新は `/extension --update`
非承認 / skip、Release body 欠落、local gate 失敗、既存 branch 検出でも tag・workflow・3 zip assets が確認済みなら extension publish 自体は完了として報告する。PR が無い場合は理由と手動作成手順を、途中失敗では重複作成しない retry 手順を併記する。
Gotchas
- Unreleased 空での実行: prepare Phase 1-1 で必ず Unreleased の中身を確認。空のままバージョンだけ上がる事故を防ぐ
- release ブランチが既に存在:
git ls-remote --heads origin "release/v${VER}" で衝突確認。あれば「前回 prepare 後にマージされず残っている」「他者が並行作業中」のいずれかなので、手動確認を促して abort
pyproject.toml::version と tag の不一致: publish Phase 2-1 で必ず突き合わせ。prepare をスキップして手で bump した場合の事故を防ぐ
__init__.py の独立 bump: バージョンは importlib.metadata 経由で pyproject.toml を読むので __init__.py を編集してはいけない。grep '__version__' src/youtube_automation/__init__.py で importlib.metadata ベースのままであることを確認
- main が prepare 中に進む: 他者が並行で main にマージしてもリリース PR は固定 SHA から枝分かれしているので影響なし。後乗せ機能は次回リリースに自動で乗る。ただし PR mergeable conflict が出たら rebase が必要
- tag だけ先に push してしまった場合: GitHub Release 作成(2-3)を再実行すれば idempotent(gh release create が既存 tag を拾う)
--generate-notes が空: 前回 tag から PR が無い場合、自動生成本文が空になる。下流の /automation --update 側が CHANGELOG.md fallback で抽出するため publish 時点では問題視しない
uv.lock の version 乖離: pyproject.toml だけ bump して uv.lock を同期し忘れると、別 PR で uv sync を叩いた瞬間に機械的な 1 行差分が無関係な PR に混入する(#515 の既往)。prepare Phase 1-5 で 必ず uv lock を実行し、bump コミットに uv.lock も含めること。uv が未導入なら nix develop --command uv lock で囲む
- extension 依頼を Python 本体と誤判定: 依頼に
suno-helper / distrokid-helper / community-helper / ext-v が含まれるのに Phase 0 に進むと pyproject.toml が誤 bump される。Phase R の判定表に従い、迷ったら AskUserQuestion
gh pr merge --delete-branch の non-zero(worktree footgun): worktree 環境では remote merge 成功後の local checkout 後処理が fatal: 'main' is already used by worktree ... で失敗し non-zero になる。remote merge 失敗と誤認して merge を再実行しない。E2-1 の通り gh pr view <N> --json state,mergeCommit で remote state を確認し、MERGED なら tag push へ進む
pnpm install --frozen-lockfile の失敗: version bump 自体では lockfile は乖離しない。失敗=依存差分の混入なので、リリースとは切り離して lockfile 同期の修正 PR を先に main へ入れる
Rules
- このスキル自体の編集は takt 経由 NG(CLAUDE.md 規約: skill 編集は通常の Claude Code 対話セッションで)
src/youtube_automation/__init__.py は 直接編集禁止(importlib.metadata 経由の動的読み込みのため、版数は pyproject.toml を bump するだけで追従する)
- リリース PR の commit メッセージは
chore(release): v<VER> リリース PR 固定(日本語 Conventional Commits 準拠 + 検索容易性)
release/v<VER> ブランチ命名は固定(state detection と publish クリーンアップが依存)
- prepare 1-4 で
Migration セクション欠落を warning する(下流の /automation --update が 所要時間 / local fix 衝突注意 を抽出する契約上の入力源)
- prepare 1-5 で 必ず
uv lock を実行し、uv.lock の version を pyproject.toml::version と同期させる(#515 再発防止)。bump コミットに uv.lock を含めず main にマージするのは禁止
- 状態判定(Phase R / Phase 0 / Phase E0)の結果は
AskUserQuestion でユーザー確認してから次に進む(誤判定時の脱出口)
- extension release は
extensions/<name>/package.json::version のみを変更する。pyproject.toml / uv.lock / CHANGELOG.md 昇格には触らない(バージョン系列は完全独立。references/release-contracts.md)
release/ext-v<VER> ブランチ命名は固定(Phase E0 の状態判定と E2-5 のクリーンアップが依存)
- extension の commit / PR タイトルは
chore(<name>): ext-v<VER> 固定(日本語 Conventional Commits 準拠 + 検索容易性)
- extension のlocal verifyは
references/verify-extensions.sh を単一ソースとし、workflow契約を変える場合は同スクリプトと同時に更新する
ext-v<VER> tag は PR の merge commit(gh pr view <N> --json mergeCommit)に打つ。tag ext-v* / asset <name>-<version>-chrome.zip の命名契約は /extension が読む側で依存しているため変えない
- prepare 1-6 で 必ず
references/verify-extensions.sh を引数なしで実行し、exit 0を確認する
Cross References
references/prepare-checklist.md — prepare 実行前のチェックリストとエッジケース
references/publish-checklist.md — publish 実行前のチェックリストとエッジケース
references/extension-release-checklist.md — extension prepare / publish 実行前のチェックリストとエッジケース
references/verify-extensions.sh — Nix toolchain / frozen install / build / zip / asset / lockfileを検証する単一ソース
references/version-rules.md — semver bump 判定ルール
references/changelog-promotion.md — CHANGELOG.md 昇格手順
references/release-contracts.md — Python Migration producer と extension GitHub Release 配布の実行契約
.github/workflows/release-extensions.yml — extension の install / build / zip 契約(local verify はこれと同一コマンド列で実行する)
extensions/README.md — 拡張の開発フローと release 添付方針
/automation --update(下流チャンネルリポジトリ)— publish 後の追従スキル
/extension — Release asset を読む消費側スキル(tag / asset 命名契約の依存先)
- CLAUDE.md「開発ワークフロー」— commit メッセージ規約(日本語 Conventional Commits)