plan-dag について
plan-dagは、GitHub上の計画を依存関係図にするスキルです。作業の順序や進捗、並行可能なタスクを確認でき、チャット内で表示しやすい高解像度PNGを出力します。
主な機能
- 01Issue、サブIssue、プルリクエストの調査
- 02完了状況に応じた作業の分類
- 03根拠に基づく依存関係の構築
- 04状態付きDAGのPNG出力
- 05着手可能な作業の強調表示
活用シーン
- 01包括的な仕様に残る作業の確認
- 02依存関係に基づく次の作業の選択
- 03ブランチ作成前の作業順序の確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ onsager-ai/ dev-skills --skill plan-dagインストール後、Claude Code で/plan-dagを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
fixtures/bad.jsoncycle.jsonhappy.jsonscripts/plan-dag-render.pyplan-dag-render.test.shsvg-to-png.mjs詳細
plan-dag
現在の計画を依存関係 DAG として描画し、順序、クリティカルパス、並行して進められる作業をひと目で確認できます。ステータスに応じた塗りつぶし、「次に着手可能」のハイライト、二重枠の完了センチネルを備えた高 DPI の PNG を出力します。スタイル設定した Graphviz レイアウトをヘッドレス Chromium でラスター化し、SendUserFile を使ってユーザーに送信します。
このスキルはリポジトリに依存しません。サブイシューのリンクと依存関係を示す文章を使う、GitHub ベースのイシュートラッカーを前提としています。特定のラベル、領域の分類体系、リポジトリ固有の開発プロセス用スキルは前提にしていません。
HTML や ASCII ではなく PNG を使う理由は? このスキルが対象とするすべてのチャット画面で表示できる出力は、PNG だけです。ASCII や Unicode の罫線は、画面側で空白が再配置されたり、シンタックスハイライトが適用されたりすると列の配置が崩れます。また、状態をひと目で把握しやすくする色や塗りつぶしも表現できません。自己完結型の HTML ページはチャット内に表示されず、添付ファイルとしてダウンロードされるため、図をひと目で確認するという目的に合いません。PNG ならチャット内に表示でき、色も保持され、ホスト側のフォントや描画上の癖にも依存しません。
使用する場面
what's nextの調査後、作業の順序を決めるとき。- サブイシューのある包括的な仕様について、「#N には何が残っている?」と聞かれたとき。
- 次のブランチを選ぶ前に、下流の作業を最も多く進められるものを確認するとき。
- 仕様から N 個のサブイシューに作業が分かれ、依存関係がすべて直線的ではないとき。
次の場合は使用しないでください。
- 単一 PR の仕様の場合 — 計画は DAG ではなく、「Plan」セクションそのものです。
- 未回答の設計上の質問がある場合 — 合意形成前に DAG を描いても、見せかけにすぎません。
- 「グラフ」が 3 個以下のノードからなる単一の直線的なチェーンの場合 — 図よりも文章のほうが短く済みます。
入力
| パラメーター | デフォルト |
|---|---|
| スコープ | 推定 — 現在のブランチの仕様、ユーザーが指定した包括的な仕様、または直前に調査したオープンイシュー。 |
| 粒度 | 仕様レベル。複数のサブイシューに分かれた包括的な仕様では、サブイシュー/PR レベルに切り替えます。 |
| 出力 | 高 DPI の PNG(唯一の出力形式)。--out <path> は必須です。絵文字のステータス表示はデフォルトで有効です。描画システムにカラー絵文字フォントがない場合は、--emoji=off で無効にできます。 |
ワークフロー
1. Discover Pull issues / sub-issues / PRs in scope
2. Classify Mark each node done / in-progress / open
3. Edge-build Read dependencies from sub-issue links + body prose
4. Render Emit JSON IR → PNG → SendUserFile + prose commentary
1. 検出
一般的なクロールではなく、会話の内容から対象範囲を特定します。よくあるきっかけは次のとおりです。
- 包括的な仕様 → サブイシューの一覧をたどります(
mcp__github__issue_readのmethod: get_sub_issuesを使用)。 - 最近のマージから続く一連のフォローアップ → ユーザーが示唆した優先度と領域のフィルターを使用します。
- 直前に調査したイシュー群 → その node id の一覧 を再利用します。ただし、描画する前に各ノードの状態を必ず再取得してください(下記の「最新状態」を参照)。古いラベルは、誤った DAG の最も一般的な原因です。
イシューの本文やラベルだけで判断しないでください。 ノードの実際のステータスは、コメントスレッドや、まだリンクされていない PR に記載されていることがよくあります。分類する前に、各ノードについて以下をすべて収集してください。
- イシュー自体:状態(
open/closed)、state_reason(completedまたはその他)、priority:*、サブイシュー一覧(包括ノードのみ)。 - イシューのコメント(
mcp__github__issue_readのmethod: get_commentsを使用):「実際には #N に置き換えられた」「#M 経由でマージ済み、クローズします」といった後からのステータス変更、本文の作成後に追加された新しいDepends on/Blocks行、「準備完了」のイシューを再び「未解決の質問」にするレビュアーからの指摘。 - リンクされた PR:本文に記載されたものだけでなく、そのイシューに言及しているすべての PR。
mcp__github__search_pull_requests(例:リポジトリを指定した#N in:body,comments)または包括イシューに安定したブランチプレフィックスがある場合はmcp__github__list_pull_requestsで探します。各 PR について、状態(open/merged/closed-unmerged)、ドラフトかどうか、method: get_status/get_check_runsから取得するマージ可能状態、PR のコメントとレビュー(インラインスレッドについてはmcp__github__pull_request_readのmethod: get_comments、get_reviews、get_review_commentsを使用)を確認します。レビューコメントに「修正が必要。#X でブロック中」とある PR は、DAG が想定する意味での進行中ではありません。 - イシューに言及している最近のコミット(デフォルトブランチで
mcp__github__list_commitsを使用し、必要に応じてsince/pathで絞り込みます)。メッセージにCloses #N/Fixes #Nが含まれるマージ済みコミットは、Bot の処理競合でイシューがまだオープンでも、完了を意味します。
単一のシグナルからステータスを推測しないでください。情報を突き合わせてください。たとえば、クローズ済みでも completed ではないイシューにオープンのフォローアップ PR がある場合は in_progress であり、Closes-PR がマージ済みのオープンイシューは、Bot による整理待ちの done です。
最新状態。 作業中にも GitHub の状態は変化します。最後の描画から 5 分後の再描画も含め、描画のたびに以下を再取得してください。
- ノード一式の現在の状態(同じセッション内の再描画で
done/in_progressをキャッシュしないでください)。 - すべての PR の現在の状態とレビュー状況(再描画の間に PR がマージされたり、強制クローズされたりすることがあります)。
- 前回ステータスが曖昧だったノードの最新コメント。
ユーザーが「X を反映して再描画」や「その DAG を更新」と依頼した場合、ラベルの付け直しではなく、全件を再調査してください。MCP の読み取りを 1 回追加するコストは、10 分前にマージされた #305 を「次の候補」とする DAG を公開するコストよりはるかに小さいものです。
2. 分類
各ノードには、次のステータスを必ず 1 つだけ割り当てます。
| 状態 | 定義 |
|---|---|
done | クローズ済みで state_reason: completed、または PR がマージ済み。 |
in_progress | オープンの PR がリンクされている(イシュー自体がオープンか、クローズ済みで completed ではないかは問いません)。 |
open | オープンで、PR がない。 |
blocked を独立したステータスとして描画しないでください。レンダラーがグラフから導出します(先行ノードに done ではないものがある open ノードが該当)。その場合は、破線の控えめな塗りつぶしで表示します。逆に、すべての先行ノードが done の open ノードは、「次に着手可能」のハイライトとして二重にエンコードされます。
3. エッジの構築
エッジの情報源は、信頼度の高い順に次の3つです。これらのいずれかを引用できないエッジは描画しないでください。推測を加えると、DAG が汚染されます。
- サブイシューリンク(
mcp__github__issue_read+method: get_sub_issues)。エッジの向きは 子 → 親(前提条件 → 依存先)です。子がすべて完了すると親もクローズされるため、DAG はクローズに向かって流れます。 - イシュー本文またはコメントスレッド内の明示的な記述:
Depends on #N、Hard depends on #N、Blocks #N、Part of #N、Closes #N。コメントも確認してください。イシューの登録後に追加された依存関係は本文ではなく、コメントに記載されています。 - PR/コミットの参照:アンブレラスペックの Plan 内にある
merged in PR #N、closed by #N、PR-A → PR-Bの順序、およびコミットメッセージと PR の説明/レビューに含まれるCloses #N/Fixes #Nのトレーラー。
「自分には明らかだが、引用がない」依存関係は、ノードのラベルで示唆してもかまいませんが、エッジには含めないでください。
4. レンダリング
以下のスキーマに準拠した JSON IR を出力してから、レンダラーを呼び出してください。ボックスを手描きしないでください。特にクロスエッジやファンアウトがある場合、レンダラーなら決定論的に正しいレイアウトを生成できますが、AI の空間推論では一致しません。
スキーマ:
{
"nodes": [
{"id": "288", "label": "MCP", "status": "done"},
{"id": "305", "label": "router", "status": "in_progress"},
{"id": "306", "label": "cleanup","status": "open"}
],
"edges": [
{"from": "288", "to": "304", "source": "sub-issue"},
{"from": "305", "to": "306", "source": "depends-on"}
],
"close": "300",
"critical_path": ["301", "305", "306", "307", "close"]
}
statusは{done, in_progress, open}のいずれかで、デフォルトはopenです。ステータスは塗りつぶし色と先頭の絵文字で表現します。labelにマーカーを埋め込まないでください。edges[].sourceは{sub-issue, depends-on, pr-link, closes, part-of}のいずれかで、必須です。これは Conventions の引用ルールを強制可能にするもので、GitHub 上に記録された情報源のないエッジは認められません。- すべての
from/toは、宣言済みのノード ID またはリテラル"close"に解決されます。 critical_pathは省略可能です。レンダリングした PNG と併せて文章で伝え、画像には含めないでください。
視覚表現。 ステータスは塗りつぶしと先頭の絵文字の2つで表現するため、ラベルを読む前に状態を把握できます。
| 状態 | 塗りつぶし | 枠線 | 絵文字 |
|---|---|---|---|
| 完了 | 落ち着いた緑 | 緑 | ✅ |
| 進行中 | 琥珀色 | より太い琥珀色 | 🟡 |
| オープン+すべての前提が完了(「次に着手可能」) | 寒色系の青 | 太い青 | 🎯 |
| オープン+ブロック中 | ほぼ白 | 灰色、破線 | ⬜ |
| クローズ用センチネル | 白 | 二重線 | 🏁 |
エッジは構造ではなく、リリース順を表します。 エッジは視覚的に2つの階層に分けます。
- 順序エッジ(
depends-on、closes、pr-link)は、始点ノードのステータスに応じた色で表示します。完了済みの作業からは緑、進行中の作業からは琥珀色、「次に着手可能」な作業からは青、ブロック中の作業からは灰色にします。ノードの枠線と同じ配色にすることで、進捗の流れを一貫した見方で把握できます。 - メンバーシップエッジ(
sub-issue、part-of)は、薄く細い灰色の補助線として描画します。これらは実際のリリース順の制約ではありません。#Bが#Aのサブイシューだからといって、#Aより先に#Bをリリースする必要はありません。そのため、視覚的な優先度を下げます。ただし、クラスター内の構造をたどれるよう、非表示にはせず描画します。
アンブレラは自動的にクラスター化されます。 あるノードがアンブレラのアンカーになるには、少なくとも1つのイシューから正式な sub-issue エッジで参照されていること(オプトインの条件)に加え、受け取る sub-issue または part-of の参照が3件以上あること(単純な2ノードのグループ化を防ぐ条件)が必要です。アンカーはフォルダタブ形状で描画され、メンバーは薄い破線のクラスター枠内に描画されます。複数のアンブレラに流れ込むノード(共有作業)は、トップレベルにとどまります。
「次に着手可能」の強調表示はグラフから計算されます(オープンで、すべての先行ノードが done)。このための IR フィールドはありません。クリティカルパスのエッジは太字にしません。どのパスが「クリティカルパス」かは呼び出し側の判断であり、視覚的に強調すると、推奨事項とグラフのトポロジーを混同させるためです。ir.critical_path にクリティカルパスを記録し、レンダリングした PNG の下に、次に選ぶべき項目を文章で示してください。
--emoji フラグは、ステータス絵文字を出力するかどうかを制御します。on(デフォルト)では ✅/🟡/🎯/⬜/🏁 の絵文字を表示し、off では末尾に ✓/… のテキストマーカーを付けます。対象システムにカラー絵文字フォントがなく、PNG に豆腐文字が表示される場合は off にしてください。
呼び出し方法。 レンダラーはスキル内に含まれています。スキルのインストール方法に合ったパスを使用してください。
- プロジェクトスコープのインストール(リポジトリのルートで
npx skills add onsager-ai/dev-skillsを実行する場合のデフォルト):.claude/skills/plan-dag/scripts/plan-dag-render.py - ユーザーグローバルインストール(
npx skills add -g …):~/.claude/skills/plan-dag/scripts/plan-dag-render.py
存在する方を選んでください。不明な場合は、test -x .claude/skills/plan-dag/scripts/plan-dag-render.py && echo project || echo global を実行してください。
SCRIPT=.claude/skills/plan-dag/scripts/plan-dag-render.py # project install
# SCRIPT=~/.claude/skills/plan-dag/scripts/plan-dag-render.py # global install
# default: high-DPI PNG with emoji status indicators
"$SCRIPT" /tmp/plan.json --out /tmp/plan-dag.png
# emoji off — falls back to ✓ / … text markers in node labels
"$SCRIPT" /tmp/plan.json --out /tmp/plan-dag.png --emoji=off
レンダラーは SVG レイアウトの手順で PATH 上の dot(graphviz。apt install graphviz/brew install graphviz)を、ラスタライズの手順で node(≥18)と Playwright Chromium(npm i -g playwright && npx playwright install chromium)を必要とします。どちらも最初にチェックされ、インストール方法を示して明確に失敗します。設計上、テキストや ASCII へのサイレントフォールバックはありません(削除された形式には制約があり、それを避けるために PNG 出力が用意されています)。
レンダラーが IR validation failed で中断した場合は、IR を修正してください。手描きで回避してはいけません。検証の対象は、実行可能にした引用ルール(「規約 › 根拠のないエッジを追加しない」)です。
レンダラー実行後の応答処理:
SendUserFileで PNG を送信し、アシスタントのメッセージ内にインライン表示してください。- ファイルの下に、クリティカルパス、次に選択可能なノード、順序の根拠についての説明を加えてください。PNG はトポロジーを示し、文章は推奨事項を伝えます。
- 同じ計画を別の形式でも再レンダリングして、両方を添付してはいけません。1 回の応答につき DAG は 1 つです。
- 1 枚の PNG では扱いにくいほど非常に幅の広いグラフ(交差エッジを含む 10 ノード超)の場合は、計画をトラックごとの DAG に分割し(トラックごとにレンダラーを 1 回実行)、既存の「§3 の交差エッジ」の規約に従って、交差エッジを最後に短い文章のリストとして示してください。
規約
- 根拠のないエッジを追加しない。 根拠(サブイシューへのリンク、本文またはコメントの記述、PR ヘッダー、コミットのトレーラー)を示せない場合は、エッジを描画してはいけません。
- 再レンダリング前に再取得する。 ノードの状態、PR の状態、最近のコメントは、レンダリングのたびに読み直してください。以前のレンダリングでの分類を再利用してはいけません。すでにマージ済みのノードを推奨する DAG は、DAG がないよりも悪いものです。
- イシュー本文の先も読む。 イシューのコメント、リンクされた PR のコメントとレビュー、最近のコミットのトレーラーには、本文に記載されていないステータスの変更や依存関係の追加が定期的にあります。これらを読み飛ばすと、自信満々でも誤ったグラフになります。
- 完了済みノードが多い場合は要約する。 1 つのトラックに完了済みノードが約 3 つを超える場合は、グラフに列挙する代わりに、文章で
Landed: #A #B #C ✓とまとめてください。 - 1 回の応答につき DAG は 1 つ。 同じ計画を異なる切り口で二度レンダリングしてはいけません。実際に尋ねられた質問に答える切り口を選んでください。
- 選択した経路で締めくくる。 推奨する順序のない DAG は、ボックスの壁にすぎません。クリティカルパスと次に選択可能なノードを、ユーザーが方向を変えられるような形で文章にまとめてください。
- 図の中で論評しない。 「これはリスクが高そう」「順序を入れ替えるべき」といったコメントは、ノードのラベル内ではなく、図の上または下の文章に記載してください。
テスト
テストはレンダラーと同じ場所にある scripts/plan-dag-render.test.sh にあり、検証(不正な IR、循環の検出、自身の ID のプレフィックスに関する境界ケース)、スタイル付き DOT の構造チェック(ステータスの塗りつぶし、次に実行可能な項目、ブロックされた項目の破線、完了状態の二重枠)、および PNG のエンドツーエンドのスモークテストを実行します。Playwright Chromium が利用できない場合、PNG のスモークテストは自動的にスキップされるため、制限のある CI でもバリデーターのテストは実行されます。レンダラーと同じインストール状況を考慮した方法で実行してください。
# project-scope install
.claude/skills/plan-dag/scripts/plan-dag-render.test.sh
# user-global install
~/.claude/skills/plan-dag/scripts/plan-dag-render.test.sh
どちらの形式も allowed-tools に含まれているため、Claude Code は権限を再確認しません。テストスクリプトは内部でスキルのルートディレクトリに cd し、子プロセスとして scripts/plan-dag-render.py を呼び出します。この子プロセスの呼び出しはスクリプト独自のシェル内で実行され、Claude Code の権限エンジンを介さないため、別途許可リストに追加する必要はありません。
dot(graphviz)が PATH 上に必要です。PNG のスモークテストには、さらに node と Playwright Chromium が必要です。
関連スキル
- リポジトリの spec-driven-development ループスキル(例:
onsager-dev-process、duhem-dev-process)— DAG が可視化する親子関係と依存関係の意味を定義します。 - リポジトリの
issue-specスキル — 親子関係と依存関係のエッジが GitHub 上でどのように永続化されるかを説明します。 - リポジトリの PR ライフサイクルスキル — PR のオープン/マージによって、レンダリングされた PNG の塗りつぶしがアンバー(オープン中の PR)から緑(マージ済み)に変わる仕組みを説明します。