Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:16:40 +08:00
commit f125e90b9f
370 changed files with 67769 additions and 0 deletions

View File

@@ -0,0 +1,14 @@
# Changelog
## 0.2.0
- Refactored to Anthropic progressive disclosure pattern
- Updated description with "Use PROACTIVELY when..." format
- Removed version/author/category/tags from frontmatter
## 0.1.0
- Initial release with RED-GREEN-REFACTOR cycle enforcement
- Automated TDD workflow with git hooks and npm scripts
- CPU usage prevention safeguards
- Integration with Testing Trophy methodology

View File

@@ -0,0 +1,340 @@
# TDD Red-Green-Refactor Automation
Automated TDD enforcement for LLM-assisted development. This skill installs infrastructure that guides Claude Code to automatically follow TDD workflow without manual intervention.
## Version
**0.2.0** - Stable release with comprehensive safety features
## What This Skill Does
Transforms your project so that when you ask Claude to implement features, it **automatically**:
1. 🔴 **RED Phase** - Writes failing tests FIRST
2. 🟢 **GREEN Phase** - Implements minimal code to pass tests
3. 🔵 **REFACTOR Phase** - Improves code quality while keeping tests green
**No slash commands. No manual prompting. Just say "implement X" and TDD happens.**
## Installation
```bash
# From claudex marketplace
/plugin install tdd-automation
# Or directly invoke the skill
tdd-automation
```
## What Gets Installed
### 1. CLAUDE.md Configuration
- Adds TDD requirements to project's `.claude/CLAUDE.md`
- **Safety**: Backs up existing files, merges cleanly, fully reversible
- Configures LLM to automatically follow TDD workflow
### 2. npm Scripts
```json
{
"test:tdd": "vitest --run",
"test:tdd:watch": "vitest",
"test:red": "vitest --run --reporter=verbose",
"test:green": "vitest --run --reporter=verbose",
"validate:tdd": "node .tdd-automation/scripts/validate-tdd.js",
"generate:test": "node .tdd-automation/scripts/generate-test.js"
}
```
### 3. Git Hooks
- **Pre-commit hook**: Validates tests exist before allowing commits
- Prevents TDD violations at the version control level
- Can be bypassed with `--no-verify` if needed
### 4. Helper Scripts
```
.tdd-automation/
├── scripts/
│ ├── generate-test.js # Create test templates
│ ├── validate-tdd.js # Check compliance
│ ├── rollback-tdd.js # Restore previous state
│ └── remove-tdd-section.js # Clean uninstall
└── templates/
└── test-template.js # Test file template
```
## Usage Examples
### Before Installation
```
You: "Add user authentication"
Claude: *writes implementation code directly* ❌
```
### After Installation
```
You: "Add user authentication"
Claude: "I'll implement user authentication following TDD red-green-refactor.
Let me create a todo list to track the phases:
- [ ] RED: Write failing test for user authentication
- [ ] Verify test fails with expected error
- [ ] GREEN: Implement minimal authentication logic
- [ ] Verify test passes
- [ ] REFACTOR: Improve code quality
Starting RED phase - I'll create the test file first..."
*Creates src/auth/authenticate.test.ts*
*Writes failing test*
*Runs: npm run test:red*
*Verifies failure*
"Test is failing as expected. Moving to GREEN phase..."
*Implements minimal code*
*Runs: npm run test:green*
*Verifies pass*
"All tests passing. Moving to REFACTOR phase..."
*Improves code structure*
*Runs: npm run test:tdd*
"✅ Feature complete with TDD workflow followed."
```
## Available Commands
### Validation
```bash
# Check if project follows TDD practices
npm run validate:tdd
```
### Test Generation
```bash
# Generate test template for implementation file
npm run generate:test src/features/auth/login.ts
```
### Maintenance
```bash
# Rollback to previous CLAUDE.md
node .tdd-automation/scripts/rollback-tdd.js
# Remove only TDD section from CLAUDE.md
node .tdd-automation/scripts/remove-tdd-section.js
# View available backups
ls -lh .claude/CLAUDE.md.backup.*
```
## Safety Features
### ✅ Non-Destructive Installation
- **Automatic backups** before any file modification
- **Merge strategy** preserves all existing content
- **Clear markers** for easy identification of additions
- **Rollback capability** to restore previous state
### ✅ Validation Before Installation
- Detects existing TDD automation (skips if present)
- Checks for existing CLAUDE.md (merges safely)
- Validates project structure
- Reports warnings for missing dependencies
### ✅ Clean Uninstallation
- Remove TDD section without affecting other content
- Restore from timestamped backups
- No orphaned files or configurations
## Configuration
### Enforcement Level
By default, TDD is **strictly enforced**. To customize:
Edit `.claude/CLAUDE.md` and modify:
```yaml
tdd-enforcement-level: strict # or: advisory, optional
```
- **strict**: LLM must follow TDD (default)
- **advisory**: LLM suggests TDD but allows flexibility
- **optional**: TDD is available but not required
### Test Framework
The skill auto-detects your test framework:
- Vitest (default, recommended)
- Jest
- Mocha
- AVA
To override, edit `.tdd-automation/config.json`:
```json
{
"testCommand": "jest",
"testExtension": ".test.ts"
}
```
## Troubleshooting
### Issue: LLM not following TDD
**Solution:**
1. Check CLAUDE.md has TDD configuration:
```bash
grep -A 5 "TDD_AUTOMATION" .claude/CLAUDE.md
```
2. Verify npm scripts installed:
```bash
npm run test:tdd
```
3. Validate installation:
```bash
npm run validate:tdd
```
### Issue: Git hook blocking commits
**Solution:**
1. Ensure tests exist for implementation files
2. Commit tests before implementation:
```bash
git add src/**/*.test.ts
git commit -m "Add tests for feature X"
git add src/**/!(*test).ts
git commit -m "Implement feature X"
```
3. Or bypass hook temporarily:
```bash
git commit --no-verify
```
### Issue: Want to remove TDD automation
**Solution:**
```bash
# Option 1: Remove only CLAUDE.md section
node .tdd-automation/scripts/remove-tdd-section.js
# Option 2: Rollback to previous CLAUDE.md
node .tdd-automation/scripts/rollback-tdd.js
# Option 3: Manual removal
# 1. Edit .claude/CLAUDE.md
# 2. Delete section between <!-- TDD_AUTOMATION_START --> and <!-- TDD_AUTOMATION_END -->
# 3. Remove .tdd-automation/ directory
# 4. Remove npm scripts from package.json
```
## Technical Details
### Architecture
```
┌─────────────────────────────────────────┐
│ User Request │
│ "Implement feature X" │
└──────────────┬──────────────────────────┘
┌─────────────────────────────────────────┐
│ CLAUDE.md (LLM Configuration) │
│ • TDD workflow requirements │
│ • Phase-by-phase instructions │
│ • TodoWrite templates │
└──────────────┬──────────────────────────┘
┌─────────────────────────────────────────┐
│ Claude Code (LLM) │
│ 1. Creates test file │
│ 2. Writes failing test (RED) │
│ 3. Runs: npm run test:red │
│ 4. Implements code (GREEN) │
│ 5. Runs: npm run test:green │
│ 6. Refactors (REFACTOR) │
│ 7. Runs: npm run test:tdd │
└──────────────┬──────────────────────────┘
┌─────────────────────────────────────────┐
│ Git Pre-Commit Hook │
│ • Validates tests exist │
│ • Blocks commits without tests │
│ • Ensures test-first compliance │
└─────────────────────────────────────────┘
```
### File Structure
```
project/
├── .claude/
│ ├── CLAUDE.md # TDD configuration (modified)
│ ├── CLAUDE.md.backup.* # Safety backups
│ └── hooks/
│ └── tdd-auto-enforcer.sh # Pre-prompt hook (optional)
├── .git/
│ └── hooks/
│ └── pre-commit # TDD validation (modified)
├── .tdd-automation/ # Installed by skill
│ ├── scripts/
│ │ ├── generate-test.js
│ │ ├── validate-tdd.js
│ │ ├── rollback-tdd.js
│ │ └── remove-tdd-section.js
│ ├── templates/
│ │ └── test-template.js
│ └── README.md
└── package.json # npm scripts added
```
## Success Metrics
Track TDD adherence:
```bash
npm run validate:tdd
```
**Target Metrics:**
- Test-first adherence: >95%
- RED-GREEN-REFACTOR cycles: >90%
- Defect escape rate: <2%
- Test coverage: >80%
## Contributing
This skill was auto-generated by the Pattern Suggestion Pipeline from 37 successful TDD applications with 86% success rate.
**Issues or improvements:**
- GitHub: https://github.com/cskiro/claudex/issues
- Pattern source: Issue #13
## License
MIT
## Version History
- **0.2.0** (2025-11-02)
- Stable release with full implementation
- Comprehensive safety features (backup, rollback, merge)
- Automatic LLM TDD enforcement via CLAUDE.md
- Git hooks for pre-commit validation
- Helper scripts (generate-test, validate-tdd)
- Multi-framework support (Vitest, Jest, Mocha, AVA)
- Complete documentation and troubleshooting guide
- **0.1.0** (2025-11-02)
- Initial proof-of-concept
- Auto-generated by Pattern Pipeline
- Basic skill structure and manifest

View File

@@ -0,0 +1,235 @@
---
name: tdd-automation
description: Use PROACTIVELY when starting new projects requiring strict TDD adherence, or when user wants to enforce test-first workflow. Automates red-green-refactor cycle for LLM-assisted development with git hooks, npm scripts, and CLAUDE.md configuration. Not for prototypes or projects without test requirements.
---
# TDD Red-Green-Refactor Automation
## Overview
This skill transforms any project to automatically enforce TDD (Test-Driven Development) workflow when using LLMs for code generation. Once installed, Claude Code will automatically follow the red-green-refactor cycle without manual prompting.
**Version:** 0.2.0
**Status:** Stable
**Success Rate:** 86% (based on 37 pattern applications)
**Source:** Pattern Suggestion Pipeline (Issue #13)
## What This Skill Does
**Automatic TDD Enforcement** - When you ask Claude to implement a feature, it will:
1. 🔴 **RED Phase**: Write failing test FIRST
2. 🟢 **GREEN Phase**: Implement minimal code to pass test
3. 🔵 **REFACTOR Phase**: Improve code quality while keeping tests green
**No manual steps required** - The LLM is automatically configured through CLAUDE.md to follow this workflow.
## Installation Process
When you invoke this skill, it will:
1. **Detect project configuration**
- Identify test framework (Vitest, Jest, Mocha, AVA)
- Check for TypeScript/JavaScript
- Validate git repository exists
2. **Configure CLAUDE.md safely**
- Create automatic backup before any changes
- Merge TDD requirements (preserves existing content)
- Add clear section markers for identification
3. **Install npm scripts**
- `test:tdd` - Run all tests once
- `test:tdd:watch` - Run tests in watch mode
- `test:red` - Verify test fails (RED phase)
- `test:green` - Verify test passes (GREEN phase)
- `validate:tdd` - Check TDD compliance
- `generate:test` - Create test template
4. **Install git hooks**
- Pre-commit hook validates tests exist
- Prevents commits without tests
- Enforces test-first workflow
5. **Create helper scripts**
- Test template generator
- TDD compliance validator
- Rollback utility
- Section removal utility
## When to Use This Skill
**Use this skill when:**
- Starting a new project that requires strict TDD adherence
- Migrating existing project to TDD workflow
- Training team members on TDD practices
- Ensuring consistent quality in LLM-generated code
- Working on mission-critical code that needs high test coverage
**Don't use this skill if:**
- Project doesn't have or need tests
- Working on prototypes or experiments
- TDD workflow conflicts with existing practices
## Trigger Phrases
The skill can be invoked by:
- Direct invocation: `tdd-automation`
- Natural language: "Set up TDD automation"
- Natural language: "Configure automatic TDD workflow"
- Natural language: "Install TDD enforcement"
## Behavior After Installation
Once installed, when you say something like:
```
"Implement user authentication"
```
Claude will **automatically**:
```
1. Create todo list with TDD phases:
- [ ] RED: Write failing test for user authentication
- [ ] Verify test fails with expected error
- [ ] GREEN: Implement minimal authentication logic
- [ ] Verify test passes
- [ ] REFACTOR: Improve code quality
- [ ] Verify all tests still pass
2. Create test file FIRST:
src/auth/authenticate.test.ts
3. Write failing test with clear description
4. Run test and verify RED state:
npm run test:red -- src/auth/authenticate.test.ts
5. Implement minimal code:
src/auth/authenticate.ts
6. Run test and verify GREEN state:
npm run test:green -- src/auth/authenticate.test.ts
7. Refactor if needed while keeping tests green
8. Final validation:
npm run test:tdd
```
## Safety Features
### Non-Destructive Installation
- Automatic backups before file modifications
- Merge strategy preserves all existing CLAUDE.md content
- Clear markers (`<!-- TDD_AUTOMATION_START/END -->`) for identification
- Rollback capability to restore previous state
### Validation Before Installation
- Detects if TDD automation already installed (skips if present)
- Checks for existing CLAUDE.md (merges safely)
- Validates project structure
- Reports warnings for missing dependencies
### Clean Uninstallation
- Remove only TDD section without affecting other content
- Restore from timestamped backups
- Utility scripts for easy maintenance
## Response Style
After installation, this skill guides Claude Code to:
- **Be explicit**: Always state which TDD phase is active
- **Be thorough**: Verify each phase completion before proceeding
- **Be traceable**: Use TodoWrite to track progress
- **Be safe**: Run tests to confirm RED/GREEN states
- **Be helpful**: Provide clear error messages if TDD is violated
## Files Modified/Created
### Modified Files
- `.claude/CLAUDE.md` - TDD workflow configuration added (backed up)
- `package.json` - npm scripts added (backed up)
- `.git/hooks/pre-commit` - TDD validation added (backed up if exists)
### Created Files
```
.tdd-automation/
├── scripts/
│ ├── generate-test.js # Test template generator
│ ├── validate-tdd.js # Compliance checker
│ ├── rollback-tdd.js # Restore previous state
│ └── remove-tdd-section.js # Clean uninstall
├── templates/
│ └── test-template.js # Test file template
└── README.md # Usage documentation
.claude/
├── CLAUDE.md.backup.* # Timestamped backups
└── hooks/
└── tdd-auto-enforcer.sh # Pre-prompt hook (optional)
```
## Success Criteria
Installation succeeds when:
1. ✅ CLAUDE.md contains TDD configuration
2. ✅ npm scripts added to package.json
3. ✅ Git pre-commit hook installed (if git repo exists)
4. ✅ Helper scripts created in .tdd-automation/
5. ✅ Backups created for all modified files
6. ✅ Validation passes: `npm run validate:tdd`
## Troubleshooting
### Issue: LLM not following TDD
**Check:**
```bash
# Verify CLAUDE.md has TDD configuration
grep "TDD_AUTOMATION" .claude/CLAUDE.md
# Verify npm scripts
npm run test:tdd
# Run validation
npm run validate:tdd
```
### Issue: Git hook blocking commits
**Solution:**
1. Ensure tests exist for implementation files
2. Commit tests before implementation
3. Or bypass temporarily: `git commit --no-verify`
### Issue: Want to remove TDD automation
**Options:**
```bash
# Option 1: Remove CLAUDE.md section only
node .tdd-automation/scripts/remove-tdd-section.js
# Option 2: Rollback to previous CLAUDE.md
node .tdd-automation/scripts/rollback-tdd.js
```
## Generated By
🤖 **Pattern Suggestion Pipeline**
- Detected from 37 successful TDD applications
- Success rate: 86%
- Source: GitHub Issue #13
- Generated: 2025-11-02
- Enhanced with full implementation: 2025-11-02
## Version History
- **0.2.0** (2025-11-02) - Stable release with full implementation
- **0.1.0** (2025-11-02) - Initial proof-of-concept
## Additional Resources
- Full documentation: `README.md`
- Helper scripts: `.tdd-automation/scripts/`
- Examples: See README.md "Usage Examples" section

View File

@@ -0,0 +1,417 @@
#!/usr/bin/env node
/**
* TDD Red-Green-Refactor Automation Skill
*
* Main orchestrator that installs TDD automation infrastructure in a project.
* Configures CLAUDE.md, git hooks, npm scripts, and helper utilities to enforce
* TDD workflow automatically for LLM-assisted development.
*
* @author Pattern Suggestion Pipeline
* @version 0.2.0
*/
const fs = require('fs');
const path = require('path');
// Import utilities
const ClaudeMdValidator = require('./utils/validate-claude-md');
const ClaudeMdMerger = require('./utils/merge-claude-md');
const ProjectDetector = require('./utils/detect-project-type');
const PackageJsonUpdater = require('./utils/update-package-json');
const HookInstaller = require('./utils/install-hooks');
class TddAutomationSkill {
constructor() {
this.projectRoot = process.cwd();
this.skillRoot = __dirname;
this.version = '0.2.0';
}
/**
* Main installation workflow
*/
async install() {
console.log('╔════════════════════════════════════════════════════════════════╗');
console.log('║ TDD Red-Green-Refactor Automation ║');
console.log('║ Version 0.2.0 ║');
console.log('╚════════════════════════════════════════════════════════════════╝\n');
const results = {
success: false,
steps: [],
errors: [],
warnings: []
};
try {
// Step 1: Detect project type
console.log('🔍 Step 1: Detecting project configuration...\n');
const projectInfo = await this.detectProject(results);
// Step 2: Validate and backup CLAUDE.md
console.log('\n🔍 Step 2: Validating CLAUDE.md...\n');
const validation = await this.validateClaudeMd(results);
if (validation.strategy === 'skip') {
console.log('✅ TDD automation already installed');
console.log(' No changes needed.\n');
return results;
}
// Step 3: Install/merge CLAUDE.md configuration
console.log('\n📝 Step 3: Configuring CLAUDE.md...\n');
await this.configureClaudeMd(validation, results);
// Step 4: Update package.json with TDD scripts
console.log('\n📦 Step 4: Adding npm scripts...\n');
await this.updatePackageJson(projectInfo, results);
// Step 5: Install hooks
console.log('\n🪝 Step 5: Installing hooks...\n');
await this.installHooks(results);
// Step 6: Verify installation
console.log('\n✅ Step 6: Verifying installation...\n');
await this.verifyInstallation(results);
// Success summary
results.success = results.errors.length === 0;
this.printSummary(results, projectInfo);
} catch (error) {
console.error('\n❌ Installation failed:', error.message);
console.error('\n Stack trace:', error.stack);
results.errors.push(error.message);
results.success = false;
}
return results;
}
/**
* Detect project type and configuration
*/
async detectProject(results) {
const detector = new ProjectDetector(this.projectRoot);
const projectInfo = detector.detect();
console.log(` Project: ${projectInfo.projectName}`);
console.log(` Type: ${projectInfo.type}`);
if (projectInfo.testFramework !== 'unknown') {
console.log(` Test Framework: ${projectInfo.testFramework}`);
}
if (projectInfo.hasTypeScript) {
console.log(' Language: TypeScript');
}
if (projectInfo.hasGit) {
console.log(' ✓ Git repository detected');
} else {
results.warnings.push('No git repository - git hooks will not be installed');
}
if (projectInfo.recommendations.length > 0) {
console.log('\n Recommendations:');
projectInfo.recommendations.forEach(rec => {
console.log(`${rec}`);
results.warnings.push(rec);
});
}
results.steps.push('Project detection completed');
return projectInfo;
}
/**
* Validate CLAUDE.md state
*/
async validateClaudeMd(results) {
const validator = new ClaudeMdValidator(this.projectRoot);
const validation = validator.validate();
console.log(` Location: ${validation.path}`);
console.log(` Exists: ${validation.exists ? 'Yes' : 'No'}`);
if (validation.exists) {
console.log(` Size: ${this.formatBytes(validation.size)}`);
console.log(` Has Content: ${validation.hasExistingContent ? 'Yes' : 'No'}`);
}
console.log(` Strategy: ${validation.strategy.toUpperCase()}`);
if (validation.warnings.length > 0) {
console.log('\n Notes:');
validation.warnings.forEach(warning => {
console.log(`${warning}`);
});
}
results.steps.push(`CLAUDE.md validation: ${validation.strategy}`);
return validation;
}
/**
* Configure CLAUDE.md with TDD automation
*/
async configureClaudeMd(validation, results) {
const validator = new ClaudeMdValidator(this.projectRoot);
if (validation.strategy === 'merge') {
// Existing content - merge safely
console.log(' Creating backup...');
const backup = validator.createBackup();
if (backup.success) {
console.log(` ✓ Backup created: ${path.basename(backup.path)}`);
results.steps.push(`Backup created: ${backup.path}`);
} else {
throw new Error(`Backup failed: ${backup.reason}`);
}
// Read existing content
const existingContent = fs.readFileSync(validator.claudeMdPath, 'utf-8');
// Merge with TDD section
console.log(' Merging TDD configuration...');
const merger = new ClaudeMdMerger(existingContent);
const mergedContent = merger.merge();
// Write merged content
fs.writeFileSync(validator.claudeMdPath, mergedContent, 'utf-8');
const newSize = mergedContent.length;
const added = newSize - validation.size;
console.log(` ✓ CLAUDE.md updated`);
console.log(` Original: ${this.formatBytes(validation.size)}`);
console.log(` New: ${this.formatBytes(newSize)} (+${this.formatBytes(added)})`);
results.steps.push('CLAUDE.md merged with TDD configuration');
} else {
// No existing content or empty - create new
console.log(' Creating new CLAUDE.md with TDD configuration...');
const claudeDir = path.join(this.projectRoot, '.claude');
if (!fs.existsSync(claudeDir)) {
fs.mkdirSync(claudeDir, { recursive: true });
}
const content = ClaudeMdMerger.createNew();
fs.writeFileSync(validator.claudeMdPath, content, 'utf-8');
console.log(` ✓ CLAUDE.md created`);
console.log(` Size: ${this.formatBytes(content.length)}`);
results.steps.push('CLAUDE.md created with TDD configuration');
}
}
/**
* Update package.json with TDD scripts
*/
async updatePackageJson(projectInfo, results) {
if (!projectInfo.hasPackageJson) {
console.log(' ⚠️ No package.json found - skipping npm scripts');
results.warnings.push('No package.json - npm scripts not added');
return;
}
const updater = new PackageJsonUpdater(this.projectRoot);
// Check if already installed
if (updater.hasTddScripts()) {
console.log(' ✓ TDD scripts already installed');
results.steps.push('TDD npm scripts already present');
return;
}
// Determine test command based on project
const testCommand = projectInfo.testFramework === 'vitest'
? 'vitest --run'
: projectInfo.testFramework === 'jest'
? 'jest'
: 'npm test';
const result = updater.addTddScripts(testCommand);
if (result.success) {
console.log(' ✓ Added TDD scripts to package.json');
if (result.added.length > 0) {
console.log('\n Scripts added:');
result.added.forEach(script => {
console.log(`${script}`);
});
}
if (result.skipped.length > 0) {
console.log('\n Scripts skipped (already exist):');
result.skipped.forEach(script => {
console.log(`${script}`);
});
}
results.steps.push(`Added ${result.added.length} npm scripts`);
} else {
results.warnings.push(`Failed to update package.json: ${result.reason}`);
}
}
/**
* Install git and Claude hooks
*/
async installHooks(results) {
const installer = new HookInstaller(this.projectRoot, this.skillRoot);
const result = installer.installAll();
if (result.installed.length > 0) {
console.log(' Installed:');
result.installed.forEach(item => {
console.log(`${item}`);
});
}
if (result.skipped.length > 0) {
console.log('\n Skipped:');
result.skipped.forEach(item => {
console.log(`${item}`);
});
}
if (result.failed.length > 0) {
console.log('\n Failed:');
result.failed.forEach(item => {
console.log(`${item}`);
results.warnings.push(`Hook installation failed: ${item}`);
});
}
results.steps.push(`Installed ${result.installed.length} hooks`);
}
/**
* Verify installation was successful
*/
async verifyInstallation(results) {
const checks = [];
// Check CLAUDE.md
const claudeMdPath = path.join(this.projectRoot, '.claude', 'CLAUDE.md');
if (fs.existsSync(claudeMdPath)) {
const content = fs.readFileSync(claudeMdPath, 'utf-8');
if (content.includes('TDD_AUTOMATION')) {
checks.push('✓ CLAUDE.md configured');
} else {
checks.push('✗ CLAUDE.md missing TDD configuration');
}
} else {
checks.push('✗ CLAUDE.md not found');
}
// Check .tdd-automation directory
const tddDir = path.join(this.projectRoot, '.tdd-automation');
if (fs.existsSync(tddDir)) {
checks.push('✓ .tdd-automation directory created');
}
// Check package.json scripts
const packageJsonPath = path.join(this.projectRoot, 'package.json');
if (fs.existsSync(packageJsonPath)) {
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
if (packageJson.scripts && packageJson.scripts['test:tdd']) {
checks.push('✓ npm scripts added');
}
}
checks.forEach(check => console.log(` ${check}`));
results.steps.push('Installation verified');
}
/**
* Print installation summary
*/
printSummary(results, projectInfo) {
console.log('\n╔════════════════════════════════════════════════════════════════╗');
console.log('║ Installation Summary ║');
console.log('╚════════════════════════════════════════════════════════════════╝\n');
if (results.success) {
console.log('✅ TDD automation installed successfully!\n');
} else {
console.log('⚠️ TDD automation installed with warnings\n');
}
console.log('📦 What was installed:\n');
console.log(' • CLAUDE.md: TDD workflow configuration for LLM');
console.log(' • npm scripts: test:tdd, validate:tdd, generate:test');
console.log(' • Git hooks: pre-commit TDD validation');
console.log(' • Helper scripts: .tdd-automation/scripts/');
console.log(' • Templates: Test file generators\n');
console.log('🚀 Next steps:\n');
console.log(' 1. Test the automation:');
console.log(' Say: "Implement user authentication"\n');
console.log(' 2. The LLM will automatically:');
console.log(' • Follow TDD red-green-refactor workflow');
console.log(' • Create tests BEFORE implementation');
console.log(' • Run tests to verify each phase');
console.log(' • Use TodoWrite to track progress\n');
console.log('📚 Available commands:\n');
console.log(' npm run test:tdd # Run all tests');
console.log(' npm run validate:tdd # Check TDD compliance');
console.log(' npm run generate:test <file> # Create test template\n');
console.log('🔧 Maintenance:\n');
console.log(' node .tdd-automation/scripts/rollback-tdd.js');
console.log(' node .tdd-automation/scripts/remove-tdd-section.js\n');
if (results.warnings.length > 0) {
console.log('⚠️ Warnings:\n');
results.warnings.forEach(warning => {
console.log(`${warning}`);
});
console.log('');
}
console.log('═══════════════════════════════════════════════════════════════\n');
}
/**
* Format bytes for display
*/
formatBytes(bytes) {
if (bytes < 1024) return `${bytes} bytes`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
}
}
/**
* Main entry point
*/
async function main() {
const skill = new TddAutomationSkill();
const results = await skill.install();
if (!results.success && results.errors.length > 0) {
console.error('Installation completed with errors');
process.exit(1);
}
process.exit(0);
}
// Run if called directly
if (require.main === module) {
main().catch(error => {
console.error('Fatal error:', error);
process.exit(1);
});
}
module.exports = TddAutomationSkill;

View File

@@ -0,0 +1,198 @@
#!/usr/bin/env node
/**
* Test Template Generator
*
* Generates test file templates based on implementation files.
* Analyzes project structure and test framework to create appropriate templates.
*/
const fs = require('fs');
const path = require('path');
function generateTest() {
const args = process.argv.slice(2);
if (args.length === 0) {
console.error('Usage: npm run generate:test <implementation-file>');
console.error('');
console.error('Example:');
console.error(' npm run generate:test src/features/auth/login.ts');
process.exit(1);
}
const implFile = args[0];
if (!fs.existsSync(implFile)) {
console.error(`❌ Error: File not found: ${implFile}`);
process.exit(1);
}
// Determine test file path
const ext = path.extname(implFile);
const testFile = implFile.replace(ext, `.test${ext}`);
if (fs.existsSync(testFile)) {
console.error(`❌ Error: Test file already exists: ${testFile}`);
console.error(' Use a different name or edit the existing file');
process.exit(1);
}
// Get implementation file info
const filename = path.basename(implFile, ext);
const implContent = fs.readFileSync(implFile, 'utf-8');
// Detect test framework
const testFramework = detectTestFramework();
// Generate appropriate template
const template = generateTemplate(filename, implFile, implContent, testFramework);
// Write test file
fs.writeFileSync(testFile, template, 'utf-8');
console.log('✅ Test file created:', testFile);
console.log('');
console.log('📝 Next steps:');
console.log(' 1. Edit the test file to add specific test cases');
console.log(' 2. Run tests to verify RED phase:');
console.log(` npm run test:red -- ${testFile}`);
console.log(' 3. Implement the feature');
console.log(' 4. Run tests to verify GREEN phase:');
console.log(` npm run test:green -- ${testFile}`);
}
function detectTestFramework() {
const packageJsonPath = path.join(process.cwd(), 'package.json');
if (!fs.existsSync(packageJsonPath)) {
return 'vitest'; // default
}
try {
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
const allDeps = {
...packageJson.dependencies,
...packageJson.devDependencies
};
if (allDeps['vitest']) return 'vitest';
if (allDeps['jest']) return 'jest';
if (allDeps['@jest/globals']) return 'jest';
if (allDeps['mocha']) return 'mocha';
return 'vitest'; // default
} catch (error) {
return 'vitest';
}
}
function generateTemplate(filename, implFile, implContent, testFramework) {
const relativePath = path.relative(path.dirname(implFile), implFile);
const importPath = './' + path.basename(implFile, path.extname(implFile));
// Extract potential functions/classes to test
const entities = extractEntities(implContent);
let template = '';
// Add imports based on test framework
if (testFramework === 'vitest') {
template += `import { describe, it, expect, beforeEach, afterEach } from 'vitest';\n`;
} else if (testFramework === 'jest') {
template += `import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';\n`;
} else {
template += `const { describe, it, expect, beforeEach, afterEach } = require('${testFramework}');\n`;
}
// Add import for implementation
template += `import * as impl from '${importPath}';\n`;
template += `\n`;
// Add main describe block
template += `describe('${filename}', () => {\n`;
template += ` // TODO: Add setup and teardown if needed\n`;
template += ` // beforeEach(() => {\n`;
template += ` // // Setup before each test\n`;
template += ` // });\n`;
template += `\n`;
template += ` // afterEach(() => {\n`;
template += ` // // Cleanup after each test\n`;
template += ` // });\n`;
template += `\n`;
if (entities.length > 0) {
// Generate test stubs for each entity
entities.forEach(entity => {
template += ` describe('${entity}', () => {\n`;
template += ` it('should [behavior] when [condition]', () => {\n`;
template += ` // Arrange\n`;
template += ` // TODO: Set up test data and dependencies\n`;
template += `\n`;
template += ` // Act\n`;
template += ` // TODO: Call the function/method being tested\n`;
template += ` // const result = impl.${entity}();\n`;
template += `\n`;
template += ` // Assert\n`;
template += ` // TODO: Verify the expected behavior\n`;
template += ` expect(true).toBe(false); // Replace with actual assertion\n`;
template += ` });\n`;
template += `\n`;
template += ` // TODO: Add more test cases:\n`;
template += ` // it('should handle edge case when [condition]', () => { ... });\n`;
template += ` // it('should throw error when [invalid input]', () => { ... });\n`;
template += ` });\n`;
template += `\n`;
});
} else {
// Generic test template
template += ` it('should [behavior] when [condition]', () => {\n`;
template += ` // Arrange\n`;
template += ` // TODO: Set up test data and dependencies\n`;
template += `\n`;
template += ` // Act\n`;
template += ` // TODO: Call the function/method being tested\n`;
template += `\n`;
template += ` // Assert\n`;
template += ` // TODO: Verify the expected behavior\n`;
template += ` expect(true).toBe(false); // Replace with actual assertion\n`;
template += ` });\n`;
template += `\n`;
template += ` // TODO: Add more test cases for different scenarios\n`;
}
template += `});\n`;
return template;
}
function extractEntities(content) {
const entities = [];
// Extract exported functions
const functionMatches = content.matchAll(/export\s+(?:async\s+)?function\s+([a-zA-Z0-9_]+)/g);
for (const match of functionMatches) {
entities.push(match[1]);
}
// Extract exported classes
const classMatches = content.matchAll(/export\s+class\s+([a-zA-Z0-9_]+)/g);
for (const match of classMatches) {
entities.push(match[1]);
}
// Extract exported const functions (arrow functions)
const constMatches = content.matchAll(/export\s+const\s+([a-zA-Z0-9_]+)\s*=\s*(?:async\s*)?\(/g);
for (const match of constMatches) {
entities.push(match[1]);
}
return entities;
}
// Run if called directly
if (require.main === module) {
generateTest();
}
module.exports = { generateTest };

View File

@@ -0,0 +1,125 @@
#!/usr/bin/env node
/**
* TDD Section Removal Utility
*
* Cleanly removes only the TDD automation section from CLAUDE.md
* while preserving all other content.
*/
const fs = require('fs');
const path = require('path');
// Import utilities
const projectRoot = process.cwd();
const skillRoot = path.join(__dirname, '..');
// Try to load ClaudeMdValidator
let ClaudeMdValidator;
try {
ClaudeMdValidator = require(path.join(skillRoot, 'utils', 'validate-claude-md.js'));
} catch {
try {
ClaudeMdValidator = require(path.join(projectRoot, '.tdd-automation', 'utils', 'validate-claude-md.js'));
} catch {
console.error('❌ Error: ClaudeMdValidator not found');
process.exit(1);
}
}
async function removeTddSection() {
console.log('🗑️ TDD Section Removal Utility\n');
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
const validator = new ClaudeMdValidator(projectRoot);
// Check if CLAUDE.md exists
if (!fs.existsSync(validator.claudeMdPath)) {
console.log('❌ CLAUDE.md not found');
console.log(` Expected location: ${validator.claudeMdPath}`);
console.log('');
process.exit(1);
}
// Check if TDD section exists
const content = fs.readFileSync(validator.claudeMdPath, 'utf-8');
if (!validator.detectTddSection(content)) {
console.log('✅ No TDD section found in CLAUDE.md');
console.log(' Nothing to remove');
console.log('');
process.exit(0);
}
console.log('🔍 TDD section detected in CLAUDE.md\n');
// Show what will be removed
const startMarker = '<!-- TDD_AUTOMATION_START -->';
const endMarker = '<!-- TDD_AUTOMATION_END -->';
const startIdx = content.indexOf(startMarker);
const endIdx = content.indexOf(endMarker);
if (startIdx !== -1 && endIdx !== -1) {
const sectionSize = endIdx - startIdx + endMarker.length;
console.log(` Section size: ${formatBytes(sectionSize)}`);
console.log(` Total file size: ${formatBytes(content.length)}`);
console.log(` After removal: ${formatBytes(content.length - sectionSize)}\n`);
}
// Perform removal
console.log('🔒 Creating backup before removal...');
const result = validator.removeTddSection();
if (result.success) {
console.log(`✅ Backup created: ${path.basename(result.backup)}\n`);
console.log('✅ TDD section removed successfully!\n');
console.log(' Statistics:');
console.log(` • Original size: ${formatBytes(result.originalSize)}`);
console.log(` • New size: ${formatBytes(result.newSize)}`);
console.log(` • Removed: ${formatBytes(result.removed)}\n`);
console.log(' Backup available at:');
console.log(` ${result.backup}\n`);
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
console.log('⚠️ TDD automation configuration removed from CLAUDE.md\n');
console.log(' Other TDD automation components remain:');
console.log(' • .tdd-automation/ directory');
console.log(' • npm scripts (test:tdd, validate:tdd, etc.)');
console.log(' • git hooks (.git/hooks/pre-commit)');
console.log(' • Claude hooks (.claude/hooks/tdd-auto-enforcer.sh)\n');
console.log(' To remove all components:');
console.log(' node .tdd-automation/scripts/uninstall-tdd.js\n');
console.log(' To restore this section:');
console.log(` node .tdd-automation/scripts/rollback-tdd.js\n`);
} else {
console.error('❌ Removal failed:', result.reason);
console.error('');
console.error(' Possible issues:');
console.error(' • Section markers not found (manual edit may be needed)');
console.error(' • File permissions issue');
console.error(' • Disk space issue');
console.error('');
console.error(' Manual removal:');
console.error(' 1. Open .claude/CLAUDE.md in editor');
console.error(' 2. Find <!-- TDD_AUTOMATION_START -->');
console.error(' 3. Delete everything until <!-- TDD_AUTOMATION_END -->');
console.error(' 4. Save file');
console.error('');
process.exit(1);
}
}
function formatBytes(bytes) {
if (bytes < 1024) return `${bytes} bytes`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
}
// Run if called directly
if (require.main === module) {
removeTddSection().catch(error => {
console.error('❌ Unexpected error:', error.message);
process.exit(1);
});
}
module.exports = { removeTddSection };

View File

@@ -0,0 +1,141 @@
#!/usr/bin/env node
/**
* TDD Automation Rollback Utility
*
* Restores previous CLAUDE.md from backup and optionally removes all TDD automation.
*/
const fs = require('fs');
const path = require('path');
// Import utilities from parent directory
const projectRoot = process.cwd();
const skillRoot = path.join(__dirname, '..');
// Try to load ClaudeMdValidator from skill location
let ClaudeMdValidator;
try {
ClaudeMdValidator = require(path.join(skillRoot, 'utils', 'validate-claude-md.js'));
} catch {
// If skill utils not available, use local copy in .tdd-automation
try {
ClaudeMdValidator = require(path.join(projectRoot, '.tdd-automation', 'utils', 'validate-claude-md.js'));
} catch {
console.error('❌ Error: ClaudeMdValidator not found');
console.error(' This script must be run from project root or .tdd-automation directory');
process.exit(1);
}
}
async function rollbackTddAutomation() {
console.log('🔄 TDD Automation Rollback Utility\n');
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
const validator = new ClaudeMdValidator(projectRoot);
// List available backups
const backups = validator.listBackups();
if (backups.length === 0) {
console.log('❌ No backup files found');
console.log(' Cannot rollback without backup');
console.log('');
console.log(' Backup files should be in: .claude/CLAUDE.md.backup.*');
console.log('');
process.exit(1);
}
console.log(`📋 Found ${backups.length} backup(s):\n`);
backups.forEach((backup, i) => {
const age = getTimeAgo(backup.created);
console.log(`${i + 1}. ${backup.name}`);
console.log(` Created: ${backup.created.toISOString()} (${age})`);
console.log(` Size: ${formatBytes(backup.size)}\n`);
});
// Use most recent backup
const latestBackup = backups[0];
console.log(`🔄 Rolling back to most recent backup:\n`);
console.log(` ${latestBackup.name}`);
console.log(` Created: ${latestBackup.created.toISOString()}\n`);
// Create backup of current state before rollback
console.log('🔒 Creating safety backup of current state...');
const currentBackup = validator.createBackup();
if (currentBackup.success) {
console.log(`✅ Current state backed up to:`);
console.log(` ${path.basename(currentBackup.path)}\n`);
} else {
console.log(`⚠️ Could not backup current state: ${currentBackup.reason}`);
console.log(` Proceeding with rollback anyway...\n`);
}
// Perform rollback
console.log('🔄 Performing rollback...');
const result = validator.rollback(latestBackup.path);
if (result.success) {
console.log('✅ Rollback successful!\n');
console.log(' CLAUDE.md has been restored from:');
console.log(` ${path.basename(result.restoredFrom)}`);
console.log(` Size: ${formatBytes(result.size)}\n`);
if (currentBackup.success) {
console.log(' Your previous state was saved to:');
console.log(` ${path.basename(currentBackup.path)}\n`);
}
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
console.log('⚠️ TDD automation configuration has been removed from CLAUDE.md\n');
console.log(' Other TDD automation components remain:');
console.log(' • .tdd-automation/ directory');
console.log(' • npm scripts (test:tdd, validate:tdd, etc.)');
console.log(' • git hooks (.git/hooks/pre-commit)');
console.log(' • Claude hooks (.claude/hooks/tdd-auto-enforcer.sh)\n');
console.log(' To remove all TDD automation components:');
console.log(' node .tdd-automation/scripts/uninstall-tdd.js\n');
console.log(' To reinstall TDD automation:');
console.log(' Run the tdd-automation skill again\n');
} else {
console.error('❌ Rollback failed:', result.reason);
console.error('');
console.error(' You may need to manually restore CLAUDE.md from backup');
console.error(` Backup location: ${latestBackup.path}`);
console.error('');
process.exit(1);
}
}
function getTimeAgo(date) {
const now = new Date();
const diff = now - date;
const seconds = Math.floor(diff / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24);
if (days > 0) return `${days} day${days > 1 ? 's' : ''} ago`;
if (hours > 0) return `${hours} hour${hours > 1 ? 's' : ''} ago`;
if (minutes > 0) return `${minutes} minute${minutes > 1 ? 's' : ''} ago`;
return `${seconds} second${seconds > 1 ? 's' : ''} ago`;
}
function formatBytes(bytes) {
if (bytes < 1024) return `${bytes} bytes`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
}
// Run if called directly
if (require.main === module) {
rollbackTddAutomation().catch(error => {
console.error('❌ Unexpected error:', error.message);
process.exit(1);
});
}
module.exports = { rollbackTddAutomation };

View File

@@ -0,0 +1,300 @@
#!/usr/bin/env node
/**
* TDD Compliance Validator
*
* Validates that project follows TDD best practices:
* - All implementation files have corresponding tests
* - Test coverage meets minimum threshold
* - Tests are properly structured
*/
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
async function validateTdd() {
console.log('🔍 TDD Compliance Validation\n');
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
const results = {
passed: [],
failed: [],
warnings: []
};
// Check 1: Verify test files exist for implementation files
console.log('📋 Check 1: Test file coverage...');
await checkTestFileCoverage(results);
// Check 2: Verify test framework is installed
console.log('\n📋 Check 2: Test framework installation...');
await checkTestFramework(results);
// Check 3: Verify TDD npm scripts exist
console.log('\n📋 Check 3: TDD npm scripts...');
await checkNpmScripts(results);
// Check 4: Verify git hooks installed
console.log('\n📋 Check 4: Git hooks...');
await checkGitHooks(results);
// Check 5: Verify CLAUDE.md has TDD configuration
console.log('\n📋 Check 5: CLAUDE.md configuration...');
await checkClaudeMd(results);
// Summary
console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
console.log('📊 Validation Summary\n');
if (results.passed.length > 0) {
console.log(`✅ Passed: ${results.passed.length}`);
results.passed.forEach(msg => console.log(`${msg}`));
console.log('');
}
if (results.warnings.length > 0) {
console.log(`⚠️ Warnings: ${results.warnings.length}`);
results.warnings.forEach(msg => console.log(`${msg}`));
console.log('');
}
if (results.failed.length > 0) {
console.log(`❌ Failed: ${results.failed.length}`);
results.failed.forEach(msg => console.log(`${msg}`));
console.log('');
}
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
if (results.failed.length > 0) {
console.log('❌ TDD compliance validation FAILED\n');
console.log(' Please address the failed checks above');
console.log(' Run: npm run generate:test <file> to create missing tests\n');
process.exit(1);
} else if (results.warnings.length > 0) {
console.log('⚠️ TDD compliance validation PASSED with warnings\n');
console.log(' Consider addressing the warnings above\n');
process.exit(0);
} else {
console.log('✅ TDD compliance validation PASSED\n');
console.log(' All checks passed successfully!\n');
process.exit(0);
}
}
async function checkTestFileCoverage(results) {
const srcDir = path.join(process.cwd(), 'src');
if (!fs.existsSync(srcDir)) {
results.warnings.push('No src/ directory found - skipping test coverage check');
return;
}
const implFiles = findImplementationFiles(srcDir);
const testFiles = findTestFiles(srcDir);
let missing = 0;
let covered = 0;
for (const implFile of implFiles) {
const testFile = findCorrespondingTest(implFile, testFiles);
if (!testFile) {
missing++;
if (missing <= 5) { // Only show first 5 to avoid spam
results.failed.push(`Missing test for: ${path.relative(process.cwd(), implFile)}`);
}
} else {
covered++;
}
}
if (missing > 5) {
results.failed.push(`... and ${missing - 5} more files without tests`);
}
const totalFiles = implFiles.length;
const coverage = totalFiles > 0 ? ((covered / totalFiles) * 100).toFixed(1) : 100;
if (missing === 0 && totalFiles > 0) {
results.passed.push(`All ${totalFiles} implementation files have tests (100%)`);
} else if (coverage >= 80) {
results.warnings.push(`Test file coverage: ${coverage}% (${covered}/${totalFiles}) - below 100%`);
} else if (totalFiles === 0) {
results.warnings.push('No implementation files found in src/');
}
}
async function checkTestFramework(results) {
const packageJsonPath = path.join(process.cwd(), 'package.json');
if (!fs.existsSync(packageJsonPath)) {
results.failed.push('package.json not found');
return;
}
try {
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
const allDeps = {
...packageJson.dependencies,
...packageJson.devDependencies
};
const testFrameworks = ['vitest', 'jest', '@jest/globals', 'mocha', 'ava'];
const installed = testFrameworks.find(fw => allDeps[fw]);
if (installed) {
results.passed.push(`Test framework installed: ${installed}`);
} else {
results.failed.push('No test framework found (install vitest, jest, or mocha)');
}
} catch (error) {
results.failed.push(`Error reading package.json: ${error.message}`);
}
}
async function checkNpmScripts(results) {
const packageJsonPath = path.join(process.cwd(), 'package.json');
if (!fs.existsSync(packageJsonPath)) {
return; // Already reported in previous check
}
try {
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
const scripts = packageJson.scripts || {};
const requiredScripts = ['test:tdd', 'validate:tdd', 'generate:test'];
const missingScripts = requiredScripts.filter(script => !scripts[script]);
if (missingScripts.length === 0) {
results.passed.push('All TDD npm scripts installed');
} else {
missingScripts.forEach(script => {
results.warnings.push(`Missing npm script: ${script}`);
});
}
} catch (error) {
results.failed.push(`Error checking npm scripts: ${error.message}`);
}
}
async function checkGitHooks(results) {
const preCommitPath = path.join(process.cwd(), '.git', 'hooks', 'pre-commit');
if (!fs.existsSync(path.join(process.cwd(), '.git'))) {
results.warnings.push('Not a git repository - no git hooks checked');
return;
}
if (!fs.existsSync(preCommitPath)) {
results.warnings.push('Git pre-commit hook not installed');
return;
}
const content = fs.readFileSync(preCommitPath, 'utf-8');
if (content.includes('TDD_AUTOMATION') || content.includes('TDD Validation')) {
results.passed.push('Git pre-commit hook installed for TDD validation');
} else {
results.warnings.push('Git pre-commit hook exists but may not have TDD validation');
}
}
async function checkClaudeMd(results) {
const claudeMdPath = path.join(process.cwd(), '.claude', 'CLAUDE.md');
if (!fs.existsSync(claudeMdPath)) {
results.warnings.push('No .claude/CLAUDE.md found');
return;
}
const content = fs.readFileSync(claudeMdPath, 'utf-8');
const tddMarkers = [
'TDD Red-Green-Refactor',
'tdd-automation-version',
'<!-- TDD_AUTOMATION_START -->'
];
const hasTddConfig = tddMarkers.some(marker => content.includes(marker));
if (hasTddConfig) {
results.passed.push('CLAUDE.md has TDD configuration');
} else {
results.warnings.push('CLAUDE.md exists but missing TDD configuration');
}
}
function findImplementationFiles(dir, files = []) {
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
// Skip node_modules, dist, build, etc.
if (!['node_modules', 'dist', 'build', '.git', 'coverage'].includes(entry.name)) {
findImplementationFiles(fullPath, files);
}
} else if (entry.isFile()) {
// Include .ts, .js, .tsx, .jsx files but exclude test files
if (/\.(ts|js|tsx|jsx)$/.test(entry.name) && !/\.(test|spec)\./.test(entry.name)) {
files.push(fullPath);
}
}
}
return files;
}
function findTestFiles(dir, files = []) {
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
if (!['node_modules', 'dist', 'build', '.git', 'coverage'].includes(entry.name)) {
findTestFiles(fullPath, files);
}
} else if (entry.isFile()) {
if (/\.(test|spec)\.(ts|js|tsx|jsx)$/.test(entry.name)) {
files.push(fullPath);
}
}
}
return files;
}
function findCorrespondingTest(implFile, testFiles) {
const dir = path.dirname(implFile);
const filename = path.basename(implFile);
const base = filename.replace(/\.(ts|js|tsx|jsx)$/, '');
// Try multiple test file patterns
const patterns = [
path.join(dir, `${base}.test.ts`),
path.join(dir, `${base}.test.js`),
path.join(dir, `${base}.test.tsx`),
path.join(dir, `${base}.test.jsx`),
path.join(dir, `${base}.spec.ts`),
path.join(dir, `${base}.spec.js`),
path.join(dir, `${base}.spec.tsx`),
path.join(dir, `${base}.spec.jsx`)
];
return testFiles.find(testFile => patterns.includes(testFile));
}
// Run if called directly
if (require.main === module) {
validateTdd().catch(error => {
console.error('❌ Unexpected error:', error.message);
process.exit(1);
});
}
module.exports = { validateTdd };

View File

@@ -0,0 +1,110 @@
#!/bin/bash
###############################################################################
# TDD_AUTOMATION: Git Pre-Commit Hook
#
# Validates that tests exist for implementation files before allowing commit.
# Ensures TDD workflow (test-first) is followed.
#
# Installed by: tdd-automation skill
###############################################################################
echo "🔍 TDD Validation: Checking test-first compliance..."
# Get list of new implementation files being committed
IMPL_FILES=$(git diff --cached --name-only --diff-filter=A | grep -E "^src/.*\.(ts|js|tsx|jsx)$" | grep -v ".test." | grep -v ".spec.")
if [ -z "$IMPL_FILES" ]; then
echo "✅ No new implementation files detected"
exit 0
fi
VIOLATIONS=0
# Check each implementation file
while IFS= read -r impl_file; do
if [ -z "$impl_file" ]; then
continue
fi
# Determine expected test file name
dir=$(dirname "$impl_file")
filename=$(basename "$impl_file")
base="${filename%.*}"
ext="${filename##*.}"
# Try multiple test file patterns
test_patterns=(
"$dir/$base.test.$ext"
"$dir/$base.spec.$ext"
"${impl_file%.*}.test.$ext"
"${impl_file%.*}.spec.$ext"
)
test_exists=false
for test_file in "${test_patterns[@]}"; do
if [ -f "$test_file" ]; then
test_exists=true
# Check if test file was committed first (git log timestamps)
test_commit_time=$(git log --diff-filter=A --format="%at" --follow -- "$test_file" 2>/dev/null | head -1)
impl_commit_time=$(date +%s) # Current time for uncommitted file
# If test file exists in git history, it was committed first - good!
if [ -n "$test_commit_time" ]; then
echo "$impl_file -> Test exists: $test_file"
break
else
# Test file exists but not committed yet
if git diff --cached --name-only | grep -q "$test_file"; then
echo "$impl_file -> Test being committed: $test_file"
break
fi
fi
fi
done
if [ "$test_exists" = false ]; then
echo "❌ TDD VIOLATION: No test file found for $impl_file"
echo " Expected one of:"
for pattern in "${test_patterns[@]}"; do
echo " - $pattern"
done
VIOLATIONS=$((VIOLATIONS + 1))
fi
done <<< "$IMPL_FILES"
# Check for test files in commit
TEST_FILES=$(git diff --cached --name-only | grep -E "\.(test|spec)\.(ts|js|tsx|jsx)$")
if [ -n "$TEST_FILES" ]; then
echo ""
echo "📝 Test files in this commit:"
echo "$TEST_FILES" | while IFS= read -r test_file; do
echo "$test_file"
done
fi
echo ""
if [ $VIOLATIONS -gt 0 ]; then
echo "❌ TDD Validation Failed: $VIOLATIONS violation(s) found"
echo ""
echo "TDD requires writing tests BEFORE implementation."
echo ""
echo "To fix:"
echo " 1. Create test file(s) for the implementation"
echo " 2. Commit tests first: git add <test-files> && git commit -m 'Add tests for [feature]'"
echo " 3. Then commit implementation: git add <impl-files> && git commit -m 'Implement [feature]'"
echo ""
echo "Or use: npm run generate:test <impl-file> to create a test template"
echo ""
echo "To bypass this check (not recommended):"
echo " git commit --no-verify"
echo ""
exit 1
fi
echo "✅ TDD Validation Passed: All implementation files have tests"
echo ""
exit 0

View File

@@ -0,0 +1,78 @@
#!/bin/bash
###############################################################################
# TDD Auto-Enforcer Hook
#
# Claude Code hook that automatically guides LLM to follow TDD workflow
# when implementing features.
#
# Installed by: tdd-automation skill
###############################################################################
# Get user's prompt
USER_PROMPT="$1"
# Keywords that indicate implementation work
IMPLEMENTATION_KEYWORDS="implement|add|create|build|feature|write.*code|develop|make.*function|make.*component"
# Check if prompt is about implementing something
if echo "$USER_PROMPT" | grep -qiE "$IMPLEMENTATION_KEYWORDS"; then
cat <<'EOF'
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
⚠️ TDD ENFORCEMENT ACTIVE
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
You are about to implement code. You MUST follow TDD red-green-refactor:
📋 Required Workflow:
1. 🔴 RED Phase (First - DO NOT SKIP)
└─ Write a FAILING test before any implementation
└─ Run test to verify it fails: npm run test:red <test-file>
└─ Use TodoWrite to track this phase
2. 🟢 GREEN Phase (After RED verified)
└─ Write MINIMAL code to make test pass
└─ Run test to verify it passes: npm run test:green <test-file>
└─ Use TodoWrite to track this phase
3. 🔵 REFACTOR Phase (After GREEN verified)
└─ Improve code quality while keeping tests green
└─ Run all tests: npm run test:tdd
└─ Use TodoWrite to track this phase
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🚨 CRITICAL RULES
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
❌ NEVER write implementation code before writing the test
❌ NEVER skip RED phase verification (must see test fail)
❌ NEVER skip GREEN phase verification (must see test pass)
✅ ALWAYS use TodoWrite to track RED-GREEN-REFACTOR phases
✅ ALWAYS run tests to verify each phase transition
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📝 TodoWrite Template (Use This)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Create a todo list with these phases before starting:
[ ] RED: Write failing test for [feature name]
[ ] Verify test fails with expected error
[ ] GREEN: Implement minimal code to pass test
[ ] Verify test passes
[ ] REFACTOR: Improve code quality
[ ] Verify all tests still pass
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Start with RED phase. Create the test file first.
EOF
fi
# Exit successfully (allow prompt to continue)
exit 0

View File

@@ -0,0 +1,163 @@
#!/usr/bin/env node
/**
* Project Type Detector
*
* Detects project type and test framework to provide appropriate TDD configuration.
*/
const fs = require('fs');
const path = require('path');
class ProjectDetector {
constructor(projectRoot) {
this.projectRoot = projectRoot;
this.packageJsonPath = path.join(projectRoot, 'package.json');
}
/**
* Detect project configuration
* @returns {object} Project details
*/
detect() {
const result = {
type: 'unknown',
testFramework: 'unknown',
hasPackageJson: false,
hasGit: false,
hasTypeScript: false,
projectName: path.basename(this.projectRoot),
recommendations: []
};
// Check for package.json
if (fs.existsSync(this.packageJsonPath)) {
result.hasPackageJson = true;
this.analyzePackageJson(result);
}
// Check for git
if (fs.existsSync(path.join(this.projectRoot, '.git'))) {
result.hasGit = true;
} else {
result.recommendations.push('Initialize git repository for version control');
}
// Check for TypeScript
if (fs.existsSync(path.join(this.projectRoot, 'tsconfig.json'))) {
result.hasTypeScript = true;
}
// Detect project type
result.type = this.detectProjectType(result);
// Add recommendations
if (result.testFramework === 'unknown') {
result.recommendations.push('Install a test framework (vitest recommended)');
}
return result;
}
/**
* Analyze package.json for dependencies and scripts
* @param {object} result - Result object to populate
*/
analyzePackageJson(result) {
try {
const packageJson = JSON.parse(fs.readFileSync(this.packageJsonPath, 'utf-8'));
result.projectName = packageJson.name || result.projectName;
const allDeps = {
...packageJson.dependencies,
...packageJson.devDependencies
};
// Detect test framework
if (allDeps['vitest']) {
result.testFramework = 'vitest';
} else if (allDeps['jest']) {
result.testFramework = 'jest';
} else if (allDeps['mocha']) {
result.testFramework = 'mocha';
} else if (allDeps['ava']) {
result.testFramework = 'ava';
}
// Detect framework/type
if (allDeps['react']) {
result.framework = 'react';
} else if (allDeps['vue']) {
result.framework = 'vue';
} else if (allDeps['@angular/core']) {
result.framework = 'angular';
} else if (allDeps['express']) {
result.framework = 'express';
} else if (allDeps['fastify']) {
result.framework = 'fastify';
} else if (allDeps['next']) {
result.framework = 'next';
}
// Check for existing test scripts
result.hasTestScript = packageJson.scripts && packageJson.scripts.test;
} catch (error) {
console.error('Error parsing package.json:', error.message);
}
}
/**
* Determine primary project type
* @param {object} result - Detection results
* @returns {string} Project type
*/
detectProjectType(result) {
if (result.framework === 'react') return 'react';
if (result.framework === 'vue') return 'vue';
if (result.framework === 'angular') return 'angular';
if (result.framework === 'next') return 'nextjs';
if (result.framework === 'express' || result.framework === 'fastify') return 'nodejs-backend';
if (result.hasTypeScript) return 'typescript';
if (result.hasPackageJson) return 'nodejs';
return 'unknown';
}
/**
* Get recommended test command for project
* @returns {string} Test command
*/
getTestCommand() {
const result = this.detect();
switch (result.testFramework) {
case 'vitest':
return 'vitest --run';
case 'jest':
return 'jest';
case 'mocha':
return 'mocha';
case 'ava':
return 'ava';
default:
return 'npm test';
}
}
/**
* Get recommended test file extension
* @returns {string} File extension
*/
getTestExtension() {
const result = this.detect();
if (result.hasTypeScript) {
return '.test.ts';
}
return '.test.js';
}
}
module.exports = ProjectDetector;

View File

@@ -0,0 +1,305 @@
#!/usr/bin/env node
/**
* Hook Installer
*
* Installs git hooks and Claude hooks for TDD enforcement
*/
const fs = require('fs');
const path = require('path');
class HookInstaller {
constructor(projectRoot, skillRoot) {
this.projectRoot = projectRoot;
this.skillRoot = skillRoot;
this.gitHooksDir = path.join(projectRoot, '.git', 'hooks');
this.claudeHooksDir = path.join(projectRoot, '.claude', 'hooks');
this.tddAutomationDir = path.join(projectRoot, '.tdd-automation');
}
/**
* Install all TDD hooks
* @returns {object} Installation result
*/
installAll() {
const results = {
success: true,
installed: [],
failed: [],
skipped: []
};
// Create directories
this.ensureDirectories();
// Copy TDD automation files
this.copyTddAutomationFiles(results);
// Install git pre-commit hook
this.installGitPreCommit(results);
// Install Claude hooks (if Claude hooks directory exists)
if (fs.existsSync(path.dirname(this.claudeHooksDir))) {
this.installClaudeHooks(results);
}
results.success = results.failed.length === 0;
return results;
}
/**
* Ensure required directories exist
*/
ensureDirectories() {
const dirs = [
this.tddAutomationDir,
path.join(this.tddAutomationDir, 'scripts'),
path.join(this.tddAutomationDir, 'templates'),
path.join(this.tddAutomationDir, 'hooks')
];
for (const dir of dirs) {
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
}
// Create Claude hooks directory if .claude exists
if (fs.existsSync(path.join(this.projectRoot, '.claude'))) {
if (!fs.existsSync(this.claudeHooksDir)) {
fs.mkdirSync(this.claudeHooksDir, { recursive: true });
}
}
}
/**
* Copy TDD automation files to project
* @param {object} results - Results object to update
*/
copyTddAutomationFiles(results) {
const scriptsDir = path.join(this.skillRoot, 'scripts');
const templatesDir = path.join(this.skillRoot, 'templates');
const targetScriptsDir = path.join(this.tddAutomationDir, 'scripts');
const targetTemplatesDir = path.join(this.tddAutomationDir, 'templates');
try {
// Copy scripts
if (fs.existsSync(scriptsDir)) {
this.copyDirectory(scriptsDir, targetScriptsDir);
results.installed.push('TDD automation scripts');
}
// Copy templates
if (fs.existsSync(templatesDir)) {
this.copyDirectory(templatesDir, targetTemplatesDir);
results.installed.push('TDD templates');
}
} catch (error) {
results.failed.push(`Copy TDD files: ${error.message}`);
}
}
/**
* Install git pre-commit hook
* @param {object} results - Results object to update
*/
installGitPreCommit(results) {
if (!fs.existsSync(this.gitHooksDir)) {
results.skipped.push('Git pre-commit hook (no .git directory)');
return;
}
const hookPath = path.join(this.gitHooksDir, 'pre-commit');
const templatePath = path.join(this.skillRoot, 'templates', 'pre-commit.sh');
try {
// Check if hook already exists
if (fs.existsSync(hookPath)) {
const existing = fs.readFileSync(hookPath, 'utf-8');
// Check if our TDD hook is already installed
if (existing.includes('TDD_AUTOMATION')) {
results.skipped.push('Git pre-commit hook (already installed)');
return;
}
// Backup existing hook
const backupPath = hookPath + '.backup';
fs.copyFileSync(hookPath, backupPath);
results.installed.push(`Git pre-commit hook backup (${backupPath})`);
// Append our hook to existing
const tddHook = fs.readFileSync(templatePath, 'utf-8');
fs.appendFileSync(hookPath, '\n\n' + tddHook);
results.installed.push('Git pre-commit hook (appended)');
} else {
// Install new hook
fs.copyFileSync(templatePath, hookPath);
fs.chmodSync(hookPath, '755');
results.installed.push('Git pre-commit hook (new)');
}
} catch (error) {
results.failed.push(`Git pre-commit hook: ${error.message}`);
}
}
/**
* Install Claude hooks
* @param {object} results - Results object to update
*/
installClaudeHooks(results) {
const hooksToInstall = [
{
name: 'tdd-auto-enforcer.sh',
description: 'TDD auto-enforcer hook'
}
];
for (const hook of hooksToInstall) {
const sourcePath = path.join(this.skillRoot, 'templates', hook.name);
const targetPath = path.join(this.claudeHooksDir, hook.name);
try {
if (fs.existsSync(targetPath)) {
results.skipped.push(`${hook.description} (already exists)`);
continue;
}
if (!fs.existsSync(sourcePath)) {
results.failed.push(`${hook.description} (template not found)`);
continue;
}
fs.copyFileSync(sourcePath, targetPath);
fs.chmodSync(targetPath, '755');
results.installed.push(hook.description);
} catch (error) {
results.failed.push(`${hook.description}: ${error.message}`);
}
}
}
/**
* Copy directory recursively
* @param {string} src - Source directory
* @param {string} dest - Destination directory
*/
copyDirectory(src, dest) {
if (!fs.existsSync(dest)) {
fs.mkdirSync(dest, { recursive: true });
}
const entries = fs.readdirSync(src, { withFileTypes: true });
for (const entry of entries) {
const srcPath = path.join(src, entry.name);
const destPath = path.join(dest, entry.name);
if (entry.isDirectory()) {
this.copyDirectory(srcPath, destPath);
} else {
fs.copyFileSync(srcPath, destPath);
// Make scripts executable
if (entry.name.endsWith('.sh') || entry.name.endsWith('.js')) {
try {
fs.chmodSync(destPath, '755');
} catch (error) {
// Ignore chmod errors on systems that don't support it
}
}
}
}
}
/**
* Uninstall all TDD hooks
* @returns {object} Uninstallation result
*/
uninstallAll() {
const results = {
success: true,
removed: [],
failed: []
};
// Remove git pre-commit hook (only our section)
this.uninstallGitPreCommit(results);
// Remove Claude hooks
this.uninstallClaudeHooks(results);
// Remove TDD automation directory
if (fs.existsSync(this.tddAutomationDir)) {
try {
fs.rmSync(this.tddAutomationDir, { recursive: true, force: true });
results.removed.push('.tdd-automation directory');
} catch (error) {
results.failed.push(`.tdd-automation directory: ${error.message}`);
}
}
results.success = results.failed.length === 0;
return results;
}
/**
* Uninstall git pre-commit hook
* @param {object} results - Results object to update
*/
uninstallGitPreCommit(results) {
const hookPath = path.join(this.gitHooksDir, 'pre-commit');
if (!fs.existsSync(hookPath)) {
return;
}
try {
const content = fs.readFileSync(hookPath, 'utf-8');
// Check if our hook is installed
if (!content.includes('TDD_AUTOMATION')) {
return;
}
// If the entire file is our hook, remove it
if (content.trim().startsWith('#!/bin/bash') && content.includes('TDD_AUTOMATION')) {
// Check if there's a backup
const backupPath = hookPath + '.backup';
if (fs.existsSync(backupPath)) {
fs.copyFileSync(backupPath, hookPath);
results.removed.push('Git pre-commit hook (restored from backup)');
} else {
fs.unlinkSync(hookPath);
results.removed.push('Git pre-commit hook (removed)');
}
}
} catch (error) {
results.failed.push(`Git pre-commit hook: ${error.message}`);
}
}
/**
* Uninstall Claude hooks
* @param {object} results - Results object to update
*/
uninstallClaudeHooks(results) {
const hooksToRemove = ['tdd-auto-enforcer.sh'];
for (const hookName of hooksToRemove) {
const hookPath = path.join(this.claudeHooksDir, hookName);
if (fs.existsSync(hookPath)) {
try {
fs.unlinkSync(hookPath);
results.removed.push(`Claude hook: ${hookName}`);
} catch (error) {
results.failed.push(`Claude hook ${hookName}: ${error.message}`);
}
}
}
}
}
module.exports = HookInstaller;

View File

@@ -0,0 +1,286 @@
#!/usr/bin/env node
/**
* CLAUDE.md Merger
*
* Safely merges TDD automation configuration into existing CLAUDE.md files
* while preserving all existing content.
*/
const fs = require('fs');
const path = require('path');
class ClaudeMdMerger {
constructor(existingContent = '') {
this.existingContent = existingContent;
this.version = '0.2.0';
}
/**
* Merge TDD section with existing content
* @returns {string} Merged CLAUDE.md content
*/
merge() {
// Strategy: Append TDD section with clear delimiters
// This preserves ALL existing content
const separator = '\n\n' + '='.repeat(80) + '\n\n';
const timestamp = new Date().toISOString();
const mergedContent = [
this.existingContent.trimEnd(),
separator,
'<!-- TDD_AUTOMATION_START -->',
`<!-- Added by tdd-automation skill -->`,
`<!-- Version: ${this.version} -->`,
`<!-- Date: ${timestamp} -->`,
'',
this.buildTddSection(),
'',
'<!-- TDD_AUTOMATION_END -->',
].join('\n');
return mergedContent;
}
/**
* Build the complete TDD automation section
* @returns {string} TDD section content
*/
buildTddSection() {
return `# TDD Red-Green-Refactor Automation (Auto-Installed)
## ⚠️ CRITICAL: TDD is MANDATORY for all feature development
\`\`\`yaml
tdd-automation-version: ${this.version}
tdd-enforcement-level: strict
tdd-phase-tracking: required
\`\`\`
## Development Workflow (STRICTLY ENFORCED)
When implementing ANY new feature or functionality, you MUST follow this sequence:
### Phase 1: RED (Write Failing Test First)
**ALWAYS START HERE. DO NOT SKIP.**
1. **Create or modify the test file BEFORE writing ANY implementation code**
2. Write a test that describes the expected behavior
3. Run the test: \`npm run test:tdd -- <test-file>\`
4. **VERIFY the test fails for the RIGHT reason** (not syntax error, but missing functionality)
5. Use TodoWrite to mark RED phase complete
**DO NOT proceed to implementation until test fails correctly.**
#### RED Phase Checklist:
- [ ] Test file created/modified
- [ ] Test describes expected behavior clearly
- [ ] Test executed and verified to fail
- [ ] Failure reason is correct (missing functionality, not syntax error)
- [ ] RED phase marked in TodoWrite
### Phase 2: GREEN (Minimal Implementation)
**Only after RED phase is verified:**
1. Write ONLY enough code to make the failing test pass
2. No extra features, no "while we're here" additions
3. Keep it simple and focused on passing the test
4. Run test: \`npm run test:tdd -- <test-file>\`
5. **VERIFY the test now passes**
6. Use TodoWrite to mark GREEN phase complete
#### GREEN Phase Checklist:
- [ ] Minimal code written (no extras)
- [ ] Test executed and verified to pass
- [ ] All existing tests still pass
- [ ] GREEN phase marked in TodoWrite
### Phase 3: REFACTOR (Improve Quality)
**Only after GREEN phase is verified:**
1. Improve code structure, naming, and quality
2. Extract duplicated code
3. Simplify complex logic
4. Run full test suite: \`npm run test:tdd\`
5. **VERIFY all tests still pass after refactoring**
6. Use TodoWrite to mark REFACTOR phase complete
#### REFACTOR Phase Checklist:
- [ ] Code structure improved
- [ ] Duplicated code extracted
- [ ] Complex logic simplified
- [ ] All tests still pass
- [ ] REFACTOR phase marked in TodoWrite
## Pre-Implementation TodoWrite Template (ALWAYS Use)
Before writing ANY implementation code, create a todo list with these phases:
\`\`\`markdown
[ ] RED: Write failing test for [feature name]
[ ] Verify test fails with expected error message
[ ] GREEN: Implement minimal code to pass test
[ ] Verify test passes
[ ] REFACTOR: Improve code quality (if needed)
[ ] Verify all tests still pass
\`\`\`
**Example:**
\`\`\`markdown
[ ] RED: Write failing test for user authentication
[ ] Verify test fails with "authenticateUser is not defined"
[ ] GREEN: Implement minimal authenticateUser function
[ ] Verify test passes
[ ] REFACTOR: Extract validation logic to separate function
[ ] Verify all tests still pass
\`\`\`
## Critical Rules (NEVER Violate)
- ❌ **NEVER** write implementation code before writing the test
- ❌ **NEVER** skip the RED phase verification
- ❌ **NEVER** skip the GREEN phase verification
- ❌ **NEVER** skip TodoWrite phase tracking
- ✅ **ALWAYS** run tests to verify RED and GREEN states
- ✅ **ALWAYS** use TodoWrite to track TDD phases
- ✅ **ALWAYS** create test files BEFORE implementation files
- ✅ **ALWAYS** commit tests BEFORE committing implementation
- ✅ **ALWAYS** use semantic test names: \`"should [behavior] when [condition]"\`
## Test Execution Commands
This project has been configured with TDD-optimized test scripts:
\`\`\`bash
# Run all tests once (non-watch mode)
npm run test:tdd
# Run tests in watch mode for development
npm run test:tdd:watch
# Run specific test file (RED/GREEN phase)
npm run test:tdd -- path/to/test.test.ts
# Validate TDD compliance
npm run validate:tdd
\`\`\`
## File Structure Convention
For every implementation file, there must be a corresponding test file:
\`\`\`
src/features/auth/login.ts → Implementation
src/features/auth/login.test.ts → Tests (created FIRST)
src/utils/validation.ts → Implementation
src/utils/validation.test.ts → Tests (created FIRST)
\`\`\`
## Test Naming Convention
Use the pattern: \`"should [behavior] when [condition]"\`
**Good Examples:**
- \`"should return true when email is valid"\`
- \`"should throw error when password is too short"\`
- \`"should update user profile when data is valid"\`
**Bad Examples:**
- \`"test email validation"\` (not descriptive)
- \`"it works"\` (not specific)
- \`"validates email"\` (missing condition)
## Violation Handling
If you accidentally start writing implementation before tests:
1. **STOP immediately**
2. Create the test file first
3. Write the failing test
4. Verify RED state
5. **THEN** proceed with implementation
## TDD Automation Features
This installation includes:
- ✅ **Pre-commit hooks**: Validate tests exist before committing implementation
- ✅ **Test scaffolding**: Generate test file templates with \`npm run generate:test <file>\`
- ✅ **TDD validation**: Check compliance with \`npm run validate:tdd\`
- ✅ **Rollback capability**: Restore previous CLAUDE.md if needed
## Help & Maintenance
### Check TDD Compliance
\`\`\`bash
npm run validate:tdd
\`\`\`
### Generate Test Template
\`\`\`bash
npm run generate:test src/features/auth/login.ts
\`\`\`
### Rollback This Automation
\`\`\`bash
node .tdd-automation/scripts/rollback-tdd.js
\`\`\`
### Remove TDD Section
\`\`\`bash
node .tdd-automation/scripts/remove-tdd-section.js
\`\`\`
### View Backups
\`\`\`bash
ls -lh .claude/CLAUDE.md.backup.*
\`\`\`
## Documentation
- Setup documentation: \`.tdd-automation/README.md\`
- Pre-commit hook: \`.git/hooks/pre-commit\`
- Test templates: \`.tdd-automation/templates/\`
## Success Metrics
Track these metrics to measure TDD adherence:
- Test-first adherence rate: >95%
- RED-GREEN-REFACTOR cycle completion: >90%
- Defect escape rate: <2%
- Test coverage: >80%
---
**Note:** This section was automatically added by the tdd-automation skill.
For support or to report issues, see: .tdd-automation/README.md`;
}
/**
* Create standalone TDD section (for new CLAUDE.md files)
* @returns {string} TDD section content without existing content
*/
static createNew() {
const merger = new ClaudeMdMerger('');
const timestamp = new Date().toISOString();
return [
'<!-- TDD_AUTOMATION_START -->',
`<!-- Added by tdd-automation skill -->`,
`<!-- Version: 0.2.0 -->`,
`<!-- Date: ${timestamp} -->`,
'',
merger.buildTddSection(),
'',
'<!-- TDD_AUTOMATION_END -->',
].join('\n');
}
}
module.exports = ClaudeMdMerger;

View File

@@ -0,0 +1,186 @@
#!/usr/bin/env node
/**
* Package.json Updater
*
* Safely adds TDD-related npm scripts to package.json
*/
const fs = require('fs');
const path = require('path');
class PackageJsonUpdater {
constructor(projectRoot) {
this.projectRoot = projectRoot;
this.packageJsonPath = path.join(projectRoot, 'package.json');
}
/**
* Add TDD scripts to package.json
* @param {string} testCommand - Base test command (e.g., 'vitest --run')
* @returns {object} Update result
*/
addTddScripts(testCommand = 'vitest --run') {
if (!fs.existsSync(this.packageJsonPath)) {
return {
success: false,
reason: 'package.json not found'
};
}
try {
// Create backup
const backupPath = this.packageJsonPath + '.backup';
fs.copyFileSync(this.packageJsonPath, backupPath);
// Read and parse package.json
const packageJson = JSON.parse(fs.readFileSync(this.packageJsonPath, 'utf-8'));
// Ensure scripts object exists
if (!packageJson.scripts) {
packageJson.scripts = {};
}
// Define TDD scripts
const tddScripts = {
'test:tdd': testCommand,
'test:tdd:watch': testCommand.replace('--run', '').trim(),
'test:red': `${testCommand} --reporter=verbose`,
'test:green': `${testCommand} --reporter=verbose`,
'validate:tdd': 'node .tdd-automation/scripts/validate-tdd.js',
'generate:test': 'node .tdd-automation/scripts/generate-test.js'
};
// Track what was added
const added = [];
const skipped = [];
// Add scripts that don't exist
for (const [key, value] of Object.entries(tddScripts)) {
if (!packageJson.scripts[key]) {
packageJson.scripts[key] = value;
added.push(key);
} else {
skipped.push(key);
}
}
// Write updated package.json with pretty formatting
fs.writeFileSync(
this.packageJsonPath,
JSON.stringify(packageJson, null, 2) + '\n',
'utf-8'
);
return {
success: true,
added,
skipped,
backup: backupPath
};
} catch (error) {
return {
success: false,
reason: error.message
};
}
}
/**
* Remove TDD scripts from package.json
* @returns {object} Removal result
*/
removeTddScripts() {
if (!fs.existsSync(this.packageJsonPath)) {
return {
success: false,
reason: 'package.json not found'
};
}
try {
// Create backup
const backupPath = this.packageJsonPath + '.backup';
fs.copyFileSync(this.packageJsonPath, backupPath);
// Read and parse package.json
const packageJson = JSON.parse(fs.readFileSync(this.packageJsonPath, 'utf-8'));
if (!packageJson.scripts) {
return {
success: true,
removed: [],
backup: backupPath
};
}
// Define TDD script keys to remove
const tddScriptKeys = [
'test:tdd',
'test:tdd:watch',
'test:red',
'test:green',
'validate:tdd',
'generate:test'
];
// Track what was removed
const removed = [];
// Remove TDD scripts
for (const key of tddScriptKeys) {
if (packageJson.scripts[key]) {
delete packageJson.scripts[key];
removed.push(key);
}
}
// Write updated package.json
fs.writeFileSync(
this.packageJsonPath,
JSON.stringify(packageJson, null, 2) + '\n',
'utf-8'
);
return {
success: true,
removed,
backup: backupPath
};
} catch (error) {
return {
success: false,
reason: error.message
};
}
}
/**
* Check if TDD scripts are already installed
* @returns {boolean} True if TDD scripts exist
*/
hasTddScripts() {
if (!fs.existsSync(this.packageJsonPath)) {
return false;
}
try {
const packageJson = JSON.parse(fs.readFileSync(this.packageJsonPath, 'utf-8'));
if (!packageJson.scripts) {
return false;
}
// Check if any TDD scripts exist
const tddScriptKeys = ['test:tdd', 'validate:tdd', 'generate:test'];
return tddScriptKeys.some(key => packageJson.scripts[key]);
} catch (error) {
return false;
}
}
}
module.exports = PackageJsonUpdater;

View File

@@ -0,0 +1,267 @@
#!/usr/bin/env node
/**
* CLAUDE.md Validator
*
* Validates and manages CLAUDE.md files with backup and rollback capabilities.
* Ensures safe installation of TDD automation without data loss.
*/
const fs = require('fs');
const path = require('path');
class ClaudeMdValidator {
constructor(projectRoot) {
this.projectRoot = projectRoot;
this.claudeDir = path.join(projectRoot, '.claude');
this.claudeMdPath = path.join(this.claudeDir, 'CLAUDE.md');
}
/**
* Validate current CLAUDE.md state and determine installation strategy
* @returns {object} Validation result with strategy recommendation
*/
validate() {
const result = {
exists: false,
hasExistingContent: false,
hasTddSection: false,
needsBackup: false,
strategy: 'create', // create | merge | skip | abort
warnings: [],
size: 0,
path: this.claudeMdPath
};
// Check if CLAUDE.md exists
if (!fs.existsSync(this.claudeMdPath)) {
result.strategy = 'create';
result.warnings.push('No CLAUDE.md found - will create new file');
return result;
}
result.exists = true;
const content = fs.readFileSync(this.claudeMdPath, 'utf-8');
result.size = content.length;
// Check if file has meaningful content (not just empty or whitespace)
if (content.trim().length > 0) {
result.hasExistingContent = true;
result.needsBackup = true;
}
// Check if TDD automation is already installed
if (this.detectTddSection(content)) {
result.hasTddSection = true;
result.strategy = 'skip';
result.warnings.push('TDD automation already installed in CLAUDE.md');
return result;
}
// Determine merge strategy
if (result.hasExistingContent) {
result.strategy = 'merge';
result.warnings.push(`Existing CLAUDE.md found (${result.size} bytes) - will merge`);
} else {
result.strategy = 'create';
result.warnings.push('Empty CLAUDE.md found - will replace');
}
return result;
}
/**
* Detect if TDD automation section exists in content
* @param {string} content - CLAUDE.md content to check
* @returns {boolean} True if TDD section detected
*/
detectTddSection(content) {
const markers = [
'<!-- TDD_AUTOMATION_START -->',
'TDD Red-Green-Refactor Automation (Auto-Installed)',
'tdd-automation-version:'
];
return markers.some(marker => content.includes(marker));
}
/**
* Create timestamped backup of current CLAUDE.md
* @returns {object} Backup result with success status and path
*/
createBackup() {
if (!fs.existsSync(this.claudeMdPath)) {
return {
success: false,
reason: 'No file to backup',
path: null
};
}
// Ensure .claude directory exists
if (!fs.existsSync(this.claudeDir)) {
fs.mkdirSync(this.claudeDir, { recursive: true });
}
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const backupPath = path.join(
this.claudeDir,
`CLAUDE.md.backup.${timestamp}`
);
try {
fs.copyFileSync(this.claudeMdPath, backupPath);
const originalSize = fs.statSync(this.claudeMdPath).size;
return {
success: true,
path: backupPath,
originalSize,
timestamp
};
} catch (error) {
return {
success: false,
reason: error.message,
path: null
};
}
}
/**
* List all backup files sorted by date (newest first)
* @returns {array} Array of backup file info objects
*/
listBackups() {
if (!fs.existsSync(this.claudeDir)) {
return [];
}
try {
return fs.readdirSync(this.claudeDir)
.filter(f => f.startsWith('CLAUDE.md.backup'))
.map(f => ({
name: f,
path: path.join(this.claudeDir, f),
created: fs.statSync(path.join(this.claudeDir, f)).mtime,
size: fs.statSync(path.join(this.claudeDir, f)).size
}))
.sort((a, b) => b.created - a.created);
} catch (error) {
console.error('Error listing backups:', error.message);
return [];
}
}
/**
* Restore CLAUDE.md from backup file
* @param {string} backupPath - Path to backup file
* @returns {object} Rollback result with success status
*/
rollback(backupPath) {
if (!fs.existsSync(backupPath)) {
return {
success: false,
reason: 'Backup file not found',
path: backupPath
};
}
try {
// Ensure .claude directory exists
if (!fs.existsSync(this.claudeDir)) {
fs.mkdirSync(this.claudeDir, { recursive: true });
}
fs.copyFileSync(backupPath, this.claudeMdPath);
return {
success: true,
restoredFrom: backupPath,
size: fs.statSync(this.claudeMdPath).size
};
} catch (error) {
return {
success: false,
reason: error.message,
path: backupPath
};
}
}
/**
* Remove TDD section from CLAUDE.md
* @returns {object} Removal result with success status
*/
removeTddSection() {
if (!fs.existsSync(this.claudeMdPath)) {
return {
success: false,
reason: 'CLAUDE.md not found'
};
}
const content = fs.readFileSync(this.claudeMdPath, 'utf-8');
// Check if TDD section exists
if (!this.detectTddSection(content)) {
return {
success: false,
reason: 'No TDD section found in CLAUDE.md'
};
}
// Create backup before removal
const backup = this.createBackup();
if (!backup.success) {
return {
success: false,
reason: `Backup failed: ${backup.reason}`
};
}
// Remove TDD section using markers
const startMarker = '<!-- TDD_AUTOMATION_START -->';
const endMarker = '<!-- TDD_AUTOMATION_END -->';
const startIdx = content.indexOf(startMarker);
const endIdx = content.indexOf(endMarker);
if (startIdx === -1 || endIdx === -1) {
return {
success: false,
reason: 'Could not find section markers for clean removal'
};
}
// Remove section and clean up extra whitespace
const beforeSection = content.substring(0, startIdx).trimEnd();
const afterSection = content.substring(endIdx + endMarker.length).trimStart();
const cleanedContent = beforeSection + (afterSection ? '\n\n' + afterSection : '');
try {
fs.writeFileSync(this.claudeMdPath, cleanedContent, 'utf-8');
const originalSize = content.length;
const newSize = cleanedContent.length;
return {
success: true,
backup: backup.path,
originalSize,
newSize,
removed: originalSize - newSize
};
} catch (error) {
// Rollback on error
this.rollback(backup.path);
return {
success: false,
reason: `Write failed: ${error.message}`
};
}
}
}
module.exports = ClaudeMdValidator;