关于 plan-dag
plan-dag 可将基于 GitHub 的计划转换为依赖关系图,帮助团队查看任务顺序、进度和可并行工作,并输出适合聊天中查看的高清 PNG。
主要功能
- 01发现议题、子议题和拉取请求
- 02按完成状态分类工作
- 03构建有依据的依赖边
- 04渲染带状态标记的 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,包含状态填充、“可立即开始”高亮和双边框结束标记——先通过无头 Chromium 将样式化的 Graphviz 布局栅格化,再通过 SendUserFile 发送给用户。
此技能与代码仓库无关。它假设使用由 GitHub 支持、带有子议题链接和依赖关系描述的议题跟踪器;不假设使用特定标签、领域分类体系或针对特定代码仓库的开发流程技能。
为什么使用 PNG,而不是 HTML / ASCII? PNG 是唯一能在此技能面向的所有聊天界面中正常显示的输出格式。ASCII / Unicode 方框绘图在界面重新排版空白或应用语法高亮时会失去列对齐,也无法呈现让状态一目了然的颜色填充。独立的 HTML 页面无法在聊天中内嵌显示——它们会作为附件下载,这就失去了图表一目了然的优势。PNG 可以内嵌显示、保留颜色,且不受宿主字体或渲染差异的影响。
使用时机
- 完成
what's next调研后,用来确定执行顺序。 - 当有人询问“#N 还剩什么?”且该议题是包含子议题的总规范时。
- 选择下一个分支之前,用来查看哪些工作能解除最多下游工作的阻塞。
- 当规范拆分为 N 个子议题,且依赖关系并非全都呈线性时。
以下情况请跳过:
- 只有一个 PR 的规范——计划就是 Plan 部分,而不是 DAG。
- 设计问题尚未解决——在达成一致之前绘制 DAG 只是作秀。
- “图”只有三个或更少节点,且排成一条线——一句话比图表更简洁。
输入
| 参数 | 默认值 |
|---|---|
| 范围 | 推断得出——当前分支的规范、用户指定的总议题,或刚刚调研过的未关闭议题。 |
| 粒度 | 规范级;对于已拆分为多个子议题的总议题,细化至子议题 / 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)。 - 最近一次合并后的一系列后续事项 → 使用用户暗示的优先级 + 范围筛选条件。
- 刚刚调查过的问题集合 → 重用那份 节点 ID 列表,但在渲染前重新获取每个节点的状态(见下方“时效性”)。过时的标签是导致 DAG 出错的最常见原因。
不要只看问题正文和标签。 节点的实际状态往往记录在评论串中,或尚未关联回来的 PR 中。分类前,先为每个节点收集以下所有信息:
- 问题本身:状态(
open/closed)、state_reason(completed或其他值)、priority:*、子问题列表(仅限总纲节点)。 - 问题评论(
mcp__github__issue_read,参数为method: get_comments):较晚出现的状态变更(“实际上已被 #N 取代”“通过 #M 合并,正在关闭”)、正文写好后新增的Depends on/Blocks行,以及评审者的反对意见(可能会让一个“已准备就绪”的问题重新变成“待确认”)。 - 关联 PR:所有提及该问题的 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”,那么按 DAG 所需的定义,该 PR 并不算进行中。 - 近期提交:引用该问题的近期提交(在默认分支上使用
mcp__github__list_commits,可选用since/path缩小范围)。提交信息中带有Closes #N/Fixes #N的合并提交表示工作已完成,即使问题因机器人处理延迟仍处于打开状态。
不要根据单一信号推断状态。需要综合判断:已关闭但 state_reason 不为 completed 的问题,如果有尚未合并的后续 PR,则为 in_progress,而非 done;如果关联 PR 已合并,则处于打开状态的问题为 done,等待机器人完成清理。
时效性。 GitHub 状态可能在工作期间发生变化。每次渲染时——包括上次渲染后五分钟内再次渲染——都必须重新获取:
- 完整节点集的当前状态(同一会话内,不要在多次渲染之间缓存
done/in_progress)。 - 每个 PR 的当前状态和评审状态(PR 可能在两次渲染之间合并或被强制关闭)。
- 上次状态不明确的节点的最新评论。
如果用户要求“使用 X 重新渲染”或“刷新该 DAG”,应将其视为完整的重新发现,而不是重新贴标签。多调用一次 MCP 读取的成本,远低于发布一份写着“下一个选 #305”、而 #305 在十分钟前已合并的 DAG 所造成的代价。
2. 分类
每个节点只能有一个状态:
| 状态 | 定义 |
|---|---|
done | 已关闭且 state_reason: completed,或 PR 已合并。 |
in_progress | 有一个关联到该节点的开放 PR(无论问题本身是打开状态,还是已关闭但 state_reason 不为 completed)。 |
open | 处于打开状态,且没有 PR。 |
不要将“受阻”渲染成单独的状态——渲染器会根据图推导该状态(任何有未完成前置节点的 open 节点),并使用虚线、低对比度填充进行样式设置。反过来,如果某个 open 节点的所有前置节点都为 done,则应通过双重编码将其突出显示为“下一个可处理”事项。
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/提交引用:
merged in PR #N、closed by #N、总览规范的 Plan 中的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。状态通过填充色和前置 emoji 表示——不要将标记嵌入label。edges[].source∈{sub-issue, depends-on, pr-link, closes, part-of},为必填项。这是将 Conventions 中的引用规则变为强制要求:GitHub 上没有记录来源的边一律不允许。- 每个
from/to都必须对应已声明的节点 id,或字面值"close"。 critical_path为可选项;应在渲染出的 PNG 旁用文字说明,不要放在图中。
视觉编码。 状态通过填充色和前置 emoji 双重编码,让读者无需阅读标签即可快速识别状态:
| 状态 | 填充色 | 边框 | Emoji |
|---|---|---|---|
| Done | 柔和绿 | 绿色 | ✅ |
| 进行中 | 琥珀色 | 较粗的琥珀色 | 🟡 |
| Open + 所有前置项均已完成(“接下来可处理”) | 冷蓝色 | 粗蓝色 | 🎯 |
| Open + 受阻 | 近白色 | 灰色、虚线 | ⬜ |
| 关闭哨兵 | 白色 | 双线 | 🏁 |
边表示交付顺序,而非结构。 边分为两个视觉层级:
- 排序边(
depends-on、closes、pr-link)的颜色根据其源节点的状态决定——已完成工作发出绿色边,进行中工作发出琥珀色边,“接下来可处理”的工作发出蓝色边,受阻工作发出灰色边。与节点边框使用同一套配色,让人能基于同一套认知模型读懂进度。 - 成员关系边(
sub-issue、part-of)绘制为淡灰色的细脚手架线。它们并非真正的交付顺序约束——不能仅仅因为#B是#A的子议题,就认为#A必须先于#B交付——因此视觉权重较低。但仍会绘制,而非隐藏,以便读者追踪群组内部的结构。
总览项会自动聚类。 如果至少有一个议题通过正式的 sub-issue 边指向某个节点(“选择加入”门槛),且该节点累计收到 ≥3 条 sub-issue 或 part-of 引用,则该节点会成为总览锚点(避免将普通的双节点分组聚在一起)。锚点使用文件夹标签形状呈现,其成员显示在淡色虚线簇框内。被多个总览项依赖的节点(共享工作)仍保留在顶层。
“接下来可处理”高亮由图自动计算(状态为 open 且每个前置项均为 done)——IR 中没有对应字段。关键路径边不会加粗:哪条路径才是“关键路径”由调用方判断,提升其视觉权重会把建议与图的拓扑结构混为一谈。将关键路径保存在 ir.critical_path 中,并在渲染出的 PNG 下方用文字给出下一项的建议。
--emoji 标志控制是否输出状态 emoji:on(默认)显示 ✅ / 🟡 / 🎯 / ⬜ / 🏁 emoji;off 则改用末尾的 ✓ / … 文本标记。如果目标系统缺少彩色 emoji 字体,且 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
渲染器需要 dot(graphviz;apt install graphviz / brew install graphviz)在 PATH 中,以完成 SVG 布局步骤;还需要 node(≥18)和 Playwright Chromium(npm i -g playwright && npx playwright install chromium),以完成栅格化步骤。两项检查都会在开始时运行,若失败会明确提示安装方法——不会静默回退到文本或 ASCII,这是有意为之(被移除的格式存在一些 PNG 输出旨在避免的局限)。
如果渲染器因 IR validation failed 中止,请修复 IR——不要手动画图来绕过验证。验证规则将引用要求(Conventions › No invented edges)落实为可执行检查。
运行渲染器后的响应处理:
- 使用
SendUserFile发送 PNG,使其以内嵌形式显示在助手消息中。 - 在文件下方添加文字说明——关键路径、下一个可选节点、排序理由。PNG 展示拓扑;文字说明给出建议。
- 不要将同一计划重新渲染为其他格式并同时附上——每条响应只放一个 DAG。
- 对于非常宽的图(>10 个带交叉边的节点),如果单张 PNG 变得难以阅读,请将计划拆分为每条工作线各自的 DAG(每条工作线调用一次渲染器),并根据第 3 节现有的“Cross-edges”约定,用一份简短的文字列表最后列出交叉边。
约定
- 不得虚构边。 如果无法引用来源(子问题链接、正文或评论文字、PR 标题、提交说明),就不要绘制这条边。
- 重新渲染前重新获取信息。 每次渲染都必须重新读取节点状态、PR 状态和近期评论——绝不要复用先前渲染时的分类。建议处理一个已经合并的节点,比没有 DAG 更糟。
- 不要只看问题正文。 问题评论、关联 PR 的评论和评审,以及近期提交说明,常常会改变状态或添加正文中未提及的依赖。跳过这些内容会生成看似笃定、实则错误的图。
- 节点较密集时总结已完成节点。 某条工作线中已完成节点超过约 3 个?用文字总结为
Landed: #A #B #C ✓,不要在图中逐一列出。 - 每条响应只放一个 DAG。 不要换个说法重复渲染同一计划——选择能回答实际问题的表达方式。
- 以推荐路径收尾。 没有推荐顺序的 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 作为子进程调用——该子进程调用在脚本自己的 shell 中运行,不经过 Claude Code 的权限引擎,因此不需要单独添加到允许列表中。
需要在 PATH 中安装 dot(graphviz);PNG 冒烟测试还需要 node 和 Playwright Chromium。
相关技能
- 仓库的 spec-driven-development loop 技能(例如
onsager-dev-process、duhem-dev-process)——DAG 所呈现的父级 / 子级 / 依赖关系语义。 - 仓库的
issue-spec技能——父级 / 子级 / 依赖关系边如何持久化到 GitHub 上。 - 仓库的 PR-lifecycle 技能——PR 开启 / 合并如何驱动渲染后 PNG 中的琥珀色填充(PR 已开启)和绿色填充(已合并)。