학습 목표
- AI 기반 자동 코드 리뷰 시스템 구축하기
- 코드 품질 메트릭스 분석과 개선 전략
- 보안 취약점 자동 탐지와 수정
- 리팩토링 제안과 자동화
- 팀 코드 스타일 일관성 유지하기
AI 자동 코드 리뷰 시스템
Cursor AI는 커밋 전 자동으로 코드를 분석하여 잠재적 문제를 찾아내고, 개선 사항을 제안합니다. 이는 코드 품질을 일관되게 유지하고 버그를 조기에 발견하는 데 도움이 됩니다.
자동 코드 리뷰 봇 구현
코드 리뷰 자동화 시스템
// .cursor/code-review-bot.js
import { CursorAI } from '@cursor/api';
import { GitHubAPI } from '@octokit/rest';
import { ESLint } from 'eslint';
import { parse } from '@babel/parser';
import traverse from '@babel/traverse';
class AICodeReviewBot {
constructor(config) {
this.cursor = new CursorAI(config.cursorApiKey);
this.github = new GitHubAPI({ auth: config.githubToken });
this.eslint = new ESLint({
baseConfig: config.eslintConfig,
useEslintrc: true
});
this.reviewRules = {
complexity: {
maxCyclomaticComplexity: 10,
maxCognitiveComplexity: 15,
maxLinesPerFunction: 50,
maxParameters: 4
},
security: {
checkSQLInjection: true,
checkXSS: true,
checkSensitiveData: true,
checkDependencies: true
},
performance: {
checkN1Queries: true,
checkMemoryLeaks: true,
checkAsyncPatterns: true,
checkBundleSize: true
},
maintainability: {
checkDuplication: true,
checkNaming: true,
checkDocumentation: true,
checkTestCoverage: true
}
};
}
async reviewPullRequest(owner, repo, pullNumber) {
const pr = await this.github.pulls.get({
owner,
repo,
pull_number: pullNumber
});
const files = await this.github.pulls.listFiles({
owner,
repo,
pull_number: pullNumber
});
const reviews = [];
for (const file of files.data) {
if (this.shouldReviewFile(file.filename)) {
const review = await this.reviewFile(file);
if (review.issues.length > 0) {
reviews.push(review);
}
}
}
return this.generateReviewReport(reviews);
}
async reviewFile(file) {
const content = await this.getFileContent(file);
const reviews = {
filename: file.filename,
issues: [],
suggestions: [],
metrics: {}
};
// 1. ESLint 검사
const lintResults = await this.runESLint(content, file.filename);
reviews.issues.push(...this.formatLintIssues(lintResults));
// 2. 복잡도 분석
const complexityIssues = await this.analyzeComplexity(content);
reviews.issues.push(...complexityIssues);
// 3. 보안 취약점 검사
const securityIssues = await this.checkSecurity(content);
reviews.issues.push(...securityIssues);
// 4. AI 기반 코드 분석
const aiReview = await this.getAIReview(content, file.filename);
reviews.suggestions.push(...aiReview.suggestions);
// 5. 성능 분석
const performanceIssues = await this.analyzePerformance(content);
reviews.issues.push(...performanceIssues);
// 6. 코드 메트릭스 수집
reviews.metrics = await this.collectMetrics(content);
return reviews;
}
async analyzeComplexity(code) {
const issues = [];
const ast = parse(code, {
sourceType: 'module',
plugins: ['jsx', 'typescript']
});
traverse(ast, {
FunctionDeclaration: (path) => {
const complexity = this.calculateCyclomaticComplexity(path.node);
if (complexity > this.reviewRules.complexity.maxCyclomaticComplexity) {
issues.push({
line: path.node.loc.start.line,
severity: 'warning',
message: `함수 '${path.node.id.name}'의 순환 복잡도가 ${complexity}입니다. (최대: ${this.reviewRules.complexity.maxCyclomaticComplexity})`,
suggestion: '함수를 더 작은 단위로 분리하는 것을 고려해보세요.'
});
}
},
ArrowFunctionExpression: (path) => {
const lineCount = path.node.loc.end.line - path.node.loc.start.line;
if (lineCount > this.reviewRules.complexity.maxLinesPerFunction) {
issues.push({
line: path.node.loc.start.line,
severity: 'warning',
message: `함수가 너무 깁니다 (${lineCount}줄). 최대 ${this.reviewRules.complexity.maxLinesPerFunction}줄을 권장합니다.`,
suggestion: '함수를 더 작은 단위로 분리하세요.'
});
}
}
});
return issues;
}
async checkSecurity(code) {
const issues = [];
// SQL Injection 검사
const sqlPatterns = [
/query\s*\(\s*['"`].*\$\{.*\}.*['"`]\s*\)/gi,
/execute\s*\(\s*['"`].*\+.*['"`]\s*\)/gi
];
sqlPatterns.forEach(pattern => {
const matches = code.matchAll(pattern);
for (const match of matches) {
const line = code.substring(0, match.index).split('\n').length;
issues.push({
line,
severity: 'error',
message: 'SQL Injection 취약점이 발견되었습니다.',
suggestion: 'Prepared statements나 파라미터화된 쿼리를 사용하세요.'
});
}
});
// XSS 검사
const xssPatterns = [
/innerHTML\s*=\s*[^'"`]*\$/gi,
/dangerouslySetInnerHTML/gi
];
xssPatterns.forEach(pattern => {
const matches = code.matchAll(pattern);
for (const match of matches) {
const line = code.substring(0, match.index).split('\n').length;
issues.push({
line,
severity: 'warning',
message: 'XSS 취약점 가능성이 있습니다.',
suggestion: 'textContent를 사용하거나 입력값을 적절히 이스케이프하세요.'
});
}
});
// 민감한 정보 노출 검사
const sensitivePatterns = [
/api[_-]?key\s*[:=]\s*['"`][^'"`]+['"`]/gi,
/password\s*[:=]\s*['"`][^'"`]+['"`]/gi,
/secret\s*[:=]\s*['"`][^'"`]+['"`]/gi
];
sensitivePatterns.forEach(pattern => {
const matches = code.matchAll(pattern);
for (const match of matches) {
const line = code.substring(0, match.index).split('\n').length;
issues.push({
line,
severity: 'error',
message: '하드코딩된 민감한 정보가 발견되었습니다.',
suggestion: '환경 변수나 보안 저장소를 사용하세요.'
});
}
});
return issues;
}
async getAIReview(code, filename) {
const prompt = `
다음 코드를 리뷰하고 개선 사항을 제안해주세요:
파일명: ${filename}
코드:
\`\`\`
${code}
\`\`\`
다음 측면을 중점적으로 검토해주세요:
1. 코드 품질과 가독성
2. 성능 최적화 가능성
3. 잠재적 버그나 엣지 케이스
4. 더 나은 패턴이나 관용구 사용
5. 테스트 가능성과 유지보수성
각 제안사항에 대해 구체적인 코드 예시를 제공해주세요.
`;
const response = await this.cursor.chat.sendMessage(prompt);
return this.parseAIResponse(response);
}
async analyzePerformance(code) {
const issues = [];
// N+1 쿼리 패턴 검사
const n1Patterns = [
/\.map\s*\(.*await.*\)/gi,
/for\s*\(.*\)\s*{[^}]*await[^}]*}/gi
];
n1Patterns.forEach(pattern => {
const matches = code.matchAll(pattern);
for (const match of matches) {
const line = code.substring(0, match.index).split('\n').length;
issues.push({
line,
severity: 'warning',
message: 'N+1 쿼리 패턴이 감지되었습니다.',
suggestion: 'Promise.all()을 사용하거나 쿼리를 배치로 처리하세요.'
});
}
});
// 메모리 누수 가능성 검사
const memoryLeakPatterns = [
/addEventListener[^}]*(?!removeEventListener)/gi,
/setInterval\s*\([^)]*\)/gi
];
memoryLeakPatterns.forEach(pattern => {
const matches = code.matchAll(pattern);
for (const match of matches) {
const line = code.substring(0, match.index).split('\n').length;
issues.push({
line,
severity: 'warning',
message: '메모리 누수 가능성이 있습니다.',
suggestion: '적절한 cleanup 로직을 추가하세요.'
});
}
});
return issues;
}
async collectMetrics(code) {
const lines = code.split('\n');
const ast = parse(code, {
sourceType: 'module',
plugins: ['jsx', 'typescript']
});
let functionCount = 0;
let classCount = 0;
let maxDepth = 0;
let currentDepth = 0;
traverse(ast, {
enter(path) {
currentDepth++;
maxDepth = Math.max(maxDepth, currentDepth);
},
exit() {
currentDepth--;
},
FunctionDeclaration() {
functionCount++;
},
ArrowFunctionExpression() {
functionCount++;
},
ClassDeclaration() {
classCount++;
}
});
return {
lines: lines.length,
functions: functionCount,
classes: classCount,
maxDepth,
commentRatio: this.calculateCommentRatio(code),
duplicateLines: this.findDuplicateLines(lines)
};
}
calculateCommentRatio(code) {
const commentLines = (code.match(/\/\/.*$/gm) || []).length +
(code.match(/\/\*[\s\S]*?\*\//g) || [])
.reduce((sum, comment) => sum + comment.split('\n').length, 0);
const totalLines = code.split('\n').length;
return (commentLines / totalLines * 100).toFixed(2);
}
findDuplicateLines(lines) {
const lineMap = new Map();
let duplicates = 0;
lines.forEach((line, index) => {
const trimmed = line.trim();
if (trimmed && trimmed.length > 10) {
if (lineMap.has(trimmed)) {
duplicates++;
} else {
lineMap.set(trimmed, index);
}
}
});
return duplicates;
}
async generateReviewReport(reviews) {
const totalIssues = reviews.reduce((sum, r) => sum + r.issues.length, 0);
const criticalIssues = reviews.reduce((sum, r) =>
sum + r.issues.filter(i => i.severity === 'error').length, 0);
const report = {
summary: {
totalFiles: reviews.length,
totalIssues,
criticalIssues,
warnings: totalIssues - criticalIssues,
overallScore: this.calculateOverallScore(reviews)
},
fileReviews: reviews,
recommendations: await this.generateRecommendations(reviews)
};
return report;
}
calculateOverallScore(reviews) {
let score = 100;
reviews.forEach(review => {
review.issues.forEach(issue => {
if (issue.severity === 'error') {
score -= 10;
} else if (issue.severity === 'warning') {
score -= 5;
}
});
});
return Math.max(0, score);
}
async generateRecommendations(reviews) {
const recommendations = [];
// 복잡도가 높은 파일들
const complexFiles = reviews.filter(r =>
r.metrics && r.metrics.maxDepth > 5
);
if (complexFiles.length > 0) {
recommendations.push({
type: 'refactoring',
priority: 'high',
message: `${complexFiles.length}개 파일의 복잡도가 높습니다. 리팩토링을 고려하세요.`,
files: complexFiles.map(f => f.filename)
});
}
// 테스트 커버리지가 낮은 파일들
const lowCoverageFiles = reviews.filter(r =>
r.metrics && r.metrics.testCoverage < 80
);
if (lowCoverageFiles.length > 0) {
recommendations.push({
type: 'testing',
priority: 'medium',
message: `${lowCoverageFiles.length}개 파일의 테스트 커버리지가 낮습니다.`,
files: lowCoverageFiles.map(f => f.filename)
});
}
return recommendations;
}
}
// GitHub Actions 통합
export async function runCodeReview() {
const bot = new AICodeReviewBot({
cursorApiKey: process.env.CURSOR_API_KEY,
githubToken: process.env.GITHUB_TOKEN,
eslintConfig: require('./.eslintrc.js')
});
const pullNumber = process.env.GITHUB_PR_NUMBER;
const [owner, repo] = process.env.GITHUB_REPOSITORY.split('/');
const report = await bot.reviewPullRequest(owner, repo, pullNumber);
// GitHub PR에 코멘트 작성
await bot.github.issues.createComment({
owner,
repo,
issue_number: pullNumber,
body: formatReviewComment(report)
});
// 심각한 이슈가 있으면 PR 블록
if (report.summary.criticalIssues > 0) {
process.exit(1);
}
}
function formatReviewComment(report) {
return `## 🤖 AI 코드 리뷰 결과
### 📊 요약
- 검토한 파일: ${report.summary.totalFiles}개
- 발견된 이슈: ${report.summary.totalIssues}개
- 심각한 이슈: ${report.summary.criticalIssues}개
- 경고: ${report.summary.warnings}개
- 전체 점수: ${report.summary.overallScore}/100
### 📋 상세 리뷰
${report.fileReviews.map(formatFileReview).join('\n\n')}
### 💡 권장사항
${report.recommendations.map(formatRecommendation).join('\n')}
`;
}
코드 품질 메트릭스 대시보드
실시간 코드 품질 모니터링
// components/CodeQualityDashboard.tsx
import React, { useState, useEffect } from 'react';
import { Line, Bar, Radar } from 'react-chartjs-2';
import { CursorAI } from '@cursor/api';
interface CodeMetrics {
complexity: number;
maintainability: number;
testCoverage: number;
duplicateCode: number;
technicalDebt: number;
securityScore: number;
}
export function CodeQualityDashboard() {
const [metrics, setMetrics] = useState(null);
const [trend, setTrend] = useState([]);
const [fileAnalysis, setFileAnalysis] = useState([]);
useEffect(() => {
loadMetrics();
const interval = setInterval(loadMetrics, 60000); // 1분마다 업데이트
return () => clearInterval(interval);
}, []);
const loadMetrics = async () => {
const cursor = new CursorAI();
// 프로젝트 전체 분석
const analysis = await cursor.ai.analyzeProject({
includeMetrics: true,
includeSecurity: true,
includePerformance: true
});
setMetrics(analysis.metrics);
setTrend(analysis.trend);
setFileAnalysis(analysis.fileAnalysis);
};
const radarData = {
labels: [
'복잡도',
'유지보수성',
'테스트 커버리지',
'코드 중복',
'기술 부채',
'보안'
],
datasets: [{
label: '현재 프로젝트',
data: metrics ? [
100 - metrics.complexity,
metrics.maintainability,
metrics.testCoverage,
100 - metrics.duplicateCode,
100 - metrics.technicalDebt,
metrics.securityScore
] : [],
backgroundColor: 'rgba(59, 130, 246, 0.2)',
borderColor: 'rgb(59, 130, 246)',
pointBackgroundColor: 'rgb(59, 130, 246)',
pointBorderColor: '#fff',
pointHoverBackgroundColor: '#fff',
pointHoverBorderColor: 'rgb(59, 130, 246)'
}]
};
return (
코드 품질 대시보드
전체 품질 점수
{metrics?.overallScore || 0}
/100
테스트 커버리지
{metrics?.testCoverage || 0}%
보안 점수
{getSecurityLevel(metrics?.securityScore)}
코드 품질 레이더 차트
품질 추세
파일별 분석
파일
복잡도
유지보수성
이슈
액션
{fileAnalysis.map((file, index) => (
5 ? 'high-risk' : ''}>
{file.path}
{file.complexity}
{file.maintainability}
{file.issues}
))}
AI 개선 제안
{suggestions.map((suggestion, index) => (
{suggestion.title}
{suggestion.description}
{suggestion.example}
))}
);
}
보안 취약점 자동 탐지
AI 기반 보안 스캐너
실시간 보안 취약점 탐지 시스템
// security/AISecurityScanner.ts
import { CursorAI } from '@cursor/api';
import { DependencyCheck } from 'dependency-check';
import { Snyk } from '@snyk/sdk';
import * as semver from 'semver';
export class AISecurityScanner {
private cursor: CursorAI;
private snyk: Snyk;
private vulnerabilityDB: Map;
constructor() {
this.cursor = new CursorAI();
this.snyk = new Snyk({ token: process.env.SNYK_TOKEN });
this.vulnerabilityDB = new Map();
this.loadVulnerabilityDatabase();
}
async scanProject(projectPath: string): Promise {
const report: SecurityReport = {
vulnerabilities: [],
dependencies: [],
codeIssues: [],
recommendations: [],
overallRisk: 'low'
};
// 1. 의존성 취약점 스캔
const depVulns = await this.scanDependencies(projectPath);
report.dependencies = depVulns;
// 2. 코드 보안 취약점 스캔
const codeVulns = await this.scanCode(projectPath);
report.codeIssues = codeVulns;
// 3. AI 기반 고급 분석
const aiAnalysis = await this.performAIAnalysis(projectPath);
report.vulnerabilities.push(...aiAnalysis.vulnerabilities);
// 4. 위험도 평가
report.overallRisk = this.assessRisk(report);
// 5. 개선 권장사항 생성
report.recommendations = await this.generateRecommendations(report);
return report;
}
async scanDependencies(projectPath: string): Promise {
const packageJson = await this.readPackageJson(projectPath);
const vulnerabilities: DependencyVulnerability[] = [];
for (const [pkg, version] of Object.entries(packageJson.dependencies || {})) {
// Snyk DB 검사
const snykVulns = await this.snyk.test(pkg, version as string);
// 자체 DB 검사
const knownVulns = this.vulnerabilityDB.get(pkg) || [];
for (const vuln of [...snykVulns, ...knownVulns]) {
if (semver.satisfies(version as string, vuln.affectedVersions)) {
vulnerabilities.push({
package: pkg,
version: version as string,
vulnerability: vuln,
severity: vuln.severity,
fixedIn: vuln.fixedIn,
recommendation: `${pkg}를 ${vuln.fixedIn} 이상으로 업데이트하세요.`
});
}
}
}
return vulnerabilities;
}
async scanCode(projectPath: string): Promise {
const vulnerabilities: CodeVulnerability[] = [];
const files = await this.getAllSourceFiles(projectPath);
for (const file of files) {
const content = await fs.readFile(file, 'utf-8');
// OWASP Top 10 체크
vulnerabilities.push(...await this.checkOWASPTop10(content, file));
// 민감한 정보 노출 체크
vulnerabilities.push(...await this.checkSensitiveData(content, file));
// 암호화 취약점 체크
vulnerabilities.push(...await this.checkCryptography(content, file));
}
return vulnerabilities;
}
async checkOWASPTop10(code: string, filePath: string): Promise {
const vulnerabilities: CodeVulnerability[] = [];
// A01:2021 – Broken Access Control
const accessControlPatterns = [
{
pattern: /router\.(get|post|put|delete)\s*\([^,]+,\s*(?!authenticate)/gi,
message: '인증 미들웨어가 누락되었을 수 있습니다.',
severity: 'high' as const
},
{
pattern: /req\.user\.\w+\s*===?\s*['"`]admin['"`]/gi,
message: '하드코딩된 권한 체크가 발견되었습니다.',
severity: 'medium' as const
}
];
// A02:2021 – Cryptographic Failures
const cryptoPatterns = [
{
pattern: /crypto\.createHash\(['"`]md5['"`]\)/gi,
message: 'MD5는 안전하지 않습니다. SHA-256 이상을 사용하세요.',
severity: 'high' as const
},
{
pattern: /Math\.random\(\)/gi,
message: '암호화 목적으로 Math.random()을 사용하지 마세요.',
severity: 'high' as const
}
];
// A03:2021 – Injection
const injectionPatterns = [
{
pattern: /exec\s*\([^)]*\$\{[^}]+\}[^)]*\)/gi,
message: '명령어 주입 취약점이 발견되었습니다.',
severity: 'critical' as const
},
{
pattern: /new Function\s*\([^)]*\$\{[^}]+\}[^)]*\)/gi,
message: '코드 주입 취약점이 발견되었습니다.',
severity: 'critical' as const
}
];
const allPatterns = [
...accessControlPatterns,
...cryptoPatterns,
...injectionPatterns
];
for (const { pattern, message, severity } of allPatterns) {
const matches = code.matchAll(pattern);
for (const match of matches) {
const line = code.substring(0, match.index).split('\n').length;
vulnerabilities.push({
type: 'OWASP',
severity,
file: filePath,
line,
message,
code: match[0],
recommendation: await this.getAIRecommendation(match[0], message)
});
}
}
return vulnerabilities;
}
async performAIAnalysis(projectPath: string): Promise {
const prompt = `
프로젝트 경로: ${projectPath}
다음 보안 측면을 분석해주세요:
1. 인증 및 권한 부여 구현
2. 데이터 검증 및 살균
3. 에러 처리 및 로깅
4. 세션 관리
5. API 보안
6. 파일 업로드 보안
7. 써드파티 통합 보안
각 취약점에 대해 다음을 포함해주세요:
- 심각도 (critical/high/medium/low)
- 구체적인 위치
- 수정 방법
- 예시 코드
`;
const response = await this.cursor.ai.analyzeCode(prompt);
return this.parseAISecurityAnalysis(response);
}
async generateRecommendations(report: SecurityReport): Promise {
const recommendations: SecurityRecommendation[] = [];
// 심각한 취약점에 대한 즉시 조치 사항
const criticalVulns = report.vulnerabilities.filter(v => v.severity === 'critical');
if (criticalVulns.length > 0) {
recommendations.push({
priority: 'immediate',
title: '심각한 보안 취약점 발견',
description: `${criticalVulns.length}개의 심각한 취약점이 발견되었습니다. 즉시 수정이 필요합니다.`,
actions: criticalVulns.map(v => ({
file: v.file,
line: v.line,
fix: v.recommendation
}))
});
}
// 의존성 업데이트 권장
const outdatedDeps = report.dependencies.filter(d => d.fixedIn);
if (outdatedDeps.length > 0) {
recommendations.push({
priority: 'high',
title: '의존성 업데이트 필요',
description: `${outdatedDeps.length}개의 취약한 의존성이 발견되었습니다.`,
actions: outdatedDeps.map(d => ({
package: d.package,
current: d.version,
recommended: d.fixedIn,
command: `npm update ${d.package}@${d.fixedIn}`
}))
});
}
// 보안 헤더 설정
recommendations.push({
priority: 'medium',
title: '보안 헤더 구성',
description: 'HTTP 보안 헤더를 설정하여 일반적인 공격을 방어하세요.',
actions: [{
code: `// Express.js 보안 헤더 설정
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
scriptSrc: ["'self'"],
imgSrc: ["'self'", "data:", "https:"],
},
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true
}
}));`
}]
});
return recommendations;
}
assessRisk(report: SecurityReport): RiskLevel {
const criticalCount = report.vulnerabilities.filter(v => v.severity === 'critical').length;
const highCount = report.vulnerabilities.filter(v => v.severity === 'high').length;
if (criticalCount > 0) return 'critical';
if (highCount > 5) return 'high';
if (highCount > 0) return 'medium';
return 'low';
}
}
// GitHub Actions 워크플로우
export async function runSecurityScan() {
const scanner = new AISecurityScanner();
const report = await scanner.scanProject(process.cwd());
// 보고서 생성
await generateSecurityReport(report);
// 심각한 취약점이 있으면 빌드 실패
if (report.overallRisk === 'critical' || report.overallRisk === 'high') {
console.error('🚨 심각한 보안 취약점이 발견되었습니다!');
process.exit(1);
}
}
실습: AI 코드 리뷰 시스템 구축
과제: 팀을 위한 자동 코드 리뷰 봇 만들기
다음 요구사항을 만족하는 코드 리뷰 자동화 시스템을 구축하세요:
요구사항:
- Pull Request 생성 시 자동으로 코드 리뷰 실행
- 코딩 스타일, 복잡도, 보안 취약점 검사
- AI 기반 개선 제안 생성
- 리뷰 결과를 PR 코멘트로 자동 작성
- 심각한 이슈 발견 시 PR 머지 차단
힌트:
- GitHub Actions와 Cursor API를 조합하여 사용
- ESLint, TSLint 등의 정적 분석 도구 통합
- 코드 복잡도는 AST 분석을 통해 계산
- 보안 패턴은 정규식과 AST 패턴 매칭 활용
핵심 요약
자동 코드 리뷰
- AI 기반 코드 분석
- 실시간 품질 모니터링
- PR 통합 자동화
품질 메트릭스
- 복잡도 측정
- 유지보수성 평가
- 테스트 커버리지
보안 스캐닝
- 취약점 자동 탐지
- 의존성 검사
- OWASP 준수 체크
개선 자동화
- 리팩토링 제안
- 자동 수정 적용
- 지속적 개선