update-issue-tree
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chineseupdate-issue-tree
update-issue-tree
既存の Issue ツリーを棚卸しし、ルート issue 本文を最新状態に再生成する。
closed 親下に残置された open issue の付け替え・孤児の再配置・phase ラベルの同期を実施し、implement-issue-tree が post-order DFS で消化できる構造を維持する。
盘点现有Issue树,并将根Issue正文更新为最新状态。
迁移已关闭父节点下残留的Open Issue、重新安置孤儿Issue、同步phase标签,维持implement-issue-tree可通过后序DFS执行的结构。
使い方
使用方法
ルートのトラッキング issue 番号を引数として渡す。
update-issue-tree 42传入根追踪Issue编号作为参数。
update-issue-tree 42前提条件
前提条件
- CLI がインストールされ、認証済みであること(
ghで確認)gh auth status - 対象リポジトリへの Issue 書き込み権限があること
- 已安装CLI并完成认证(可通过
gh确认)gh auth status - 拥有目标仓库的Issue写入权限
フロー
流程
Step 1: ツリー全体を再帰取得する
Step 1: 递归获取整个树结构
ルート issue から sub_issues API を再帰的に呼び出し、全階層のツリー構造を取得する。
ページネーションを考慮し、 で全件取得する。
ページネーションを考慮し、
per_page=100bash
ROOT_NUMBER="<ルート issue 番号>"从根Issue开始递归调用sub_issues API,获取全层级的树结构。
考虑分页,通过获取所有数据。
per_page=100bash
ROOT_NUMBER="<根Issue编号>"ルート直下の sub-issues を取得(ページネーション対応)
获取根节点下的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 }
"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() {
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 }
"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)・ラベル・タイトルを記録してツリーマップを作成する。fetch_sub_issues "${ROOT_NUMBER}"
记录每个Issue的`state`(open/closed)、标签、标题,创建树映射。Step 2: 棚卸し対象を特定する
Step 2: 确定盘点对象
取得したツリーマップを分析し、以下のケースを特定する。
| ケース | 対応方針 |
|---|---|
| closed 親の下に open issue が残置されている | 適切な open Phase 親へ付け替え |
| どの親にも紐付いていない孤児 issue がある | 該当 Phase 親へ紐付け(Phase が不明な場合はユーザーに確認) |
| phase ラベルが親と一致しない issue がある | ラベルを同期 |
| 既存 Phase に収まらない新規タスクがある | 新 Phase 親の新設を検討 |
| 4h 超の issue が分解されていない | sub-issue に分解(create-issue-tree と同じ粒度基準) |
棚卸し対象の一覧をユーザーに提示し、方針確認を取ってから変更を実行する。
分析获取到的树映射,识别以下情况:
| 情况 | 应对方案 |
|---|---|
| 已关闭父节点下残留Open Issue | 迁移至合适的Open Phase父节点 |
| 存在未关联任何父节点的孤儿Issue | 关联至对应Phase父节点(Phase不明时向用户确认) |
| Issue的phase标签与父节点不一致 | 同步标签 |
| 存在无法归入现有Phase的新任务 | 考虑新建Phase父节点 |
| 耗时超4h的Issue未拆分 | 拆分为sub-issue(与create-issue-tree粒度标准一致) |
向用户展示盘点对象列表,确认方案后再执行变更。
Step 3: closed 親下の残置 open issue を付け替える
Step 3: 迁移已关闭父节点下的残留Open Issue
closed 親の下に残置されている open issue を、対応する open Phase 親へ移動する。
bash
undefined将已关闭父节点下残留的Open Issue迁移至对应的Open Phase父节点。
bash
undefinedsub_issue_id は issue 番号ではなく database id を渡す(GitHub sub-issues API 仕様)
sub_issue_id需传入database id而非Issue编号(GitHub sub-issues API规范)
ISSUE_ID=$(gh api "repos/{owner}/{repo}/issues/${ISSUE_NUMBER}" --jq '.id')
ISSUE_ID=$(gh api "repos/{owner}/{repo}/issues/${ISSUE_NUMBER}" --jq '.id')
既存の親から外す(sub_issues API の DELETE)
从原父节点移除(sub_issues API的DELETE方法)
gh api
--method DELETE
"repos/{owner}/{repo}/issues/${OLD_PARENT}/sub_issues"
-F "sub_issue_id=${ISSUE_ID}"
--method DELETE
"repos/{owner}/{repo}/issues/${OLD_PARENT}/sub_issues"
-F "sub_issue_id=${ISSUE_ID}"
gh api
--method DELETE
"repos/{owner}/{repo}/issues/${OLD_PARENT}/sub_issues"
-F "sub_issue_id=${ISSUE_ID}"
--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}"
--method POST
"repos/{owner}/{repo}/issues/${NEW_PARENT}/sub_issues"
-F "sub_issue_id=${ISSUE_ID}"
undefinedgh api
--method POST
"repos/{owner}/{repo}/issues/${NEW_PARENT}/sub_issues"
-F "sub_issue_id=${ISSUE_ID}"
--method POST
"repos/{owner}/{repo}/issues/${NEW_PARENT}/sub_issues"
-F "sub_issue_id=${ISSUE_ID}"
undefinedStep 4: 孤児 issue を再配置する
Step 4: 重新安置孤儿Issue
どの親にも紐付いていない孤児 issue を適切な Phase 親へ紐付ける。
Phase が不明な issue はタイトル・本文を読んで判断し、判断できない場合はユーザーに確認する。
Phase が不明な issue はタイトル・本文を読んで判断し、判断できない場合はユーザーに確認する。
bash
undefined将未关联任何父节点的孤儿Issue关联至合适的Phase父节点。
对于Phase不明的Issue,通过标题和正文判断,无法判断时向用户确认。
bash
undefinedsub_issue_id は database id を渡す(issue 番号ではない)
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}"
--method POST
"repos/{owner}/{repo}/issues/${PHASE_NUMBER}/sub_issues"
-F "sub_issue_id=${ORPHAN_ID}"
undefinedORPHAN_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}"
--method POST
"repos/{owner}/{repo}/issues/${PHASE_NUMBER}/sub_issues"
-F "sub_issue_id=${ORPHAN_ID}"
undefinedStep 5: 必要に応じて新 Phase 親を新設する
Step 5: 按需新建Phase父节点
既存 Phase に収まらない新規タスクが多い場合、新 Phase 親 issue を作成してルートへ紐付ける。
bash
undefined当无法归入现有Phase的新任务较多时,新建Phase父Issue并关联至根节点。
bash
undefinedphase ラベルが存在しないリポジトリでは issue 作成が失敗するため、必ず事前作成する
若仓库不存在phase标签,创建Issue会失败,因此需预先创建
(作成済みの場合は失敗を無視して続行する)
(已存在则忽略错误继续执行)
gh label create "phase:N" --color "0075ca" 2>/dev/null || true
gh label create "phase:N" --color "0075ca" 2>/dev/null || true
gh issue create は URL を出力する(--json 非対応)。URL 末尾から番号を抽出する
gh issue create会输出URL(不支持--json),从URL末尾提取编号
NEW_PHASE_URL=$(gh issue create
--title "feat(phase-N): Phase N タイトル"
--label "phase:N"
--body "$(cat <<'EOF'
--title "feat(phase-N): Phase N タイトル"
--label "phase:N"
--body "$(cat <<'EOF'
NEW_PHASE_URL=$(gh issue create
--title "feat(phase-N): Phase N 标题"
--label "phase:N"
--body "$(cat <<'EOF'
--title "feat(phase-N): Phase N 标题"
--label "phase:N"
--body "$(cat <<'EOF'
概要
概述
Phase N の実装タスクをまとめる親 issue。
汇总Phase N的实现任务的父Issue。
タスク一覧
任务列表
| Issue | タイトル | 分解 |
|---|---|---|
| EOF | ||
| )") | ||
| NEW_PHASE_NUMBER=$(printf '%s' "${NEW_PHASE_URL}" | grep -oE '[0-9]+$') |
| Issue | 标题 | 拆分 |
|---|---|---|
| EOF | ||
| )") | ||
| NEW_PHASE_NUMBER=$(printf '%s' "${NEW_PHASE_URL}" | grep -oE '[0-9]+$') |
ルートへ紐付け。sub_issue_id は issue 番号ではなく database id を渡す(GitHub sub-issues API 仕様)
关联至根节点。sub_issue_id需传入database id而非Issue编号(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}"
--method POST
"repos/{owner}/{repo}/issues/${ROOT_NUMBER}/sub_issues"
-F "sub_issue_id=${NEW_PHASE_ID}"
undefinedNEW_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}"
--method POST
"repos/{owner}/{repo}/issues/${ROOT_NUMBER}/sub_issues"
-F "sub_issue_id=${NEW_PHASE_ID}"
undefinedStep 6: phase ラベルを同期する
Step 6: 同步phase标签
各 issue の phase ラベルが親 Phase と一致しているか確認し、不一致のラベルを修正する。
bash
undefined检查每个Issue的phase标签是否与父Phase一致,修正不一致的标签。
bash
undefinedラベルを追加
添加标签
gh issue edit "${ISSUE_NUMBER}" --add-label "phase:1"
gh issue edit "${ISSUE_NUMBER}" --add-label "phase:1"
古いラベルを削除
删除旧标签
gh issue edit "${ISSUE_NUMBER}" --remove-label "phase:0"
undefinedgh issue edit "${ISSUE_NUMBER}" --remove-label "phase:0"
undefinedStep 7: 4h 超の issue を sub-issue に分解する
Step 7: 将耗时超4h的Issue拆分为sub-issue
棚卸し中に 4h 超と判断した issue は、create-issue-tree と同じ粒度基準で sub-issue に分解する。
bash
undefined盘点中判断为耗时超4h的Issue,按照与create-issue-tree相同的粒度标准拆分为sub-issue。
bash
undefinedphase ラベルが存在しない場合に備えて事前作成する(作成済みなら no-op)
预先创建phase标签(已存在则无操作),避免创建Issue失败
gh label create "phase:N" --color "0075ca" 2>/dev/null || true
gh label create "phase:N" --color "0075ca" 2>/dev/null || true
sub-issue を作成(URL 末尾から番号を抽出)
创建sub-issue(从URL末尾提取编号)
SUB_URL=$(gh issue create
--title "feat: サブタスク名"
--label "phase:N"
--body "...") SUB_NUMBER=$(printf '%s' "${SUB_URL}" | grep -oE '[0-9]+$')
--title "feat: サブタスク名"
--label "phase:N"
--body "...") SUB_NUMBER=$(printf '%s' "${SUB_URL}" | grep -oE '[0-9]+$')
SUB_URL=$(gh issue create
--title "feat: 子任务名称"
--label "phase:N"
--body "...") SUB_NUMBER=$(printf '%s' "${SUB_URL}" | grep -oE '[0-9]+$')
--title "feat: 子任务名称"
--label "phase:N"
--body "...") SUB_NUMBER=$(printf '%s' "${SUB_URL}" | grep -oE '[0-9]+$')
親 issue へ紐付け(sub_issue_id は database id)
关联至父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}"
--method POST
"repos/{owner}/{repo}/issues/${ISSUE_NUMBER}/sub_issues"
-F "sub_issue_id=${SUB_ID}"
undefinedSUB_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}"
--method POST
"repos/{owner}/{repo}/issues/${ISSUE_NUMBER}/sub_issues"
-F "sub_issue_id=${SUB_ID}"
undefinedStep 8: ルート issue 本文を再生成して更新する
Step 8: 重新生成并更新根Issue正文
棚卸し後の最新ツリー状態を反映したルート issue 本文を生成し、 で更新する。
gh issue editbash
gh issue edit "${ROOT_NUMBER}" --body "$(cat <<'EOF'生成反映盘点后最新树状态的根Issue正文,通过更新。
gh issue editbash
gh issue edit "${ROOT_NUMBER}" --body "$(cat <<'EOF'概要
概述
全 open issue を Phase 別に 1 ツリーへ整理。各 Phase 親 issue を sub-issues として紐付け。
将所有Open Issue按Phase整理为单个树结构。每个Phase父Issue作为sub-issues关联。
棚卸しで実施した整理(YYYY-MM-DD)
盘点执行的整理操作(YYYY-MM-DD)
- closed 親下の残置 issue の付け替え: N 件
- 孤児の再配置: N 件
- phase ラベル同期: N 件
- 新 Phase 親の新設: N 件
- 迁移已关闭父节点下的残留Issue:N件
- 重新安置孤儿Issue:N件
- 同步phase标签:N件
- 新建Phase父节点:N件
Phase 別実装計画
Phase分类实现计划
| Phase | 親 issue | 直下 | 総 open 件数 |
|---|---|---|---|
| Phase 1 | #<phase1_number> タイトル | N | N |
| Phase 2 | #<phase2_number> タイトル | N | N |
| Phase | 父Issue | 直接子节点数 | 总Open数量 |
|---|---|---|---|
| Phase 1 | #<phase1_number> 标题 | N | N |
| Phase 2 | #<phase2_number> 标题 | N | N |
Phase 1: タイトル
Phase 1: 标题
| Issue | タイトル | 分解 |
|---|---|---|
| #N | タイトル | - |
| #N | タイトル | sub-issue あり |
| Issue | 标题 | 拆分 |
|---|---|---|
| #N | 标题 | - |
| #N | 标题 | 有sub-issue |
運用
运维规则
- 新規 issue は起票時に Phase 親へ紐付ける
- 実行順は sub-issues リスト順が正
- closed 親の下に open issue を残置しない
- implement-issue-tree が post-order DFS で消化可能な構造を維持する EOF )"
undefined- 新建Issue时需关联至Phase父节点
- 执行顺序以sub-issues列表顺序为准
- 不得在已关闭父节点下残留Open Issue
- 维持implement-issue-tree可通过后序DFS执行的结构 EOF )"
undefinedStep 9: 棚卸し結果を報告する
Step 9: 报告盘点结果
undefinedundefinedupdate-issue-tree 完了レポート
update-issue-tree 完成报告
対象ルート issue
目标根Issue
- #N: タイトル
- #N: 标题
棚卸し実施内容
盘点执行内容
| 操作 | 件数 |
|---|---|
| closed 親下の残置 issue 付け替え | N 件 |
| 孤児 issue の再配置 | N 件 |
| phase ラベル同期 | N 件 |
| 新 Phase 親の新設 | N 件 |
| 4h 超 issue の sub-issue 分解 | N 件 |
| 操作 | 数量 |
|---|---|
| 迁移已关闭父节点下的残留Issue | N件 |
| 重新安置孤儿Issue | N件 |
| 同步phase标签 | N件 |
| 新建Phase父节点 | N件 |
| 将耗时超4h的Issue拆分为sub-issue | N件 |
現在の Phase 別サマリー
当前Phase分类汇总
| Phase | 親 issue | open 件数 |
|---|---|---|
| Phase 1 | #N | N 件 |
| Phase | 父Issue | Open数量 |
|---|---|---|
| Phase 1 | #N | N件 |
要確認事項(自動配置できなかった issue)
需确认事项(无法自动安置的Issue)
- #N: タイトル — 確認理由
undefined- #N: 标题 — 确认理由
undefined検証
验证
- ルート issue 本文の Phase 別表が更新されていることを確認する
- closed Phase 親の下に open issue が残置されていないことを確認する
bash
undefined- 确认根Issue正文中的Phase分类表已更新
- 确认已关闭Phase父节点下无残留Open Issue
bash
undefined全 sub-issues の state を確認
确认所有sub-issues的状态
gh api "repos/{owner}/{repo}/issues/${ROOT_NUMBER}/sub_issues"
--jq '.[] | {number: .number, state: .state, title: .title}'
--jq '.[] | {number: .number, state: .state, title: .title}'
gh api "repos/{owner}/{repo}/issues/${ROOT_NUMBER}/sub_issues"
--jq '.[] | {number: .number, state: .state, title: .title}'
--jq '.[] | {number: .number, state: .state, title: .title}'
Phase 親の sub-issues も確認
确认Phase父节点的sub-issues
gh api "repos/{owner}/{repo}/issues/${PHASE_NUMBER}/sub_issues"
--jq '.[] | {number: .number, state: .state}'
--jq '.[] | {number: .number, state: .state}'
gh api "repos/{owner}/{repo}/issues/${PHASE_NUMBER}/sub_issues"
--jq '.[] | {number: .number, state: .state}'
--jq '.[] | {number: .number, state: .state}'
phase ラベルの同期確認(各 Phase 親直下で確認)
确认phase标签同步情况(在各Phase父节点下确认)
gh api "repos/{owner}/{repo}/issues/${PHASE_NUMBER}/sub_issues"
--jq '.[] | {number: .number, labels: [.labels[].name]}'
--jq '.[] | {number: .number, labels: [.labels[].name]}'
undefinedgh api "repos/{owner}/{repo}/issues/${PHASE_NUMBER}/sub_issues"
--jq '.[] | {number: .number, labels: [.labels[].name]}'
--jq '.[] | {number: .number, labels: [.labels[].name]}'
undefined注意事項
注意事项
- 棚卸し前に変更内容をユーザーに提示して確認を取る(Step 2 参照)
- ページネーション: sub-issues が 100 件を超える場合は でページングして全件取得する
per_page=100&page=N - シェルコマンドの変数は必ず でクォートする(コマンドインジェクション対策)
"${var}" - は絶対に使用しない
--no-verify - は
gh issue create非対応。issue URL を stdout に出力するため、--jsonで末尾の番号を抽出する| grep -oE '[0-9]+$' - sub_issues API(POST / DELETE)の は issue 番号ではなく database id(GitHub 仕様)。
sub_issue_idで id を取得してから渡す。番号をそのまま渡すと誤った issue を操作する/404 になるgh api "repos/{owner}/{repo}/issues/<number>" --jq '.id' - 孤児 issue の Phase が判断できない場合は推測せずにユーザーへ確認する
- sub_issues の DELETE API で付け替えを行う際、操作対象の issue 番号を必ず確認してから実行する
- ツリー更新後は implement-issue-tree が post-order DFS で正しく消化できる構造になっているか確認する
- 盘点前需向用户展示变更内容并确认(参考Step 2)
- 分页处理:sub-issues超过100件时,通过分页获取全部数据
per_page=100&page=N - shell命令的变量必须用包裹(防止命令注入)
"${var}" - 绝对禁止使用
--no-verify - 不支持
gh issue create。会将Issue URL输出至stdout,需通过--json提取末尾的编号| grep -oE '[0-9]+$' - sub_issues API(POST/DELETE)的需传入database id而非Issue编号(GitHub规范)。需通过
sub_issue_id获取id后传入。直接传入编号会导致误操作或404错误gh api "repos/{owner}/{repo}/issues/<number>" --jq '.id' - 无法判断孤儿Issue的Phase时,请勿猜测,需向用户确认
- 通过sub_issues的DELETE API迁移时,必须确认操作的Issue编号后再执行
- 更新树结构后,需确认implement-issue-tree可通过后序DFS正确执行