Modes Plugin
Modal execution for Claude Code - enforce workflows like TDD by limiting actions based on current mode.
The Problem
Claude Code is great at completing tasks, but it often takes shortcuts. Ask it to do TDD and it might:
- Jump straight to implementation without writing a test
- Modify your test file while "fixing" a bug
- Skip the red-green-refactor cycle entirely
You can remind Claude to follow the process, but those instructions get lost during context compaction. There's no enforcement - just hope.
The Solution
Define your own workflow as a state machine. You create modes with:
- Permissions - what files/tools Claude can use in each mode
- Transition constraints - conditions for moving between modes
You decide what works for you. Want strict TDD? Lock down source files until tests fail. Prefer a lighter touch? Skip permissions and just use transitions as guideposts.
Example: a TDD workflow
idle ──────────► test-dev ──────────► feature-dev ──────────► idle
"describe a "test exists "all tests
bug/feature" and fails" pass"
This config restricts test-dev to test files only, feature-dev to source files only. But that's just one approach - your modes.yaml defines whatever workflow fits your process.
Why this works:
- Mode state survives context compaction (injected every prompt)
- Permissions enforced by hooks, not just instructions
- Constraints visible to Claude, guiding rather than just blocking
- Fully customizable to match how you actually work
Installation
/plugin marketplace add kerinin/claude-modes
/plugin install modes@claude-modes
Then run /modes:setup to configure permissions. This adds the modes MCP tools to your allow list so Claude can check and update mode state without prompting you each time.
Setup
Quick Start
Copy the example TDD workflow to your project:
cp -r ~/.claude/plugins/modes/examples/tdd/* .claude/
This gives you a working TDD workflow out of the box. Read on to understand what each file does and how to customize it.
Configuration Files
Modes config lives in your project's .claude/ directory:
.claude/modes.yaml - The Workflow Definition
This is your state machine. It defines what modes exist and when Claude can move between them.
name: tdd-workflow
default: idle
modes:
idle:
transitions:
- to: test-dev
constraint: User has described a bug or feature to work on
test-dev:
transitions:
- to: feature-dev
constraint: |
A test exists that targets the bug/feature.
The test has been executed and is currently failing.
feature-dev:
transitions:
- to: idle
constraint: All tests are passing.
The constraint is shown to Claude and guides when it should transition. Claude evaluates whether the constraint is satisfied and calls the transition tool when ready.
.claude/CLAUDE.<mode>.md - Mode Instructions
These work just like your project's root CLAUDE.md, but only get loaded when Claude is in that mode. Use them to give Claude mode-specific guidance.
<!-- .claude/CLAUDE.test-dev.md -->
You are writing a failing test. Focus on:
1. Understanding the expected behavior
2. Writing a test that verifies that behavior
3. Running the test to confirm it fails
Do NOT modify implementation code in this mode.
Your root CLAUDE.md is always loaded. Mode instructions are additive - they don't replace your base instructions, they supplement them for that phase of work.
.claude/settings.<mode>.json - Mode Permissions
These constrain what Claude can do while in a specific mode. The allow and deny lists use glob patterns to control file and tool access.
{
"permissions": {
"allow": [
"Read(**)",
"Write({test/**,**/*.test.ts})",
"Edit({test/**,**/*.test.ts})",
"Bash(npm test*)"
],
"deny": [
"Write(src/**)",
"Edit(src/**)"
]
}
}
In this example, while in test-dev mode Claude can read anything, write/edit test files, and run tests - but it cannot touch source files. The hook blocks the action before it happens.
Permissions are optional. If you just want transitions as guideposts without hard enforcement, skip the settings files entirely.
Usage
How It Works in Practice
Once configured, modes work automatically. Here's what a TDD session might look like:
You: Fix the bug where users can't log in with email addresses containing a plus sign
Claude: I'll help fix that login bug. Let me first write a test to verify the issue.
[MODE: idle → test-dev]
Creating test for plus sign in email...
[Creates test/auth.test.ts]
Running the test to confirm it fails...
[Runs: npm test]
Test fails as expected. Now I'll implement the fix.
[MODE: test-dev → feature-dev]