Files
gh-allex-znews-cc-workflow-…/hooks/post-implementation.js
2025-11-29 17:52:09 +08:00

274 lines
8.6 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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);
}