Files
gh-cskiro-claudex-testing-t…/skills/tdd-automation/index.js
2025-11-29 18:17:04 +08:00

418 lines
14 KiB
JavaScript
Executable File

#!/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;