Loading...
Loading...
Phase 分割された GitHub Issue ツリーを新規作成するスキル。「イシューツリー作って」「タスクを Phase 別に Issue 化」「Issue ツリーを起票して」で使用。 要件・タスク一覧を受け取り、タスク分解(4h 粒度)→ Phase 分割 → ルート(トラッキング)issue → Phase 親 issue → 子 issue の階層を sub_issues API で紐付け。 phase ラベル付与・ルート issue 本文の Phase 別表生成まで自動化。任意の phase 指定で部分起票にも対応。 ツリーの棚卸し・更新は update-issue-tree、実装消化は implement-issue-tree を参照。
npx skill4agent add fandhe-ai/agent-cli-skills create-issue-tree--phase--root--milestone--rootcreate-issue-tree "ユーザー認証機能を実装する"
create-issue-tree requirements.md
create-issue-tree requirements.md --phase 2 --root 123
create-issue-tree requirements.md --milestone "v2.0"ghgh auth status--phasechore(global): 全 open issue の Phase 別トラッキング (YYYY-MM-DD)--rootROOT_NUMBER# --root で渡された Issue 番号を実際の値で代入する(実行時に Claude が置き換える)
# 例: --root 123 が指定された場合 → ROOT_NUMBER="123" / --root 未指定の場合 → ROOT_NUMBER=""
ROOT_NUMBER="<--root で渡された Issue 番号(未指定なら空文字)>"
# --root 指定時のみ: OPEN でなければ milestone 操作より前に中止する
# (新規ツリー作成で ROOT_NUMBER が空の場合はこの検証をスキップする)
if [[ -n "${ROOT_NUMBER}" ]]; then
ROOT_STATE=$(gh issue view "${ROOT_NUMBER}" --json state --jq '.state')
if [[ "${ROOT_STATE}" != "OPEN" ]]; then
echo "エラー: ルート issue #${ROOT_NUMBER} は OPEN ではありません (state: ${ROOT_STATE})。中止します。"
exit 1
fi
fi--milestoneMILESTONEMILESTONE_COUNT=$(gh api "repos/{owner}/{repo}/milestones?state=all" --jq 'length')
# MILESTONE_COUNT が 0 かつ --milestone 未指定なら、このステップの残りをスキップする--milestone--root--milestoneMILESTONE--root--milestone--milestone--rootMILESTONE=$(gh issue view "${ROOT_NUMBER}" --json milestone --jq '.milestone.title // empty')MILESTONE--rootMILESTONEMILESTONEgh api "repos/{owner}/{repo}/milestones" --jq '.[] | select(.state=="open") | .title'gh issue create --milestonegh api --method POST "repos/{owner}/{repo}/milestones" -f "title=${MILESTONE}"--rootMILESTONEif [[ -n "${ROOT_NUMBER}" && -n "${MILESTONE}" ]]; then
gh issue edit "${ROOT_NUMBER}" --milestone "${MILESTONE}"
fi--root--phaseROOT_NUMBER--root# MILESTONE が空でなければ --milestone を付与する(Step 2.5 で決定済み)
ROOT_ARGS=(--title "chore: 全 open issue の Phase 別トラッキング ($(date +%Y-%m-%d))")
if [[ -n "${MILESTONE}" ]]; then
ROOT_ARGS+=(--milestone "${MILESTONE}")
fi
# gh issue create は issue URL を stdout に出力する(--json 非対応)。URL 末尾から番号を抽出する
ROOT_URL=$(gh issue create "${ROOT_ARGS[@]}" \
--body "$(cat <<'EOF'
## 概要
全 open issue を Phase 別に 1 ツリーへ整理する。各 Phase 親 issue を sub-issues として紐付け。
## Phase 別実装計画
| Phase | 親 issue | 直下 | 総 open 件数 |
|-------|----------|------|-------------|
| (作成後に更新) | | | |
## 運用
- 新規 issue は起票時に Phase 親へ紐付ける
- 実行順は sub-issues リスト順が正
- closed 親の下に open issue を残置しない
- implement-issue-tree が post-order DFS で消化可能な構造を維持する
EOF
)")
ROOT_NUMBER=$(printf '%s' "${ROOT_URL}" | grep -oE '[0-9]+$')
echo "ルート issue: ${ROOT_NUMBER}"PHASE--phasePHASE# 処理対象の Phase 番号(--phase 指定時はその番号、全 Phase 起票時はループ中の番号)
PHASE=1
# phase ラベルが存在しないリポジトリでは issue 作成が失敗するため、必ず事前作成する
# (作成済みの場合は失敗を無視して続行する)
gh label create "phase:${PHASE}" --color "0075ca" 2>/dev/null || true
# --root 再実行時の重複防止: ルート直下に同じ Phase の open な親が既にあれば再利用する
# closed な親は再利用しない(closed 親の下に open issue を残置しない運用ルールと整合させる)。
# phase ラベルだけでは同ラベルの一般 issue がルート直下に混在した場合に誤マッチするため、
# Phase 親のタイトル規約(feat(phase-N): 接頭辞)でも絞り込む。
# 直下が 100 件を超える場合に備えてページングで全件走査する
PHASE_NUMBER=""
PAGE=1
while true; do
RESULT=$(gh api \
"repos/{owner}/{repo}/issues/${ROOT_NUMBER}/sub_issues?per_page=100&page=${PAGE}")
PHASE_NUMBER=$(echo "${RESULT}" | jq -r \
"[.[] | select(.state == \"open\"
and (.title | startswith(\"feat(phase-${PHASE}):\"))
and any(.labels[]?; .name == \"phase:${PHASE}\"))][0].number // empty")
COUNT=$(echo "${RESULT}" | jq 'length')
if [[ -n "${PHASE_NUMBER}" || "${COUNT}" -lt 100 ]]; then break; fi
PAGE=$((PAGE + 1))
done
[[ -n "${PHASE_NUMBER}" ]] && echo "既存の Phase 親 issue を再利用: #${PHASE_NUMBER}"
# 再利用する Phase 親が milestone 未設定の場合はここで揃える(milestone 導入前に
# 作られたツリーへの追記で、新規の子だけに milestone が付く不整合を防ぐ。冪等)
if [[ -n "${PHASE_NUMBER}" && -n "${MILESTONE}" ]]; then
gh issue edit "${PHASE_NUMBER}" --milestone "${MILESTONE}"
fifeat(phase-N):PHASE_NUMBER# MILESTONE が空でなければ --milestone を付与する(Step 2.5 で決定済み)
PHASE_ARGS=(--title "feat(phase-${PHASE}): Phase ${PHASE} 基盤整備" --label "phase:${PHASE}")
if [[ -n "${MILESTONE}" ]]; then
PHASE_ARGS+=(--milestone "${MILESTONE}")
fi
# Phase 親 issue を作成(URL 末尾から番号を抽出)
PHASE_URL=$(gh issue create "${PHASE_ARGS[@]}" \
--body "$(cat <<'EOF'
## 概要
この Phase の実装タスクをまとめる親 issue。
## タスク一覧
| Issue | タイトル | 分解 |
|-------|---------|------|
| (子 issue 作成後に更新) | | |
EOF
)")
PHASE_NUMBER=$(printf '%s' "${PHASE_URL}" | grep -oE '[0-9]+$')
# ルートへ紐付け。sub_issue_id は issue 番号ではなく database id を渡す(GitHub sub-issues API 仕様)
PHASE_ID=$(gh api "repos/{owner}/{repo}/issues/${PHASE_NUMBER}" --jq '.id')
gh api \
--method POST \
"repos/{owner}/{repo}/issues/${ROOT_NUMBER}/sub_issues" \
-F "sub_issue_id=${PHASE_ID}"# MILESTONE が空でなければ --milestone を付与する(Step 2.5 で決定済み)
CHILD_ARGS=(--title "feat: タスク名" --label "phase:${PHASE}")
if [[ -n "${MILESTONE}" ]]; then
CHILD_ARGS+=(--milestone "${MILESTONE}")
fi
# 子 issue を作成(URL 末尾から番号を抽出)。PHASE は Step 4 で設定した番号を引き継ぐ
CHILD_URL=$(gh issue create "${CHILD_ARGS[@]}" \
--body "$(cat <<'EOF'
## 概要
...
## 受け入れ条件
- [ ] 条件1
- [ ] 条件2
EOF
)")
CHILD_NUMBER=$(printf '%s' "${CHILD_URL}" | grep -oE '[0-9]+$')
# Phase 親へ紐付け(sub_issue_id は database id)
CHILD_ID=$(gh api "repos/{owner}/{repo}/issues/${CHILD_NUMBER}" --jq '.id')
gh api \
--method POST \
"repos/{owner}/{repo}/issues/${PHASE_NUMBER}/sub_issues" \
-F "sub_issue_id=${CHILD_ID}"
# 4h 超の場合は sub-issue を作成して子 issue へ紐付け(同じく MILESTONE を付与)
SUB_ARGS=(--title "feat: サブタスク名" --label "phase:${PHASE}")
if [[ -n "${MILESTONE}" ]]; then
SUB_ARGS+=(--milestone "${MILESTONE}")
fi
SUB_URL=$(gh issue create "${SUB_ARGS[@]}" --body "...")
SUB_NUMBER=$(printf '%s' "${SUB_URL}" | grep -oE '[0-9]+$')
SUB_ID=$(gh api "repos/{owner}/{repo}/issues/${SUB_NUMBER}" --jq '.id')
gh api \
--method POST \
"repos/{owner}/{repo}/issues/${CHILD_NUMBER}/sub_issues" \
-F "sub_issue_id=${SUB_ID}"per_page=100# ページネーション例(全 sub-issues を取得)
PAGE=1
while true; do
RESULT=$(gh api \
"repos/{owner}/{repo}/issues/${PHASE_NUMBER}/sub_issues?per_page=100&page=${PAGE}")
COUNT=$(echo "${RESULT}" | jq 'length')
echo "${RESULT}"
if [ "${COUNT}" -lt 100 ]; then break; fi
PAGE=$((PAGE + 1))
done--rootgh issue edit--root# --root 指定時: 既存本文を取得し、今回の Phase 分をマージしてから編集する
CURRENT_BODY=$(gh issue view "${ROOT_NUMBER}" --json body --jq '.body')
# CURRENT_BODY の「Phase 別実装計画」表へ今回の Phase 行を追記し、
# 「### Phase N」セクションを追加した本文を組み立てて gh issue edit --body に渡す。
# 既存ツリーの棚卸しを伴う場合は update-issue-tree への委譲でもよい--rootgh issue edit "${ROOT_NUMBER}" --body "$(cat <<'EOF'
## 概要
全 open issue を Phase 別に 1 ツリーへ整理する。各 Phase 親 issue を sub-issues として紐付け。
## Phase 別実装計画
| Phase | 親 issue | 直下 | 総 open 件数 |
|-------|----------|------|-------------|
| Phase 1 | #<phase1_number> タイトル | N | N |
| Phase 2 | #<phase2_number> タイトル | N | N |
### Phase 1: 基盤整備
| Issue | タイトル | 分解 |
|-------|---------|------|
| #N | タイトル | - |
| #N | タイトル | sub-issue あり |
## 運用
- 新規 issue は起票時に Phase 親へ紐付ける
- 実行順は sub-issues リスト順が正
- closed 親の下に open issue を残置しない
- implement-issue-tree が post-order DFS で消化可能な構造を維持する
EOF
)"## create-issue-tree 完了レポート
### ルート issue
- #N: タイトル
### Phase 別作成サマリー
| Phase | 親 issue | 起票数 |
|-------|----------|-------|
| Phase 1 | #N | N 件 |
### 作成した issue 一覧
- #N: タイトル(Phase 1 / 親: #M)
...
### 次のアクション
- 実装消化: implement-issue-tree スキルに ルート issue 番号を渡す
- ツリー更新: update-issue-tree スキルでトラッキング issue を棚卸しするgh issue view "${ROOT_NUMBER}"MILESTONEgh issue view <N> --json milestone --jq '.milestone.title'${MILESTONE}# sub-issues は 1 ページ最大 100 件。100 件超のツリーは Step 5 のページネーション
# ループで全件確認する(以下は per_page=100 で先頭ページのみ。件数が 100 未満なら全件)
# ルート直下の sub-issues を確認
gh api "repos/{owner}/{repo}/issues/${ROOT_NUMBER}/sub_issues?per_page=100" --jq '.[].number'
# Phase 親直下の sub-issues を確認
gh api "repos/{owner}/{repo}/issues/${PHASE_NUMBER}/sub_issues?per_page=100" --jq '.[].number'
# phase ラベルの同期確認(Phase 親・子 issue にラベルが付いているか確認)
gh api "repos/{owner}/{repo}/issues/${PHASE_NUMBER}/sub_issues?per_page=100" \
--jq '.[] | {number: .number, labels: [.labels[].name]}'| 問題 | 回避策 |
|---|---|
| 2 回目以降は必ず |
| |
| phase ラベルが存在しないリポジトリで issue 作成が失敗する | Step 4 冒頭の |
| リポジトリに milestone が存在する場合、Step 2.5 は継承結果が空ならユーザー確認フローへ自動的に合流する(確認で milestone を選ぶとルート issue にも反映される)。milestone が 1 件もない非運用リポジトリでは非運用ガードによる milestone なし起票が正常動作 |
| closed 親の下に open issue が残置される | Phase 親を close する前に全子 issue の close を確認する |
feat:fix:chore:--phase--root <既存ルートissue番号>per_page=100&page=N"${var}"--no-verifygh issue create--json| grep -oE '[0-9]+$'sub_issue_idgh api "repos/{owner}/{repo}/issues/<number>" --jq '.id'gh label create "phase:${PHASE}" --color "0075ca"