Skip to content
Agent Orchestration
Skill

/statechart

Check a TLA+ model of a system that moves between states, such as a product flow or a protocol, and draw it as a Mermaid statechart. The report gives the shortest path to each state that breaks a rule. Use for /statechart.

From plugin
oh-my-claudecode
41k84 skills21 agents21 commands11 hooks
+1

Claude Code

$ npx -y skills add Yeachan-Heo/oh-my-claudecode --skill statechart --agent claude-code

SKILL.md

statechart.SKILL.md
name: statechart
description: "Check a TLA+ model of a system that moves between states, such as a product flow or a protocol, and draw it as a Mermaid statechart. The report gives the shortest path to each state that breaks a rule. Use for /statechart."
aliases: []
metadata:
  credits:
    statechart: pstack
    author: Lauren Tan
    url: "https://github.com/cursor/plugins/tree/main/pstack"
    port: pstack-claude
    port-author: Michael Denyer
    port-url: "https://github.com/michael-denyer/pstack-claude"

Statechart

The chart shows how a system moves between states, in the words of the person who reads it. That person, the reader, decides whether the behavior is right.

Run

Work in a scratch folder outside the repo. Write a TLA+ spec and its config. Then run:

<skill dir>/scripts/statechart SPEC.tla [--config FILE] [--view FILE] [--out DIR]

`scripts/statechart.ts` is TypeScript, so run it with a runtime that can execute TypeScript.

The first run installs TLC and Java with mise and needs the network.

Write the spec

  • Write each rule that the reader agrees to as an INVARIANT. A rule can be about a screen, a result, a stored value, or a message. If the model has no variable for a rule, add the variable. Never weaken a rule to make it hold.
  • Write `Next` as a disjunction of the actions, such as `YouPressPlay \/ YouPressStop`. If you do not, TLC names every step `Next`.
  • A state with no step out breaks `NoDeadlock`, also when it is a correct end state. When an INVARIANT or a PROPERTY already has that name, the rule is `CHECK_DEADLOCK`. Add to `Next` an action that is true only in the end states and changes nothing, such as `Done == phase \in {"paid", "failed"} /\ UNCHANGED vars`, and give it a label in `step()`.

Pick the chart

You do the work that needs judgment: the words on each state and arrow, the phases and their colors, the title, and the report to the reader. Put your work in a view module, and give it with `--view`.

To show the reader's words, the view exports `state(vars)`. The value that `state()` returns picks the form:

  • A string gives a flat chart: `export const state = (v) => (v.paid ? "Paid" : "Paying");`
  • An array gives parent states: `export const state = (v) => v.main.split(".");`
  • An object gives one parallel region for each key: `export const state = (v) => ({ Playback: v.playback, Volume: v.volume });`

Label each state with what the reader can see or cares about, such as "Paying" or "Lease held, write pending". Keep variable names out of labels. In `vars`, a TLA+ sequence is an object with keys from 1, so `v.pc[1]` is `pc[1]`. A set is an array.

The view can also export `step(action, from, to)`. It gets the TLC action name and the values before and after the step, and it returns the label of the arrow. Write each step label as the user sees it happen, such as "you press play" or "the connect fails". Do not name the code or the TLA+ action. Two actions that end the same in the chart get the same label, so that they share one arrow.

When a step can occur only in some conditions, end its label with the condition in brackets, in the reader's words, such as `you press play [while the file loads]`. In a regions chart, the command writes the condition on the other regions on the arrow, in the words of their labels, such as `[when Offline]`. Your brackets replace it, and two arrows from one state can then look the same. So in a regions chart, do not write brackets for a condition on another region. When a value that no label shows decides where a step goes, name the result in the label, such as "the roster read fails".

The view can also export these:

  • `group(vars)` returns the name of a phase. Each chart state gets the color of its group. When the model states behind one chart state are in different groups, that state gets no color.
  • `colors` gives each group name a color class.
  • `title` is a string that the chart shows at the top.

The command calls `state()` and `group()` at most once for each model state, and `step()` at most once for each step. The chart and the report both use those answers. So make each function depend only on its arguments.

The chart does not draw a step that stays in one label. Split a label when the reader must see such a step.

Aim for about 20 arrows or fewer, because a busier chart is hard to read. First return coarser labels from `state()`. Give one label to values that act the same, and leave out a detail that the reader does not need, such as a call in flight whose return changes nothing. Keep each state of a bug path, and keep each mark. When a merge changes a `breaks` state to `mayBreak`, it hides where a rule breaks. Do not merge two states that break different rules, because the reader then cannot see which rule breaks where. If the chart is still too busy, return `null` from `step()` for the steps that the reader does not need, such as a reset that every state has, or a step whose arrow repeats what other arrows show. Obey these rules when you hide steps:

  • Keep each chart state reachable by arrows from a start state.
  • Keep at least one arrow out of each chart state that has a step out in the model.
  • Hide all the steps of an arrow, or none of them. When you hide only some, its condition gets narrower.
  • In the hand-off, say which steps the chart hides.

Read the report

  • In the chart, a state that breaks a rule has the class `breaks`. When only some model states behind it break the rule, it has the class `mayBreak`. The Legend names the rules of each mark. When two rules break, it names both on one mark, so say in the hand-off which state breaks which rule. In a regions chart, a state gets a mark when each combination of region states that includes it can break a rule. When a combination can break a rule and none of its states has a mark, each state in that combination gets a mark.
  • TLC goes past a state that breaks a rule, so the chart shows the states after a bug.
  • Because TL
Read more
@yeachan-heo
Ships withoh-my-claudecode

For Codex users: Check out oh-my-codex — the same orchestration experience for OpenAI Codex CLI. Liked OmC but found it a bit overkill? Try gajae-code.

41k84 skills21 agents1 MCP
Get the whole plugin

Stars on GitHub

Momentum

stars in total
40.7k
Star history of Yeachan-Heo/oh-my-claudecode on GitHub, a chart by star-history.com

Other skills on oh-my-claudecode.

architect

architect

Settle types and module shape before cross-boundary code — competing designs explored in parallel, compared on API surface and…

arena

arena

Run N parallel candidates at one brief and synthesize: rubric-first framing, isolated fan-out, a cross-judge on a different…

More skills from other plugins.

1password

1password

Set up and use 1Password CLI for sign-in, desktop integration, and reading or injecting secrets.

clawdis11d393k
@steipete@steipeteInstall Skill
computer-use

computer-use

Drives the GUI of a visible local app window through `orca computer`: accessibility tree, clicks, typing, menus, dialogs, and…

orca10d111k
@stablyai@stablyaiInstall Skill
agentmail

agentmail

Use your assigned AgentMail inbox to read email tasks, explicitly send or reply, and check delivery. Provided automatically by…

paperclip10d109k
@paperclipai@paperclipaiInstall Skill
adr-create

adr-create

Create a new Architecture Decision Record with sequential numbering and AgentDB registration

@ruvnet@ruvnetInstall Skill

Questions about statechart.

Which coding agents can use statechart?
statechart installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.