miyabi-agent-sdk
Version:
Miyabi Autonomous Agent SDK - 7 Agents based on Shikigaku Theory with 100% cost reduction mode
273 lines • 11.1 kB
JavaScript
/**
* 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