Permanent vs temporary test organization and immutable contract philosophy. Use when creating, modifying, or reviewing tests.
Organizes tests into permanent (immutable contract) and temporary (validation) directories. Guides test placement decisions and enforces "fix code, not tests" philosophy when failures occur.
/plugin marketplace add binee108/nine-step-workflow-plugin/plugin install nine-step-workflow@lilylab-marketplaceThis skill inherits all available tools. When active, it can use any tool Claude has access to.
{{PERM_TEST_DIR}}/ - Immutable Contract
{{TEMP_TEST_DIR}}/ - Temporary Validation
Common Configurations:
tests/ (permanent) vs .test/ (temporary)tests/unit/ (permanent) vs tests/temp/ (temporary)__tests__/ (permanent) vs .tmp/ (temporary)Test failure = Code is broken (NOT test is wrong)
{{PERM_TEST_DIR}}/ is the contract. Fix code, not tests.
New test in {{PERM_TEST_DIR}}/ - Approve if:
Modify {{PERM_TEST_DIR}}/ - Approve ONLY if:
Always reject:
# ✅ Permanent ({{PERM_TEST_DIR}}/)
# tests/test_user_auth.py
def test_user_requires_password():
'''Core rule: users must have passwords'''
with pytest.raises(ValidationError):
User.create({'username': 'john'}) # Missing password
# ✅ Temporary ({{TEMP_TEST_DIR}}/)
# .test/test_api_integration.py
def test_login_endpoint():
'''Ad-hoc feature validation'''
response = client.post('/api/login', json={'username': 'test', 'password': 'test123'})
assert response.status_code == 200
# Delete this file after feature complete
// ✅ Permanent ({{PERM_TEST_DIR}}/)
// tests/user.test.js
test('user requires password', () => {
expect(() => {
User.create({username: 'john'}); // Missing password
}).toThrow(ValidationError);
});
// ✅ Temporary ({{TEMP_TEST_DIR}}/)
// .test/api-integration.test.js
test('login endpoint works', async () => {
const response = await request(app)
.post('/api/login')
.send({username: 'test', password: 'test123'});
expect(response.status).toBe(200);
// Delete this file after feature complete
});
// ✅ Permanent ({{PERM_TEST_DIR}}/)
// tests/user_test.go
func TestUserRequiresPassword(t *testing.T) {
// Core rule: users must have passwords
_, err := CreateUser(User{Username: "john"}) // Missing Password
if err == nil {
t.Error("Expected validation error for missing password")
}
}
// ✅ Temporary ({{TEMP_TEST_DIR}}/)
// .test/api_integration_test.go
func TestLoginEndpoint(t *testing.T) {
// Ad-hoc feature validation
payload := `{"username":"test","password":"test123"}`
resp, _ := http.Post("http://localhost:8080/api/login", "application/json", strings.NewReader(payload))
if resp.StatusCode != 200 {
t.Errorf("Expected 200, got %d", resp.StatusCode)
}
// Delete this file after feature complete
}
Write test → {{PERM_TEST_DIR}}/? → Approval needed
→ {{TEMP_TEST_DIR}}/? → Proceed freely
↓
Test fails?
├─ {{PERM_TEST_DIR}}/ failing → Fix CODE
└─ {{TEMP_TEST_DIR}}/ failing → Debug and fix CODE
↓
Feature complete → Clean up {{TEMP_TEST_DIR}}/
{{PERM_TEST_DIR}}/?Ask these questions:
Is this core business logic?
Would failure block deployment?
Will this be relevant in 6+ months?
If 2+ YES → {{PERM_TEST_DIR}}/
If mostly NO → {{TEMP_TEST_DIR}}/
{{PERM_TEST_DIR}}/ - Permanent TestsUnit Tests:
# Core business logic
test_user_validation()
test_password_hashing()
test_order_calculation()
# Critical utilities
test_date_parsing()
test_encryption_decryption()
Integration Tests:
# API contracts
test_user_registration_endpoint()
test_payment_webhook_handling()
# Database constraints
test_unique_email_constraint()
test_foreign_key_relationships()
Contract Tests:
# External API expectations
test_payment_gateway_response_format()
test_shipping_api_contract()
{{TEMP_TEST_DIR}}/ - Temporary TestsFeature Validation:
# New feature smoke tests
test_new_dashboard_loads()
test_export_csv_format()
# Delete after feature ships
Debugging Scripts:
# Reproduce bug #42
test_reproduce_null_pointer_bug()
# Delete after bug fixed
Performance Benchmarks:
# Load testing
test_api_handles_1000_concurrent_users()
# Move results to docs, delete test
Data Migration Verification:
# Verify migration_20231215_add_column
test_all_users_have_new_field()
# Delete after migration confirmed in production
Request: "Add test for new feature X"
test-reviewer decision:
## Review
**Proposed location:** {{PERM_TEST_DIR}}/test_feature_x.py
**Analysis:**
- Core functionality? {{YES/NO}}
- Deployment-blocking? {{YES/NO}}
- Long-term relevant? {{YES/NO}}
**Decision:**
- If 2+ YES → APPROVED for {{PERM_TEST_DIR}}/
- If mostly NO → NEEDS_REVISION, suggest {{TEMP_TEST_DIR}}/
Request: "Update test_user_validation to allow empty email"
test-reviewer decision:
## Review
**File:** {{PERM_TEST_DIR}}/test_user_validation.py
**Analysis:**
- API contract change? YES (users can now have empty email)
- Multiple reviewers approved? {{CHECK}}
- Backward compatible? {{CHECK}}
**Decision:**
- If contract change justified → APPROVED_WITH_CONDITIONS (require architecture review)
- If convenience change → REJECTED (fix code, not test)
Issue: "test_payment_processing failing on CI"
test-reviewer guidance:
## Guidance
**File:** {{PERM_TEST_DIR}}/test_payment_processing.py
**Rule:** Test failure = Code is broken
**Actions:**
1. Investigate code changes (git diff)
2. Identify which code change broke the contract
3. Revert code OR update code to meet contract
4. NEVER modify test to pass
**Only modify test if:**
- API contract intentionally changed (requires multiple approvals)
- Test itself has bug (rare, needs proof)
Manual cleanup:
# Review temporary tests
ls {{TEMP_TEST_DIR}}/
# Remove feature-specific tests
rm {{TEMP_TEST_DIR}}/test_feature_x_*.{{ext}}
# Keep permanent tests
ls {{PERM_TEST_DIR}}/ # Should not change
Automated cleanup (Step 7 completion):
# feature-tester responsibility
cd {{WORKTREE_PATH}}
rm -rf {{TEMP_TEST_DIR}}/*
# OR keep specific files
mv {{TEMP_TEST_DIR}}/important_findings.txt docs/
rm -rf {{TEMP_TEST_DIR}}/*
Checklist:
# {{PERM_TEST_DIR}}/test_user.py
def test_user_requires_email():
# Changed from raise ValidationError to return False
# WRONG! This weakens the contract!
user = User.create({'username': 'john'}) # Missing email
assert user.email is None # ❌ Weakened test
Correct approach: Fix the code
# user.py
def create(data):
if 'email' not in data:
raise ValidationError("Email required") # ✅ Enforce contract
# {{TEMP_TEST_DIR}}/test_critical_auth.py
def test_authentication_requires_password():
# This is core functionality!
# WRONG! Should be in {{PERM_TEST_DIR}}/
Correct approach:
# {{PERM_TEST_DIR}}/test_authentication.py
def test_authentication_requires_password():
# ✅ Core functionality in permanent tests
# {{TEMP_TEST_DIR}}/ grows to 100+ files
ls {{TEMP_TEST_DIR}}/ | wc -l
# 127 files from last 6 months!
Correct approach:
# Regular cleanup (feature-tester)
# Keep only active feature tests
ls {{TEMP_TEST_DIR}}/
# 3-5 files for current features
For detailed rules, see reference.md For more examples, see examples.md
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 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 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.