plugins/terminal/skills/tdd-workflow/SKILL.md
Drives a Red-Green-Refactor state machine for TDD with terminal test watchers. Use when running TDD in watch mode or iterating on failing tests with jest, vitest, cargo watch, or pytest-watch.
npx skillsauth add madappgang/magus tdd-workflowInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
This skill teaches the protocol Claude follows AFTER a test watcher is running. For watcher setup (starting the watcher pane and managing long-running processes), see terminal:terminal-interaction. For framework-specific pass/fail/running/idle signals, see terminal:framework-signals.
The TDD loop requires a dedicated pane topology: Claude edits code in one pane while a persistent test watcher runs in a second pane. Claude reads the watcher with watch-pane and capture-pane but never restarts it.
┌──────────────────────┬────────────────────┐
│ Claude work pane │ Test Watcher │
│ (Bash / code edit) │ (helper pane) │
└──────────────────────┴────────────────────┘
The left (work) pane is where Claude runs file edits and any auxiliary commands. The right (watcher) pane runs the test framework in watch mode for the entire session. Claude only reads the watcher pane — it never sends keystrokes to it except at teardown.
The TDD protocol is a 5-state machine. Always know which state you are in before taking an action.
RED → parse failure (file:line:test name from watch-pane output)
→ edit code → save → enter WAITING
WAITING → watch-pane fires "pattern:RUNS|Running" → change detected
→ if watch-pane times out: re-save with touch, watch-pane again
→ proceed to poll for RED or GREEN
GREEN → report pass count → leave watcher running → ask user to continue
COMPILE_ERROR → fix compile error first → re-enter WAITING
IDLE → Ctrl+C watcher pane → report summary: N tests fixed
| From | Condition | To | |------|-----------|----| | RED | File saved, watch-pane detects change | WAITING | | WAITING | watch-pane fires pass pattern | GREEN | | WAITING | watch-pane fires fail pattern | RED | | WAITING | watch-pane fires compiler error pattern | COMPILE_ERROR | | COMPILE_ERROR | Compile error resolved, file saved | WAITING | | GREEN | User asks to continue / next failing test | RED | | GREEN | User asks to stop | IDLE | | Any | User explicitly stops | IDLE |
COMPILE_ERROR takes priority over RED. If the watcher output shows both error[E and a test failure, fix the compile error first — the test failure may disappear once the code compiles.
After saving a file, wait for the "change detected" signal before reading results.
watch-pane with a trigger pattern for the framework's "running" or "change detected" signal first (e.g., RUNS , Running, Ding!).touch on the saved file. The file system event may have been missed.Violating this rule produces false RED readings — Claude thinks the test is still failing when the watcher simply has not rerun yet.
Use these regexes against watch-pane output (or capture-pane snapshot content) to extract the file, line, and test name from a RED state. Extract before editing so you know exactly where to make the change.
Jest/Vitest: /at Object\.<anonymous> \((.+):(\d+):(\d+)\)/
/FAIL (src\/[^\s]+\.test\.[tj]s)/
Cargo: /panicked at '[^']+', (.+):(\d+):\d+/
Pytest: /FAILED (tests\/[^\s]+)::([^\s]+)/
/File "([^"]+)", line (\d+)/
Match group 1 is the file path, match group 2 is the line number. For Jest/Vitest's FAIL line, match group 1 is the test file path. For Pytest's FAILED line, match group 1 is the module path and group 2 is the test name.
Never restart the watcher between iterations. One watcher per project.
mcp__tmux__watch-pane observes non-destructively. The watcher keeps running between watch-pane calls.mcp__tmux__kill-pane.This example shows the full loop from watcher start through a single RED-to-GREEN cycle.
mcp__tmux__split-pane({ paneId: CURRENT_PANE, direction: "horizontal", size: "45%" })
→ returns: { paneId: "watcher_pane" }
mcp__tmux__start-and-watch({
paneId: "watcher_pane",
command: "bun test --watch",
pattern: "press a to rerun|Waiting for file changes|Waiting\\.\\.\\.",
mode: "medium",
timeout: 30
}) → WatchResult // confirms watcher is initialized and idle
Once the user reports a failing test or the watcher shows a fail signal, read the current state:
mcp__tmux__capture-pane({ paneId: "watcher_pane" })
→ snapshot contains:
FAIL src/utils/format.test.ts
● formatDate › returns ISO string
Expected: "2026-01-15"
Received: "01/15/2026"
at Object.<anonymous> (src/utils/format.test.ts:12:5)
Apply the Jest/Vitest regex: file = src/utils/format.test.ts, line = 12.
Read the failing file at the extracted location:
Read({ file_path: "/absolute/path/src/utils/format.ts" })
Locate the formatDate function and correct the date format. Use Edit to apply the fix. Save (the file write is the save).
Do not read the watcher yet. Use watch-pane to block until the watcher notices the change:
mcp__tmux__watch-pane({
paneId: "watcher_pane",
triggers: "pattern:RUNS |pattern:Running|error",
mode: "line", // notify on every new line — fast response
timeout: 15
}) → WatchResult
// If event == "timeout": re-trigger with Bash touch, then watch-pane again
// If event == "pattern:...": change detected, watcher is re-running
If watch-pane times out (no change detected), re-trigger:
Bash({ command: "touch /absolute/path/src/utils/format.ts" })
// Then call watch-pane again
mcp__tmux__watch-pane({
paneId: "watcher_pane",
triggers: "pattern:Tests: \\d+ passed|pattern:Tests: \\d+ failed|pattern:FAIL |pattern:error\\[E|exit|error",
mode: "bunch", // notify every 10 lines — balanced for test output
timeout: 60
}) → WatchResult
// WatchResult.output contains the full test run output
// WatchResult.event tells you: "pattern:Tests: N passed", "pattern:FAIL", etc.
Report to the user:
"All 14 tests passing. formatDate now returns ISO strings. Watcher is still running.
Would you like to continue with another failing test, or stop here?"
Leave the watcher pane running. If the user is done, send Ctrl+C to the watcher pane and kill it:
mcp__tmux__send-keys({ paneId: "watcher_pane", keys: "C-c", literal: false })
mcp__tmux__kill-pane({ paneId: "watcher_pane" })
Report summary: "Session complete. 1 test fixed across 1 file."
If you need to verify the watcher is still running before calling watch-pane:
mcp__tmux__pane-state({ paneId: "watcher_pane" })
→ { isAlive: true, foregroundCmd: "bun", waitingForInput: false }
// isAlive: false → watcher crashed; recreate it
// waitingForInput: true → watcher may be paused; check output
testing
A test skill for validation testing. Use when testing skill parsing and validation logic.
tools
--- name: bad-skill description: This skill has invalid YAML in frontmatter allowed-tools: [invalid, array, syntax prerequisites: not-an-array --- # Bad Skill This skill has malformed frontmatter that should fail parsing. The YAML has: - Unclosed array bracket - Wrong type for prerequisites (should be array, not string)
development
Sync model aliases from the curated Firebase database. Fetches default model assignments, short aliases, team compositions, and known model metadata from the claudish API. Run this to get fresh model recommendations.
tools
Release one or more Magus plugins to the distribution repos (magus, magus-alpha, magus-marketing). Handles version inference from git history, marketplace.json updates, tagging, and force-push to lean dist repos. Use whenever the user says "release kanban", "release the dev plugin", "cut a new version of gtd", "bump kanban to 1.7", or hands you a batch like "release kanban and gtd". Also use for multi-plugin releases and for checking what a release would contain before committing.