본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 23. 09:04

정적 분석을 통해 46개의 리포지토리를 아우르는 하나의 지식 그래프 구축하기 (Part 1)

요약

46개의 리포지토리에 흩어진 코드베이스를 정적 분석을 통해 하나의 지식 그래프(code-graph)로 통합하는 과정을 다룹니다. AI의 컨텍스트 윈도우 한계와 환각 문제를 해결하기 위해 서비스 간 의존성을 가시화하는 방법을 제시합니다.

핵심 포인트

  • 대규모 코드베이스 분석 시 AI의 컨텍스트 윈도우 및 환각 문제 해결 필요
  • 정적 분석을 활용하여 리포지토리 간 경계를 넘나드는 지식 그래프 구축
  • 서비스 간 의존성(API, DB, Event)을 엣지로 연결하여 영향 범위 파악
  • 복잡하게 얽힌 n:1, n:n 관계의 의존성을 가시화하는 경계 노드 추출

안녕하세요, airCloset의 CTO인 Ryan입니다.

이 포스트는 정적 분석 (Static Analysis)을 사용하여 여러 서비스에 걸쳐 있는 **46개의 리포지토리 (Repositories)**를 하나의 지식 그래프 (Knowledge Graph)로 통합하는 것에 관한 내용입니다.

내부적으로 우리는 이를 code-graph라고 부르며, 저는 올해 1월부터 3월 사이에 이를 구축했습니다.

제가 기록하고 싶은 세 가지는 다음과 같습니다:

  • 왜 "단순히 AI가 코드를 읽게 하는 것"만으로는 충분하지 않은지, 그리고 왜 리포지토리 경계를 넘나드는 연결 고리들을 추적해야만 했는지
  • 46개의 리포지토리와 수많은 프레임워크 (jQuery / AngularJS / Express / NestJS / TypeORM / Redux Axios ...) 사이의 경계를 어떻게 추출했는지
  • 3개월간의 시행착오를 통해 무엇을 해결했고, 무엇을 해결하지 못했는지

이 글은 Part 1으로, code-graph 자체의 구축 과정, 고통스러웠던 부분들, 그리고 남아있는 문제들을 다룹니다. Part 2는 정적 분석만으로는 할 수 없었던 부분을 보완하기 위해 code-graph 위에 구축한 레이어인 **service-product-graph (SPG)**에 대해 다룰 예정입니다.

이것은 무엇을 위한 것이었나?

오랫동안 운영된 프로덕션 코드베이스 (Production Codebase)는 보통 다음과 같은 모습을 띱니다:

  • 여러 서비스와 여러 팀이 코드를 수정함
  • 각 시대의 프레임워크가 여전히 살아있고 혼재되어 있음
  • API, DB, 그리고 이벤트 (Event)를 통한 의존성 (Dependencies)이 깔끔한 1:1 전후방 관계가 아니라 뒤엉켜 있음:
    • 동일한 API가 여러 리포지토리에서 호출됨 (= n:1 호출자)
    • 동일한 DB 테이블이 여러 리포지토리에 걸쳐 쓰이고 읽힘 (= n:n)

시작점은 이 전체 코드베이스에 대해 AI에게 "영향 범위 (Blast Radius)를 보여줘", "이것을 변경하면 무엇이 깨지는지 알려줘"라고 질문하고 싶다는 것이었습니다.

단순한 해결책은 이렇습니다: "46개 리포지토리 분량의 코드를 모두 AI에게 넘겨주고 분석하게 한다."

하지만 여기에는 두 가지 이유로 인해 작동하지 않는 문제가 있습니다:

  • 컨텍스트 윈도우 (Context window): 46개의 리포지토리 × 수년간 축적된 코드는 한 번에 AI에게 넘겨줄 수 있는 규모가 아닙니다.
  • 환각 (Hallucination): 설령 가능하다고 하더라도, "모든 것을 읽고 관계를 추출하라"는 것은 추론 (Inference) 작업입니다. 이는 무언가를 놓치거나 실수를 저지릅니다. 이는 프로덕션 시스템의 영향 분석 (Impact analysis)에 사용하기에는 적합하지 않습니다.

따라서 제가 처음으로 도달한 아이디어는 다음과 같습니다: 정적 분석 (Static analysis)을 통해 외부에서 지식 그래프 (Knowledge graph)를 구축하는 것. 이것이 code-graph의 시작점입니다.

규모: 46개의 리포지토리

대상은 두 개의 그래프로 나뉩니다:

  • air-closet 그래프 (37개 리포지토리): airCloset, Men's, WMS 등 여러 서비스에 걸쳐 있는 그래프
  • mall 그래프 (9개 리포지토리): airCloset Mall 및 관련 서비스

따라서 총 46개의 리포지토리입니다.

주의할 점은 이것이 "37개의 리포지토리를 가진 하나의 서비스"가 아니라는 것입니다. 이는 해당 규모를 구성하는 여러 서비스의 집합입니다. 서비스 경계를 넘나드는 의존성 (Dependencies)을 리포지토리 간의 엣지 (Cross-repo edges)로 가시화하는 것이 바로 아래에서 다룰 경계 노드 (Boundary nodes) 논의의 핵심입니다.

경계 노드가 중요한 이유

이것이 이 글의 핵심입니다.

AI가 코드를 이해하게 만들고 싶을 때, "눈앞에 있는 것과 그 옆에 있는 것을 읽게 하는 것"은 솔직히 어렵지 않습니다. grep으로 찾고, 파일을 열어서 모델에게 전달하면 잘 작동합니다.

작은 코드베이스라면 그것으로 충분합니다. 하지만 규모가 커지면 앞서 언급한 컨텍스트 윈도우와 환각 문제에 부딪히게 됩니다. 대부분의 독자분들이 공감하실 부분이라 생각합니다.

이를 개선하는 한 가지 방법은 코드베이스를 정적으로 분석하여 지식 그래프로 변환한 뒤, MCP를 통해 AI에게 제공하는 것입니다. 그것이 바로 제가 취한 접근 방식입니다.

첫 번째 단계는 tree-sitter를 이용한 정적 분석이었습니다 (tree-sitter는 소스 코드를 구문 트리 (Syntax trees)로 파싱하는 오픈 소스 (OSS) 라이브러리입니다. 많은 언어를 지원하며 VS Code 및 유사한 에디터들이 구문 강조 (Syntax highlighting)를 위해 사용하는 도구입니다. 이 분야에서 무언가를 구축하고 싶다면 진심으로 추천합니다). 매우 훌륭한 도구이지만, 이것만으로는 모든 문제를 해결할 수 없습니다.

이 도구가 해결하지 못하는 것은 경계(boundaries)를 가로지르는 관계 — 즉, API, 데이터베이스 등을 추적하는 것입니다. tree-sitter는 변수, 함수 및 기타 언어 내 구성 요소(in-language constructs) 간의 관계를 추출할 수 있습니다. 하지만 이러한 경계를 추출할 수는 없습니다.

실제로 인간과 AI 모두가 어려움을 겪는 지점은 바로 이것입니다. 경계를 가로지르는 코드 연결 관계입니다:

  • 당신이 보고 있지 않은 다른 리포지토리(repo)에서 동일한 API가 호출되는 경우

    • 리포지토리 A의 프론트엔드와 리포지토리 C의 야간 배치(nightly batch) 작업이 모두 /api/v1/users/me를 호출할 수 있습니다.
    • 단 하나의 리포지토리만 보고 있다면, AI는 이를 알 방법이 없습니다.
  • 당신이 알지 못하는 어떤 배치 프로세스에 의해 동일한 DB 테이블이 읽히거나 쓰이는 경우

    • 서비스 측 코드를 수정할 때, 다른 위치에 있는 어떤 배치가 동일한 테이블을 읽고 쓰고 있을 수 있습니다.
    • 영향 범위(blast radius)를 잘못 판단하면 데이터 불일치(data inconsistency)가 발생합니다.
  • 당신이 모르는 어딘가에서 무언가가 실행되는 경우

요약하자면: 환각(hallucination) 없이 경계 너머에 있는 코드를 AI가 이해하도록 만드는 것, 그것이 목표입니다.

Boundary nodes bridge code across repository walls

경계 노드(boundary nodes)가 있다면, AI는 "이 API는 리포지토리 X에서도 호출됩니다"라고 사실(fact)로서 답변할 수 있습니다. AI에게 추론(infer)하도록 요청하는 대신, 이미 해결된 사실을 전달하는 것입니다.

물론 추출 단계에서 추론이 발생합니다. TypeScript Compiler와 Gemini가 모두 기여합니다. 하지만 그 결과는 그래프 내에 확인된 값으로 영구 저장되며, 매일 실행되는 경계 분석 크론(boundary-analysis cron, 아래에서 다룸)을 통해 다음 날 아침에 변화(drift)를 감지할 수 있습니다. AI가 그래프를 소비할 때쯤이면, 검증된 사실만이 AI에게 전달됩니다.

AI는 "모른다"라고 말하기보다 "자신이 볼 수 있는 것 무엇이든"을 가지고 답변하려는 경향이 있습니다. 바로 이 지점에서 AI도 사람도 알아채지 못하는 잘못된 답변, 즉 조용한 환각 (silent hallucinations)이 스며듭니다. 경계 노드 (Boundary nodes)는 이를 물리적으로 방지하는 역할을 합니다. 경계 노드는 AI가 발을 딛고 설 수 있는 검증된 지점을 제공합니다.

구축: tree-sitter 기반, 필요에 따라 TypeScript Compiler 및 Gemini 활용

일반적인 코드 구조(함수 호출 (function calls), 클래스 상속 (class inheritance), 임포트 (imports))는 tree-sitter를 사용하여 추출하기가 비교적 간단합니다. AST (Abstract Syntax Tree)를 따라가며 함수 / 메서드 / 클래스 / 필드를 노드로 변환하고, 참조를 엣지 (edges)로 연결하기만 하면 됩니다. 그냥 밀어붙이면 됩니다.

문제는 tree-sitter가 구문 트리 (syntax trees)를 구축하는 데는 뛰어나지만, 타입 정보 (type information)와 스코프 해석 (scope resolution)에는 취약하다는 점입니다. user.preferences.theme와 같은 필드 액세스 체인 (field access chain)을 정확하게 추적하려면, 변수 user가 어떤 타입인지, 그리고 어디에 정의되어 있는지를 해석해야 합니다. tree-sitter 단독으로는 여기에 도달할 수 없습니다.

따라서 필드 액세스 해석 (field-access resolution)을 위해 우리는 TypeScript Compiler APIGemini를 조합하여 사용합니다. tree-sitter가 구조를 추출하고 → TypeScript Compiler가 변수와 타입을 해석하며 → 그조차 도달할 수 없는 동적인 케이스에 대해서는 Gemini가 추론합니다. 명확한 책임을 가진 세 단계의 과정을 통해 필드 액세스 정확도를 높입니다.

3-stage field access resolution: tree-sitter, TypeScript Compiler, Gemini

우리는 21개의 엣지 (edge) 타입을 정의합니다:

  • CALLS (함수 호출) / EXTENDS (상속) / IMPLEMENTS (인터페이스 구현) 등 — tree-sitter가 제공할 수 있는 기본 구조
  • CALLS_API (호출자) / HANDLES_API (핸들러) — API 경계
  • WRITES_TO / READS_FROM — DB 경계
  • 기타 등등

경계 노드 추출 및 결합: 3개월간의 시행착오 (1월~3월)

일반적인 코드와 달리, 경계(Boundaries; API 엔드포인트, DB 테이블, 이벤트 토픽)는 프레임워크, 언어, 기술 영역, 라이브러리, 리포지토리(Repository), 그리고 작성자에 따라 매우 상이한 방식으로 작성됩니다.

"API 엔드포인트 정의"를 예로 들어보겠습니다. Express인가요? @Get() 데코레이터를 사용하는 NestJS인가요? 아니면 Fastify 라우트인가요? 각각은 완전히 다른 AST(Abstract Syntax Tree) 형태를 생성합니다. 또한 동일한 리포지토리 내에 여러 패턴이 동시에 존재할 수도 있습니다.

단순히 추출하는 것만 어려운 것이 아닙니다. 추출된 경계들을 그래프 상에서 결합(Joining)하는 것 또한 그 자체로 골칫거리입니다. 동일한 API 경로 또는 DB 테이블 이름에 대해 다음과 같은 변형이 발생하기 때문입니다:

  • 케이스(Casing) 변형: camelCase / snake_case / PascalCase
  • Trailing-slash 변형 (/users/me vs. users/me)
  • 경계 이름 자체가 변수인 경우 (${baseUrl}/users/me)

그리고 이 작업은 46개의 모든 리포지토리와 프레임워크의 다양성(Framework zoo)을 대상으로 수행되어야 했습니다.

해당 기간의 실제 git 히스토리를 돌이켜보면, 거의 매주 새로운 파서(Parser)와 탐지기(Detector)가 추가되고, 노이즈 필터(Noise filter)가 도입되며, 개념의 명칭이 변경되는 것을 볼 수 있습니다. 다음은 1월부터 3월까지의 주요 커밋 내역입니다(커밋 접두사는 graph-rag로 시작합니다. 이 스택은 원래 "LLM 소비를 위한 지식 그래프(Knowledge Graph) + RAG"라는 프레임워크를 따서 명명되었으며, 2월 15일에 code-graph로 변경되었습니다. 2월 말의 일부 커밋에는 이 전환 과정에서 사용된 짧은 기간의 graph 접두사가 여전히 남아 있습니다):

1월: 시작, 그리고 tree-sitter만으로는 부족하다는 깨달음

  • 2026-01-15feat(graph-rag): add TypeScript parser with tree-sitter — 시작 커밋
  • 2026-01-15feat(graph-rag): add graph builder with BigQuery storage — 그래프 데이터를 BigQuery에 기록
  • 2026-01-19feat(graph-rag): add TypeScript Compiler-based variable resolution for field extractiontree-sitter만으로는 변수 타입(Variable type)을 해결할 수 없다는 것을 깨닫고, TypeScript Compiler API를 함께 도입

2월: 프레임워크의 다양성, 노이즈와의 싸움

  • 2026-02-02feat(graph-rag): add frontend parser for jQuery/Vanilla JS codebasejQuery / Vanilla JS 프론트엔드 코드
  • 2026-02-03feat(graph-rag): add AngularJS Page detection for frontend BFSAngularJS 페이지 탐지 (오래된 프레임워크이지만 여전히 활발히 사용됨)
  • 2026-02-15refactor(code-graph): consolidate 18 MCP tools into 5 with deep subgraph traversal — 도구 세트가 18개까지 불어났으나, 심층 서브그래프 순회 (deep subgraph traversal)를 통해 5개로 통합 (또한 스택이 code-graph라는 이름 아래 통일된 시점이기도 함)
  • 2026-02-18fix(code-graph): reduce graph noise by filtering Type nodes, external lib CALLS, and Storybook files — 노이즈 감소: Type 노드, 외부 라이브러리 호출 (CALLS), Storybook 파일 필터링
  • 2026-02-19fix(code-graph): extract path aliases from tsconfig paths in addition to make-symlink + fix(code-graph): resolve @alias path imports for CommonJS symlink patterns경로 별칭 (path-alias)의 고충: tsconfig paths, make-symlink, 그리고 여기에 더해 CommonJS 심볼릭 링크 (symlink) 패턴 — 지원해야 할 세 가지 서로 다른 메커니즘
  • 2026-02-19feat(code-graph): add stop_at=boundary option to trace_connections — 경계 노드에서 순회를 중단하는 옵션 (명시적 순회 범위 지정 / 노드 폭발 (node-explosion) 완화)
  • 2026-02-21feat(graph): add typeORM JOIN detection, NestJS decorator parsing, Fetcher API detectionTypeORM JOIN / NestJS 데코레이터 (decorator) / Fetcher API 지원
  • 2026-02-21fix(graph): pass fullFileCode to Redux Axios variable resolver for scope-based extraction — 범위 기반 추출 (scope-based extraction)을 위한 Redux Axios 변수 리졸버 (resolver) 수정

3월: 개념 정리 및 정밀도 향상

  • 2026-03-08refactor(code-graph): rename __external__ to __boundary__개념 정리 (concept cleanup): "외부 리소스 (external resource)" 대신 "경계 노드 (boundary node)"로 표준화
  • 2026-03-16refactor: remove db-dictionary from code-graph stack — DB 스키마 딕셔너리 (테이블/컬럼 정의를 조회할 수 있게 해주는 레이어)를 별도의 그래프로 분리하여 독립적으로 발전시킬 수 있도록 함
  • 2026-03-24fix(code-graph): infer table names from dynamic variable names — 동적 변수 이름으로부터 테이블 이름 추론 (table-name inference)
  • 2026-03-24feat(code-graph): add orphan boundary node cleanup script — 고립된 경계 노드 (orphan boundary nodes)를 위한 정리 스크립트 추가

이 타임라인이 알려주는 것

매주 새로운 프레임워크나 패턴이 처리되고 있습니다. "경계 노드 추출 (extracting boundary nodes)" 작업은 근본적으로, 사람들이 경계를 작성하는 각각의 새로운 방식에 대해 파서 (parser)를 추가하는 것입니다.

등장한 프레임워크 / 메커니즘을 나열하자면 다음과 같습니다:

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0