plan-dag
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chineseplan-dag
plan-dag
Render the current plan as a dependency DAG so sequencing, the critical path, and parallelizable work are visible at a glance. Output is a high-DPI PNG with status fills, an "available next" highlight, and a double-bordered close sentinel — rasterised from a styled graphviz layout through headless Chromium, then sent to the user via .
SendUserFileThis skill is repo-agnostic. It assumes a GitHub-backed issue tracker with sub-issue links and dependency-language prose; it makes no assumptions about specific labels, area taxonomies, or repo-specific dev-process skills.
Why PNG and not HTML / ASCII? PNG is the only output that survives every chat surface the skill targets. ASCII / Unicode box-drawing loses column alignment when surfaces reflow whitespace or apply syntax highlighting, and it can't carry the color/status fills that make state legible at a glance. Self-contained HTML pages don't render inline in chat — they download as attachments, which defeats the point of an at-a-glance diagram. PNG renders inline, keeps color, and doesn't depend on the host's font or rendering quirks.
将当前计划渲染为依赖DAG,让任务顺序、关键路径和可并行工作一目了然。输出为带有状态填充、“可启动”高亮和双边框完成标记的高DPI PNG——通过无头Chromium将经过样式化的graphviz布局栅格化后,再通过发送给用户。
SendUserFile此技能与代码库无关。它假设使用基于GitHub的议题跟踪系统,带有子议题链接和依赖描述文本;不依赖特定标签、领域分类或代码库专属的开发流程技能。
为什么选择PNG而非HTML/ASCII? PNG是唯一能在该技能面向的所有聊天场景中正常显示的输出格式。ASCII/Unicode框线绘图在场景重新排版空白或应用语法高亮时会丢失列对齐,且无法承载让状态一目了然的颜色/状态填充。独立HTML页面无法在聊天中内联渲染——它们会作为附件下载,这违背了“一目了然”的初衷。PNG可内联渲染、保留颜色,且不依赖宿主的字体或渲染特性。
When to use
使用场景
- After a survey, to commit to a sequence.
what's next - When asked "what's left for #N" on an umbrella spec with sub-issues.
- Before picking the next branch — to see what unblocks the most downstream work.
- When a spec fans out into N sub-issues and the dependency edges aren't all linear.
Skip when:
- A single-PR spec — the plan is the Plan section, not a DAG.
- An unanswered design question — drawing a DAG before alignment is theater.
- The "graph" is one linear chain of three or fewer nodes — a sentence is shorter than a diagram.
- 在“what's next”调查后,用于确定任务顺序。
- 当被问及某个包含子议题的总览规范“#N还剩下什么”时。
- 在选择下一个分支之前——查看什么任务能解锁最多下游工作。
- 当一个规范扩展为N个子议题且依赖关系并非全线性时。
无需使用的场景:
- 单一PR的规范——计划就是Plan部分,无需DAG。
- 未解决的设计问题——在达成共识前绘制DAG只是形式主义。
- “图”是包含三个或更少节点的单一线性链——一句话比图表更简洁。
Inputs
输入参数
| Parameter | Default |
|---|---|
| Scope | Inferred — current branch's spec, the umbrella the user named, or the open issues just surveyed. |
| Granularity | Spec-level; drop to sub-issue / PR level for an umbrella that has fanned out. |
| Output | High-DPI PNG (only target). |
| 参数 | 默认值 |
|---|---|
| Scope(范围) | 自动推断——当前分支的规范、用户指定的总览议题,或刚调查过的开放议题。 |
| Granularity(粒度) | 规范级别;对于已扩展的总览议题,可降至子议题/PR级别。 |
| Output(输出) | 高DPI PNG(唯一目标格式)。 |
Workflow
工作流程
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 commentary1. 发现 获取范围内的议题/子议题/PR
2. 分类 标记每个节点为已完成/进行中/开放
3. 构建边 从子议题链接和正文文本中读取依赖关系
4. 渲染 生成JSON中间表示(IR)→ 转为PNG → 通过SendUserFile发送并附带文本说明1. Discover
1. 发现
Resolve the scope from the conversation, not from a generic crawl. Typical triggers:
- An umbrella spec → walk its sub-issue list (with
mcp__github__issue_read).method: get_sub_issues - A track of follow-ups from a recent merge → use the priority + area filter the user implied.
- The set of issues just surveyed → reuse that list verbatim, don't refetch.
For each node, capture:
- Issue state (/
open) andclosed(state_reasonvs other).completed - Status labels (,
in-progress,planned) anddraft.priority:* - Linked PRs — look for "merged in PR #N" in the body or comments.
- Sub-issue list (only for umbrella nodes).
从对话中确定范围,而非通用爬取。典型触发场景:
- 总览规范→遍历其子议题列表(调用并使用
mcp__github__issue_read)。method: get_sub_issues - 最近合并后的后续任务跟踪→使用用户隐含的优先级和领域筛选条件。
- 刚调查过的议题集合→直接复用该列表,无需重新获取。
为每个节点捕获以下信息:
- 议题状态(/
open)和closed(state_reason或其他)。completed - 状态标签(、
in-progress、planned)和draft。priority:* - 关联的PR——在正文或评论中查找“merged in PR #N”。
- 子议题列表(仅针对总览节点)。
2. Classify
2. 分类
Each node gets exactly one status:
| State | Definition |
|---|---|
| Closed + |
| Open + |
| Open + |
Don't render "blocked" as a separate status — the renderer derives it from the graph (any node with a non-done predecessor) and styles it with a dashed muted fill. Conversely, an node whose predecessors are all is dual-encoded as the "available next" highlight.
openopendone每个节点仅对应一种状态:
| 状态 | 定义 |
|---|---|
| 已关闭且 |
| 开放且带有 |
| 开放且带有 |
不要将“blocked”作为单独状态渲染——渲染器会从图中推导该状态(任何带有未完成前置节点的节点),并使用虚线浅填充样式。反之,所有前置节点均为的节点会被双重编码为“可启动”高亮。
opendoneopen3. Edge-build
3. 构建边
Edges come from three sources, in this order of trust. Don't draw an edge you can't cite from one of these — speculation pollutes the DAG.
- Sub-issue links (+
mcp__github__issue_read). Edge direction is child → parent (prerequisite → dependent) — the parent closes when its children close, so the DAG flows toward closure.method: get_sub_issues - Explicit prose in issue body: ,
Depends on #N,Hard depends on #N,Blocks #N,Part of #N.Closes #N - PR/commit references: ,
merged in PR #N,closed by #Nordering inside an umbrella spec's Plan.PR-A → PR-B
If a dependency is "obvious to me but uncited", the node label can hint at it; the edge stays out.
边来自三个来源,按信任优先级排序。不要绘制无法从以下来源引用的边——推测会污染DAG。
- 子议题链接(+
mcp__github__issue_read)。边的方向为子节点→父节点(前置任务→依赖任务)——父议题在子议题完成后关闭,因此DAG流向完成状态。method: get_sub_issues - 议题正文中的明确文本:、
Depends on #N、Hard depends on #N、Blocks #N、Part of #N。Closes #N - PR/提交引用:、
merged in PR #N、总览规范Plan部分中的closed by #N顺序。PR-A → PR-B
如果某个依赖“对我来说很明显但未被引用”,可以在节点标签中暗示,但不要添加边。
4. Render
4. 渲染
Emit a JSON IR matching the schema below, then invoke the renderer. Do not hand-draw boxes — the renderer produces deterministically correct layout that the AI's spatial reasoning will not match, especially with cross-edges and fan-out.
Schema:
json
{
"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; defaults to{done, in_progress, open}. Status is rendered as a fill color plus a leading emoji — do not embed markers inopen.label - ∈
edges[].source, required. This is the citation rule from Conventions made enforceable: no edge without a documented source on GitHub.{sub-issue, depends-on, pr-link, closes, part-of} - Every /
fromresolves to a declared node id, or the literalto."close" - is optional; communicate it in prose alongside the rendered PNG, not inside the image.
critical_path
Visual encoding. Status is dual-encoded by fill and a leading emoji so the eye picks up state before reading the label:
| State | Fill | Border | Emoji |
|---|---|---|---|
| Done | muted green | green | ✅ |
| In-progress | amber | thicker amber | 🟡 |
| Open + all preds done ("available next") | cool blue | thick blue | 🎯 |
| Open + blocked | near-white | grey, dashed | ⬜ |
| Close sentinel | white | double | 🏁 |
The "available next" highlight is computed from the graph (open + every predecessor is ) — no IR field for it. Critical-path edges are not bolded: which path is "the" critical path is a caller judgement, and elevating it visually would conflate the recommendation with the graph's topology. Keep the critical path in and let prose carry the next-pick recommendation under the rendered PNG.
doneir.critical_pathThe flag controls whether status emoji are emitted: (default) shows ✅ / 🟡 / 🎯 / ⬜ / 🏁 emoji; falls back to trailing ✓ / … text markers. Turn if the target system lacks a color emoji font and the PNG shows tofu boxes.
--emojionoffoffInvocation. The renderer ships inside the skill. Use the path that matches how the skill was installed:
- Project-scope install (default for from a repo root):
npx skills add onsager-ai/dev-skills.claude/skills/plan-dag/scripts/plan-dag-render.py - User-global install ():
npx skills add -g …~/.claude/skills/plan-dag/scripts/plan-dag-render.py
Pick whichever exists. If unsure, .
test -x .claude/skills/plan-dag/scripts/plan-dag-render.py && echo project || echo globalbash
SCRIPT=.claude/skills/plan-dag/scripts/plan-dag-render.py # project install生成符合以下 schema 的JSON中间表示(IR),然后调用渲染器。不要手动绘制方框——渲染器会生成确定正确的布局,这是AI空间推理无法匹配的,尤其是在处理交叉边和分支时。
Schema:
json
{
"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,为必填项。这是《约定》中引用规则的可执行形式:没有GitHub上的文档来源,就不能添加边。{sub-issue, depends-on, pr-link, closes, part-of} - 每个/
from必须解析为已声明的节点ID,或字面量to。"close" - 为可选;在渲染后的PNG旁的文本说明中提及,不要放在图像内。
critical_path
视觉编码:状态通过填充色和前置表情双重编码,让用户在阅读标签前就能识别状态:
| 状态 | 填充色 | 边框 | 表情 |
|---|---|---|---|
| 已完成 | 暗绿色 | 绿色 | ✅ |
| 进行中 | 琥珀色 | 较粗琥珀色 | 🟡 |
| 开放且所有前置任务已完成(“可启动”) | 冷蓝色 | 粗蓝色 | 🎯 |
| 开放且被阻塞 | 近白色 | 灰色虚线 | ⬜ |
| 完成标记 | 白色 | 双边框 | 🏁 |
“可启动”高亮由图计算得出(开放且所有前置节点为)——无需在IR中设置字段。关键路径的边不加粗:哪条路径是“关键路径”由调用者判断,视觉上突出它会将建议与图的拓扑结构混淆。将关键路径保留在中,在渲染后的PNG下方通过文本说明给出下一个任务的建议。
doneir.critical_path--emojionoffoff调用方式:渲染器随技能一起发布。使用与技能安装方式匹配的路径:
- 项目范围安装(从代码库根目录执行的默认方式):
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 globalbash
SCRIPT=.claude/skills/plan-dag/scripts/plan-dag-render.py # 项目安装SCRIPT=~/.claude/skills/plan-dag/scripts/plan-dag-render.py # global install
SCRIPT=~/.claude/skills/plan-dag/scripts/plan-dag-render.py # 全局安装
default: high-DPI PNG with emoji status indicators
默认:带表情状态指示器的高DPI PNG
"$SCRIPT" /tmp/plan.json --out /tmp/plan-dag.png
"$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
The renderer needs `dot` (graphviz; `apt install graphviz` / `brew install graphviz`) on PATH for the SVG layout step, and `node` (≥18) + Playwright Chromium (`npm i -g playwright && npx playwright install chromium`) for the rasterisation step. Both checks run upfront and fail loudly with install guidance — there is no silent fallback to text or ASCII, by design (the formats removed had limitations the PNG output exists to avoid).
If the renderer aborts with `IR validation failed`, fix the IR — do not work around it by hand-drawing. The validation surface is the citation rule (`Conventions › No invented edges`) made executable.
**Response handling after running the renderer:**
- Send the PNG via `SendUserFile` so it renders inline as part of the assistant message.
- Add prose commentary below the file — critical path, next pickable node, sequencing rationale. The PNG carries the topology; the prose carries the recommendation.
- Do **not** re-render the same plan in another format and attach both — one DAG per response.
- For very wide graphs (>10 nodes with cross-edges) where the single PNG becomes unwieldy, split the plan into per-track DAGs (one renderer call per track) and render the cross-edges as a final short prose list, per the existing "Cross-edges" convention in §3."$SCRIPT" /tmp/plan.json --out /tmp/plan-dag.png --emoji=off
渲染器需要PATH中存在`dot`(graphviz;可通过`apt install graphviz`/`brew install graphviz`安装)用于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”约定,将交叉边作为最终的简短文本列表呈现。Conventions
约定
- No invented edges. If you can't cite the source (sub-issue link, body prose, PR header), don't draw it.
- Summarize done nodes when dense. More than ~3 done nodes in a track? Collapse to in prose rather than enumerating them in the graph.
Landed: #A #B #C ✓ - One DAG per response. Don't render the same plan twice under different framings — pick the framing that answers the question that was actually asked.
- End with the picked path. A DAG without a recommended sequence is a wall of boxes. Close with the critical path and the next pickable node in prose, framed so the user can redirect.
- Don't editorialize inside the diagram. Commentary ("this looks risky", "we should reorder") goes in prose above or below, never inside a node label.
- 禁止虚构边:如果无法引用来源(子议题链接、正文文本、PR标题),请勿绘制边。
- 密集时汇总已完成节点:某个跟踪项中有超过约3个已完成节点?在文本中折叠为,而非在图中逐一列出。
已完成:#A #B #C ✓ - 每个响应一个DAG:不要以不同框架重复渲染同一计划——选择能实际回答问题的框架。
- 以选定路径结尾:没有推荐顺序的DAG只是一堆方框。在文本中以关键路径和可选择的下一个任务结尾,方便用户调整。
- 不要在图中加入评论:评论(如“这看起来有风险”“我们应该重新排序”)放在图上方或下方的文本中,永远不要放在节点标签内。
Tests
测试
Tests live next to the renderer at and exercise validation (bad IR, cycle detection, own-id-prefix boundary cases), the styled DOT structural checks (status fills, available-next, blocked-dashed, close double border), and the end-to-end PNG smoke. The PNG smoke is auto-skipped when Playwright Chromium is unavailable so the validator coverage still runs in restricted CI. Run with the same install-aware path the renderer uses:
scripts/plan-dag-render.test.shbash
undefined测试用例与渲染器一同存放在,涵盖验证(错误IR、循环检测、自身ID前缀边界情况)、样式化DOT结构检查(状态填充、可启动高亮、阻塞虚线、完成双边框)和端到端PNG冒烟测试。当Playwright Chromium不可用时,PNG冒烟测试会自动跳过,以便在受限CI环境中仍能运行验证覆盖。使用与渲染器相同的安装感知路径运行:
scripts/plan-dag-render.test.shbash
undefinedproject-scope install
项目范围安装
.claude/skills/plan-dag/scripts/plan-dag-render.test.sh
.claude/skills/plan-dag/scripts/plan-dag-render.test.sh
user-global install
用户全局安装
~/.claude/skills/plan-dag/scripts/plan-dag-render.test.sh
Both forms are in `allowed-tools` so Claude Code doesn't re-prompt for permission. The test script internally `cd`s into the skill root and invokes `scripts/plan-dag-render.py` as a child process — that child invocation runs inside the script's own shell, not through Claude Code's permission engine, so it doesn't need a separate allowlist entry.
Requires `dot` (graphviz) on PATH; the PNG smoke additionally requires `node` + Playwright Chromium.~/.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。Related skills
相关技能
- The repo's spec-driven-development loop skill (e.g. ,
onsager-dev-process) — the parent / child / depends-on semantics the DAG visualizes.duhem-dev-process - The repo's skill — how parent / child / depends-on edges are persisted on GitHub.
issue-spec - The repo's PR-lifecycle skill — how "in-progress" status flips on PR open / merge, which drives the amber fill on the rendered PNG.
- 代码库的规范驱动开发循环技能(如、
onsager-dev-process)——DAG可视化所基于的父/子/依赖语义。duhem-dev-process - 代码库的技能——父/子/依赖边在GitHub上的持久化方式。
issue-spec - 代码库的PR生命周期技能——PR打开/合并时“in-progress”状态如何切换,这决定了渲染PNG中的琥珀色填充。