update-issue-tree

>

Skill file

Preview skill file
---
name: update-issue-tree
description: >
  既存の GitHub Issue ツリーを棚卸し・更新するスキル。「ツリーを棚卸しして」「イシューツリーを更新して」「トラッキング issue を整理して」で使用。
  ルートのトラッキング issue 番号を受け取り、sub_issues API でツリー全体を再帰取得 → closed 親下の残置 open issue 付け替え・孤児の再配置・新 Phase 親の新設・phase ラベル同期 →
  ルート issue 本文の Phase 別表・棚卸しセクションを再生成して更新する。
  ツリー新規作成は create-issue-tree、実装消化は implement-issue-tree を参照。
model: opus
user-invocable: true
argument-hint: "<ルートトラッキング issue 番号>"
---

# update-issue-tree

既存の Issue ツリーを棚卸しし、ルート issue 本文を最新状態に再生成する。
closed 親下に残置された open issue の付け替え・孤児の再配置・phase ラベルの同期を実施し、implement-issue-tree が post-order DFS で消化できる構造を維持する。

## 使い方

ルートのトラッキング issue 番号を引数として渡す。

```
update-issue-tree 42
```

## 前提条件

- `gh` CLI がインストールされ、認証済みであること(`gh auth status` で確認)
- 対象リポジトリへの Issue 書き込み権限があること

## フロー

### Step 1: ツリー全体を再帰取得する

ルート issue から sub_issues API を再帰的に呼び出し、全階層のツリー構造を取得する。  
ページネーションを考慮し、`per_page=100` で全件取得する。

```bash
ROOT_NUMBER="<ルート issue 番号>"

# ルート直下の sub-issues を取得(ページネーション対応)
fetch_sub_issues() {
  local PARENT="${1}"
  local PAGE=1
  while true; do
    RESULT=$(gh api \
      "repos/{owner}/{repo}/issues/${PARENT}/sub_issues?per_page=100&page=${PAGE}")
    echo "${RESULT}"
    COUNT=$(echo "${RESULT}" | jq 'length')
    if [ "${COUNT}" -lt 100 ]; then break; fi
    PAGE=$((PAGE + 1))
  done
}

# ルートから再帰的にツリーを構築
fetch_sub_issues "${ROOT_NUMBER}"
```

各 issue の `state`(open / closed)・ラベル・タイトルを記録してツリーマップを作成する。

### Step 2: 棚卸し対象を特定する

取得したツリーマップを分析し、以下のケースを特定する。

| ケース | 対応方針 |
|--------|---------|
| closed 親の下に open issue が残置されている | 適切な open Phase 親へ付け替え |
| どの親にも紐付いていない孤児 issue がある | 該当 Phase 親へ紐付け(Phase が不明な場合はユーザーに確認) |
| phase ラベルが親と一致しない issue がある | ラベルを同期 |
| 既存 Phase に収まらない新規タスクがある | 新 Phase 親の新設を検討 |
| 4h 超の issue が分解されていない | sub-issue に分解(create-issue-tree と同じ粒度基準) |

棚卸し対象の一覧をユーザーに提示し、方針確認を取ってから変更を実行する。

### Step 3: closed 親下の残置 open issue を付け替える

closed 親の下に残置されている open issue を、対応する open Phase 親へ移動する。

```bash
# sub_issue_id は issue 番号ではなく database id を渡す(GitHub sub-issues API 仕様)
ISSUE_ID=$(gh api "repos/{owner}/{repo}/issues/${ISSUE_NUMBER}" --jq '.id')

# 既存の親から外す(sub_issues API の DELETE)
gh api \
  --method DELETE \
  "repos/{owner}/{repo}/issues/${OLD_PARENT}/sub_issues" \
  -F "sub_issue_id=${ISSUE_ID}"

# 新しい親へ紐付ける
gh api \
  --method POST \
  "repos/{owner}/{repo}/issues/${NEW_PARENT}/sub_issues" \
  -F "sub_issue_id=${ISSUE_ID}"
```

### Step 4: 孤児 issue を再配置する

どの親にも紐付いていない孤児 issue を適切な Phase 親へ紐付ける。  
Phase が不明な issue はタイトル・本文を読んで判断し、判断できない場合はユーザーに確認する。

```bash
# sub_issue_id は database id を渡す(issue 番号ではない)
ORPHAN_ID=$(gh api "repos/{owner}/{repo}/issues/${ORPHAN_NUMBER}" --jq '.id')
gh api \
  --method POST \
  "repos/{owner}/{repo}/issues/${PHASE_NUMBER}/sub_issues" \
  -F "sub_issue_id=${ORPHAN_ID}"
```

### Step 5: 必要に応じて新 Phase 親を新設する

既存 Phase に収まらない新規タスクが多い場合、新 Phase 親 issue を作成してルートへ紐付ける。

```bash
# phase ラベルが存在しないリポジトリでは issue 作成が失敗するため、必ず事前作成する
# (作成済みの場合は失敗を無視して続行する)
gh label create "phase:N" --color "0075ca" 2>/dev/null || true

# gh issue create は URL を出力する(--json 非対応)。URL 末尾から番号を抽出する
NEW_PHASE_URL=$(gh issue create \
  --title "feat(phase-N): Phase N タイトル" \
  --label "phase:N" \
  --body "$(cat <<'EOF'
## 概要

Phase N の実装タスクをまとめる親 issue。

## タスク一覧

| Issue | タイトル | 分解 |
|-------|---------|------|
EOF
)")
NEW_PHASE_NUMBER=$(printf '%s' "${NEW_PHASE_URL}" | grep -oE '[0-9]+$')

# ルートへ紐付け。sub_issue_id は issue 番号ではなく database id を渡す(GitHub sub-issues API 仕様)
NEW_PHASE_ID=$(gh api "repos/{owner}/{repo}/issues/${NEW_PHASE_NUMBER}" --jq '.id')
gh api \
  --method POST \
  "repos/{owner}/{repo}/issues/${ROOT_NUMBER}/sub_issues" \
  -F "sub_issue_id=${NEW_PHASE_ID}"
```

### Step 6: phase ラベルを同期する

各 issue の phase ラベルが親 Phase と一致しているか確認し、不一致のラベルを修正する。

```bash
# ラベルを追加
gh issue edit "${ISSUE_NUMBER}" --add-label "phase:1"

# 古いラベルを削除
gh issue edit "${ISSUE_NUMBER}" --remove-label "phase:0"
```

### Step 7: 4h 超の issue を sub-issue に分解する

棚卸し中に 4h 超と判断した issue は、create-issue-tree と同じ粒度基準で sub-issue に分解する。

```bash
# phase ラベルが存在しない場合に備えて事前作成する(作成済みなら no-op)
gh label create "phase:N" --color "0075ca" 2>/dev/null || true

# sub-issue を作成(URL 末尾から番号を抽出)
SUB_URL=$(gh issue create \
  --title "feat: サブタスク名" \
  --label "phase:N" \
  --body "...")
SUB_NUMBER=$(printf '%s' "${SUB_URL}" | grep -oE '[0-9]+$')

# 親 issue へ紐付け(sub_issue_id は database id)
SUB_ID=$(gh api "repos/{owner}/{repo}/issues/${SUB_NUMBER}" --jq '.id')
gh api \
  --method POST \
  "repos/{owner}/{repo}/issues/${ISSUE_NUMBER}/sub_issues" \
  -F "sub_issue_id=${SUB_ID}"
```

### Step 8: ルート issue 本文を再生成して更新する

棚卸し後の最新ツリー状態を反映したルート issue 本文を生成し、`gh issue edit` で更新する。

```bash
gh issue edit "${ROOT_NUMBER}" --body "$(cat <<'EOF'
## 概要

全 open issue を Phase 別に 1 ツリーへ整理。各 Phase 親 issue を sub-issues として紐付け。

## 棚卸しで実施した整理(YYYY-MM-DD)

- closed 親下の残置 issue の付け替え: N 件
- 孤児の再配置: N 件
- phase ラベル同期: N 件
- 新 Phase 親の新設: N 件

## 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
)"
```

### Step 9: 棚卸し結果を報告する

```
## update-issue-tree 完了レポート

### 対象ルート issue
- #N: タイトル

### 棚卸し実施内容
| 操作 | 件数 |
|------|------|
| closed 親下の残置 issue 付け替え | N 件 |
| 孤児 issue の再配置 | N 件 |
| phase ラベル同期 | N 件 |
| 新 Phase 親の新設 | N 件 |
| 4h 超 issue の sub-issue 分解 | N 件 |

### 現在の Phase 別サマリー
| Phase | 親 issue | open 件数 |
|-------|----------|----------|
| Phase 1 | #N | N 件 |

### 要確認事項(自動配置できなかった issue)
- #N: タイトル — 確認理由
```

## 検証

- ルート issue 本文の Phase 別表が更新されていることを確認する
- closed Phase 親の下に open issue が残置されていないことを確認する

```bash
# 全 sub-issues の state を確認
gh api "repos/{owner}/{repo}/issues/${ROOT_NUMBER}/sub_issues" \
  --jq '.[] | {number: .number, state: .state, title: .title}'

# Phase 親の sub-issues も確認
gh api "repos/{owner}/{repo}/issues/${PHASE_NUMBER}/sub_issues" \
  --jq '.[] | {number: .number, state: .state}'

# phase ラベルの同期確認(各 Phase 親直下で確認)
gh api "repos/{owner}/{repo}/issues/${PHASE_NUMBER}/sub_issues" \
  --jq '.[] | {number: .number, labels: [.labels[].name]}'
```

## 注意事項

- **棚卸し前に変更内容をユーザーに提示して確認を取る**(Step 2 参照)
- ページネーション: sub-issues が 100 件を超える場合は `per_page=100&page=N` でページングして全件取得する
- シェルコマンドの変数は必ず `"${var}"` でクォートする(コマンドインジェクション対策)
- `--no-verify` は絶対に使用しない
- **`gh issue create` は `--json` 非対応**。issue URL を stdout に出力するため、`| grep -oE '[0-9]+$'` で末尾の番号を抽出する
- **sub_issues API(POST / DELETE)の `sub_issue_id` は issue 番号ではなく database id**(GitHub 仕様)。`gh api "repos/{owner}/{repo}/issues/<number>" --jq '.id'` で id を取得してから渡す。番号をそのまま渡すと誤った issue を操作する/404 になる
- 孤児 issue の Phase が判断できない場合は推測せずにユーザーへ確認する
- sub_issues の DELETE API で付け替えを行う際、操作対象の issue 番号を必ず確認してから実行する
- ツリー更新後は implement-issue-tree が post-order DFS で正しく消化できる構造になっているか確認する

Source

Creator's repository · fandhe-ai/agent-cli-skills

View on GitHub

Security

Security checks in progress
Results will appear here once audits complete
Checked by 3 independent security firms
Does it try to trick the AI?Not yet checkedPending · Gen Agent Trust Hub
Does it sneak in hidden code?Not yet checkedPending · Socket
Does it have known bugs?Not yet checkedPending · Snyk