/opsx-tdd
Adds a TDD gate to OpenSpec — write REAL failing tests from specs/**/*.md before implementation. Run after opsx:propose and before opsx:apply. Solid RED — never expect.fail placeholders.
$ npx -y skills add yuritoledo/openspec-tdd --skill opsx-tdd --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/opsx-tdd
Context preview
The summary Claude sees to decide when to auto-load this skill.
Adds a TDD gate to OpenSpec — write REAL failing tests from specs/**/*.md before implementation. Run after opsx:propose and before opsx:apply. Solid RED — never expect.fail placeholders.
SKILL.md
opsx-tdd.SKILL.mdname: opsx:tdd
description: Adds a TDD gate to OpenSpec — write REAL failing tests from specs/**/*.md before implementation. Run after opsx:propose and before opsx:apply. Solid RED — never expect.fail placeholders.
license: MIT
compatibility: Requires the openspec CLI and a JS/TS test runner (vitest, jest, bun test, or node:test).
metadata:
author: yuritoledo
version: "3.0"
Write **real failing test files** from a change's `specs/**/*.md` and confirm a *solid* RED — tests that exercise the true contract and fail only because the implementation is missing — before implementation begins. Never `expect.fail("TDD Red Phase")` placeholders: those prove nothing, get fully rewritten at green, and leave the feature with zero real coverage.
Reads directly from the canonical spec files. Re-run after a spec change and tests regenerate from source-of-truth automatically.
**Input**: Optionally specify a change name. If omitted, infer from conversation context or prompt.
**Steps**
0. **Discover project conventions** (do this first — never hardcode)
This plugin runs in *any* repo. Detect the host project's testing setup, in priority order:
1. **`.openspec-tdd.json`** at repo root, if present. Fields (all optional):
{
"testRunner": "npx vitest run", // command to run a single test file
"renderHelper": "@/test/render", // import path for the project's custom render()
"canonicalTest": "src/example.test.tsx", // a file to mirror for imports/mocks
"testIdQuery": "getByTestId", // how the project queries test ids
"testFileGlob": "**/*.test.{ts,tsx}"
}2. **`CLAUDE.md` / `AGENTS.md` / `.cursorrules`** — read for testing conventions, the canonical-reference test file, render-helper location, and selector rules. 3. **`package.json`** — infer the runner from `scripts.test` / `devDependencies` (`vitest` → `npx vitest run`; `jest` → `npx jest`; `bun` → `bun test`; otherwise `node --test`). Infer React/Testing-Library presence from deps. 4. **Fallback defaults** — bare `vitest`, plain testing-library render, `getByTestId`.
Announce what you detected: "Runner: <cmd> · Render helper: <path|none> · Canonical ref: <file|none>". See `references/frameworks.md` for per-runner commands and `references/non-react.md` / `references/e2e.md` for non-component scenarios.
1. **Select the change**
If a name is provided, use it. Otherwise:
- Infer from conversation context
- Auto-select if only one active change exists
- If ambiguous, run `openspec list --json` and use **AskUserQuestion tool** to let user select
Announce: "Using change: <name>"
2. **Parse scenarios from specs**
find openspec/changes/<name>/specs -name "*.md" 2>/dev/null
If no `specs/**/*.md` exist in the change, stop: > "No spec files found in `openspec/changes/<name>/specs/`. Run `opsx:propose` first to > generate specs with `#### Scenario:` blocks, then re-run `/opsx:tdd`."
Parse every `specs/**/*.md` file. For each file:
a. **Extract scenarios** — collect every `#### Scenario:` block with its parent `### Requirement:` name and its WHEN / THEN lines.
If a file has no `#### Scenario:` blocks, skip it and note it in the report.
b. **Derive the test file path** — in priority order:
1. **design.md Implementation Map**: scan `openspec/changes/<name>/design.md` for a markdown table with columns `Capability`, `File` (or similar). Extract the row matching this capability's name.
| `ability-card` | `features/combat/AbilityCard.tsx` | `AbilityCard` |
2. **design.md file path mentions**: scan design.md for any `.ts` / `.tsx` paths that mention the capability name or a related module. 3. **Project search**: `find . \( -name "*.ts" -o -name "*.tsx" \) | grep -v node_modules | grep -i <capability>` — pick the best match (non-test file). 4. **Prompt the user**: use **AskUserQuestion** with the capability name and a list of candidate paths found. Never guess silently.
Test file = implementation file path with `.test.ts` / `.test.tsx` extension, same directory. Example: `features/combat/AbilityCard.tsx` → `features/combat/AbilityCard.test.tsx`.
c. **Translate THEN clauses to assertion intent** — for each scenario, capture the full THEN text. Step 4 uses this to write the real assertion body.
THEN clauses that are too vague to assert (e.g. "THEN the UI updates") are flagged as spec quality gaps in the step-7 report — never padded with a stub assertion.
Announce: "<N> specs · <M> scenarios · source-of-truth: specs/**/*.md"
3. **Write REAL failing tests** (solid RED, not forced fail)
The goal is a *genuine* red: the test renders the real component, calls the real function, queries real `data-testid`s, and fails because the implementation does not exist yet. NOT `expect.fail(...)` placeholders.
Before writing, gather concrete selectors and behavior from:
- `openspec/changes/<name>/proposal.md` and `design.md` — component names, props,
`data-testid` values, expected copy/values, API/mutation shapes.
- The WHEN/THEN clauses extracted in step 2 — translate THEN directly into assertions.
- Existing sibling code and the **canonical test reference** discovered in step 0.
For each scenario group:
- If the target file does not exist, create it using the conventions from step 0: the
project's custom render/wrapper helper, `screen`, `userEvent`, and the shared mock setup — never a bare testing-library render for component tests when the project has a helper.
- If a `describe` block with the exact name already exists, append `it()`s inside it.
Otherwise append a new `describe` block.
- For each scenario, write a **real test body**:
- Render the component / invoke the unit u
Read more
name: opsx:tdd description: Adds a TDD gate to OpenSpec — write REAL failing tests from specs/**/*.md before implementation. Run after opsx:propose and before opsx:apply. Solid RED — never expect.fail placeholders. license: MIT compatibility: Requires the openspec CLI and a JS/TS test runner (vitest, jest, bun test, or node:test). metadata: author: yuritoledo version: "3.0"
Write **real failing test files** from a change's `specs/**/*.md` and confirm a *solid* RED — tests that exercise the true contract and fail only because the implementation is missing — before implementation begins. Never `expect.fail("TDD Red Phase")` placeholders: those prove nothing, get fully rewritten at green, and leave the feature with zero real coverage.
Reads directly from the canonical spec files. Re-run after a spec change and tests regenerate from source-of-truth automatically.
**Input**: Optionally specify a change name. If omitted, infer from conversation context or prompt.
**Steps**
0. **Discover project conventions** (do this first — never hardcode)
This plugin runs in *any* repo. Detect the host project's testing setup, in priority order:
1. **`.openspec-tdd.json`** at repo root, if present. Fields (all optional):
{
"testRunner": "npx vitest run", // command to run a single test file
"renderHelper": "@/test/render", // import path for the project's custom render()
"canonicalTest": "src/example.test.tsx", // a file to mirror for imports/mocks
"testIdQuery": "getByTestId", // how the project queries test ids
"testFileGlob": "**/*.test.{ts,tsx}"
}2. **`CLAUDE.md` / `AGENTS.md` / `.cursorrules`** — read for testing conventions, the canonical-reference test file, render-helper location, and selector rules. 3. **`package.json`** — infer the runner from `scripts.test` / `devDependencies` (`vitest` → `npx vitest run`; `jest` → `npx jest`; `bun` → `bun test`; otherwise `node --test`). Infer React/Testing-Library presence from deps. 4. **Fallback defaults** — bare `vitest`, plain testing-library render, `getByTestId`.
Announce what you detected: "Runner: <cmd> · Render helper: <path|none> · Canonical ref: <file|none>". See `references/frameworks.md` for per-runner commands and `references/non-react.md` / `references/e2e.md` for non-component scenarios.
1. **Select the change**
If a name is provided, use it. Otherwise:
- Infer from conversation context
- Auto-select if only one active change exists
- If ambiguous, run `openspec list --json` and use **AskUserQuestion tool** to let user select
Announce: "Using change: <name>"
2. **Parse scenarios from specs**
find openspec/changes/<name>/specs -name "*.md" 2>/dev/null
If no `specs/**/*.md` exist in the change, stop: > "No spec files found in `openspec/changes/<name>/specs/`. Run `opsx:propose` first to > generate specs with `#### Scenario:` blocks, then re-run `/opsx:tdd`."
Parse every `specs/**/*.md` file. For each file:
a. **Extract scenarios** — collect every `#### Scenario:` block with its parent `### Requirement:` name and its WHEN / THEN lines.
If a file has no `#### Scenario:` blocks, skip it and note it in the report.
b. **Derive the test file path** — in priority order:
1. **design.md Implementation Map**: scan `openspec/changes/<name>/design.md` for a markdown table with columns `Capability`, `File` (or similar). Extract the row matching this capability's name.
| `ability-card` | `features/combat/AbilityCard.tsx` | `AbilityCard` |
2. **design.md file path mentions**: scan design.md for any `.ts` / `.tsx` paths that mention the capability name or a related module. 3. **Project search**: `find . \( -name "*.ts" -o -name "*.tsx" \) | grep -v node_modules | grep -i <capability>` — pick the best match (non-test file). 4. **Prompt the user**: use **AskUserQuestion** with the capability name and a list of candidate paths found. Never guess silently.
Test file = implementation file path with `.test.ts` / `.test.tsx` extension, same directory. Example: `features/combat/AbilityCard.tsx` → `features/combat/AbilityCard.test.tsx`.
c. **Translate THEN clauses to assertion intent** — for each scenario, capture the full THEN text. Step 4 uses this to write the real assertion body.
THEN clauses that are too vague to assert (e.g. "THEN the UI updates") are flagged as spec quality gaps in the step-7 report — never padded with a stub assertion.
Announce: "<N> specs · <M> scenarios · source-of-truth: specs/**/*.md"
3. **Write REAL failing tests** (solid RED, not forced fail)
The goal is a *genuine* red: the test renders the real component, calls the real function, queries real `data-testid`s, and fails because the implementation does not exist yet. NOT `expect.fail(...)` placeholders.
Before writing, gather concrete selectors and behavior from:
- `openspec/changes/<name>/proposal.md` and `design.md` — component names, props,
`data-testid` values, expected copy/values, API/mutation shapes.
- The WHEN/THEN clauses extracted in step 2 — translate THEN directly into assertions.
- Existing sibling code and the **canonical test reference** discovered in step 0.
For each scenario group:
- If the target file does not exist, create it using the conventions from step 0: the
project's custom render/wrapper helper, `screen`, `userEvent`, and the shared mock setup — never a bare testing-library render for component tests when the project has a helper.
- If a `describe` block with the exact name already exists, append `it()`s inside it.
Otherwise append a new `describe` block.
- For each scenario, write a **real test body**:
- Render the component / invoke the unit u
Adds a TDD gate to OpenSpec — a new opsx:tdd command that sits between opsx:propose and opsx:apply. OpenSpec generates BDD scenarios in specs/**/*.md but jumps straight to implementation — no test-first step exists. This plugin adds one.
Repo: yuritoledo/openspec-tdd

