본문 바로가기

AI 개발

Repository Intelligence 완전 가이드 — AI가 코드 한 줄이 아니라 코드베이스 전체를 이해하는 법

반응형

자동완성은 끝났습니다. 2026년 AI 코딩 도구의 진짜 경쟁은 "얼마나 빠르게 다음 줄을 예측하느냐"가 아닙니다. "코드베이스 전체를 얼마나 깊이 이해하느냐"입니다. GitHub CPO Mario Rodriguez가 이걸 Repository Intelligence라고 불렀습니다.

[핵심 요약]
→ Repository Intelligence: 코드 한 줄이 아닌 코드베이스 전체를 이해하는 AI 능력
→ 4개 레이어: 시맨틱 분석 → 의존성 그래프 → Git 히스토리 → 팀 컨텍스트
→ 기존 코드 검색 vs RI: grep(문자열) → 콜 그래프·의도·변경 맥락 이해
→ 핵심 기술: tree-sitter(구조 파싱) + 코드 임베딩 + 그래프 DB + Git 분석
→ 도구별 구현: GitHub Copilot(자동 인덱싱) / Augment Code(1M 파일) / Cursor(Repo Map)
→ 실전 효과: "인증 에러 처리를 어디서 하지?" → 파일명 몰라도 즉시 답변
→ 개발자 관점: 컨텍스트 윈도우 관리 = 새로운 메모리 관리
→ 직접 구축 가능: tree-sitter + 코드 임베딩 + pgvector 조합

자동완성에서 Repository Intelligence로

2021 (Copilot 출시):
→ 현재 파일 + 커서 주변 몇 줄
→ 다음 줄 예측
→ 파일 하나 수준 이해

2023~2024:
→ 열린 탭들 + RAG 검색
→ 다중 파일 편집
→ 폴더 수준 이해

2026 (Repository Intelligence):
→ 전체 코드베이스 구조 인덱싱
→ 함수·클래스 간 관계 그래프
→ Git 히스토리 + 변경 패턴 분석
→ 팀 컨벤션 자동 학습
→ 레포지토리 수준 이해

실제로 달라지는 것:

[기존 코드 검색 vs Repository Intelligence]

질문: "인증 에러 처리를 어디서 해?"

기존 grep/검색:
→ "auth error" 문자열 검색
→ 파일명에 auth 있는 것만
→ 실제 에러 처리 로직 못 찾을 수 있음

Repository Intelligence:
→ 콜 그래프 분석
→ 미들웨어 체인 추적
→ processPayment(), handleCardTransaction() 등
   이름이 달라도 관련 함수 전부 발견
→ 어느 엔드포인트가 rate limit 미들웨어 쓰는지
   import 아닌 실제 실행 경로 분석

질문: "이 함수 왜 이렇게 짰어?"
→ 관련 PR 커밋 메시지 분석
→ 코드 리뷰 코멘트 참조
→ "JIRA-1234 보안 취약점 패치 당시 이 방식 선택" 응답

실전 1 — Repository Intelligence의 4개 레이어

레이어 1: 시맨틱 코드 분석

# tree-sitter로 코드 구조 파싱 — 텍스트가 아닌 AST로 이해
import tree_sitter_python as tspython
from tree_sitter import Language, Parser

PY_LANGUAGE = Language(tspython.language())
parser = Parser(PY_LANGUAGE)

def extract_code_entities(source_code: str) -> dict:
    """
    소스코드 → 구조화된 엔티티 추출
    단순 텍스트 파싱이 아닌 AST 기반 정확한 파싱
    """
    tree = parser.parse(source_code.encode())
    root = tree.root_node

    entities = {
        "functions": [],
        "classes": [],
        "imports": [],
        "calls": []
    }

    def traverse(node, parent_class=None):
        # 함수 정의 추출
        if node.type == "function_definition":
            func_name = node.child_by_field_name("name").text.decode()
            params = extract_params(node)
            docstring = extract_docstring(node)
            entities["functions"].append({
                "name": func_name,
                "class": parent_class,
                "params": params,
                "docstring": docstring,
                "start_line": node.start_point[0],
                "end_line": node.end_point[0],
            })

        # 클래스 정의 추출
        elif node.type == "class_definition":
            class_name = node.child_by_field_name("name").text.decode()
            entities["classes"].append({
                "name": class_name,
                "bases": extract_bases(node),
                "methods": []
            })
            parent_class = class_name

        # 함수 호출 추출 (콜 그래프 구축용)
        elif node.type == "call":
            caller = extract_call_target(node)
            if caller:
                entities["calls"].append({
                    "caller": caller,
                    "line": node.start_point[0]
                })

        for child in node.children:
            traverse(child, parent_class)

    traverse(root)
    return entities
[AST 파싱 vs 텍스트 파싱]
텍스트 파싱:
→ "def process" 검색 → 주석 안의 def도 걸림
→ 함수 범위 정확히 파악 불가
→ 중첩 클래스 이해 불가

AST 파싱 (tree-sitter):
→ 언어 문법 이해 기반 정확한 파싱
→ 100개+ 언어 지원
→ 함수·클래스·import 정확한 범위 파악
→ 심볼 간 관계 추출 가능

레이어 2: 의존성 그래프

import networkx as nx
from pathlib import Path
from typing import Generator

class DependencyGraphBuilder:
    """
    코드베이스 전체의 의존성 그래프 구축
    노드: 함수, 클래스, 모듈
    엣지: 호출, 상속, 임포트 관계
    """

    def __init__(self, repo_path: str):
        self.repo_path = Path(repo_path)
        self.graph = nx.DiGraph()

    def build(self) -> nx.DiGraph:
        """레포지토리 전체 스캔 → 그래프 구축"""
        for py_file in self.repo_path.rglob("*.py"):
            self._process_file(py_file)
        return self.graph

    def _process_file(self, filepath: Path):
        source = filepath.read_text()
        entities = extract_code_entities(source)
        module = str(filepath.relative_to(self.repo_path))

        # 노드 추가
        for func in entities["functions"]:
            node_id = f"{module}::{func['name']}"
            self.graph.add_node(node_id, **{
                "type": "function",
                "module": module,
                "docstring": func.get("docstring", ""),
                "line": func["start_line"],
            })

        # 콜 관계 엣지 추가
        for call in entities["calls"]:
            caller_id = self._resolve_symbol(call["caller"], module)
            if caller_id and self.graph.has_node(caller_id):
                self.graph.add_edge(
                    f"{module}::current",
                    caller_id,
                    type="calls",
                    line=call["line"]
                )

    def find_impact(self, function_name: str) -> list[str]:
        """
        특정 함수 변경 시 영향받는 모든 함수 탐색
        Repository Intelligence의 핵심 기능
        """
        # 역방향 BFS: 이 함수를 호출하는 모든 함수 탐색
        affected = []
        for node in nx.ancestors(self.graph, function_name):
            affected.append(node)
        return affected

    def find_auth_flow(self) -> list[str]:
        """
        인증 관련 코드 경로 전체 추적
        grep으로는 불가능한 쿼리
        """
        # "auth"를 이름에 포함하거나 JWT 관련 노드 찾기
        auth_nodes = [
            n for n in self.graph.nodes
            if any(kw in n.lower() for kw in ["auth", "jwt", "token", "login"])
        ]
        # 이 노드들에서 도달 가능한 전체 경로 분석
        return auth_nodes

# 사용 예시
builder = DependencyGraphBuilder("/path/to/repo")
graph = builder.build()

# "payment_processor 바꾸면 뭐가 영향받아?"
affected = builder.find_impact("payment::PaymentProcessor.process")
print(f"영향받는 함수: {len(affected)}개")
# → auth.middleware.validate_payment, api.routes.checkout, tests.payment_test...

레이어 3: Git 히스토리 분석

import subprocess
from datetime import datetime
from collections import defaultdict

class GitHistoryAnalyzer:
    """
    Git 히스토리에서 변경 패턴, 의도, 컨텍스트 추출
    "이 코드 왜 이렇게 됐지?" 에 답하는 레이어
    """

    def get_file_evolution(self, filepath: str) -> list[dict]:
        """파일의 변경 히스토리 + 각 변경의 맥락"""
        log = subprocess.run([
            "git", "log",
            "--follow",           # 파일명 변경 추적
            "--diff-filter=M",    # 수정된 커밋만
            "--format=%H|%an|%ae|%at|%s",  # 해시|이름|이메일|타임스탬프|제목
            "--", filepath
        ], capture_output=True, text=True).stdout

        commits = []
        for line in log.strip().split("\n"):
            if not line:
                continue
            parts = line.split("|")
            if len(parts) < 5:
                continue

            hash_, author, email, timestamp, subject = parts
            # 각 커밋의 실제 diff 내용 가져오기
            diff = subprocess.run([
                "git", "show", "--stat", hash_, "--", filepath
            ], capture_output=True, text=True).stdout

            commits.append({
                "hash": hash_,
                "author": author,
                "date": datetime.fromtimestamp(int(timestamp)).isoformat(),
                "message": subject,
                "diff_summary": diff,
            })

        return commits

    def find_co_changed_files(self, filepath: str) -> dict[str, int]:
        """
        특정 파일과 함께 자주 변경되는 파일 분석
        논리적으로 연결된 파일 자동 발견
        """
        # 이 파일이 변경된 커밋 목록
        commits = subprocess.run([
            "git", "log", "--format=%H", "--", filepath
        ], capture_output=True, text=True).stdout.strip().split("\n")

        co_changed = defaultdict(int)
        for commit_hash in commits[:50]:  # 최근 50개 커밋
            # 같은 커밋에서 변경된 다른 파일들
            files = subprocess.run([
                "git", "diff-tree", "--no-commit-id", "-r",
                "--name-only", commit_hash
            ], capture_output=True, text=True).stdout.strip().split("\n")

            for f in files:
                if f and f != filepath:
                    co_changed[f] += 1

        # 빈도순 정렬
        return dict(sorted(co_changed.items(),
                          key=lambda x: x[1], reverse=True))

    def detect_hotspots(self) -> list[dict]:
        """
        변경 빈도 × 복잡도 = 리팩토링 우선순위
        자주 바뀌는 복잡한 코드 = 위험 신호
        """
        # 파일별 변경 횟수
        change_counts = subprocess.run([
            "git", "log", "--name-only", "--format=",
            "--since=6.months"
        ], capture_output=True, text=True).stdout

        file_changes = defaultdict(int)
        for line in change_counts.split("\n"):
            if line.strip():
                file_changes[line.strip()] += 1

        hotspots = []
        for filepath, changes in file_changes.items():
            if changes > 10:  # 6개월 내 10회 이상 변경
                hotspots.append({
                    "file": filepath,
                    "change_frequency": changes,
                    "risk": "high" if changes > 30 else "medium"
                })

        return sorted(hotspots, key=lambda x: x["change_frequency"],
                     reverse=True)

# 사용
analyzer = GitHistoryAnalyzer()

# "payment.py가 바뀔 때 항상 같이 바뀌는 파일은?"
co_changed = analyzer.find_co_changed_files("src/payment.py")
# → {"src/billing.py": 23, "tests/test_payment.py": 21, "api/routes.py": 15}
# → "payment 건드리면 billing이랑 routes도 확인해야 해"

# 위험 파일 탐지
hotspots = analyzer.detect_hotspots()
# → auth.py: 47회 변경 (6개월) → 리팩토링 필요

레이어 4: 코드 임베딩 + 시맨틱 검색

from sentence_transformers import SentenceTransformer
import numpy as np
import psycopg2  # pgvector

class CodeSemanticSearch:
    """
    코드 + 자연어 쿼리를 같은 임베딩 공간에
    "인증 처리" → 함수명이 달라도 관련 코드 탐색
    """

    def __init__(self):
        # 코드 특화 임베딩 모델
        self.model = SentenceTransformer("microsoft/codebert-base")

    def index_codebase(self, entities: list[dict]):
        """코드 엔티티 전체 임베딩 후 pgvector 저장"""
        conn = psycopg2.connect("postgresql://localhost/codebase")
        cur = conn.cursor()

        for entity in entities:
            # 함수명 + docstring + 파라미터를 하나의 텍스트로
            text = f"""
            function: {entity['name']}
            module: {entity['module']}
            description: {entity.get('docstring', '')}
            params: {', '.join(entity.get('params', []))}
            """
            embedding = self.model.encode(text).tolist()

            cur.execute("""
                INSERT INTO code_entities
                (name, module, entity_type, embedding, metadata)
                VALUES (%s, %s, %s, %s::vector, %s)
                ON CONFLICT (name, module) DO UPDATE
                SET embedding = EXCLUDED.embedding
            """, (
                entity["name"],
                entity["module"],
                entity.get("type", "function"),
                embedding,
                str(entity)
            ))

        conn.commit()

    def search(self, natural_language_query: str, top_k: int = 10) -> list[dict]:
        """
        자연어 → 관련 코드 엔티티 시맨틱 검색
        "결제 처리 함수" → process_payment, handleTransaction 등 발견
        """
        query_embedding = self.model.encode(natural_language_query).tolist()

        conn = psycopg2.connect("postgresql://localhost/codebase")
        cur = conn.cursor()

        cur.execute("""
            SELECT name, module, entity_type, metadata,
                   1 - (embedding <=> %s::vector) as similarity
            FROM code_entities
            ORDER BY embedding <=> %s::vector
            LIMIT %s
        """, (query_embedding, query_embedding, top_k))

        results = []
        for row in cur.fetchall():
            results.append({
                "name": row[0],
                "module": row[1],
                "type": row[2],
                "similarity": float(row[4]),
            })

        return results

# 사용
search = CodeSemanticSearch()
search.index_codebase(all_entities)

# 자연어로 코드 탐색
results = search.search("사용자 인증 토큰 검증")
# → [{"name": "validate_jwt", "similarity": 0.94},
#    {"name": "verify_access_token", "similarity": 0.91},
#    {"name": "auth_middleware", "similarity": 0.89}]
# → 함수명에 "auth" 없어도 발견

실전 2 — Repository Intelligence 통합 파이프라인

from dataclasses import dataclass
from anthropic import Anthropic

@dataclass
class RepoContext:
    """Repository Intelligence가 LLM에 주입하는 컨텍스트"""
    relevant_functions: list[dict]   # 시맨틱 검색 결과
    dependency_paths: list[str]      # 의존성 그래프 경로
    git_context: list[dict]          # 관련 커밋 히스토리
    co_changed_files: list[str]      # 함께 변경되는 파일들
    hotspots: list[dict]             # 위험 파일 목록

class RepositoryIntelligenceAgent:
    """
    4개 레이어를 통합한 Repository Intelligence 에이전트
    """

    def __init__(self, repo_path: str):
        self.repo_path = repo_path
        self.graph_builder = DependencyGraphBuilder(repo_path)
        self.git_analyzer = GitHistoryAnalyzer()
        self.semantic_search = CodeSemanticSearch()
        self.anthropic = Anthropic()

        # 초기 인덱싱 (1회)
        print("코드베이스 인덱싱 중...")
        self.graph = self.graph_builder.build()
        self.semantic_search.index_codebase(self._extract_all_entities())
        print("완료")

    def answer(self, question: str) -> str:
        """
        자연어 질문 → 코드베이스 전체 이해 기반 답변
        """
        # 1. 시맨틱 검색으로 관련 코드 탐색
        relevant = self.semantic_search.search(question, top_k=5)

        # 2. 의존성 그래프에서 연관 파일 확장
        all_related = set()
        for entity in relevant:
            node_id = f"{entity['module']}::{entity['name']}"
            if self.graph.has_node(node_id):
                # 이 함수와 연결된 모든 노드
                neighbors = list(self.graph.predecessors(node_id)) + \
                           list(self.graph.successors(node_id))
                all_related.update(neighbors[:3])

        # 3. Git 히스토리 컨텍스트 추가
        git_context = []
        for entity in relevant[:2]:
            history = self.git_analyzer.get_file_evolution(entity["module"])
            git_context.extend(history[:3])

        # 4. 함께 변경되는 파일
        co_changed = {}
        if relevant:
            co_changed = self.git_analyzer.find_co_changed_files(
                relevant[0]["module"]
            )

        # 5. LLM에 컨텍스트 주입 후 답변 생성
        context = self._build_context(relevant, list(all_related),
                                      git_context, co_changed)

        response = self.anthropic.messages.create(
            model="claude-sonnet-4-6",
            max_tokens=2000,
            system="""당신은 코드베이스 전문가입니다.
            주어진 코드 컨텍스트를 기반으로 정확한 답변을 제공합니다.
            코드베이스의 구조, 히스토리, 관계를 이해하고 있습니다.""",
            messages=[{
                "role": "user",
                "content": f"""코드베이스 컨텍스트:
{context}

질문: {question}"""
            }]
        )

        return response.content[0].text

# 사용 예시
agent = RepositoryIntelligenceAgent("/path/to/my-project")

# grep으로 불가능한 질문들
print(agent.answer("인증 에러를 처리하는 모든 위치가 어디야?"))
print(agent.answer("payment.py를 수정하면 뭘 같이 테스트해야 해?"))
print(agent.answer("이 프로젝트에서 가장 위험한 파일이 뭐야?"))
print(agent.answer("로그인 플로우 전체를 설명해줘"))

실전 3 — 도구별 Repository Intelligence 비교

[2026년 도구별 구현 수준]

GitHub Copilot:
→ 자동 레포지토리 인덱싱 (설정 없이)
→ PR 컨텍스트, 이슈, 코드 리뷰 자동 참조
→ GitHub Actions 이력 분석
→ 한계: 파일 크기·수 제한, 심층 그래프 미지원

Augment Code:
→ 최대 100만 파일 인덱싱 (가장 큰 코드베이스)
→ 멀티 레포지토리 동시 인덱싱
→ JetBrains, VS Code 둘 다 지원
→ 세션 간 컨텍스트 유지 (지속적 메모리)
→ SOC 2 Type II (엔터프라이즈 환경)

Claude Code:
→ 현재 디렉토리 구조 + 파일 내용 직접 읽기
→ CLAUDE.md로 아키텍처 규칙 주입
→ 대용량 컨텍스트 (1M 토큰) 강점
→ Git 히스토리 직접 실행해서 읽음
→ 자동 인덱싱 없음 — 직접 탐색

Cursor (Repo Map):
→ tree-sitter 기반 코드 구조 자동 맵
→ 쿼리에 관련 파일 자동 포함
→ 토큰 예산 내에서 관련도 기반 필터링
→ 한계: 대규모 모노레포에서 맵 품질 저하

Aider:
→ Repo Map: tree-sitter로 전체 레포 구조 생성
→ 쿼리 관련 파일 자동 탐지
→ /map 명령으로 현재 맵 확인
→ /map-refresh로 대규모 리팩토링 후 갱신

실전 4 — CLAUDE.md로 Repository Intelligence 강화

# CLAUDE.md — Repository Intelligence 최대화 설정

## 아키텍처 개요
이 레포는 FastAPI 백엔드 + React 프론트엔드 모노레포.
도메인: 결제, 인증, 사용자 관리, 알림 4개 도메인.

## 핵심 파일 관계 맵

src/auth/ → 인증 도메인 (JWT 발급/검증) ├── handler.py → HTTP 엔드포인트 ├── service.py → 비즈니스 로직 (핵심) └── models.py → Pydantic 스키마

src/payment/ → 결제 도메인 ├── processor.py → 실제 결제 처리 (Stripe 연동) └── webhook.py → Stripe 웹훅 처리

의존 관계: payment/processor → auth/service (토큰 검증) payment/webhook → notification/sender (알림)

## 자주 같이 변경되는 파일 (히스토리 분석 결과)
- src/auth/service.py ↔ src/payment/processor.py
- src/payment/webhook.py ↔ src/notification/sender.py
- 위 파일 변경 시 함께 확인 필수

## 핫스팟 (위험 파일)
- src/auth/service.py: 6개월 내 47회 변경 → 리팩토링 예정
- src/payment/processor.py: 결제 핵심, 변경 시 반드시 테스트

## 코드 탐색 힌트
- "인증 에러" → src/auth/service.py의 AuthError 클래스
- "결제 실패" → src/payment/processor.py의 PaymentError
- "알림 발송" → src/notification/sender.py
[CLAUDE.md가 Repository Intelligence를 보완하는 방법]

자동 인덱싱으로 알 수 없는 것:
→ "왜 이렇게 설계했지?" (의도)
→ "이 파일이 핫스팟인데 리팩토링 예정" (계획)
→ "이 두 파일은 비즈니스적으로 연결됨" (도메인 지식)

CLAUDE.md로 보완:
→ 아키텍처 의도 명시
→ 위험 파일 미리 표시
→ 도메인 간 관계 설명
→ 히스토리 분석 결과 요약

결합 효과:
→ AI가 자동 발견한 것 + 개발자가 아는 것 → 완전한 이해

마무리

✅ Repository Intelligence 활용이 빛나는 경우
→ 처음 보는 레거시 코드베이스 파악할 때
→ "이거 바꾸면 어디에 영향가?" 파악해야 할 때
→ 반복적으로 같이 변경되는 파일 패턴 발견
→ 코드 리뷰 시 누락된 연관 파일 탐지
→ 신규 입사자 온보딩 — "이 프로젝트 구조 설명해줘"

❌ 한계 — 아직 안 되는 것
→ 런타임 동적 행동 (reflection, dynamic dispatch)
→ 외부 API·서드파티 라이브러리 내부
→ 인프라·배포 컨텍스트 (코드 외부)
→ 비즈니스 의도 (왜 이 기능이 필요한지)
   → CLAUDE.md + LLMWiki로 보완 필요

관련 글


 

반응형