update-issue-tree

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

update-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

前提条件

前提条件

  • gh
    CLI がインストールされ、認証済みであること(
    gh auth status
    で確認)
  • 対象リポジトリへの Issue 書き込み権限があること
  • 已安装
    gh
    CLI并完成认证(可通过
    gh auth status
    确认)
  • 拥有目标仓库的Issue写入权限

フロー

流程

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

Step 1: 递归获取整个树结构

ルート issue から sub_issues API を再帰的に呼び出し、全階層のツリー構造を取得する。
ページネーションを考慮し、
per_page=100
で全件取得する。
bash
ROOT_NUMBER="<ルート issue 番号>"
从根Issue开始递归调用sub_issues API,获取全层级的树结构。 考虑分页,通过
per_page=100
获取所有数据。
bash
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 }
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)・ラベル・タイトルを記録してツリーマップを作成する。
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
undefined

sub_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}"
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}"
undefined
gh api
--method POST
"repos/{owner}/{repo}/issues/${NEW_PARENT}/sub_issues"
-F "sub_issue_id=${ISSUE_ID}"
undefined

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

Step 4: 重新安置孤儿Issue

どの親にも紐付いていない孤児 issue を適切な Phase 親へ紐付ける。
Phase が不明な issue はタイトル・本文を読んで判断し、判断できない場合はユーザーに確認する。
bash
undefined
将未关联任何父节点的孤儿Issue关联至合适的Phase父节点。 对于Phase不明的Issue,通过标题和正文判断,无法判断时向用户确认。
bash
undefined

sub_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}"
undefined
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}"
undefined

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

Step 5: 按需新建Phase父节点

既存 Phase に収まらない新規タスクが多い場合、新 Phase 親 issue を作成してルートへ紐付ける。
bash
undefined
当无法归入现有Phase的新任务较多时,新建Phase父Issue并关联至根节点。
bash
undefined

phase ラベルが存在しないリポジトリでは 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'
NEW_PHASE_URL=$(gh issue create
--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}"
undefined
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}"
undefined

Step 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"
undefined
gh issue edit "${ISSUE_NUMBER}" --remove-label "phase:0"
undefined

Step 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
undefined

phase ラベルが存在しない場合に備えて事前作成する(作成済みなら 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]+$')
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)

关联至父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}"
undefined
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}"
undefined

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

Step 8: 重新生成并更新根Issue正文

棚卸し後の最新ツリー状態を反映したルート issue 本文を生成し、
gh issue edit
で更新する。
bash
gh issue edit "${ROOT_NUMBER}" --body "$(cat <<'EOF'
生成反映盘点后最新树状态的根Issue正文,通过
gh issue edit
更新。
bash
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> タイトルNN
Phase 2#<phase2_number> タイトルNN
Phase父Issue直接子节点数总Open数量
Phase 1#<phase1_number> 标题NN
Phase 2#<phase2_number> 标题NN

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 )"
undefined

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

Step 9: 报告盘点结果

undefined
undefined

update-issue-tree 完了レポート

update-issue-tree 完成报告

対象ルート issue

目标根Issue

  • #N: タイトル
  • #N: 标题

棚卸し実施内容

盘点执行内容

操作件数
closed 親下の残置 issue 付け替えN 件
孤児 issue の再配置N 件
phase ラベル同期N 件
新 Phase 親の新設N 件
4h 超 issue の sub-issue 分解N 件
操作数量
迁移已关闭父节点下的残留IssueN件
重新安置孤儿IssueN件
同步phase标签N件
新建Phase父节点N件
将耗时超4h的Issue拆分为sub-issueN件

現在の Phase 別サマリー

当前Phase分类汇总

Phase親 issueopen 件数
Phase 1#NN 件
Phase父IssueOpen数量
Phase 1#NN件

要確認事項(自動配置できなかった 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}'
gh api "repos/{owner}/{repo}/issues/${ROOT_NUMBER}/sub_issues"
--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}'
gh api "repos/{owner}/{repo}/issues/${PHASE_NUMBER}/sub_issues"
--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]}'
undefined
gh api "repos/{owner}/{repo}/issues/${PHASE_NUMBER}/sub_issues"
--jq '.[] | {number: .number, labels: [.labels[].name]}'
undefined

注意事項

注意事项

  • 棚卸し前に変更内容をユーザーに提示して確認を取る(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 で正しく消化できる構造になっているか確認する
  • 盘点前需向用户展示变更内容并确认(参考Step 2)
  • 分页处理:sub-issues超过100件时,通过
    per_page=100&page=N
    分页获取全部数据
  • shell命令的变量必须用
    "${var}"
    包裹(防止命令注入)
  • 绝对禁止使用
    --no-verify
  • gh issue create
    不支持
    --json
    。会将Issue URL输出至stdout,需通过
    | grep -oE '[0-9]+$'
    提取末尾的编号
  • sub_issues API(POST/DELETE)的
    sub_issue_id
    需传入database id而非Issue编号
    (GitHub规范)。需通过
    gh api "repos/{owner}/{repo}/issues/<number>" --jq '.id'
    获取id后传入。直接传入编号会导致误操作或404错误
  • 无法判断孤儿Issue的Phase时,请勿猜测,需向用户确认
  • 通过sub_issues的DELETE API迁移时,必须确认操作的Issue编号后再执行
  • 更新树结构后,需确认implement-issue-tree可通过后序DFS正确执行