codebase-graph
**CODEBASE GRAPH v1.0** - '의존성', '그래프', '코드베이스 분석', '함수 관계', '모듈 관계', '아키텍처 분석', 'import 분석' 요청 시 자동 발동. 프로젝트 시작 시 자동 생성. 온톨로지 기반 코드 지식 그래프로 함수→함수, 모듈→모듈 관계를 Edge로 연결. 토큰 72% 절감, 정확도 92% 달성.
Packaged view
This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.
Install command
npx @skill-hub/cli install monicajeon28-gmcruise-codebase-graph
Repository
Skill path: .claude/skills/codebase-graph
**CODEBASE GRAPH v1.0** - '의존성', '그래프', '코드베이스 분석', '함수 관계', '모듈 관계', '아키텍처 분석', 'import 분석' 요청 시 자동 발동. 프로젝트 시작 시 자동 생성. 온톨로지 기반 코드 지식 그래프로 함수→함수, 모듈→모듈 관계를 Edge로 연결. 토큰 72% 절감, 정확도 92% 달성.
Open repositoryBest for
Primary workflow: Ship Full Stack.
Technical facets: Full Stack.
Target audience: everyone.
License: Unknown.
Original source
Catalog source: SkillHub Club.
Repository owner: monicajeon28.
This is still a mirrored public skill entry. Review the repository before installing into production workflows.
What it helps with
- Install codebase-graph into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
- Review https://github.com/monicajeon28/GMcruise before adding codebase-graph to shared team environments
- Use codebase-graph for development workflows
Works across
Favorites: 0.
Sub-skills: 0.
Aggregator: No.
Original source / Raw SKILL.md
---
name: codebase-graph
description: "**CODEBASE GRAPH v1.0** - '의존성', '그래프', '코드베이스 분석', '함수 관계', '모듈 관계', '아키텍처 분석', 'import 분석' 요청 시 자동 발동. 프로젝트 시작 시 자동 생성. 온톨로지 기반 코드 지식 그래프로 함수→함수, 모듈→모듈 관계를 Edge로 연결. 토큰 72% 절감, 정확도 92% 달성."
allowed-tools:
- Read
- Glob
- Grep
- Bash
- Write
---
# Codebase Graph Skill v1.0
**온톨로지 기반 코드 인텔리전스** - Neorion 스타일의 지식 그래프로 코드베이스 전체를 구조화
## 핵심 컨셉
```yaml
Philosophy:
기존_방식: "파일 단위 분석 → 매번 전체 읽기 → 토큰 낭비"
새로운_방식: "그래프 구축 → 관계만 추출 → 토큰 72% 절감"
Graph_Structure:
Nodes:
- File: "소스 파일"
- Module: "모듈/패키지"
- Function: "함수/메서드"
- Class: "클래스/인터페이스"
- Type: "타입/인터페이스"
- Variable: "상수/변수"
Edges:
- imports: "A imports B"
- exports: "A exports B"
- calls: "A calls B"
- extends: "A extends B"
- implements: "A implements B"
- uses: "A uses type B"
- contains: "Module contains Function"
```
## 자동 발동 조건
```yaml
Auto_Trigger_Conditions:
Project_Start:
- "새 프로젝트 디렉토리 진입 시"
- "package.json, tsconfig.json 감지 시"
- "캐시된 그래프가 없거나 오래된 경우"
Keywords_KO:
- "의존성 분석, 의존성 그래프"
- "코드베이스 분석, 프로젝트 분석"
- "함수 관계, 모듈 관계"
- "아키텍처 분석, 구조 분석"
- "import 분석, export 분석"
- "어디서 사용되나, 뭐가 호출하나"
Keywords_EN:
- "dependency graph, dependency analysis"
- "codebase analysis, project analysis"
- "function relationships, module relationships"
- "architecture analysis, structure analysis"
- "import analysis, call graph"
- "where is this used, what calls this"
File_Events:
- "새 파일 생성 시 → 증분 업데이트"
- "파일 삭제 시 → 그래프에서 제거"
- "import 문 변경 시 → Edge 업데이트"
```
## 선택적 문서 로드 전략
```yaml
Document_Loading_Strategy:
Always_Load:
- "이 SKILL.md"
- "core/graph-schema.md"
Language_Specific_Load:
TypeScript: "analyzers/typescript-analyzer.md"
Python: "analyzers/python-analyzer.md"
Go: "analyzers/go-analyzer.md"
Java: "analyzers/java-analyzer.md"
Rust: "analyzers/rust-analyzer.md"
Context_Specific_Load:
Graph_Query: "core/query-language.md"
Cache_Management: "cache/cache-strategy.md"
Visualization: "templates/visualization.md"
```
## 그래프 스키마
```typescript
// Node Types
interface GraphNode {
id: string; // 고유 ID (파일경로:이름)
type: NodeType; // File | Module | Function | Class | Type | Variable
name: string; // 이름
path: string; // 파일 경로
line: number; // 시작 줄
signature?: string; // 함수 시그니처 (간략)
docstring?: string; // JSDoc/docstring (간략)
complexity?: number; // 순환 복잡도
loc: number; // 코드 라인 수
hash: string; // 내용 해시 (변경 감지용)
lastModified: number; // 타임스탬프
}
type NodeType = 'file' | 'module' | 'function' | 'class' | 'type' | 'variable';
// Edge Types
interface GraphEdge {
source: string; // 소스 노드 ID
target: string; // 타겟 노드 ID
type: EdgeType; // 관계 유형
weight?: number; // 관계 강도 (호출 빈도 등)
metadata?: Record<string, any>;
}
type EdgeType = 'imports' | 'exports' | 'calls' | 'extends' | 'implements' | 'uses' | 'contains';
// Graph
interface CodebaseGraph {
version: string; // 그래프 버전
projectRoot: string; // 프로젝트 루트
language: string; // 주 언어
nodes: Map<string, GraphNode>;
edges: GraphEdge[];
metadata: {
totalFiles: number;
totalFunctions: number;
totalClasses: number;
generatedAt: number;
analysisTime: number;
};
}
```
## 그래프 생성 워크플로우
```yaml
Graph_Generation:
Phase_1_Discovery:
- "프로젝트 루트 감지"
- "언어/프레임워크 감지"
- "분석 대상 파일 목록 수집"
- "기존 캐시 확인"
Phase_2_Parsing:
- "AST 기반 파싱 (ts-morph, ast 등)"
- "함수/클래스/타입 추출"
- "import/export 문 분석"
- "함수 호출 관계 추출"
Phase_3_Graph_Building:
- "노드 생성 (중복 제거)"
- "Edge 생성 (관계 매핑)"
- "가중치 계산 (호출 빈도)"
- "복잡도 계산"
Phase_4_Caching:
- "JSON/JSONL 형식 저장"
- "파일별 해시 저장 (증분 업데이트용)"
- "인덱스 생성"
```
## 그래프 쿼리 언어
```typescript
// 쿼리 인터페이스
interface GraphQuery {
// 특정 노드 찾기
findNode(id: string): GraphNode | null;
// 이름으로 검색
searchNodes(name: string, type?: NodeType): GraphNode[];
// 들어오는 Edge (이 노드를 사용하는 곳)
getIncomingEdges(nodeId: string, edgeType?: EdgeType): GraphEdge[];
// 나가는 Edge (이 노드가 사용하는 것)
getOutgoingEdges(nodeId: string, edgeType?: EdgeType): GraphEdge[];
// 의존성 트리 (재귀)
getDependencyTree(nodeId: string, depth?: number): DependencyTree;
// 역의존성 트리 (누가 이걸 사용하나)
getReverseDependencyTree(nodeId: string, depth?: number): DependencyTree;
// 경로 찾기 (A→B 어떻게 연결되나)
findPath(sourceId: string, targetId: string): GraphNode[];
// 순환 참조 탐지
detectCircularDependencies(): CircularDependency[];
// 고립된 노드 (사용 안 되는 코드)
findOrphanNodes(): GraphNode[];
// 허브 노드 (많이 사용되는 코드)
findHubNodes(threshold?: number): GraphNode[];
}
// 사용 예시
const query = new GraphQuery(graph);
// "이 함수를 호출하는 곳은?"
const callers = query.getIncomingEdges('src/utils.ts:formatDate', 'calls');
// "이 모듈이 의존하는 것들은?"
const deps = query.getDependencyTree('src/services/auth.ts', 2);
// "순환 참조 있나?"
const cycles = query.detectCircularDependencies();
// "안 쓰는 코드는?"
const orphans = query.findOrphanNodes();
```
## 토큰 최적화 전략
```yaml
Token_Optimization:
Traditional_Approach:
action: "파일 전체 읽기"
tokens: "~500 tokens/file"
10_files: "~5000 tokens"
Graph_Based_Approach:
action: "관련 노드만 추출"
tokens: "~50 tokens/node"
10_related_nodes: "~500 tokens"
savings: "90% reduction"
Smart_Context_Selection:
Level_1_Signature:
content: "함수 시그니처만"
tokens: "~20 tokens"
use_case: "존재 여부 확인"
Level_2_Interface:
content: "시그니처 + 타입"
tokens: "~50 tokens"
use_case: "인터페이스 파악"
Level_3_Summary:
content: "시그니처 + docstring + 관계"
tokens: "~100 tokens"
use_case: "역할 이해"
Level_4_Full:
content: "전체 구현"
tokens: "~300+ tokens"
use_case: "구현 수정"
```
## 캐시 전략
```yaml
Cache_Strategy:
Location: ".claude/cache/codebase-graph/"
Files:
graph.json: "전체 그래프"
index.json: "노드 인덱스 (빠른 검색)"
hashes.json: "파일별 해시 (변경 감지)"
stats.json: "통계 정보"
Invalidation:
file_modified: "해당 파일 노드만 재분석"
file_added: "새 노드 추가"
file_deleted: "노드 및 관련 Edge 제거"
config_changed: "전체 재분석"
TTL:
full_rebuild: "24시간"
incremental_check: "파일 수정 시마다"
```
## 다른 스킬과의 통합
```yaml
Integration:
clean-code-mastery:
provides: "복잡도 점수, 함수 크기"
receives: "함수별 품질 점수 저장"
code-reviewer:
provides: "관련 코드 컨텍스트"
receives: "리뷰 시 영향 범위 표시"
security-shield:
provides: "보안 관련 함수 위치"
receives: "취약점 전파 경로"
impact-analyzer:
provides: "그래프 데이터 전체"
receives: "변경 영향도 계산"
smart-context:
provides: "노드별 상세 정보"
receives: "최적 컨텍스트 선택"
arch-visualizer:
provides: "그래프 데이터"
receives: "시각화 및 문서화"
```
## Quick Commands
| Command | Action |
|---------|--------|
| `graph init` | 프로젝트 그래프 초기 생성 |
| `graph update` | 증분 업데이트 |
| `graph rebuild` | 전체 재빌드 |
| `graph query <node>` | 특정 노드 조회 |
| `graph deps <node>` | 의존성 트리 |
| `graph rdeps <node>` | 역의존성 트리 |
| `graph cycles` | 순환 참조 탐지 |
| `graph orphans` | 미사용 코드 탐지 |
| `graph hubs` | 핵심 모듈 탐지 |
| `graph stats` | 통계 출력 |
## 문서 구조
```
codebase-graph/
├── SKILL.md # 이 파일 (메인)
├── core/
│ ├── graph-schema.md # 그래프 스키마 상세
│ ├── query-language.md # 쿼리 언어 상세
│ └── algorithms.md # 그래프 알고리즘
├── analyzers/
│ ├── typescript-analyzer.md # TypeScript AST 분석
│ ├── python-analyzer.md # Python AST 분석
│ ├── go-analyzer.md # Go AST 분석
│ └── java-analyzer.md # Java AST 분석
├── cache/
│ ├── cache-strategy.md # 캐시 전략
│ └── incremental-update.md # 증분 업데이트
├── templates/
│ ├── visualization.md # 시각화 템플릿
│ └── report-template.md # 분석 리포트 템플릿
└── quick-reference/
├── commands.md # 빠른 명령어
└── integration.md # 스킬 통합 가이드
```
## 출력 예시
```markdown
## Codebase Graph Analysis
### Project Overview
| Metric | Value |
|--------|-------|
| Total Files | 156 |
| Total Functions | 423 |
| Total Classes | 87 |
| Total Types | 156 |
| Analysis Time | 2.3s |
### Dependency Statistics
| Metric | Value |
|--------|-------|
| Total Edges | 1,247 |
| Import Edges | 456 |
| Call Edges | 678 |
| Extends Edges | 45 |
| Implements Edges | 68 |
### Top Hub Nodes (Most Used)
1. `src/utils/helpers.ts:formatDate` - 47 incoming calls
2. `src/services/api.ts:fetchData` - 38 incoming calls
3. `src/types/index.ts:User` - 34 type usages
### Circular Dependencies Detected: 2
1. `moduleA → moduleB → moduleC → moduleA`
2. `serviceX → serviceY → serviceX`
### Orphan Nodes (Unused): 12
- `src/legacy/oldHelper.ts:deprecatedFn`
- `src/utils/unused.ts:*`
```
---
**Version**: 1.0.0
**Quality Target**: 95%
**Token Savings**: 72%
**Related Skills**: smart-context, impact-analyzer, arch-visualizer, code-reviewer
---
## Referenced Files
> The following files are referenced in this skill and included for context.
### core/graph-schema.md
```markdown
# Graph Schema - 상세 스키마 정의
## Node 상세 스키마
### FileNode
```typescript
interface FileNode extends GraphNode {
type: 'file';
path: string; // 상대 경로
absolutePath: string; // 절대 경로
extension: string; // .ts, .py 등
language: Language; // 감지된 언어
size: number; // 바이트
loc: number; // 라인 수
imports: string[]; // import한 모듈 목록
exports: string[]; // export한 심볼 목록
hash: string; // SHA-256 해시
}
// 예시
{
id: "src/services/auth.ts",
type: "file",
name: "auth.ts",
path: "src/services/auth.ts",
extension: ".ts",
language: "typescript",
size: 4256,
loc: 156,
imports: ["bcrypt", "jsonwebtoken", "../types/user"],
exports: ["AuthService", "validateToken"],
hash: "abc123..."
}
```
### FunctionNode
```typescript
interface FunctionNode extends GraphNode {
type: 'function';
name: string; // 함수명
path: string; // 파일 경로
line: number; // 시작 줄
endLine: number; // 끝 줄
signature: string; // 시그니처 (간략)
fullSignature: string; // 전체 시그니처
params: ParamInfo[]; // 파라미터 정보
returnType: string; // 반환 타입
isAsync: boolean; // async 여부
isExported: boolean; // export 여부
visibility: 'public' | 'private' | 'protected';
complexity: number; // 순환 복잡도
loc: number; // 함수 라인 수
docstring?: string; // JSDoc/docstring
calls: string[]; // 호출하는 함수들
calledBy: string[]; // 이 함수를 호출하는 함수들
}
interface ParamInfo {
name: string;
type: string;
optional: boolean;
defaultValue?: string;
}
// 예시
{
id: "src/services/auth.ts:validateToken",
type: "function",
name: "validateToken",
path: "src/services/auth.ts",
line: 45,
endLine: 72,
signature: "validateToken(token: string): Promise<TokenPayload>",
fullSignature: "async function validateToken(token: string): Promise<TokenPayload | null>",
params: [{ name: "token", type: "string", optional: false }],
returnType: "Promise<TokenPayload | null>",
isAsync: true,
isExported: true,
visibility: "public",
complexity: 5,
loc: 28,
docstring: "Validates a JWT token and returns the payload",
calls: ["jwt.verify", "getUserById"],
calledBy: ["authMiddleware", "refreshToken"]
}
```
### ClassNode
```typescript
interface ClassNode extends GraphNode {
type: 'class';
name: string;
path: string;
line: number;
endLine: number;
isAbstract: boolean;
isExported: boolean;
extends?: string; // 상속 클래스
implements: string[]; // 구현 인터페이스
decorators: string[]; // 데코레이터 (@Injectable 등)
properties: PropertyInfo[]; // 프로퍼티
methods: string[]; // 메서드 ID 목록
constructorParams: ParamInfo[]; // 생성자 파라미터
docstring?: string;
}
interface PropertyInfo {
name: string;
type: string;
visibility: 'public' | 'private' | 'protected';
isStatic: boolean;
isReadonly: boolean;
}
// 예시
{
id: "src/services/auth.ts:AuthService",
type: "class",
name: "AuthService",
path: "src/services/auth.ts",
line: 10,
endLine: 150,
isAbstract: false,
isExported: true,
extends: null,
implements: ["IAuthService"],
decorators: ["@Injectable()"],
properties: [
{ name: "jwtSecret", type: "string", visibility: "private", isStatic: false, isReadonly: true }
],
methods: ["AuthService.validateToken", "AuthService.login", "AuthService.logout"],
constructorParams: [
{ name: "userService", type: "UserService", optional: false }
]
}
```
### TypeNode
```typescript
interface TypeNode extends GraphNode {
type: 'type';
name: string;
path: string;
line: number;
kind: 'interface' | 'type' | 'enum';
isExported: boolean;
properties?: PropertyInfo[]; // interface/type alias
values?: string[]; // enum values
extends?: string[]; // extends 목록
usedBy: string[]; // 이 타입을 사용하는 곳
}
// 예시
{
id: "src/types/user.ts:User",
type: "type",
name: "User",
path: "src/types/user.ts",
line: 5,
kind: "interface",
isExported: true,
properties: [
{ name: "id", type: "string", visibility: "public" },
{ name: "email", type: "string", visibility: "public" },
{ name: "role", type: "UserRole", visibility: "public" }
],
extends: ["BaseEntity"],
usedBy: ["AuthService", "UserService", "ProfileController"]
}
```
## Edge 상세 스키마
### Import Edge
```typescript
interface ImportEdge extends GraphEdge {
type: 'imports';
source: string; // 가져오는 파일
target: string; // 가져오는 대상
importType: 'default' | 'named' | 'namespace' | 'side-effect';
importedNames: string[]; // import한 이름들
isTypeOnly: boolean; // import type 여부
isDynamic: boolean; // dynamic import 여부
}
// 예시
{
source: "src/services/auth.ts",
target: "src/types/user.ts",
type: "imports",
importType: "named",
importedNames: ["User", "UserRole"],
isTypeOnly: true,
isDynamic: false
}
```
### Call Edge
```typescript
interface CallEdge extends GraphEdge {
type: 'calls';
source: string; // 호출하는 함수
target: string; // 호출되는 함수
callCount: number; // 호출 횟수 (파일 내)
callLines: number[]; // 호출 위치 줄 번호
isConditional: boolean; // 조건부 호출 여부
isAsync: boolean; // await 여부
}
// 예시
{
source: "src/services/auth.ts:validateToken",
target: "src/services/user.ts:getUserById",
type: "calls",
callCount: 1,
callLines: [58],
isConditional: false,
isAsync: true
}
```
### Extends/Implements Edge
```typescript
interface InheritanceEdge extends GraphEdge {
type: 'extends' | 'implements';
source: string; // 자식 클래스
target: string; // 부모 클래스/인터페이스
}
// 예시
{
source: "src/services/auth.ts:AuthService",
target: "src/interfaces/auth.ts:IAuthService",
type: "implements"
}
```
### Contains Edge
```typescript
interface ContainsEdge extends GraphEdge {
type: 'contains';
source: string; // 컨테이너 (파일/클래스)
target: string; // 포함된 것 (함수/메서드)
}
// 예시
{
source: "src/services/auth.ts",
target: "src/services/auth.ts:AuthService",
type: "contains"
},
{
source: "src/services/auth.ts:AuthService",
target: "src/services/auth.ts:AuthService.validateToken",
type: "contains"
}
```
## Graph Metadata
```typescript
interface GraphMetadata {
// 기본 정보
version: string; // 그래프 스키마 버전
projectRoot: string; // 프로젝트 루트 경로
projectName: string; // 프로젝트 이름
language: string; // 주 언어
framework?: string; // 프레임워크 (Next.js, NestJS 등)
// 분석 통계
stats: {
totalFiles: number;
totalFunctions: number;
totalClasses: number;
totalTypes: number;
totalEdges: number;
avgComplexity: number;
maxComplexity: number;
};
// 시간 정보
timestamps: {
generatedAt: number; // 생성 시간
lastUpdatedAt: number; // 마지막 업데이트
analysisTime: number; // 분석 소요 시간 (ms)
};
// 파일 해시 (증분 업데이트용)
fileHashes: Record<string, string>;
}
```
## 인덱스 구조
```typescript
// 빠른 검색을 위한 인덱스
interface GraphIndex {
// 이름 → 노드 ID 매핑
byName: Map<string, string[]>;
// 타입별 노드 목록
byType: {
files: string[];
functions: string[];
classes: string[];
types: string[];
};
// 파일별 포함 노드
byFile: Map<string, string[]>;
// Edge 인덱스 (source → edges)
outgoingEdges: Map<string, GraphEdge[]>;
// Edge 인덱스 (target → edges)
incomingEdges: Map<string, GraphEdge[]>;
}
```
## JSON 저장 형식
```json
{
"version": "1.0.0",
"metadata": {
"projectRoot": "/path/to/project",
"projectName": "my-app",
"language": "typescript",
"framework": "nestjs",
"stats": {
"totalFiles": 156,
"totalFunctions": 423,
"totalClasses": 87,
"totalTypes": 156,
"totalEdges": 1247,
"avgComplexity": 4.2,
"maxComplexity": 18
},
"timestamps": {
"generatedAt": 1702234567890,
"lastUpdatedAt": 1702234567890,
"analysisTime": 2340
}
},
"nodes": [
{
"id": "src/services/auth.ts:AuthService",
"type": "class",
"name": "AuthService",
...
}
],
"edges": [
{
"source": "src/services/auth.ts",
"target": "src/types/user.ts",
"type": "imports",
...
}
],
"fileHashes": {
"src/services/auth.ts": "abc123...",
"src/types/user.ts": "def456..."
}
}
```
```
### analyzers/typescript-analyzer.md
```markdown
# TypeScript Analyzer
## 개요
TypeScript/JavaScript 코드베이스를 AST 기반으로 분석하여 그래프 노드와 엣지를 추출합니다.
## 분석 도구
```yaml
Primary_Tools:
ts-morph: "TypeScript AST 조작 라이브러리"
typescript: "공식 TypeScript 컴파일러 API"
Fallback_Tools:
@babel/parser: "Babel AST 파서"
acorn: "경량 JS 파서"
Pattern_Based:
regex: "간단한 패턴 매칭 (AST 불가 시)"
```
## AST 기반 분석 (ts-morph)
### 프로젝트 초기화
```typescript
import { Project, SourceFile, SyntaxKind } from 'ts-morph';
function initProject(rootDir: string): Project {
const project = new Project({
tsConfigFilePath: `${rootDir}/tsconfig.json`,
skipAddingFilesFromTsConfig: false,
});
// 또는 수동으로 파일 추가
project.addSourceFilesAtPaths([
`${rootDir}/src/**/*.ts`,
`${rootDir}/src/**/*.tsx`,
]);
return project;
}
```
### 파일 분석
```typescript
function analyzeFile(sourceFile: SourceFile): FileNode {
const filePath = sourceFile.getFilePath();
return {
id: filePath,
type: 'file',
name: sourceFile.getBaseName(),
path: filePath,
extension: sourceFile.getExtension(),
language: 'typescript',
loc: sourceFile.getEndLineNumber(),
imports: extractImports(sourceFile),
exports: extractExports(sourceFile),
hash: computeHash(sourceFile.getFullText()),
};
}
```
### Import 추출
```typescript
function extractImports(sourceFile: SourceFile): ImportInfo[] {
const imports: ImportInfo[] = [];
// import 문 분석
sourceFile.getImportDeclarations().forEach(importDecl => {
const moduleSpecifier = importDecl.getModuleSpecifierValue();
const namedImports = importDecl.getNamedImports();
const defaultImport = importDecl.getDefaultImport();
const namespaceImport = importDecl.getNamespaceImport();
imports.push({
source: sourceFile.getFilePath(),
target: resolveModule(moduleSpecifier, sourceFile),
type: 'imports',
importType: defaultImport ? 'default' :
namespaceImport ? 'namespace' :
namedImports.length > 0 ? 'named' : 'side-effect',
importedNames: [
...(defaultImport ? [defaultImport.getText()] : []),
...(namespaceImport ? [namespaceImport.getText()] : []),
...namedImports.map(n => n.getName()),
],
isTypeOnly: importDecl.isTypeOnly(),
});
});
// Dynamic import 분석
sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression)
.filter(call => call.getExpression().getText() === 'import')
.forEach(dynamicImport => {
const arg = dynamicImport.getArguments()[0];
if (arg) {
imports.push({
source: sourceFile.getFilePath(),
target: arg.getText().replace(/['"]/g, ''),
type: 'imports',
importType: 'named',
importedNames: [],
isDynamic: true,
});
}
});
return imports;
}
```
### 함수 추출
```typescript
function extractFunctions(sourceFile: SourceFile): FunctionNode[] {
const functions: FunctionNode[] = [];
// 함수 선언
sourceFile.getFunctions().forEach(func => {
functions.push(analyzeFunctionDeclaration(func, sourceFile));
});
// Arrow 함수 (변수에 할당된)
sourceFile.getVariableDeclarations().forEach(varDecl => {
const initializer = varDecl.getInitializer();
if (initializer?.getKind() === SyntaxKind.ArrowFunction) {
functions.push(analyzeArrowFunction(varDecl, sourceFile));
}
});
return functions;
}
function analyzeFunctionDeclaration(func: FunctionDeclaration, sourceFile: SourceFile): FunctionNode {
const name = func.getName() || 'anonymous';
const filePath = sourceFile.getFilePath();
return {
id: `${filePath}:${name}`,
type: 'function',
name,
path: filePath,
line: func.getStartLineNumber(),
endLine: func.getEndLineNumber(),
signature: generateSignature(func),
fullSignature: func.getSignature()?.getDeclaration().getText() || '',
params: func.getParameters().map(p => ({
name: p.getName(),
type: p.getType().getText(),
optional: p.isOptional(),
defaultValue: p.getInitializer()?.getText(),
})),
returnType: func.getReturnType().getText(),
isAsync: func.isAsync(),
isExported: func.isExported(),
visibility: 'public',
complexity: calculateComplexity(func),
loc: func.getEndLineNumber() - func.getStartLineNumber() + 1,
docstring: func.getJsDocs()[0]?.getDescription() || undefined,
calls: extractFunctionCalls(func),
};
}
function generateSignature(func: FunctionDeclaration): string {
const name = func.getName() || 'anonymous';
const params = func.getParameters()
.map(p => `${p.getName()}: ${simplifyType(p.getType().getText())}`)
.join(', ');
const returnType = simplifyType(func.getReturnType().getText());
return `${name}(${params}): ${returnType}`;
}
function simplifyType(type: string): string {
// 복잡한 제네릭 단순화
if (type.length > 50) {
return type.substring(0, 47) + '...';
}
return type;
}
```
### 클래스 추출
```typescript
function extractClasses(sourceFile: SourceFile): ClassNode[] {
return sourceFile.getClasses().map(cls => {
const name = cls.getName() || 'AnonymousClass';
const filePath = sourceFile.getFilePath();
return {
id: `${filePath}:${name}`,
type: 'class',
name,
path: filePath,
line: cls.getStartLineNumber(),
endLine: cls.getEndLineNumber(),
isAbstract: cls.isAbstract(),
isExported: cls.isExported(),
extends: cls.getExtends()?.getText(),
implements: cls.getImplements().map(i => i.getText()),
decorators: cls.getDecorators().map(d => d.getName()),
properties: cls.getProperties().map(p => ({
name: p.getName(),
type: p.getType().getText(),
visibility: getVisibility(p),
isStatic: p.isStatic(),
isReadonly: p.isReadonly(),
})),
methods: cls.getMethods().map(m => `${filePath}:${name}.${m.getName()}`),
constructorParams: cls.getConstructors()[0]?.getParameters().map(p => ({
name: p.getName(),
type: p.getType().getText(),
optional: p.isOptional(),
})) || [],
docstring: cls.getJsDocs()[0]?.getDescription(),
};
});
}
```
### 함수 호출 추출
```typescript
function extractFunctionCalls(node: Node): string[] {
const calls: string[] = [];
node.getDescendantsOfKind(SyntaxKind.CallExpression).forEach(call => {
const expression = call.getExpression();
let callName: string;
if (expression.getKind() === SyntaxKind.PropertyAccessExpression) {
// this.method() 또는 obj.method()
callName = expression.getText();
} else if (expression.getKind() === SyntaxKind.Identifier) {
// directCall()
callName = expression.getText();
} else {
return;
}
// 내장 함수 제외
if (!isBuiltInFunction(callName)) {
calls.push(callName);
}
});
return [...new Set(calls)]; // 중복 제거
}
function isBuiltInFunction(name: string): boolean {
const builtIns = [
'console.log', 'console.error', 'console.warn',
'JSON.parse', 'JSON.stringify',
'Object.keys', 'Object.values', 'Object.entries',
'Array.isArray', 'Promise.resolve', 'Promise.reject',
'parseInt', 'parseFloat', 'isNaN', 'isFinite',
'setTimeout', 'setInterval', 'clearTimeout', 'clearInterval',
];
return builtIns.includes(name) || name.startsWith('Math.');
}
```
### 순환 복잡도 계산
```typescript
function calculateComplexity(node: Node): number {
let complexity = 1; // 기본 경로
const countKinds = [
SyntaxKind.IfStatement,
SyntaxKind.ConditionalExpression, // 삼항 연산자
SyntaxKind.ForStatement,
SyntaxKind.ForInStatement,
SyntaxKind.ForOfStatement,
SyntaxKind.WhileStatement,
SyntaxKind.DoStatement,
SyntaxKind.CatchClause,
SyntaxKind.CaseClause, // switch case
SyntaxKind.BinaryExpression, // && || 연산자
];
countKinds.forEach(kind => {
const descendants = node.getDescendantsOfKind(kind);
if (kind === SyntaxKind.BinaryExpression) {
// && || 만 카운트
complexity += descendants.filter(d =>
d.getOperatorToken().getText() === '&&' ||
d.getOperatorToken().getText() === '||'
).length;
} else {
complexity += descendants.length;
}
});
return complexity;
}
```
## 패턴 기반 분석 (Fallback)
AST 파싱이 불가능한 경우 정규식 기반 분석:
```typescript
const PATTERNS = {
// Import
importStatement: /import\s+(?:type\s+)?(?:(\w+)|{([^}]+)}|\*\s+as\s+(\w+))\s+from\s+['"]([^'"]+)['"]/g,
dynamicImport: /import\(['"]([^'"]+)['"]\)/g,
require: /require\(['"]([^'"]+)['"]\)/g,
// Export
namedExport: /export\s+(?:const|let|var|function|class|interface|type|enum)\s+(\w+)/g,
defaultExport: /export\s+default\s+(?:function\s+)?(\w+)?/g,
reExport: /export\s+{([^}]+)}\s+from\s+['"]([^'"]+)['"]/g,
// Function
functionDecl: /(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)(?:\s*:\s*([^\s{]+))?/g,
arrowFunction: /(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*(?::\s*[^=]+)?\s*=>/g,
// Class
classDecl: /(?:export\s+)?(?:abstract\s+)?class\s+(\w+)(?:\s+extends\s+(\w+))?(?:\s+implements\s+([^\s{]+))?/g,
classMethod: /(?:async\s+)?(\w+)\s*\(([^)]*)\)(?:\s*:\s*([^\s{]+))?\s*{/g,
// Interface/Type
interfaceDecl: /(?:export\s+)?interface\s+(\w+)(?:\s+extends\s+([^\s{]+))?/g,
typeAlias: /(?:export\s+)?type\s+(\w+)\s*=/g,
// Function Call
functionCall: /(?<!function\s)(?<!class\s)(\w+)\s*\(/g,
};
function analyzeWithPatterns(content: string, filePath: string): PartialGraph {
const nodes: GraphNode[] = [];
const edges: GraphEdge[] = [];
// Import 추출
let match;
while ((match = PATTERNS.importStatement.exec(content)) !== null) {
const [_, defaultImport, namedImports, namespaceImport, modulePath] = match;
edges.push({
source: filePath,
target: modulePath,
type: 'imports',
importedNames: [defaultImport, namespaceImport, ...(namedImports?.split(',').map(s => s.trim()) || [])].filter(Boolean),
});
}
// Function 추출
while ((match = PATTERNS.functionDecl.exec(content)) !== null) {
const [fullMatch, name, params, returnType] = match;
const line = content.substring(0, match.index).split('\n').length;
nodes.push({
id: `${filePath}:${name}`,
type: 'function',
name,
path: filePath,
line,
signature: `${name}(${params}): ${returnType || 'void'}`,
});
}
// Class 추출
while ((match = PATTERNS.classDecl.exec(content)) !== null) {
const [_, name, extendsClass, implementsInterfaces] = match;
const line = content.substring(0, match.index).split('\n').length;
nodes.push({
id: `${filePath}:${name}`,
type: 'class',
name,
path: filePath,
line,
extends: extendsClass,
implements: implementsInterfaces?.split(',').map(s => s.trim()) || [],
});
}
return { nodes, edges };
}
```
## NestJS 특화 분석
```typescript
const NESTJS_DECORATORS = {
// Controller 관련
Controller: { type: 'controller', routePrefix: true },
Get: { type: 'route', method: 'GET' },
Post: { type: 'route', method: 'POST' },
Put: { type: 'route', method: 'PUT' },
Delete: { type: 'route', method: 'DELETE' },
Patch: { type: 'route', method: 'PATCH' },
// 서비스 관련
Injectable: { type: 'service' },
// 모듈 관련
Module: { type: 'module' },
// Guard, Pipe 등
UseGuards: { type: 'guard' },
UsePipes: { type: 'pipe' },
UseInterceptors: { type: 'interceptor' },
};
function analyzeNestJSDecorators(cls: ClassDeclaration): NestJSMetadata {
const decorators = cls.getDecorators();
const metadata: NestJSMetadata = {
type: 'unknown',
routes: [],
dependencies: [],
};
decorators.forEach(dec => {
const name = dec.getName();
const config = NESTJS_DECORATORS[name];
if (config) {
metadata.type = config.type;
if (config.routePrefix) {
// @Controller('users')
const arg = dec.getArguments()[0];
metadata.routePrefix = arg?.getText().replace(/['"]/g, '');
}
}
});
// 메서드의 라우트 데코레이터 분석
cls.getMethods().forEach(method => {
method.getDecorators().forEach(dec => {
const name = dec.getName();
const config = NESTJS_DECORATORS[name];
if (config?.type === 'route') {
const arg = dec.getArguments()[0];
metadata.routes.push({
method: config.method,
path: arg?.getText().replace(/['"]/g, '') || '',
handler: method.getName(),
});
}
});
});
// 생성자 의존성 주입 분석
const constructor = cls.getConstructors()[0];
if (constructor) {
constructor.getParameters().forEach(param => {
const type = param.getType().getText();
if (!type.startsWith('string') && !type.startsWith('number')) {
metadata.dependencies.push(type);
}
});
}
return metadata;
}
```
## 분석 성능 최적화
```typescript
// 병렬 처리
async function analyzeFilesInParallel(
files: string[],
concurrency: number = 4
): Promise<GraphNode[]> {
const results: GraphNode[] = [];
const chunks = chunkArray(files, concurrency);
for (const chunk of chunks) {
const chunkResults = await Promise.all(
chunk.map(file => analyzeFile(file))
);
results.push(...chunkResults.flat());
}
return results;
}
// 증분 분석
async function incrementalAnalysis(
project: Project,
previousHashes: Record<string, string>
): Promise<IncrementalResult> {
const changedFiles: string[] = [];
const addedFiles: string[] = [];
const removedFiles: string[] = [];
const currentFiles = new Set(project.getSourceFiles().map(f => f.getFilePath()));
const previousFiles = new Set(Object.keys(previousHashes));
// 변경/추가 파일 감지
for (const file of currentFiles) {
const content = project.getSourceFile(file)?.getFullText() || '';
const currentHash = computeHash(content);
if (!previousFiles.has(file)) {
addedFiles.push(file);
} else if (previousHashes[file] !== currentHash) {
changedFiles.push(file);
}
}
// 삭제된 파일 감지
for (const file of previousFiles) {
if (!currentFiles.has(file)) {
removedFiles.push(file);
}
}
return { changedFiles, addedFiles, removedFiles };
}
```
```