litmus
by retemper
README.md
# litmus
**Never let a test plan slip.** `litmus` is a Claude Code plugin that watches your edits, works out which test types the change likely needs, and — depending on the cadence you pick — either quietly reminds you or holds the turn until you've triaged a plan. It ships language-agnostic guides for nine test types and a `/tdd` skill.
litmus is **not** a linter and it does **not** grade the tests you write. It sits one step earlier: making sure test planning doesn't get forgotten, and injecting the right writing rules as context when you do write tests. For grading written tests, reach for `/code-review` instead.
It belongs to no vendor, phones home to nothing, and collects no telemetry.
## How it works — three axes
| Axis | Question | What it is |
|------|----------|------------|
| **Hooks** | *When* do I get reminded? | Detect source changes, accumulate them per session, and nudge for a test plan at the end of a turn. |
| **MCP server** | *What* do I test, by which rules? | Serves the test-type map and per-type guides as tools (`list_test_types`, `get_guide`, `recommend_guide`) and resources, read from disk at call time. |
| **Skill** | *How* do I drive it? | [`/tdd`](skills/tdd/SKILL.md) — a red-green-refactor loop that loads the right guide before writing tests. |
The guides themselves are nine language-agnostic docs in [`guides/`](guides/) — per-type must-cover cases and writing principles. The MCP server is one way to read them; you can also open the files directly.
## What you'll see
When you change source and end a turn, litmus reminds you. In `advisory` mode it's a non-blocking note:
```
🧪 litmus — 2 source file(s) changed (140 lines) this session without a test plan.
Test types this change likely touches:
- Integration Test (high) — API endpoint change; persistence / DB change
Load a type's rules with the litmus get_guide tool (e.g. get_guide "regression"),
or read guides/ directly. Run /tdd to drive it test-first.
```
In `block` mode it holds the turn and walks you through a two-step triage instead:
```
STOP — litmus (block mode) requires a test plan before this turn ends.
Scope: 3 source file(s), 123 lines. Triage mode: FULL.
STEP 1 — TRIAGE (one markdown table, nothing else) ... then ask which types to detail.
STEP 2 — DESIGN (only the confirmed types) ... load each guide, extend existing tests.
```
The suggested types are regex-based guesses (from file paths and diff signals), tagged
`high`/`medium` confidence — hints to confirm against the code, not verdicts. Editing a
test file, or only docs/config, triggers nothing.
## Test type map
Four levels, nine types:
| Level | Test type | Owner | Guide |
|-------|-----------|-------|-------|
| Code | Unit Test | Engineer | [`01`](guides/01-unit-test.md) |
| Code | Integration Test | Engineer | [`02`](guides/02-integration-test.md) |
| System | Sanity Test | Engineer | [`03`](guides/03-sanity-test.md) |
| System | Functional Test | Engineer | [`04`](guides/04-functional-test.md) |
| System | Scenario Test | Engineer | [`05`](guides/05-scenario-test.md) |
| System | Performance Test | Engineer | [`06`](guides/06-performance-test.md) |
| Regression | Regression Test | Engineer + PM | [`07`](guides/07-regression-test.md) |
| Product | Policy Test / UAT | PM | [`08`](guides/08-policy-test-uat.md) |
| Product | Design / UX Test | PD | [`09`](guides/09-design-ux-test.md) |
## Entry points by role
| Role | Start here |
|------|-----------|
| Engineer, changing logic | Unit (`01`), then Integration (`02`) if a boundary is crossed |
| Engineer, changing an API/endpoint | Integration (`02`) |
| Engineer, changing persistence/DB | Integration (`02`), Regression (`07`) if shared |
| Engineer, changing the UI | Functional (`04`), Design/UX (`09`) |
| Engineer, before a release | Sanity (`03`), Regression (`07`) |
| PM / Product Owner | Policy Test / UAT (`08`) |
| Designer (PD) | Design / UX Test (`09`) |
## Install
**Requires Node.js 18+** on your `PATH` — Claude Code runs the hooks and the MCP server with `node`.
```
/plugin marketplace add retemper/litmus
/plugin install litmus@retemper
```
Local development (point the marketplace at a local checkout instead):
```
/plugin marketplace add /path/to/litmus
/plugin install litmus@retemper
```
Run `/reload-plugins` (or restart Claude Code) to apply. On first use, litmus asks you two questions (cadence + forcefulness) and saves your answers. Until then it runs in its quietest useful mode.
## Updating
Self-hosted marketplaces don't auto-update by default, so new versions won't arrive on their own. Either update on demand:
```
/plugin update litmus@retemper
```
…or enable auto-update once by adding the marketplace to your Claude Code `settings.json`:
```json
{ "extraKnownMarketplaces": { "retemper": { "autoUpdate": true } } }
```
With that set, litmus updates itself whenever Claude Code starts. (Your personal config in `~/.claude/litmus` lives outside the plugin cache, so updates never touch your settings.)
## Configuration
Two knobs plus two thresholds.
**cadence** — when to nudge:
| Value | Behaviour |
|-------|-----------|
| `per-task` | every turn that changed source |
| `by-scale` | only when the change is large (see thresholds) |
| `per-branch` | once per git branch |
| `daily` | once a day |
| `off` | disabled |
**forcefulness** — how to nudge:
| Value | Behaviour |
|-------|-----------|
| `advisory` | a non-blocking system message |
| `block` | holds the turn and walks you through a 2-step triage |
**Thresholds** (only used by `by-scale`): `minFiles` (default 3) and `minLines` (default 80). A change at or under **both** is "light" and skipped by `by-scale`.
### Where config comes from (highest precedence first)
1. Environment: `LITMUS_CADENCE`, `LITMUS_FORCEFULNESS`, `LITMUS_MIN_FILES`, `LITMUS_MIN_LINES`
2. Project: `.litmus.json` (searched from the cwd up to 8 levels)
3. Personal: `~/.claude/litmus/config.json`
4. Unset
**Safe defaults — litmus never goes silently dead:**
- Fully unset → `by-scale` + `advisory` (the quietest mode that still speaks up).
- If you set `cadence` but not `forcefulness`, forcefulness defaults to `block`.
- To turn it off completely, set `cadence: "off"`.
Example `.litmus.json`:
```json
{ "cadence": "by-scale", "forcefulness": "advisory", "minFiles": 3, "minLines": 80 }
```
## The block-mode workflow
In `block` mode the reminder isn't a warning — it's a **2-step triage** that keeps a turn from dumping a wall of test cases:
1. **Triage** — one markdown table: each test type, needed? (✅/❌/🔶), a one-line reason, and any existing test path (you search first). Then it stops and asks which types to detail.
2. **Design** — only for the types you confirm, it reads that type's guide and writes cases — extending existing tests rather than duplicating them.
## MCP tools
The plugin starts a small stdio MCP server (`litmus`) exposing:
| Tool | Input | Returns |
|------|-------|---------|
| `list_test_types` | — | the four-level, nine-type map as a table |
| `get_guide` | `test_type` (name / file / number / alias) | the full guide for that type |
| `recommend_guide` | `role?`, `situation?` | which guide(s) to read, scored by keyword |
README and each guide are also exposed as resources (`litmus://README.md`, `litmus://01-unit-test.md`, …). Everything is read from disk at call time, so editing a guide takes effect immediately — no rebuild.
## Development
The MCP server lives in [`mcp/`](mcp/) (TypeScript). Because Claude Code clones plugins without running `npm install`, the server ships as a single self-contained bundle at `mcp/bundle/litmus-mcp.mjs`, which **must be committed**.
```
cd mcp
npm install
npm run smoke # parser checks against the real guides
npm run bundle # rebuild mcp/bundle/litmus-mcp.mjs (commit the result)
node test-handshake.mjs # spawn the bundle and exercise the MCP protocol
```
Hooks, guides, and the skill are plain files — no build step; edit and commit. The version lives only in `.claude-plugin/plugin.json`; the MCP server reads it from there at runtime, so it can never drift.
## State & privacy
- **Volatile** per-session change markers live in your OS temp dir and are cleaned after 24h.
- **Persistent** config / onboarding / cadence state live in `~/.claude/litmus` (or `$LITMUS_DATA`), outside the plugin cache so they survive updates.
- No network calls. No usage tracking. Nothing leaves your machine.
## License
MIT © retemper
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues