UNPKG

miyabi-agent-sdk

Version:

Miyabi Autonomous Agent SDK - 7 Agents based on Shikigaku Theory with 100% cost reduction mode

273 lines 11.1 kB
/** * ReviewAgent - コード品質判定Agent * * 識学理論適用: * - 責任: 生成されたコードを品質チェック * - 権限: 品質合否判定(80点以上で合格)、改善提案、セキュリティスキャン * - 階層: Specialist Layer * * Phase 8-2: Real API Integration */ import { AnthropicClient } from "../clients/AnthropicClient.js"; import { ClaudeCodeClient } from "../clients/ClaudeCodeClient.js"; /** * ReviewAgent実装 * * 静的解析 → セキュリティスキャン → カバレッジ確認 → スコアリング → 改善提案 */ export class ReviewAgent { anthropicClient; claudeCodeClient; constructor(config) { if (config) { if (config.useClaudeCode) { this.claudeCodeClient = new ClaudeCodeClient(); } else if (config.anthropicApiKey) { this.anthropicClient = new AnthropicClient(config.anthropicApiKey); } } } /** * メイン実行ロジック */ async review(input) { try { const anthropicClient = input.anthropicClient || this.anthropicClient; const claudeCodeClient = input.claudeCodeClient || this.claudeCodeClient; const useRealAPI = input.useRealAPI !== false && !!(anthropicClient || claudeCodeClient); let tokensUsed; let cost; if (useRealAPI && claudeCodeClient) { // Phase 9: Claude Code review const result = await claudeCodeClient.reviewCode(input.files.map(f => ({ path: f.path, content: f.content }))); // Calculate coverage separately (Claude Code doesn't provide it) const coverage = input.standards.requireTests ? await this.checkCoverage(input.files) : { percentage: 0 }; return { success: true, data: { qualityScore: result.qualityScore, passed: result.passed, issues: result.issues.map(issue => ({ severity: issue.severity, file: issue.file || "", line: issue.line, message: issue.message, })), coverage: coverage.percentage, suggestions: result.suggestions, tokensUsed: { input: 0, output: 0 }, cost: 0, // Free! }, }; } else if (useRealAPI && anthropicClient) { // Real Claude API review const result = await anthropicClient.reviewCode(input.files.map(f => ({ path: f.path, content: f.content })), input.standards); tokensUsed = result.tokensUsed; cost = anthropicClient.calculateCost(result.tokensUsed); // Calculate coverage separately (Claude doesn't provide it) const coverage = input.standards.requireTests ? await this.checkCoverage(input.files) : { percentage: 0 }; return { success: true, data: { qualityScore: result.qualityScore, passed: result.passed, issues: result.issues, coverage: coverage.percentage, suggestions: result.suggestions, tokensUsed, cost, }, }; } else { // Mock implementation (fallback) const [staticAnalysis, securityScan, coverage] = await Promise.all([ this.runStaticAnalysis(input.files), input.standards.securityScan ? this.runSecurityScan(input.files) : Promise.resolve({ passed: true, issues: [] }), input.standards.requireTests ? this.checkCoverage(input.files) : Promise.resolve({ percentage: 0 }), ]); const qualityScore = this.calculateQualityScore({ staticAnalysis, securityScan, coverage, }); const passed = qualityScore >= input.standards.minQualityScore; const suggestions = this.generateSuggestions({ staticAnalysis, securityScan, coverage, qualityScore, minQualityScore: input.standards.minQualityScore, }); return { success: true, data: { qualityScore, passed, issues: [...staticAnalysis.issues, ...securityScan.issues], coverage: coverage.percentage, suggestions, tokensUsed: undefined, cost: undefined, }, }; } } catch (error) { return { success: false, error: error instanceof Error ? error.message : "Unknown error occurred", }; } } /** * 静的解析実行(ESLint/Clippy) * * TODO: 実際のLinterを統合 */ async runStaticAnalysis(files) { // Mock implementation const issues = []; // 簡易チェック: ファイルごとに基本的な問題を検出 for (const file of files) { // TODO: 実際にESLint/Clippyを実行 // - TypeScript: ESLint実行 // - Rust: Clippy実行 // - Python: Pylint実行 // Mock: ファイル名に"bad"が含まれる場合にエラー if (file.path.includes("bad")) { issues.push({ severity: "error", file: file.path, line: 1, message: "Mock error: bad pattern detected", }); } // Mock: ファイルサイズが大きい場合に警告 if (file.content.length > 1000) { issues.push({ severity: "warning", file: file.path, message: "File is too large, consider splitting", }); } } return { errorCount: issues.filter((i) => i.severity === "error").length, warningCount: issues.filter((i) => i.severity === "warning").length, issues, }; } /** * セキュリティスキャン実行(Gitleaks) * * TODO: 実際のセキュリティツールを統合 */ async runSecurityScan(files) { // Mock implementation const issues = []; for (const file of files) { // TODO: 実際にGitleaks/Truffleを実行 // - Secrets検出 // - 脆弱性パターン検出 // Mock: "password"などの危険なパターン検出 const dangerousPatterns = [ "password", "api_key", "secret", "token", "private_key", ]; for (const pattern of dangerousPatterns) { if (file.content.toLowerCase().includes(pattern)) { issues.push({ severity: "error", file: file.path, message: `Potential secret detected: ${pattern}`, }); } } } return { passed: issues.length === 0, issues, }; } /** * テストカバレッジ確認 * * TODO: 実際のカバレッジツールを統合 */ async checkCoverage(files) { // Mock implementation // TODO: 実際にvitest/cargo testを実行してカバレッジ取得 const testFiles = files.filter((f) => f.path.includes(".test.")); const sourceFiles = files.filter((f) => !f.path.includes(".test.")); // 簡易計算: テストファイル数 / ソースファイル数 * 100 const percentage = sourceFiles.length > 0 ? Math.min(100, (testFiles.length / sourceFiles.length) * 100) : 0; return { percentage }; } /** * 品質スコア計算 * * スコアリング配分: * - 静的解析: 40点(エラー0件で満点) * - セキュリティ: 30点(問題なしで満点) * - カバレッジ: 30点(80%以上で満点) */ calculateQualityScore(metrics) { // 静的解析スコア(40点満点) const staticScore = Math.max(0, 40 - metrics.staticAnalysis.errorCount * 10 - metrics.staticAnalysis.warningCount * 2); // セキュリティスコア(30点満点) const securityScore = metrics.securityScan.passed ? 30 : 0; // カバレッジスコア(30点満点) // 80%以上で満点、それ以下は線形にスコア減少 const coverageScore = Math.min(30, (metrics.coverage.percentage / 80) * 30); const totalScore = staticScore + securityScore + coverageScore; return Math.round(Math.max(0, Math.min(100, totalScore))); } /** * 改善提案生成 */ generateSuggestions(params) { const suggestions = []; // 静的解析に関する提案 if (params.staticAnalysis.errorCount > 0) { suggestions.push(`Fix ${params.staticAnalysis.errorCount} static analysis error(s)`); } if (params.staticAnalysis.warningCount > 5) { suggestions.push(`Address ${params.staticAnalysis.warningCount} linter warnings`); } // セキュリティに関する提案 if (!params.securityScan.passed) { suggestions.push(`Security scan failed - remove secrets and fix vulnerabilities`); } // カバレッジに関する提案 if (params.coverage.percentage < 80) { suggestions.push(`Increase test coverage from ${params.coverage.percentage.toFixed(1)}% to at least 80%`); } // 全体的な提案 if (params.qualityScore < params.minQualityScore) { const gap = params.minQualityScore - params.qualityScore; suggestions.push(`Quality score is ${gap} points below threshold (${params.qualityScore}/${params.minQualityScore})`); } // 合格している場合の提案 if (suggestions.length === 0) { suggestions.push("Code quality meets all standards - ready for PR"); } return suggestions; } } //# sourceMappingURL=ReviewAgent.js.map