Use when implementing any feature or bugfix, before writing implementation code - write the test first using REAL systems (NO MOCKS), watch it fail, write minimal code to pass; ensures tests actually verify behavior by requiring failure first and real system integration
Before writing any implementation code, write a minimal test that fails using real systems (browser, database, HTTP), then write just enough code to pass it. Use this for all features and bugfixes to ensure tests actually verify production behavior.
/plugin marketplace add krzemienski/shannon-framework/plugin install shannon@shannon-frameworkThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Write the test first using REAL systems. Watch it fail. Write minimal code to pass.
Core principle: If you didn't watch the test fail against a real system, you don't know if it tests the right thing.
Violating the letter of the rules is violating the spirit of the rules.
This is NOT optional. Shannon TDD combines:
Result: Tests that actually prove software works in production.
Always:
Exceptions (ask your human partner):
Thinking "skip TDD just this once"? Stop. That's rationalization.
NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST
NO MOCKS IN TESTS (Shannon requirement)
Write code before the test? Delete it. Start over.
Use mocks in tests? Delete them. Use real systems.
No exceptions:
Implement fresh from tests. Period.
digraph tdd_cycle {
rankdir=LR;
red [label="RED\nWrite failing test\n(REAL systems)", shape=box, style=filled, fillcolor="#ffcccc"];
verify_red [label="Verify fails\ncorrectly", shape=diamond];
green [label="GREEN\nMinimal code", shape=box, style=filled, fillcolor="#ccffcc"];
verify_green [label="Verify passes\nAll green", shape=diamond];
refactor [label="REFACTOR\nClean up", shape=box, style=filled, fillcolor="#ccccff"];
verify_mocks [label="NO MOCKS\ncheck", shape=diamond, style=filled, fillcolor="#ffffcc"];
next [label="Next", shape=ellipse];
red -> verify_mocks;
verify_mocks -> verify_red [label="no mocks"];
verify_mocks -> red [label="MOCKS\nFOUND"];
verify_red -> green [label="yes"];
verify_red -> red [label="wrong\nfailure"];
green -> verify_green;
verify_green -> refactor [label="yes"];
verify_green -> green [label="no"];
refactor -> verify_green [label="stay\ngreen"];
verify_green -> next;
next -> red;
}
Write one minimal test showing what should happen using REAL systems.
<Good> ```typescript // ✅ GOOD: Real browser, real DOM test('user can submit contact form', async () => { const browser = await puppeteer.launch(); const page = await browser.newPage();await page.goto('http://localhost:3000/contact'); await page.type('#email', 'test@example.com'); await page.type('#message', 'Hello'); await page.click('button[type="submit"]');
const successMsg = await page.$eval('.success', el => el.textContent);
expect(successMsg).toBe('Message sent!'); await browser.close(); });
Clear name, tests real browser, one thing
</Good>
<Bad>
```typescript
// ❌ BAD: Mocked DOM, not real browser
test('form submission works', async () => {
const mockSubmit = jest.fn();
const { getByRole } = render(<ContactForm onSubmit={mockSubmit} />);
fireEvent.click(getByRole('button'));
expect(mockSubmit).toHaveBeenCalled();
});
Uses mocking, tests mock not real system </Bad>
Shannon requirement: NO MOCKS
Real systems to use:
await page.goto(...))ios_simulator.launch_app(...))await db.connect(real_connection_string))await fetch(actual_url))fs.readFile(actual_path))NOT acceptable:
jest.mock('puppeteer')@testing-library/react with mocked DOMunittest.mock.patch(...)sinon.stub(...)Requirements:
MANDATORY. Never skip.
npm test path/to/test.test.ts
Confirm:
Test passes? You're testing existing behavior. Fix test.
Test errors? Fix error, re-run until it fails correctly.
Test uses mocks? Delete mocks. Use real systems.
Shannon checkpoint: Before proceeding to GREEN phase
Scan test file for mock violations:
# Check for mock imports
grep -r "import.*mock\|from.*mock\|jest.mock" test/file.test.ts
# If violations found → DELETE MOCKS, rewrite with real systems
Common violations and fixes:
| Violation | Real System Alternative |
|---|---|
jest.mock('database') | Use real test database |
@testing-library/react | Use Puppeteer against real browser |
fetch = jest.fn() | Use real HTTP with test API |
fs = mockFs() | Use real filesystem with temp directory |
sinon.stub(auth) | Use real auth with test credentials |
Can't use real system? Ask your partner. Mocking is NEVER the answer in Shannon.
Write simplest code to pass the test.
<Good> ```typescript async function submitContactForm(email: string, message: string) { await db.query( 'INSERT INTO contact_messages (email, message) VALUES ($1, $2)', [email, message] ); return { success: true, message: 'Message sent!' }; } ``` Just enough to pass, uses real database </Good> <Bad> ```typescript async function submitContactForm( email: string, message: string, options?: { retries?: number; timeout?: number; onSuccess?: (id: number) => void; onError?: (err: Error) => void; } ) { // YAGNI - over-engineered } ``` Over-engineered, features not tested </Bad>Don't add features, refactor other code, or "improve" beyond the test.
MANDATORY.
npm test path/to/test.test.ts
Confirm:
Test fails? Fix code, not test.
Other tests fail? Fix now.
Uses mocks? You violated NO MOCKS. Start over.
After green only:
Keep tests green. Don't add behavior.
Still NO MOCKS: Refactoring cannot introduce mocking.
Next failing test for next feature.
| Quality | Good | Bad |
|---|---|---|
| Minimal | One thing. "and" in name? Split it. | test('validates email and domain and whitespace') |
| Clear | Name describes behavior | test('test1') |
| Real Systems | Uses Puppeteer/actual DB/real HTTP | Uses mocks, stubs, fakes |
| Shows intent | Demonstrates desired API | Obscures what code should do |
TDD is part of Shannon's 3-tier validation:
TDD tests written, code compiles
TDD tests pass (RED-GREEN cycle verified)
TDD tests use REAL systems (NO MOCKS)
Reporting format:
## TDD Verification
✅ RED Phase: Test failed (feature missing)
Real system used: Puppeteer (Chrome browser)
Failure: Cannot find element '.success'
✅ GREEN Phase: Test passed (feature implemented)
Real system used: Puppeteer (Chrome browser)
Success: Message sent successfully
✅ NO MOCKS: Verified
Scan result: 0 mock imports found
Real systems: Puppeteer, PostgreSQL, Real HTTP
**TDD Cycle**: Complete
**Validation Tier**: 3/3 (Functional - Real Systems)
"I'll write tests after to verify it works"
Tests written after code pass immediately. Passing immediately proves nothing:
Test-first forces you to see the test fail, proving it actually tests something.
"I already manually tested all the edge cases"
Manual testing is ad-hoc. You think you tested everything but:
Automated tests with real systems are systematic. They run the same way every time.
"Deleting X hours of work is wasteful"
Sunk cost fallacy. The time is already gone. Your choice now:
The "waste" is keeping code you can't trust. Working code without real tests is technical debt.
Required for: Any web UI, forms, navigation, rendering
Pattern:
describe('User Registration Flow', () => {
let browser: Browser;
let page: Page;
beforeEach(async () => {
browser = await puppeteer.launch();
page = await browser.newPage();
});
afterEach(async () => {
await browser.close();
});
test('user can register with valid email', async () => {
await page.goto('http://localhost:3000/register');
await page.type('#email', 'newuser@example.com');
await page.type('#password', 'SecurePass123!');
await page.click('button[type="submit"]');
await page.waitForSelector('.welcome-message');
const welcomeText = await page.$eval('.welcome-message', el => el.textContent);
expect(welcomeText).toContain('Welcome, newuser@example.com');
});
});
MCP Integration: Use Puppeteer MCP if available for better integration.
Required for: iOS/Android apps, mobile-specific behavior
Pattern:
import { iosSimulator } from '@mcp/ios-simulator';
describe('Mobile App Launch', () => {
test('app launches and shows home screen', async () => {
const app = await iosSimulator.launch({
appPath: './build/MyApp.app',
device: 'iPhone 14'
});
const screenshot = await app.screenshot();
const homeButton = await app.findElement({ id: 'home-tab' });
expect(homeButton.isDisplayed()).toBe(true);
await app.terminate();
});
});
Required for: Database queries, transactions, migrations
Pattern:
import { Client } from 'pg';
describe('User Database Operations', () => {
let db: Client;
beforeEach(async () => {
db = new Client({
host: 'localhost',
database: 'myapp_test', // Real test database
user: 'testuser',
password: 'testpass'
});
await db.connect();
// Clean test data
await db.query('TRUNCATE users CASCADE');
});
afterEach(async () => {
await db.end();
});
test('creates user with hashed password', async () => {
const result = await db.query(
'INSERT INTO users (email, password_hash) VALUES ($1, $2) RETURNING id',
['test@example.com', 'hashed_password_here']
);
expect(result.rows[0].id).toBeGreaterThan(0);
const user = await db.query('SELECT * FROM users WHERE email = $1', ['test@example.com']);
expect(user.rows[0].email).toBe('test@example.com');
});
});
NOT acceptable: In-memory SQLite, mocked database, fake database.
Required for: API endpoints, HTTP clients, external integrations
Pattern:
describe('API Endpoints', () => {
const baseURL = 'http://localhost:3001'; // Real test server
test('GET /users returns user list', async () => {
const response = await fetch(`${baseURL}/users`);
const data = await response.json();
expect(response.status).toBe(200);
expect(Array.isArray(data)).toBe(true);
});
test('POST /users creates new user', async () => {
const response = await fetch(`${baseURL}/users`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: 'new@example.com' })
});
const data = await response.json();
expect(response.status).toBe(201);
expect(data.email).toBe('new@example.com');
expect(data.id).toBeDefined();
});
});
NOT acceptable: jest.mock('fetch'), nock(), mocked HTTP client.
| Excuse | Reality |
|---|---|
| "Too simple to test" | Simple code breaks. Test takes 30 seconds. |
| "I'll test after" | Tests passing immediately prove nothing. |
| "Tests after achieve same goals" | Tests-after = "what does this do?" Tests-first = "what should this do?" |
| "Already manually tested" | Ad-hoc ≠ systematic. No record, can't re-run. |
| "Deleting X hours is wasteful" | Sunk cost fallacy. Keeping unverified code is technical debt. |
| "Keep as reference, write tests first" | You'll adapt it. That's testing after. Delete means delete. |
| "Need to explore first" | Fine. Throw away exploration, start with TDD. |
| "Test hard = design unclear" | Listen to test. Hard to test = hard to use. |
| "TDD will slow me down" | TDD faster than debugging. Pragmatic = test-first. |
| "Manual test faster" | Manual doesn't prove edge cases. You'll re-test every change. |
| "Existing code has no tests" | You're improving it. Add tests for existing code. |
| "Real systems are too slow" | Slow tests > fast mocks that don't prove anything. |
| "Mocking is industry standard" | Shannon targets mission-critical domains. NO MOCKS. |
| "Can't test without mocks" | Wrong. Use test database, test browser, test API. |
ALL OF THESE MEAN: Delete code. Start over with TDD using REAL systems.
Bug: Empty email accepted in registration
RED (Real system test):
test('rejects registration with empty email', async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('http://localhost:3000/register');
// Leave email empty
await page.type('#password', 'SecurePass123!');
await page.click('button[type="submit"]');
const errorMessage = await page.$eval('.error', el => el.textContent);
expect(errorMessage).toBe('Email required');
await browser.close();
});
Verify RED:
$ npm test
FAIL: expected 'Email required', got null (no error shown)
GREEN:
// In registration form component
function handleSubmit() {
if (!email?.trim()) {
setError('Email required');
return;
}
// ... rest of registration
}
Verify GREEN:
$ npm test
PASS: All tests passed (using real browser)
Before marking work complete:
Can't check all boxes? You skipped TDD or used mocks. Start over.
This skill is prerequisite for:
This skill requires:
Complementary skills:
Production code → test exists and failed first
Test → uses REAL systems (NO MOCKS)
Otherwise → not TDD, not Shannon-compliant
No exceptions without your human partner's permission.
Shannon difference: Other frameworks allow mocking. Shannon doesn't. Mission-critical domains (Finance, Healthcare, Legal, Security, Aerospace) cannot tolerate tests that don't prove real system behavior.
Mocks don't fail in production. Real systems do. Test what fails.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.