Skip to content

Commit 4c66236

Browse files
tikazyqclaude
andauthored
chore(skills): sync plan-dag from onsager-skills (PNG-only renderer) (#293)
Pulls the latest plan-dag (upstream commits #9#13): renderer collapsed to PNG-only, status fills + available-next highlight, duplicate-id labels rejected, and the obsolete ascii/tb/svg/html fixture artifacts dropped. Moves the skill from .claude/skills/plan-dag/ (real directory) to .agents/skills/plan-dag/ with a .claude/skills/plan-dag symlink, matching the multi-agent layout the skills CLI now emits. Co-authored-by: Claude <noreply@anthropic.com>
1 parent 5f2ad4c commit 4c66236

20 files changed

Lines changed: 877 additions & 1551 deletions

.agents/skills/plan-dag/SKILL.md

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
---
2+
name: plan-dag
3+
description: Render the current plan as a high-DPI PNG dependency DAG — nodes are issues / sub-issues / PRs, edges come from sub-issue links plus dependency-language prose ("Depends on", "Part of", "Blocks", "Closes") and PR / commit cross-references, and every node is color-coded done / in-progress / available-next / blocked so sequencing and critical path are obvious at a glance. Use when asked "plan as dag", "draw a dag", "dag diagram", "show the dependency graph", "what's blocking what", "what's the critical path", "what can be parallelized", "what's left for #N", or right after a `what's next` survey when sequencing the next pick is the actual question.
4+
allowed-tools: Read, Bash(git log:*), Bash(git status:*), Bash(git branch:*), Bash(.claude/skills/plan-dag/scripts/plan-dag-render.py:*), Bash(~/.claude/skills/plan-dag/scripts/plan-dag-render.py:*), Bash(.claude/skills/plan-dag/scripts/plan-dag-render.test.sh:*), Bash(~/.claude/skills/plan-dag/scripts/plan-dag-render.test.sh:*), mcp__github__issue_read, mcp__github__list_issues, mcp__github__search_issues, mcp__github__list_pull_requests, mcp__github__pull_request_read
5+
---
6+
7+
# plan-dag
8+
9+
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 `SendUserFile`.
10+
11+
This 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.
12+
13+
**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.
14+
15+
## When to use
16+
17+
- After a `what's next` survey, to commit to a sequence.
18+
- When asked "what's left for #N" on an umbrella spec with sub-issues.
19+
- Before picking the next branch — to see what unblocks the most downstream work.
20+
- When a spec fans out into N sub-issues and the dependency edges aren't all linear.
21+
22+
Skip when:
23+
24+
- A single-PR spec — the plan *is* the Plan section, not a DAG.
25+
- An unanswered design question — drawing a DAG before alignment is theater.
26+
- The "graph" is one linear chain of three or fewer nodes — a sentence is shorter than a diagram.
27+
28+
## Inputs
29+
30+
| Parameter | Default |
31+
|-----------|----------|
32+
| **Scope** | Inferred — current branch's spec, the umbrella the user named, or the open issues just surveyed. |
33+
| **Granularity** | Spec-level; drop to sub-issue / PR level for an umbrella that has fanned out. |
34+
| **Output** | High-DPI PNG (only target). `--out <path>` is required; emoji status indicators are on by default and can be turned off with `--emoji=off` if the rendering system lacks a color emoji font. |
35+
36+
## Workflow
37+
38+
```
39+
1. Discover Pull issues / sub-issues / PRs in scope
40+
2. Classify Mark each node done / in-progress / open
41+
3. Edge-build Read dependencies from sub-issue links + body prose
42+
4. Render Emit JSON IR → PNG → SendUserFile + prose commentary
43+
```
44+
45+
### 1. Discover
46+
47+
Resolve the scope from the conversation, not from a generic crawl. Typical triggers:
48+
49+
- An umbrella spec → walk its sub-issue list (`mcp__github__issue_read` with `method: get_sub_issues`).
50+
- A track of follow-ups from a recent merge → use the priority + area filter the user implied.
51+
- The set of issues just surveyed → reuse that list verbatim, don't refetch.
52+
53+
For each node, capture:
54+
55+
- Issue state (`open` / `closed`) and `state_reason` (`completed` vs other).
56+
- Status labels (`in-progress`, `planned`, `draft`) and `priority:*`.
57+
- Linked PRs — look for "merged in PR #N" in the body or comments.
58+
- Sub-issue list (only for umbrella nodes).
59+
60+
### 2. Classify
61+
62+
Each node gets exactly one status:
63+
64+
| State | Definition |
65+
|-------|------------|
66+
| `done` | Closed + `state_reason: completed`, or merged PR. |
67+
| `in_progress` | Open + `in-progress` label, or an open PR exists for it. |
68+
| `open` | Open + `planned` / `draft`, no PR. |
69+
70+
Don't render "blocked" as a separate status — the renderer derives it from the graph (any `open` node with a non-done predecessor) and styles it with a dashed muted fill. Conversely, an `open` node whose predecessors are all `done` is dual-encoded as the "available next" highlight.
71+
72+
### 3. Edge-build
73+
74+
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.
75+
76+
1. **Sub-issue links** (`mcp__github__issue_read` + `method: get_sub_issues`). Edge direction is **child → parent** (prerequisite → dependent) — the parent closes when its children close, so the DAG flows toward closure.
77+
2. **Explicit prose** in issue body: `Depends on #N`, `Hard depends on #N`, `Blocks #N`, `Part of #N`, `Closes #N`.
78+
3. **PR/commit references**: `merged in PR #N`, `closed by #N`, `PR-A → PR-B` ordering inside an umbrella spec's Plan.
79+
80+
If a dependency is "obvious to me but uncited", the node label can hint at it; the edge stays out.
81+
82+
### 4. Render
83+
84+
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.
85+
86+
**Schema:**
87+
88+
```json
89+
{
90+
"nodes": [
91+
{"id": "288", "label": "MCP", "status": "done"},
92+
{"id": "305", "label": "router", "status": "in_progress"},
93+
{"id": "306", "label": "cleanup","status": "open"}
94+
],
95+
"edges": [
96+
{"from": "288", "to": "304", "source": "sub-issue"},
97+
{"from": "305", "to": "306", "source": "depends-on"}
98+
],
99+
"close": "300",
100+
"critical_path": ["301", "305", "306", "307", "close"]
101+
}
102+
```
103+
104+
- `status``{done, in_progress, open}`; defaults to `open`. Status is rendered as a fill color plus a leading emoji — do not embed markers in `label`.
105+
- `edges[].source``{sub-issue, depends-on, pr-link, closes, part-of}`, required. This is the citation rule from Conventions made enforceable: no edge without a documented source on GitHub.
106+
- Every `from` / `to` resolves to a declared node id, or the literal `"close"`.
107+
- `critical_path` is optional; communicate it in prose alongside the rendered PNG, not inside the image.
108+
109+
**Visual encoding.** Status is dual-encoded by fill and a leading emoji so the eye picks up state before reading the label:
110+
111+
| State | Fill | Border | Emoji |
112+
|-------|------|--------|-------|
113+
| Done | muted green | green ||
114+
| In-progress | amber | thicker amber | 🟡 |
115+
| Open + all preds done ("available next") | cool blue | thick blue | 🎯 |
116+
| Open + blocked | near-white | grey, dashed ||
117+
| Close sentinel | white | double | 🏁 |
118+
119+
The "available next" highlight is computed from the graph (open + every predecessor is `done`) — 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 `ir.critical_path` and let prose carry the next-pick recommendation under the rendered PNG.
120+
121+
The `--emoji` flag controls whether status emoji are emitted: `on` (default) shows ✅ / 🟡 / 🎯 / ⬜ / 🏁 emoji; `off` falls back to trailing ✓ / … text markers. Turn `off` if the target system lacks a color emoji font and the PNG shows tofu boxes.
122+
123+
**Invocation.** The renderer ships inside the skill. Use the path that matches how the skill was installed:
124+
125+
- **Project-scope install** (default for `npx skills add onsager-ai/onsager-skills` from a repo root): `.claude/skills/plan-dag/scripts/plan-dag-render.py`
126+
- **User-global install** (`npx skills add -g …`): `~/.claude/skills/plan-dag/scripts/plan-dag-render.py`
127+
128+
Pick whichever exists. If unsure, `test -x .claude/skills/plan-dag/scripts/plan-dag-render.py && echo project || echo global`.
129+
130+
```bash
131+
SCRIPT=.claude/skills/plan-dag/scripts/plan-dag-render.py # project install
132+
# SCRIPT=~/.claude/skills/plan-dag/scripts/plan-dag-render.py # global install
133+
134+
# default: high-DPI PNG with emoji status indicators
135+
"$SCRIPT" /tmp/plan.json --out /tmp/plan-dag.png
136+
137+
# emoji off — falls back to ✓ / … text markers in node labels
138+
"$SCRIPT" /tmp/plan.json --out /tmp/plan-dag.png --emoji=off
139+
```
140+
141+
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).
142+
143+
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.
144+
145+
**Response handling after running the renderer:**
146+
147+
- Send the PNG via `SendUserFile` so it renders inline as part of the assistant message.
148+
- Add prose commentary below the file — critical path, next pickable node, sequencing rationale. The PNG carries the topology; the prose carries the recommendation.
149+
- Do **not** re-render the same plan in another format and attach both — one DAG per response.
150+
- 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.
151+
152+
## Conventions
153+
154+
- **No invented edges.** If you can't cite the source (sub-issue link, body prose, PR header), don't draw it.
155+
- **Summarize done nodes when dense.** More than ~3 done nodes in a track? Collapse to `Landed: #A #B #C ✓` in prose rather than enumerating them in the graph.
156+
- **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.
157+
- **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.
158+
- **Don't editorialize inside the diagram.** Commentary ("this looks risky", "we should reorder") goes in prose above or below, never inside a node label.
159+
160+
## Tests
161+
162+
Tests live next to the renderer at `scripts/plan-dag-render.test.sh` 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:
163+
164+
```bash
165+
# project-scope install
166+
.claude/skills/plan-dag/scripts/plan-dag-render.test.sh
167+
168+
# user-global install
169+
~/.claude/skills/plan-dag/scripts/plan-dag-render.test.sh
170+
```
171+
172+
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.
173+
174+
Requires `dot` (graphviz) on PATH; the PNG smoke additionally requires `node` + Playwright Chromium.
175+
176+
## Related skills
177+
178+
- The repo's spec-driven-development loop skill (e.g. `onsager-dev-process`, `duhem-dev-process`) — the parent / child / depends-on semantics the DAG visualizes.
179+
- The repo's `issue-spec` skill — how parent / child / depends-on edges are persisted on GitHub.
180+
- The repo's PR-lifecycle skill — how "in-progress" status flips on PR open / merge, which drives the amber fill on the rendered PNG.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
{"id": "304", "label": "chat", "status": "shipped"},
66
{"id": "305", "status": "open"},
77
{"id": "307", "label": "bad \"quote\""},
8+
{"id": "308", "label": "#308 dup-id-prefix", "status": "open"},
89
{"id": "close", "label": "reserved"},
910
"not-an-object"
1011
],
File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)