Help us improve
Share bugs, ideas, or general feedback.
From moplan
Plan development work with a local-first interactive marimo notebook. Generates a YAML plan, populates slices and questions, runs dual-agent critique, opens the notebook for human review (approvals persist to .moplan/moplan.db), and finalizes the plan YAML as the deliverable. Use when asked to plan, create a development plan, or build a feature with design clarity first.
npx claudepluginhub shakestzd/moplan --plugin moplanHow this skill is triggered — by the user, by Claude, or both
Slash command
/moplan:planThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Use this skill when asked to plan development work, organize tasks, build a feature with human review before implementation, or structure a design before writing code.
Measures whether skills, rules, and agent definitions are actually followed by auto-generating test scenarios at 3 strictness levels and reporting compliance rates with full tool call timelines.
Share bugs, ideas, or general feedback.
Use this skill when asked to plan development work, organize tasks, build a feature with human review before implementation, or structure a design before writing code.
Trigger keywords: create plan, development plan, plan tasks, organize work, task breakdown, crispi, interactive plan, plan with review, design and build, plan this feature, review before building, generate plan, scaffold plan
Generates a structured YAML plan, populates it with vertical slices, design questions with recommendations, and dual-agent critique. Opens the marimo notebook for interactive human review — approvals persist into .moplan/moplan.db on every click. On finalize, the YAML is rewritten with the approved state and handed back to the user as the deliverable. No work-tracking CLI is invoked. The YAML file is the output.
Architecture (moplan-native, no external tools required):
| Artifact | Location | Written by | Read by |
|---|---|---|---|
| Plan content | .moplan/plans/plan-<id>.yaml | the plan agent (Step 2) | the notebook + critique agents + humans |
| Approval state | .moplan/moplan.db (SQLite) | the notebook (on every click) | the notebook (on reload) + finalize flow |
| Static archive | .moplan/plans/plan-<id>.html | marimo export html (Step 6) | humans, PR reviewers |
The .moplan/ directory is bootstrapped automatically by MoplanStore (plugin/notebooks/moplan_store.py) the first time /plan runs. It walks up from CWD until it finds an existing .moplan/ or a git root, and creates the store at the git root. This behaviour is logged clearly to stderr — it never silently no-ops.
Before launching the notebook, spend a few minutes with the user clarifying:
If the request is vague, ask clarifying questions first. A vague plan is worse than no plan.
Spawn research agents in a single message — do not proceed until both complete:
Agent(description="Understand current codebase state", subagent_type="htmlgraph:researcher",
prompt="Investigate [area]. Answer: current state, key files, existing patterns, constraints.")
Agent(description="Check for prior plans", subagent_type="Explore",
prompt="Search .moplan/plans/ for any existing plan YAMLs related to [area]. Read each
one and report: plan ID, title, status (draft/review/finalized), and a one-line summary.")
Research must answer:
Skip research only for: trivial changes (<10 lines), bug fixes with known root cause, documentation-only changes.
Generate an 8-hex plan id (e.g. 9ae3f59b) and write the plan directly to:
.moplan/plans/plan-<id>.yaml
If .moplan/ does not yet exist, MoplanStore.discover() will bootstrap it the first time the notebook runs — but the agent can also create the directory up-front with mkdir -p .moplan/plans.
Every plan YAML MUST follow this exact structure:
meta:
id: plan-<hex8>
track_id: "" # optional — freeform identifier, not tied to any CLI
title: "<plan title>"
description: >
One paragraph describing what this plan designs and why.
created_at: "YYYY-MM-DD"
status: draft # draft | review | finalized
created_by: claude-opus
design:
problem: >
What is the current state? What's wrong? Why does this matter?
Include measurements, evidence, or user impact.
goals:
- "**Goal 1** — measurable outcome"
- "**Goal 2** — measurable outcome"
constraints:
- "Constraint 1 — why it exists"
- "Constraint 2 — why it exists"
approved: false
comment: ""
slices:
- id: feat-<hex8>
num: 1
title: "<slice title>"
what: >
What exactly will be implemented. Be specific — name functions,
files, APIs. An agent reading this should know what to build.
why: >
Why this slice exists. What problem does it solve? What breaks
without it? Link to the design goals.
files:
- path/to/file1.py
- path/to/file2.py
deps: [] # slice numbers this depends on, e.g. [1, 3]
done_when:
- "Acceptance criterion 1 — testable, concrete"
- "Acceptance criterion 2 — testable, concrete"
effort: S # S | M | L
risk: Low # Low | Med | High
tests: |
Unit: specific test description with expected input/output
Integration: specific integration test
Regression: what existing tests must still pass
approved: false
comment: ""
questions:
- id: q-<kebab-name>
text: "The design question in plain English?"
description: >
Context paragraph explaining WHY this question matters, what
tradeoffs are involved, and what evidence exists for each option.
recommended: <option-key>
options:
- key: option-a
label: "A: Short name — Full description and implications"
- key: option-b
label: "B: Short name — Full description and implications"
answer: null
critique: null
Slices — ALL fields are MANDATORY:
what: Specific enough that an agent can implement it without asking questionswhy: Links to a design goal or explains the business needfiles: At least one file pathdone_when: At least two concrete, testable acceptance criteriatests: At least one unit test and one integration/regression testeffort: S (<50 lines), M (50-200 lines), L (>200 lines)risk: Low (pure addition), Med (modifies existing), High (changes hot path or shared interfaces)Questions — ALL fields are MANDATORY:
description: At least 2 sentences on context and tradeoffsrecommended: Agent MUST pick a default — the human overrides only where they disagreeoptions: At least 2 options, each with a descriptive labelDesign — ALL subsections are MANDATORY:
problem: Evidence-based description of current stategoals: Measurable outcomes (not vague aspirations)constraints: Real limitations that affect design choicesBefore launching the notebook, validate the YAML programmatically:
import yaml
plan = yaml.safe_load(open(".moplan/plans/plan-<id>.yaml").read())
assert plan["meta"]["id"].startswith("plan-")
assert plan["meta"]["status"] in ("draft", "review", "finalized")
assert plan.get("design", {}).get("problem")
assert plan.get("design", {}).get("goals")
assert plan.get("design", {}).get("constraints")
for s in plan.get("slices", []):
for field in ("what", "why", "files", "done_when", "tests", "effort", "risk"):
assert s.get(field), f"slice {s['num']} missing {field}"
for q in plan.get("questions", []):
assert q.get("description")
assert q.get("recommended")
assert len(q.get("options", [])) >= 2
print("plan structurally valid")
Fix any assertion failure before moving on.
Trigger: Always run critique for plans with >= 3 slices. Skip for trivial plans.
Dispatch two critique agents in parallel. Each reads the plan YAML at .moplan/plans/plan-<id>.yaml and produces structured findings. The agents themselves are defined in this plugin at plugin/agents/plan-critic.md and plugin/agents/feasibility-reviewer.md.
Agent(description="Design critique", subagent_type="plan-critic",
prompt="Read .moplan/plans/plan-<id>.yaml. Produce structured critique per the
template in your system prompt.")
Agent(description="Feasibility critique", subagent_type="feasibility-reviewer",
prompt="Read .moplan/plans/plan-<id>.yaml. Verify claims against the actual
codebase and produce structured critique per the template in your system prompt.")
Each agent emits text in this shape (parsed by the orchestrator into the plan YAML):
ASSUMPTIONS:
A1: [VERIFIED|PLAUSIBLE|UNVERIFIED|QUESTIONABLE|FALSIFIED] text | evidence
SLICE_ASSESSMENTS:
S1: assessment with specific concerns
RISKS:
R1: risk description | severity: Low/Medium/High | mitigation
GAPS:
- What the plan doesn't address
ALTERNATIVES:
- Different approaches worth considering
PRIOR_ART:
- Relevant existing solutions or patterns (feasibility critic only)
The orchestrator parses both outputs and merges them into the critique section of the same YAML file, then re-validates. Use PyYAML's dump with sort_keys=False to preserve order.
For each FALSIFIED assumption or HIGH severity risk, update the plan in place before handing it to the human:
what / done_when to incorporate the falsification or mitigationdesign.constraints when the finding affects the whole planRe-write the YAML. Commit the revision (git add .moplan/plans/plan-<id>.yaml && git commit) so the human reviews the critique-informed version, not the stale original.
Skip Step 4b only if all assumptions are verified/plausible AND no HIGH-severity risks exist.
Launch the plan review notebook:
cd ${CLAUDE_PLUGIN_ROOT}/notebooks && uv run --script plan_notebook.py -- --plan plan-<id>
This is exactly what /plan plan-<id> does — the slash command and skill use the same invocation. uv run --script reads the PEP 723 header inside plan_notebook.py and installs marimo, pyyaml, anywidget, traitlets, and anthropic into an isolated environment on first run.
Tell the human:
Plan ready for review. I'll launch the notebook now.
In the marimo interface you will see:
1. Design Discussion — problem, goals, constraints. Approve or comment.
2. Slice Cards — one per slice. Click to expand; approve each one.
3. Open Questions — pick an option per question (defaults highlighted).
4. AI Critique — read the findings. Optionally chat with Claude in the
sidebar to propose amendments; accepted amendments are applied on
finalize.
5. Feedback Summary — progress bar showing completion.
6. Finalize — click when all sections are approved.
Every click persists to .moplan/moplan.db so you can close and
reopen the notebook without losing progress.
I will wait until you finalize before moving on.
STOP. Do not proceed until the human finalizes the plan.
This is the contract between the notebook surface and the MoplanStore. It exists so the agent running this skill knows exactly what the notebook is doing on the human's behalf — no notebook-native hand-waving.
Store discovery. On startup, the notebook calls MoplanStore.discover() which walks up from CWD until it finds .moplan/ or a git root. If neither is found, it bootstraps .moplan/ at the git root (or CWD if not a git repo) and logs the action to stderr. The store exposes store.root, store.db_path, store.plans_dir, and store.project_dir (the parent of .moplan/).
Plan selection. The notebook reads mo.cli_args().get("plan"). The value may be either a bare plan id (plan-9ae3f59b) or a full path. If empty, the notebook shows store.list_plans() as a dropdown. There is no MOPLAN_PLAN_ID environment variable — the CLI arg is the single source of truth.
Approve/reject for slices and design. Each click writes a row via store.persist_feedback(plan_id, section, action, value). Concretely:
section="design", action="approve", value="true"feat-abc: section="slice", action="approve", value="true", question_id="feat-abc"section="design" or section="slice", action="comment", value=<text>Answering questions. section="question", action="answer", value=<option-key>, question_id=<q-id>. The recommended option is pre-selected; the human overrides only where they disagree.
Amendments from chat. The chat sidebar streams Claude via ClaudeChatBackend(plan_context=..., store=store, plan_id=...). Claude can emit AMEND slice-N: ... directives which the notebook parses and stores via store.persist_amendment(plan_id, amendment_dict, amendment_id). Each amendment starts as proposed. The human accepts or rejects via a dropdown beside the amendment; state transitions via store.update_amendment_status.
Finalize emits the plan artifact. When the human clicks Finalize, plan_persistence.finalize_plan() runs:
meta.status = "finalized", design.approved = true, and slice.approved for each approved slice..moplan/plans/plan-<id>.yaml.marimo export html to produce plan-<id>.html alongside the YAML as a static archive.The YAML file is the deliverable. No work-tracking CLI is invoked. The agent running this skill reads the finalized YAML file in Step 6 and hands the approved slices back to the user (or to a separate execution workflow like /htmlgraph:execute if the user has HtmlGraph installed — but that is an external tool, not a moplan dependency).
After the notebook exits (the human has clicked Finalize):
import yaml
plan = yaml.safe_load(open(".moplan/plans/plan-<id>.yaml").read())
assert plan["meta"]["status"] == "finalized"
approved_slices = [s for s in plan["slices"] if s.get("approved")]
rejected_slices = [s for s in plan["slices"] if not s.get("approved")]
decisions = {q["id"]: q.get("answer") for q in plan.get("questions", [])}
Parse the results and report back to the user:
Plan finalized: .moplan/plans/plan-<id>.yaml
Approved slices (N of M):
feat-XXXX Slice 1 title
feat-XXXX Slice 2 title
Rejected slices:
Slice 3 title — excluded
Design decisions:
Q1 (Migration caching): schema-version (recommended, accepted)
Q2 (Error handling): metric-counter (overrode recommendation: structured-log)
The YAML is the handoff. If the user wants to execute the slices, they can use an external executor (e.g. /htmlgraph:execute) or implement them manually — both read the same YAML.
If the user has an execution workflow installed that consumes plan YAMLs, point it at the file:
.moplan/plans/plan-<id>.yaml
Moplan itself does not ship an executor — it ships the plan. Keeping those concerns separate is the point.
| Agent | When to use |
|---|---|
plan-critic | Design review critique — challenge assumptions, identify gaps |
feasibility-reviewer | Feasibility critique — verify claims against actual code, find prior art |
htmlgraph:sonnet-coder | Multi-file editing (if HtmlGraph is installed) |
htmlgraph:researcher | Codebase research (if HtmlGraph is installed) |
The first two are bundled with moplan. The latter two are from HtmlGraph and are used when available — the plan skill falls back to the built-in Explore agent if HtmlGraph is not installed.
.moplan/plans/ — not SQLite, not an external service.moplan/moplan.db — atomic SQLite writes, survives restarts.moplan/, it bootstraps and logs; it never fails silently