mcpField
P
plan-dag

plan-dag

github.com/onsager-ai开发761 次安装2 周前更新
代码仓库

关于 plan-dag

plan-dag 可将基于 GitHub 的计划转换为依赖关系图,帮助团队查看任务顺序、进度和可并行工作,并输出适合聊天中查看的高清 PNG。

主要功能

  1. 01发现议题、子议题和拉取请求
  2. 02按完成状态分类工作
  3. 03构建有依据的依赖边
  4. 04渲染带状态标记的 PNG 图
  5. 05突出显示可开始的工作

适用场景

  1. 01梳理总规格中的剩余工作
  2. 02根据依赖关系选择下一项任务
  3. 03创建分支前检查任务顺序

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/onsager-ai/dev-skills --skill plan-dag

安装后在 Claude Code 中输入/plan-dag确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

fixtures/
bad.json
cycle.json
happy.json
scripts/
plan-dag-render.py
plan-dag-render.test.sh
svg-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。

  1. 子议题链接(mcp__github__issue_read + method: get_sub_issues)。边的方向为子项 → 父项(前置项 → 依赖项)——父项会在其子项关闭后关闭,因此 DAG 朝关闭方向流动。
  2. 议题正文或评论串中的明确表述:Depends on #N、Hard depends on #N、Blocks #N、Part of #N、Closes #N。也要扫描评论——议题创建后添加的依赖关系会出现在评论里,而非正文中。
  3. 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 已开启)和绿色填充(已合并)。

评论

支持 Markdown