#!/usr/bin/env node /** * 実装後のチェックフック * * このフックは /implement コマンド実行後に自動的に実行され、 * テストの成功を確認します。 */ const fs = require('fs'); const path = require('path'); const { execSync } = require('child_process'); // テストの実行 function runTests() { console.log('🧪 テストを実行中...\n'); const commands = [ { check: 'package.json', command: 'npm test', name: 'npm test' }, { check: 'pytest.ini', command: 'pytest', name: 'pytest' }, { check: 'go.mod', command: 'go test ./...', name: 'go test' }, { check: 'pom.xml', command: 'mvn test', name: 'mvn test' }, { check: 'Gemfile', command: 'bundle exec rspec', name: 'rspec' }, ]; for (const { check, command, name } of commands) { if (fs.existsSync(check)) { try { console.log(`実行中: ${name}\n`); const output = execSync(command, { encoding: 'utf-8', stdio: 'pipe', maxBuffer: 10 * 1024 * 1024, // 10MB }); console.log(output); return { success: true, output, command: name }; } catch (error) { console.error(`❌ テストが失敗しました\n`); console.error(error.stdout || error.message); return { success: false, output: error.stdout || error.message, command: name, error: error.message, }; } } } console.log('⚠️ テスト実行コマンドが見つかりません'); return { success: false, output: 'テストコマンドが見つかりません' }; } // カバレッジの確認 function checkCoverage() { console.log('\n📊 コードカバレッジを確認中...\n'); const commands = [ { check: 'package.json', command: 'npm test -- --coverage --silent', name: 'Jest coverage', }, { check: 'pytest.ini', command: 'pytest --cov=. --cov-report=term-missing', name: 'pytest coverage', }, { check: 'go.mod', command: 'go test -cover ./...', name: 'Go coverage', }, ]; for (const { check, command, name } of commands) { if (fs.existsSync(check)) { try { console.log(`実行中: ${name}\n`); const output = execSync(command, { encoding: 'utf-8', stdio: 'pipe', maxBuffer: 10 * 1024 * 1024, }); console.log(output); // カバレッジ率の抽出(簡易的) const coverageMatch = output.match(/(\d+\.?\d*)%/); if (coverageMatch) { const coverage = parseFloat(coverageMatch[1]); return { success: true, coverage, output, sufficient: coverage >= 80, }; } return { success: true, coverage: null, output }; } catch (error) { console.error('⚠️ カバレッジの取得に失敗しました'); console.error(error.stdout || error.message); return { success: false, error: error.message }; } } } console.log('⚠️ カバレッジコマンドが見つかりません'); return { success: false }; } // メイン処理 function main() { console.log('✨ 実装のテストを実行中...\n'); console.log('=' .repeat(50)); // テストの実行 const testResult = runTests(); if (!testResult.success) { console.error('\n❌ 実装が完了していません'); console.error(' テストを通すまで実装を続けてください\n'); console.error('TDDのフロー:'); console.error(' 1. テストが失敗している(Red)'); console.error(' 2. 実装してテストを通す(Green) ← 今ここ'); console.error(' 3. リファクタリング'); process.exit(1); } console.log('\n✅ すべてのテストが成功しました!'); // ステップ3: カバレッジの確認 console.log('\n' + '='.repeat(50)); const coverageResult = checkCoverage(); if (coverageResult.success && coverageResult.coverage !== null) { if (coverageResult.sufficient) { console.log(`\n✅ カバレッジ: ${coverageResult.coverage}% (目標: 80%以上)`); } else { console.log(`\n⚠️ カバレッジ: ${coverageResult.coverage}% (目標: 80%以上)`); console.log(' テストを追加してカバレッジを向上させることを推奨'); } } // 最終サマリー console.log('\n' + '='.repeat(50)); console.log('\n✅ 実装フェーズが完了しました\n'); console.log('✅ すべてのテストが成功'); if (coverageResult.coverage) { console.log(`✅ コードカバレッジ: ${coverageResult.coverage}%`); } console.log('\n次のステップ:'); console.log(' 1. コードレビューを依頼'); console.log(' 2. ドキュメントを更新'); console.log(' 3. コミット & プッシュ'); console.log(' 4. プルリクエストを作成(該当する場合)'); // タスク状態の自動更新 updateTaskStatus(4, 'completed'); } // タスク状態を更新する関数 function updateTaskStatus(taskId, status) { const tasksFile = '.tasks.json'; const historyFile = '.tasks-history.json'; if (!fs.existsSync(tasksFile)) { console.log('\n⚠️ タスクファイルが見つかりません。タスク状態は更新されませんでした。'); return; } try { const data = JSON.parse(fs.readFileSync(tasksFile, 'utf-8')); const taskIndex = data.tasks.findIndex(t => t.id === taskId); if (taskIndex === -1) { console.log(`\n⚠️ タスク #${taskId} が見つかりません。`); return; } const task = data.tasks[taskIndex]; task.status = status; task.completedAt = new Date().toISOString(); console.log(`\n📋 タスク #${taskId} (${task.name}) を完了としてマークしました`); // 完了したタスクを履歴に移動 if (status === 'completed') { moveTaskToHistory(data.feature, task, historyFile); // アクティブタスクから削除 data.tasks.splice(taskIndex, 1); data.updatedAt = new Date().toISOString(); } // .tasks.json を更新(完了したタスクは除外される) fs.writeFileSync(tasksFile, JSON.stringify(data, null, 2)); // 進捗の計算(履歴も含めて計算) const history = loadHistory(historyFile); const currentFeature = history.features.find(f => f.feature === data.feature && !f.completedAt); const completedCount = currentFeature ? currentFeature.tasks.length : 0; const activeCount = data.tasks.length; const totalTasks = completedCount + activeCount; if (totalTasks > 0) { const progress = Math.round((completedCount / totalTasks) * 100); console.log(`\n📊 全体の進捗: ${completedCount}/${totalTasks} (${progress}%)`); } // すべてのタスクが完了した場合 if (data.tasks.length === 0 && currentFeature) { currentFeature.completedAt = new Date().toISOString(); fs.writeFileSync(historyFile, JSON.stringify(history, null, 2)); fs.unlinkSync(tasksFile); console.log('\n✅ すべてのタスクが完了しました!機能開発が完了しました。'); } } catch (error) { console.log(`\n⚠️ タスク状態の更新に失敗しました: ${error.message}`); } } // 履歴ファイルの読み込み function loadHistory(historyFile) { if (!fs.existsSync(historyFile)) { return { features: [] }; } return JSON.parse(fs.readFileSync(historyFile, 'utf-8')); } // タスクを履歴に移動 function moveTaskToHistory(featureName, task, historyFile) { const history = loadHistory(historyFile); // 現在の機能を検索(まだ完了していないもの) let currentFeature = history.features.find(f => f.feature === featureName && !f.completedAt); if (!currentFeature) { // 新しい機能エントリを作成 currentFeature = { feature: featureName, createdAt: new Date().toISOString(), tasks: [] }; history.features.push(currentFeature); } // タスクを履歴に追加 currentFeature.tasks.push({ id: task.id, type: task.type, name: task.name, status: task.status, command: task.command, parent: task.parent, dependencies: task.dependencies, createdAt: task.createdAt, startedAt: task.startedAt, completedAt: task.completedAt }); // 履歴ファイルに書き込み fs.writeFileSync(historyFile, JSON.stringify(history, null, 2)); } // エラーハンドリング try { main(); } catch (error) { console.error('❌ チェック中にエラーが発生しました:', error.message); console.error(error.stack); process.exit(1); }