Use whenever the user wants to document, visualize, or update the workflows between packages or components of an application as a single-page HTML diagram. Triggers include "document flows", "visualize the architecture", "show how X works across packages", "map the data flow for action Y", "create flows.json", "regenerate workflow docs", or requests to build/edit an interactive package interaction diagram. Owns the canonical schema for flows.json and the single-file HTML template that renders it as a dark-navy swim-lane diagram with gold-accented numbered step arrows.
How this skill is triggered — by the user, by Claude, or both
Slash command
/claude-agents-visualizer:workflow-visualizerThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
This skill owns:
This skill owns:
PROMPT.md — the source-of-truth instructions an AI must follow when generating the diagram. Layout, look & feel, interaction, and data model. Do not deviate.flows.json schema (categories, columns, components, flows) that documents what an application does.template.html — a single self-contained HTML viewer (vanilla HTML/CSS/JS + SVG). No external dependencies, works offline from file://.example-flows.json — a reference dataset (ToDesktop-flavoured) demonstrating every schema feature.Use the skill any time the user wants to:
flows.json for an app.For sustained work on an app's docs, prefer invoking the workflow-doc-generator subagent — it wraps this skill with project-resolution logic and validation.
flows.json resolved in this order:
$PWD/flows.json — committed to the repo, shared with the team.$PWD/.claude/flows.json — typically gitignored, project-local.$PWD/docs/flows.json — alongside the rest of project docs.$HOME/.claude/workflow-docs/projects/<project-slug>/flows.json — per-project sandbox under ~/.claude (zero project pollution). Auto-created on first session in a new project by the bootstrap-flows.sh hook. The slug is <basename(PWD)>-<8-char-hash-of-PWD> (e.g. my-app-a1b2c3d4) so the directory is human-scannable but two same-named projects in different parents never collide.$HOME/.claude/workflow-docs/flows.json — global fallback.example-flows.json.$HOME/.claude/workflow-docs/projects/<project-slug>/index.html (single self-contained file with JSON inlined). The HTTP server serves the docs dir as root, so the page loads from http://127.0.0.1:<port>/projects/<project-slug>/index.html. Each project's tab is independent — opening Claude Code in repo A never clobbers repo B's view.$HOME/.claude/workflow-docs/projects/<project-slug>/activity.jsonl. The Live agents pane in each project's tab only shows that project's activity.$HOME/.claude/skills/workflow-visualizer/template.html (this skill).$HOME/.claude/skills/workflow-visualizer/PROMPT.md.When you start a new Claude Code session in a directory that has no flows.json anywhere on the priority chain, the bootstrap-flows.sh hook fires and:
~/.claude/workflow-docs/projects/<cwd-hash>/flows.json seeded from the bundled example..needs-generation marker in the same directory.SessionStart context message asking the main agent to invoke the workflow-doc-generator subagent immediately, replace the placeholder with the real architecture, and delete the marker when done.This keeps every project diagram private to your machine (under ~/.claude, not in the repo) unless you explicitly ask to promote it to $PWD/flows.json.
On every session start in a project that already has a flows.json, bootstrap-flows.sh reads it and emits a compact summary as SessionStart.additionalContext. The agent sees:
Actors → Client surfaces → … → External services)name [column / category] — subtitle), capped at 50name (N steps) — description), capped at 20Typical payload is 500–1500 tokens depending on project size. The agent uses this as background context for architecture and data-flow questions without needing to re-read the JSON. To edit the underlying file, the user invokes the workflow-doc-generator subagent — never modify flows.json from this context-injection path.
The template has a <script type="application/json" id="flows-data">__FLOWS_JSON__</script> placeholder; the hook ($HOME/.claude/hooks/open-workflow-docs.sh) replaces __FLOWS_JSON__ with the JSON content so the page is fully self-contained.
{
"app": { "name": "string", "description": "one-line subtitle" },
"categories": [
{ "id": "kebab", "label": "Human readable", "color": "#rrggbb" }
],
"columns": [
{ "id": "kebab", "label": "UPPERCASE LANE HEADER" }
],
"components": [
{
"id": "kebab-unique",
"name": "monospace name", // rendered in monospace inside the card
"subtitle": "short caption", // smaller sans-serif line under the name
"column": "<columns.id>", // which lane it sits in
"category": "<categories.id>", // category tint + legend swatch
"paths": ["packages/cli/"] // optional — substrings matched against
// tool targets so the "Live agents"
// pane pulses this card when the agent
// touches files/URLs/commands containing
// any of these strings
}
],
"flows": [
{
"id": "kebab-unique",
"name": "Human readable action",
"description": "One line that fits in the sidebar",
"category": "optional sidebar grouping",
"steps": [
{
"from": "<components.id>",
"to": "<components.id>",
"label": "imperative action — POST /v1/invites or publishRelease()",
"detail": "short prose: what's passed, where the code lives, edge cases"
}
]
}
]
}
Left-to-right, in request-flow direction:
Actors → Client surfaces → Backend/functions → Storage/data → Pipeline → Distribution → External services
Adapt the labels to the app, but keep the tiering.
| Category id | Colour | Use for |
|---|---|---|
actor | #f472b6 pink | Humans / external systems initiating flows |
client | #22d3ee cyan | Frontends / CLIs / browser apps |
firebase-fn | #a78bfa violet | Backend functions / service code |
firebase-data | #fb923c orange | Data stores |
pipeline | #34d399 green | Build / CI / processing |
distribution | #60a5fa blue | CDN / edge / workers |
external | #9ca3af gray | Third-party APIs the app calls |
step.label is read at a glance from the diagram. Write it like a log line, not like prose:
POST /v1/invites { email, role } → 201 { inviteId }publish event "user.invited" to SNSuseInviteUser() → graphql mutation inviteUser($input)Sends a request to the API (too vague)The web app does some processing and then talks to the auth service (narrative, not data)step.detail is the prose line beneath: one sentence on what changes, where the code lives, or a non-obvious side-effect.
Refuse to write JSON that fails any of these:
step.from and step.to must reference an existing components[].id.component.column must reference an existing columns[].id.component.category must reference an existing categories[].id.id values are unique within categories[], columns[], components[], and flows[].categories, columns, components, and flows are non-empty.steps is non-empty for each flow.The renderer also accepts an older packages schema (with package.label, step.annotation, step.payload) and normalises it at load time. Always write new files in the canonical schema above — the old format is for read-compat only.
The template has a tab toggle at the top of <main> with two views:
status: "pending"status: "in_progress" (column glows gold; spinning dot on the heading)status: "completed"Task cards show: content (up to 3 lines), a priority badge (High / Medium / Low with colour coding), and the owner/agent name. Left border colour matches priority (red = high, gold = medium, gray = low).
The Kanban tab shows a badge counting In Dev tasks. Cards animate in when they appear and smoothly re-sort as status changes.
The Kanban has two sources, used in priority order:
Tier 1 — tasks.json (from TodoWrite)
tasks.json is written automatically by log-activity.sh whenever Claude Code calls the native TodoWrite tool. This only fires when the agent explicitly plans multi-step work — it will not populate in short single-step sessions.
To trigger it, ask Claude to plan before doing:
Before starting, write out a task list for everything you need to do using your todo tool.
or simply:
Plan this work as tasks first.
Once TodoWrite fires, log-activity.sh extracts tool_input.todos (the full array with id, content, status, priority) and writes it to ~/.claude/workflow-docs/projects/<slug>/tasks.json. The board picks it up on the next 1 s poll. Cards show the exact task text and priority as written by the agent; no live badge.
Tier 2 — Synthesized from the agents map (always-on fallback)
When tasks.json is empty or missing — which is most sessions — the board synthesizes a live view from the agents map maintained by the activity poller:
UserPromptSubmit / Task-tool spawn prompt) or, if no goal is known, the last recorded tool + target.in_progress if the agent was heard from within the last 30 s, completed once subagent-stop fires, pending if it hasn't been heard from yet.live badge to signal they are synthesized, not explicitly planned.The synthesized view populates as soon as the first tool call fires (within the first poll cycle, 800 ms). It refreshes continuously. When the agent eventually calls TodoWrite, the explicit tasks take over and the live badges disappear.
| Source | When active | Card content | live badge |
|---|---|---|---|
tasks.json (TodoWrite) | After agent calls TodoWrite | Exact task text + priority | No |
| Synthesized from agents | Always — fallback | Agent goal or last tool+target | Yes (green) |
Implementation detail: log-activity.sh detects tool_name = "TodoWrite" in PreToolUse payloads and uses jq (or python3 as fallback) to extract and write the todos array. TaskCreate / TaskUpdate MCP tool calls also emit a task-change event in activity.jsonl.
workflow-kanban-task skillThe companion skill workflow-kanban-task provides plain-English task management. Claude routes to it automatically. Example phrases:
add a task: implement auth refresh → creates card in Planned
start the auth task → moves to In Dev
done with the auth task → moves to Done
show tasks → prints the board as a table
clear done tasks → removes all completed items
See ~/.claude/skills/workflow-kanban-task/SKILL.md for the full operation reference and natural-language mapping table.
The template implements PROMPT.md literally. Three regions:
from → to route, action label, and short detail.When a flow is selected: involved cards stay bright (yellow tint + glow), others dim to ~20% opacity, curved SVG bezier arrows render between cards in step order with gold circular step-number badges at each arrow's midpoint. Bidirectional step pairs (A→B and B→A) curve in opposite directions. Self-actions (from === to) place the badge directly on the card.
Click the same flow again to clear, or use the "Clear selection" button. Arrows redraw on window resize.
The template is fully self-contained: vanilla HTML/CSS/JS, custom SVG arrow rendering, no external CDN, no build step.
flows.json lives. Default to $PWD/flows.json for project-level docs.PROMPT.md first, then copy example-flows.json as a starting point and adapt.Glob/Grep to find package boundaries (packages/*/package.json, top-level service folders, monorepo workspaces, firebase.json, terraform/**).index.html):
bash $HOME/.claude/hooks/open-workflow-docs.sh session-start
On macOS the hook also opens Chrome.template.html to suit a single project. If a rendering change is needed across all apps, edit the skill's template and the hook will propagate it.PROMPT.md for layout, look & feel, or interaction. That document wins.Two built-in themes, selectable via the toggle button in the top-right of the canvas (sun ↔ moon, animated). Persisted in localStorage under workflow-docs-theme. Defaults to dark.
shades-of-purple (default, dark) — navy/indigo backgrounds, gold accent, green for live activity.ayu-light (light) — warm off-white, orange accent, green for live activity.Both palettes are declared as CSS variables in two [data-theme="…"] blocks at the top of template.html. Adding a third theme is a matter of duplicating one block, renaming, and shipping new colour values.
aside.side) is horizontally resizable from 280 px to 480 px via a drag handle on its left edge.localStorage under workflow-docs-layout.When opened from http://127.0.0.1:<port>/ (the default), three independent live loops are running:
| Loop | Source | Cadence |
|---|---|---|
| Flows / steps / board | flows.json (symlinked to the resolved source) | every 1 s |
| Live agents pane | activity.jsonl (appended to by hook) | every 800 ms |
| Component pulse | new activity rows matched against component.paths[] | event-driven |
Edits to $PWD/flows.json (or any of the resolved source paths) propagate to the diagram within ~1 s with no hook fire required — the docs dir's flows.json is a symlink, so the page sees source edits directly. Tool calls fired by any agent in any Claude Code session writing to the same machine appear in the Live agents pane within ~800 ms.
Opened from file:// you keep the static snapshot baked into index.html at hook-fire time — no live loops, no live agents.
The bottom pane has two tabs:
UserPromptSubmit payload for the main agent, or from the spawning Task tool's prompt field for subagents), and the most recent tool + target.Both are fed by the same activity.jsonl stream. The hook captures:
| Hook event | Logged as | Fields |
|---|---|---|
UserPromptSubmit | user-prompt | sets main's goal |
PreToolUse (tool=Task) | spawn (synthetic) + pre-tool-use | sets the subagent's goal |
PreToolUse / PostToolUse | pre-tool-use / post-tool-use | tool + target |
SubagentStart / SubagentStop | subagent-start / subagent-stop | status transitions |
When the page is served from http://127.0.0.1:<port>/ (the included workflow-docs-server.sh hook brings this up automatically on session start), a third right-side pane appears: Live agents. It polls ./activity.jsonl every ~800ms and shows the last 80 events with timestamp, agent name, tool, and target.
Each event is matched against every component's paths[] (longest substring wins). On match, that card briefly pulses gold. Components with no paths simply don't pulse — they still appear in the diagram. Match patterns are plain substrings, not globs: packages/cli/ matches any file path containing that string, todesktop build matches any Bash command line containing it, https://api.stripe.com matches that URL.
Events come from four hook types — PreToolUse, PostToolUse, SubagentStart, SubagentStop. The agent name on each event is the subagent type when it's a subagent event, and main otherwise. Logged tools include Read, Write, Edit, Bash, Glob, Grep, WebFetch, plus anything else Claude Code runs.
If the page is opened directly from file://, the pane shows an "offline (open via http://)" message — fetch is blocked under CORS so live polling doesn't work. The hook's open script automatically picks the HTTP URL when the server is up.
PROMPT.md — canonical spec, copy-pasteable into a fresh chat to regenerate the template from scratch.example-flows.json — full example demonstrating every schema feature, including self-actions, bidirectional steps, and paths for live agent matching.template.html — the renderer. Treat as read-only from this skill's perspective unless a cross-project rendering change is needed./workflow-server slash
command. The flag lives at ~/.claude/workflow-docs/config.json
(serverEnabled: true|false) and is read by
~/.claude/hooks/workflow-docs-server.sh on every fire. If a user reports
the live-agents pane is offline, check that flag first.Guides creation, editing, and verification of skills for AI coding agents using test-driven development with subagent scenarios. Use when authoring or debugging skills.
npx claudepluginhub vianch/workflow-docs --plugin claude-agents-visualizer