impact-analyzer
Impact Analyzer Skill v1.0
변경 영향도 분석기 - 코드 수정 전 영향 범위를 정확히 파악하여 안전한 변경 보장
핵심 컨셉
Problem:
Without_Analysis: "함수 수정 → 예상치 못한 곳에서 에러 → 롤백"
Reality: "한 줄 변경이 100개 파일에 영향을 줄 수 있음"
Solution:
Impact_Analysis:
- "변경 대상의 역의존성 트리 추적"
- "직접/간접 영향 범위 계산"
- "위험도 점수 산출"
- "테스트 범위 자동 제안"
Key_Metrics:
Direct_Impact: "직접 호출하는 함수들"
Indirect_Impact: "연쇄적으로 영향받는 범위"
Risk_Score: "변경 위험도 (0-100)"
Test_Coverage: "영향 범위 대비 테스트 커버리지"
자동 발동 조건
Auto_Trigger_Conditions:
Before_Edit:
- "Edit 도구 사용 전 자동 분석 제안"
- "공유 코드(shared, utils) 수정 시"
- "핵심 모듈(auth, api) 수정 시"
Keywords_KO:
- "영향도 분석, 영향 범위"
- "이거 바꾸면 어디 영향?"
- "변경 영향, 수정 영향"
- "리팩토링 영향, 리팩토링 범위"
- "위험도, 얼마나 위험"
- "테스트 범위, 테스트해야 할 곳"
Keywords_EN:
- "impact analysis, impact scope"
- "what will this affect"
- "change impact, modification impact"
- "refactoring scope, refactoring impact"
- "risk assessment, risk score"
- "test scope, what to test"
High_Risk_Patterns:
- "export 변경/삭제"
- "함수 시그니처 변경"
- "타입 정의 변경"
- "API 엔드포인트 변경"
- "shared 모듈 변경"
영향도 분석 알고리즘
interface ImpactAnalysisRequest {
target: string; // 변경 대상 노드 ID
changeType: ChangeType; // 변경 유형
depth?: number; // 분석 깊이 (기본 3)
}
type ChangeType =
| 'signature' // 시그니처 변경 (파라미터, 반환타입)
| 'implementation' // 구현 변경 (로직)
| 'rename' // 이름 변경
| 'delete' // 삭제
| 'move' // 이동
| 'type' // 타입 변경
;
interface ImpactResult {
target: GraphNode; // 변경 대상
changeType: ChangeType;
directImpact: ImpactNode[]; // 직접 영향
indirectImpact: ImpactNode[]; // 간접 영향
totalAffected: number; // 총 영향받는 노드 수
riskScore: number; // 위험도 (0-100)
riskLevel: 'low' | 'medium' | 'high' | 'critical';
testSuggestions: TestSuggestion[]; // 테스트 제안
warnings: string[]; // 경고 메시지
}
interface ImpactNode {
node: GraphNode;
depth: number; // 영향 깊이 (1=직접, 2+=간접)
impactType: 'caller' | 'type-user' | 'importer' | 'extender';
confidence: number; // 영향 확실성 (0-1)
reason: string; // 영향 이유
}
영향 범위 계산
class ImpactAnalyzer {
private graph: CodebaseGraph;
constructor(graph: CodebaseGraph) {
this.graph = graph;
}
/**
* 영향도 분석 실행
*/
analyze(request: ImpactAnalysisRequest): ImpactResult {
const target = this.graph.findNode(request.target);
if (!target) {
throw new Error(`Node not found: ${request.target}`);
}
// 1. 직접 영향 수집
const directImpact = this.collectDirectImpact(target, request.changeType);
// 2. 간접 영향 수집 (재귀)
const indirectImpact = this.collectIndirectImpact(
directImpact,
request.depth || 3
);
// 3. 위험도 계산
const riskScore = this.calculateRiskScore(target, directImpact, indirectImpact, request.changeType);
// 4. 테스트 제안 생성
const testSuggestions = this.generateTestSuggestions(target, directImpact, indirectImpact);
// 5. 경고 생성
const warnings = this.generateWarnings(target, directImpact, request.changeType);
return {
target,
changeType: request.changeType,
directImpact,
indirectImpact,
totalAffected: directImpact.length + indirectImpact.length,
riskScore,
riskLevel: this.getRiskLevel(riskScore),
testSuggestions,
warnings,
};
}
/**
* 직접 영향 수집
*/
private collectDirectImpact(target: GraphNode, changeType: ChangeType): ImpactNode[] {
const impacts: ImpactNode[] = [];
// 1. 이 노드를 호출하는 함수들
const callers = this.graph.getIncomingEdges(target.id, 'calls');
for (const edge of callers) {
const caller = this.graph.findNode(edge.source);
if (caller) {
impacts.push({
node: caller,
depth: 1,
impactType: 'caller',
confidence: changeType === 'signature' ? 1.0 : 0.7,
reason: `Calls ${target.name}`,
});
}
}
// 2. 이 타입을 사용하는 곳
if (target.type === 'type' || target.type === 'class') {
const typeUsers = this.graph.getIncomingEdges(target.id, 'uses');
for (const edge of typeUsers) {
const user = this.graph.findNode(edge.source);
if (user) {
impacts.push({
node: user,
depth: 1,
impactType: 'type-user',
confidence: changeType === 'type' ? 1.0 : 0.5,
reason: `Uses type ${target.name}`,
});
}
}
}
// 3. 이 모듈을 import하는 파일들
const importers = this.graph.getIncomingEdges(target.id, 'imports');
for (const edge of importers) {
const importer = this.graph.findNode(edge.source);
if (importer && !impacts.some(i => i.node.id === importer.id)) {
impacts.push({
node: importer,
depth: 1,
impactType: 'importer',
confidence: changeType === 'rename' || changeType === 'delete' ? 1.0 : 0.3,
reason: `Imports from ${target.path}`,
});
}
}
// 4. 이 클래스를 상속하는 클래스들
if (target.type === 'class') {
const extenders = this.graph.getIncomingEdges(target.id, 'extends');
for (const edge of extenders) {
const extender = this.graph.findNode(edge.source);
if (extender) {
impacts.push({
node: extender,
depth: 1,
impactType: 'extender',
confidence: 1.0,
reason: `Extends ${target.name}`,
});
}
}
}
return impacts;
}
/**
* 간접 영향 수집 (BFS)
*/
private collectIndirectImpact(directImpact: ImpactNode[], maxDepth: number): ImpactNode[] {
const indirect: ImpactNode[] = [];
const visited = new Set<string>(directImpact.map(i => i.node.id));
const queue: ImpactNode[] = [...directImpact];
while (queue.length > 0) {
const current = queue.shift()!;
if (current.depth >= maxDepth) continue;
// 현재 노드의 호출자들 수집
const callers = this.graph.getIncomingEdges(current.node.id, 'calls');
for (const edge of callers) {
if (visited.has(edge.source)) continue;
const caller = this.graph.findNode(edge.source);
if (!caller) continue;
visited.add(caller.id);
const impactNode: ImpactNode = {
node: caller,
depth: current.depth + 1,
impactType: 'caller',
confidence: current.confidence * 0.7, // 깊이에 따라 감소
reason: `Indirect via ${current.node.name}`,
};
indirect.push(impactNode);
queue.push(impactNode);
}
}
return indirect;
}
/**
* 위험도 점수 계산
*/
private calculateRiskScore(
target: GraphNode,
direct: ImpactNode[],
indirect: ImpactNode[],
changeType: ChangeType
): number {
let score = 0;
// 1. 변경 유형 기본 점수
const changeTypeScores: Record<ChangeType, number> = {
implementation: 20,
signature: 50,
type: 60,
rename: 40,
move: 45,
delete: 80,
};
score += changeTypeScores[changeType];
// 2. 직접 영향 수 (각 +3점, 최대 30점)
score += Math.min(direct.length * 3, 30);
// 3. 간접 영향 수 (각 +1점, 최대 20점)
score += Math.min(indirect.length, 20);
// 4. 대상 특성 보정
if (target.isExported) score += 10; // export된 것
if (target.path.includes('shared')) score += 15; // shared 모듈
if (target.path.includes('api')) score += 10; // API 관련
if (target.path.includes('auth')) score += 15; // 인증 관련
if (target.type === 'type') score += 10; // 타입 정의
// 5. 테스트 커버리지 보정
const hasTests = this.hasTestCoverage(target);
if (!hasTests) score += 10; // 테스트 없으면 위험
return Math.min(score, 100);
}
/**
* 위험 수준 결정
*/
private getRiskLevel(score: number): 'low' | 'medium' | 'high' | 'critical' {
if (score < 30) return 'low';
if (score < 50) return 'medium';
if (score < 75) return 'high';
return 'critical';
}
/**
* 테스트 제안 생성
*/
private generateTestSuggestions(
target: GraphNode,
direct: ImpactNode[],
indirect: ImpactNode[]
): TestSuggestion[] {
const suggestions: TestSuggestion[] = [];
// 1. 대상 테스트
suggestions.push({
target: target.id,
priority: 'critical',
reason: 'Direct change target',
testType: 'unit',
});
// 2. 직접 영향 테스트
for (const impact of direct.slice(0, 10)) { // 상위 10개
suggestions.push({
target: impact.node.id,
priority: impact.confidence > 0.8 ? 'high' : 'medium',
reason: impact.reason,
testType: impact.impactType === 'caller' ? 'integration' : 'unit',
});
}
// 3. 고확신 간접 영향 테스트
const highConfidenceIndirect = indirect.filter(i => i.confidence > 0.5);
for (const impact of highConfidenceIndirect.slice(0, 5)) {
suggestions.push({
target: impact.node.id,
priority: 'low',
reason: `${impact.reason} (indirect)`,
testType: 'integration',
});
}
return suggestions;
}
/**
* 경고 생성
*/
private generateWarnings(
target: GraphNode,
direct: ImpactNode[],
changeType: ChangeType
): string[] {
const warnings: string[] = [];
// 1. 높은 영향 범위
if (direct.length > 20) {
warnings.push(`HIGH IMPACT: This change affects ${direct.length} direct callers`);
}
// 2. export 삭제/변경
if (target.isExported && (changeType === 'delete' || changeType === 'rename')) {
warnings.push(`BREAKING: Modifying exported symbol may break external consumers`);
}
// 3. 핵심 모듈 변경
if (target.path.includes('shared') || target.path.includes('core')) {
warnings.push(`CORE MODULE: Changes to shared/core modules require extra caution`);
}
// 4. 테스트 부족
if (!this.hasTestCoverage(target)) {
warnings.push(`NO TESTS: Target has no test coverage - add tests before changing`);
}
// 5. 순환 참조 관련
const cycles = this.graph.detectCircularDependencies();
if (cycles.some(c => c.includes(target.id))) {
warnings.push(`CIRCULAR: Target is part of circular dependency - changes may cascade unpredictably`);
}
return warnings;
}
private hasTestCoverage(node: GraphNode): boolean {
// 테스트 파일 존재 확인
const testPatterns = [
node.path.replace('.ts', '.test.ts'),
node.path.replace('.ts', '.spec.ts'),
node.path.replace('/src/', '/test/'),
];
return testPatterns.some(pattern =>
this.graph.findNode(pattern) !== null
);
}
}
위험도 점수 기준
Risk_Scoring:
Base_Scores_by_Change_Type:
implementation: 20 # 로직만 변경
signature: 50 # 시그니처 변경
type: 60 # 타입 정의 변경
rename: 40 # 이름 변경
move: 45 # 위치 이동
delete: 80 # 삭제
Modifiers:
direct_impact: "+3 per caller (max 30)"
indirect_impact: "+1 per node (max 20)"
is_exported: "+10"
in_shared: "+15"
in_api: "+10"
in_auth: "+15"
is_type: "+10"
no_tests: "+10"
Risk_Levels:
low: "0-29 (안전한 변경)"
medium: "30-49 (주의 필요)"
high: "50-74 (신중한 검토 필요)"
critical: "75-100 (위험, 충분한 테스트 필수)"
출력 템플릿
## Impact Analysis Report
### Target: {{targetName}}
- **Path**: {{targetPath}}:{{targetLine}}
- **Type**: {{targetType}}
- **Change Type**: {{changeType}}
---
### Risk Assessment
| Metric | Value |
|--------|-------|
| Risk Score | {{riskScore}}/100 |
| Risk Level | {{riskLevel}} |
| Direct Impact | {{directCount}} nodes |
| Indirect Impact | {{indirectCount}} nodes |
| Total Affected | {{totalAffected}} nodes |
---
### Direct Impact ({{directCount}} nodes)
| Node | Type | Reason | Confidence |
|------|------|--------|------------|
{{#each directImpact}}
| {{node.name}} | {{impactType}} | {{reason}} | {{confidence}}% |
{{/each}}
---
### Indirect Impact (Top 10 of {{indirectCount}})
| Node | Depth | Reason | Confidence |
|------|-------|--------|------------|
{{#each indirectImpact}}
| {{node.name}} | {{depth}} | {{reason}} | {{confidence}}% |
{{/each}}
---
### Warnings
{{#each warnings}}
- {{this}}
{{/each}}
---
### Recommended Tests
| Target | Priority | Type | Reason |
|--------|----------|------|--------|
{{#each testSuggestions}}
| {{target}} | {{priority}} | {{testType}} | {{reason}} |
{{/each}}
---
### Verdict
{{#if riskLevel === 'critical'}}
**CRITICAL**: Do NOT proceed without comprehensive test coverage and review.
{{else if riskLevel === 'high'}}
**HIGH RISK**: Proceed with caution. Ensure all suggested tests pass.
{{else if riskLevel === 'medium'}}
**MEDIUM RISK**: Safe to proceed with standard review process.
{{else}}
**LOW RISK**: Safe to proceed. Minor change with limited impact.
{{/if}}
Quick Commands
| Command | Action |
|---|---|
impact <target> |
기본 영향도 분석 |
impact signature <target> |
시그니처 변경 영향도 |
impact delete <target> |
삭제 영향도 |
impact rename <target> |
이름 변경 영향도 |
impact depth <n> <target> |
깊이 지정 분석 |
impact test <target> |
테스트 제안만 |
impact warnings <target> |
경고만 |
다른 스킬과의 통합
Integration:
codebase-graph:
type: "데이터 소스"
usage: "그래프 쿼리, 역의존성 추적"
smart-context:
type: "협력"
usage: "영향 범위 컨텍스트 생성"
code-reviewer:
type: "소비자"
usage: "리뷰 시 영향도 표시"
tdd-guardian:
type: "소비자"
usage: "테스트 범위 제안 수신"
vibe-coding-orchestrator:
type: "제공자"
usage: "대규모 리팩토링 전 자동 분석"
문서 구조
impact-analyzer/
├── SKILL.md # 이 파일 (메인)
├── core/
│ ├── impact-calculation.md # 영향도 계산 상세
│ ├── risk-scoring.md # 위험도 점수 체계
│ └── test-suggestion.md # 테스트 제안 알고리즘
├── algorithms/
│ ├── reverse-dependency.md # 역의존성 추적
│ ├── propagation.md # 영향 전파 알고리즘
│ └── confidence.md # 확신도 계산
├── templates/
│ ├── report-template.md # 리포트 템플릿
│ └── warning-templates.md # 경고 메시지 템플릿
└── quick-reference/
├── commands.md # 명령어 가이드
└── risk-guide.md # 위험도 해석 가이드
Version: 1.0.0 Quality Target: 95% Required Skill: codebase-graph Related Skills: smart-context, code-reviewer, tdd-guardian
More from monicajeon28/gmcruise
hwp-text-replacer
HWP/HWPX 한글 문서 텍스트 자동 치환 도구. 한글 파일에서 특정 텍스트를 찾아 치환할 때 사용. 템플릿 문서 자동화, 대량 문서 수정에 활용.
13supabase-connect
Direct Supabase PostgreSQL database connection and SQL execution. Use when creating tables, running SQL queries, managing schemas, or setting up database migrations. Automatically reads connection info from project .env files.
11project-architect
**PROJECT ARCHITECT** - '새 프로젝트', '프로젝트 시작', '폴더 구조', '아키텍처 설계', '구조 잡아줘', '프로젝트 세팅' 요청 시 자동 발동. 프로 개발자 수준의 확장 가능한 프로젝트 구조 설계. 레이어 분리, 모듈화, 베스트 프랙티스 적용.
11vibe-coding-orchestrator
**VIBE CODING ORCHESTRATOR v5.0** - 16개 AI 스킬 자동 조율 시스템. 비개발자도 프로급 코딩. 바이브 코딩에서 프로덕션까지. 4단계 성숙도 모델. 2025 현대 도구 통합.
11ai-model-reference
AI 모델 API 호출명 및 가격 참조 가이드. API 키로 AI 모델을 호출할 때 정확한 모델명(model string)과 최신 가격 정보를 제공합니다. 사용 시점: (1) OpenAI, Anthropic, Google, DeepSeek 등의 API 호출 시 모델명이 필요할 때, (2) 토큰 비용/가격 비교가 필요할 때, (3) 최신 추론 모델/FAST 모델/가성비 모델 선택이 필요할 때, (4) 프롬프트 캐싱/배치 처리 비용 최적화가 필요할 때
10requirements-analyzer
**REQUIREMENTS ANALYZER** - '이런 거 만들고 싶어', '기능 정리', '뭐부터 만들어야', 'MVP 정의', '요구사항 분석', '기획서 분석' 요청 시 자동 발동. 자연어 아이디어를 기술 명세로 변환. MVP 분리, 우선순위 매트릭스, 의존성 그래프 제공.
10