github-mcp-auto-git
Version:
GitHub MCP Auto Git v3.0 - メモリ効率化・統合MCP・モジュール化完了の完全自動Git操作システム
372 lines • 15 kB
JavaScript
/**
* Independent SubAgent Implementation (非Claude Code依存)
* 実際のAI分析機能を提供する独立したサブエージェント実装
*/
import { promises as fs } from 'fs';
import { join } from 'path';
// 危険なパターンの定数定義
const DANGEROUS_PATTERNS = {
// 機密情報パターン
secrets: [
/(?:password|pwd|secret|key|token|auth|credential)\s*[:=]\s*['"][^'"]+['"]/gi,
/(?:api_key|access_key|secret_key|private_key)\s*[:=]\s*['"][^'"]+['"]/gi,
/(?:mongodb|mysql|postgres):\/\/\w+:\w+@[\w.-]+/gi,
/sk-[a-zA-Z0-9]{48,}/gi, // OpenAI API key pattern
/ghp_[a-zA-Z0-9]{36}/gi, // GitHub personal access token
/glpat-[a-zA-Z0-9_-]{20}/gi // GitLab personal access token
],
// 破壊的操作パターン
destructive: [
/rm\s+-rf\s+\//gi,
/DROP\s+(?:TABLE|DATABASE)\s+/gi,
/DELETE\s+FROM\s+\w+(?:\s+WHERE\s+1\s*=\s*1)?/gi,
/\.delete\(\)\s*\.exec\(\)/gi,
/process\.exit\(\d*\)/gi
],
// 設定ファイルパターン
config: [
/\.env$/,
/config\.json$/,
/secrets\.yaml$/,
/credentials\./,
/\.pem$/,
/\.key$/
]
};
// コミットメッセージの規則
const COMMIT_PATTERNS = {
feature: /(?:add|create|implement|introduce)/i,
fix: /(?:fix|resolve|repair|correct|patch)/i,
update: /(?:update|modify|change|improve|enhance)/i,
refactor: /(?:refactor|restructure|reorganize)/i,
docs: /(?:doc|documentation|readme|comment)/i,
test: /(?:test|spec|testing)/i,
style: /(?:style|format|lint|prettier)/i,
chore: /(?:chore|build|ci|deps|dependency)/i
};
export class IndependentSubAgents {
/**
* Git Safety Analyzer - 独立実装
*/
async analyzeSafety(files, workingDir) {
const risks = [];
const recommendations = [];
let totalScore = 100;
for (const file of files) {
try {
const filePath = join(workingDir, file);
const content = await fs.readFile(filePath, 'utf-8');
// 機密情報チェック
for (const pattern of DANGEROUS_PATTERNS.secrets) {
if (pattern.test(content)) {
risks.push({
type: 'secret_detected',
severity: 'critical',
description: `機密情報パターンが検出されました: ${file}`
});
totalScore -= 40;
recommendations.push(`${file} から機密情報を削除してください`);
}
}
// 破壊的操作チェック
for (const pattern of DANGEROUS_PATTERNS.destructive) {
if (pattern.test(content)) {
risks.push({
type: 'destructive_operation',
severity: 'high',
description: `破壊的操作が検出されました: ${file}`
});
totalScore -= 25;
recommendations.push(`${file} の破壊的操作を確認してください`);
}
}
// 大きなファイルサイズチェック
const stats = await fs.stat(filePath);
if (stats.size > 1024 * 1024) { // 1MB以上
risks.push({
type: 'large_file',
severity: 'medium',
description: `大きなファイルが検出されました: ${file} (${Math.round(stats.size / 1024)}KB)`
});
totalScore -= 10;
recommendations.push(`${file} のファイルサイズを確認してください`);
}
// 設定ファイルチェック
if (DANGEROUS_PATTERNS.config.some(pattern => pattern.test(file))) {
risks.push({
type: 'config_file',
severity: 'medium',
description: `設定ファイルが含まれています: ${file}`
});
totalScore -= 15;
recommendations.push(`${file} に機密情報が含まれていないか確認してください`);
}
}
catch (error) {
// ファイル読み取りエラーは警告として扱う
risks.push({
type: 'file_access_error',
severity: 'low',
description: `ファイルアクセスエラー: ${file}`
});
totalScore -= 5;
}
}
// スコア調整
const safetyScore = Math.max(0, Math.min(100, totalScore));
// レベル判定
let level;
if (safetyScore >= 80)
level = 'SAFE';
else if (safetyScore >= 60)
level = 'WARNING';
else
level = 'DANGER';
// 自動承認判定
const autoApprove = level === 'SAFE' && risks.every(risk => risk.severity !== 'critical');
return {
safetyScore,
level,
risks,
recommendations,
autoApprove,
confidence: 0.9 // 独立実装での信頼度
};
}
/**
* Commit Message Generator - 独立実装
*/
async generateCommitMessage(analysis, files) {
const { type, impact, description } = analysis;
// コミットタイプをConventional Commitsに変換
const conventionalType = this.mapToConventionalType(type, files);
// 影響度に基づくスコープ判定
const scope = this.determineScope(files, impact);
// 日本語メッセージ生成
const title = this.generateJapaneseTitle(type, description, files.length);
const body = this.generateJapaneseBody(analysis, files);
// Conventional Commits形式
const conventional = scope
? `${conventionalType}(${scope}): ${this.generateEnglishDescription(type, files)}`
: `${conventionalType}: ${this.generateEnglishDescription(type, files)}`;
return {
title,
body,
conventional,
confidence: 0.85
};
}
/**
* PR Management Agent - 独立実装
*/
async generatePRManagement(analysis, files, commitMessage) {
const { type, impact } = analysis;
// PR自動マージ判定
const autoMerge = this.shouldAutoMerge(type, impact, files);
// マージ戦略決定
const mergeStrategy = this.determineMergeStrategy(type, files.length);
// レビュワー推薦
const reviewers = this.recommendReviewers(type, files);
// ラベル生成
const labels = this.generateLabels(type, impact);
// PR説明文生成
const prBody = this.generatePRBody(analysis, files, commitMessage);
return {
prTitle: commitMessage.split('\n')[0] || 'Update files', // タイトル行を使用
prBody,
autoMerge,
mergeStrategy,
reviewers,
labels,
assignees: [], // 基本的には空
deleteBranch: true, // 通常はブランチ削除
reasoning: this.generateReasoning(autoMerge, type, impact)
};
}
// Private helper methods
mapToConventionalType(type, files) {
// ファイル名からもヒントを得る
const fileExtensions = files.map(f => f.split('.').pop() || '').join(' ');
const hasTests = files.some(f => f.includes('test') || f.includes('spec'));
const hasDocs = files.some(f => f.includes('README') || f.includes('.md'));
if (hasTests)
return 'test';
if (hasDocs)
return 'docs';
switch (type) {
case 'feature': return 'feat';
case 'bugfix': return 'fix';
case 'refactor': return 'refactor';
case 'docs': return 'docs';
case 'style': return 'style';
case 'test': return 'test';
case 'chore': return 'chore';
default: return 'chore';
}
}
determineScope(files, impact) {
// ディレクトリベースのスコープ判定
const directories = files.map(f => f.split('/')[0]).filter(Boolean);
const uniqueDirs = [...new Set(directories)];
if (uniqueDirs.length === 1 && uniqueDirs[0]) {
return uniqueDirs[0];
}
// 複数ディレクトリの場合は影響度で判定
if (impact === 'high')
return 'core';
if (impact === 'medium')
return 'feature';
return null;
}
generateJapaneseTitle(type, description, fileCount) {
const fileText = fileCount === 1 ? 'ファイル' : `${fileCount}個のファイル`;
switch (type) {
case 'feature':
return `新機能: ${description}`;
case 'bugfix':
return `修正: ${description}`;
case 'refactor':
return `リファクタリング: ${fileText}を整理`;
case 'docs':
return `ドキュメント: ${description}`;
case 'test':
return `テスト: ${fileText}のテストを追加・更新`;
case 'style':
return `スタイル: ${fileText}のフォーマットを調整`;
default:
return `更新: ${fileText}を変更`;
}
}
generateJapaneseBody(analysis, files) {
const { metrics, impact } = analysis;
let body = `## 変更内容\n\n`;
body += `- 変更ファイル数: ${files.length}\n`;
body += `- 追加行数: ${metrics?.linesAdded || 0}\n`;
body += `- 削除行数: ${metrics?.linesDeleted || 0}\n`;
body += `- 影響度: ${impact}\n\n`;
body += `## 変更ファイル\n\n`;
files.forEach(file => {
body += `- \`${file}\`\n`;
});
return body;
}
generateEnglishDescription(type, files) {
const fileCount = files.length;
const mainFile = files[0];
switch (type) {
case 'feature':
return fileCount === 1 ? `add new feature in ${mainFile}` : `implement new features across ${fileCount} files`;
case 'bugfix':
return fileCount === 1 ? `fix issue in ${mainFile}` : `resolve issues in ${fileCount} files`;
case 'refactor':
return fileCount === 1 ? `refactor ${mainFile}` : `restructure ${fileCount} files`;
case 'docs':
return fileCount === 1 ? `update documentation in ${mainFile}` : `improve documentation`;
case 'test':
return fileCount === 1 ? `add tests for ${mainFile}` : `enhance test coverage`;
default:
return fileCount === 1 ? `update ${mainFile}` : `modify ${fileCount} files`;
}
}
shouldAutoMerge(type, impact, files) {
// 危険度の高い変更は自動マージしない
if (impact === 'high')
return false;
if (files.length > 10)
return false;
// 安全な変更のみ自動マージ
const safeTypes = ['docs', 'style', 'test'];
if (safeTypes.includes(type) && impact === 'low')
return true;
return false;
}
determineMergeStrategy(type, fileCount) {
// 小さな変更はsquash
if (fileCount <= 3)
return 'squash';
// 機能追加は通常のmerge
if (type === 'feature')
return 'merge';
// その他はsquash
return 'squash';
}
recommendReviewers(type, files) {
// 実際の環境では設定ファイルから読み込む
const reviewers = [];
// 重要な変更には必ずレビュワーを設定
if (files.some(f => f.includes('core') || f.includes('main'))) {
reviewers.push('tech-lead');
}
// セキュリティ関連
if (files.some(f => f.includes('auth') || f.includes('security'))) {
reviewers.push('security-team');
}
return reviewers;
}
generateLabels(type, impact) {
const labels = [];
// タイプベースラベル
switch (type) {
case 'feature':
labels.push('enhancement');
break;
case 'bugfix':
labels.push('bug');
break;
case 'docs':
labels.push('documentation');
break;
case 'test':
labels.push('testing');
break;
}
// 影響度ラベル
switch (impact) {
case 'high':
labels.push('high-impact');
break;
case 'medium':
labels.push('medium-impact');
break;
case 'low':
labels.push('low-impact');
break;
}
return labels;
}
generatePRBody(analysis, files, commitMessage) {
const { type, impact, metrics } = analysis;
let body = `## 概要\n\n${analysis.description}\n\n`;
body += `## 変更詳細\n\n`;
body += `- **タイプ**: ${type}\n`;
body += `- **影響度**: ${impact}\n`;
body += `- **ファイル数**: ${files.length}\n`;
body += `- **追加行**: ${metrics?.linesAdded || 0}\n`;
body += `- **削除行**: ${metrics?.linesDeleted || 0}\n\n`;
body += `## チェックリスト\n\n`;
body += `- [ ] コードレビュー完了\n`;
body += `- [ ] テスト実行確認\n`;
body += `- [ ] ドキュメント更新確認\n`;
if (impact === 'high') {
body += `- [ ] セキュリティレビュー完了\n`;
body += `- [ ] パフォーマンステスト実行\n`;
}
return body;
}
generateReasoning(autoMerge, type, impact) {
if (autoMerge) {
return `安全な変更 (${type}, ${impact}影響) のため自動マージ対象です`;
}
else {
const reasons = [];
if (impact === 'high')
reasons.push('高影響度の変更');
if (type === 'feature')
reasons.push('新機能追加');
if (type === 'bugfix')
reasons.push('バグ修正');
return `${reasons.join(', ')}のためレビューが必要です`;
}
}
}
//# sourceMappingURL=independent-subagents.js.map