들어가며: grep 대신 그래프를 질의한다
AI 코딩 어시스턴트에게 "이 인증 로직이 DB와 어떻게 연결되나요?"라고 물으면, 도구는 대개 파일을 하나씩 grep하고 읽으며 매번 처음부터 코드베이스를 다시 파악합니다. 프로젝트가 커질수록 이 비용은 선형으로 늘고, 같은 질문을 다시 해도 아무것도 축적되지 않습니다. safishamsi/graphify는 이 문제를 정면으로 겨냥합니다. 코드·문서·PDF·이미지·영상을 한 번 훑어 질의 가능한 지식 그래프로 만들어 두고, 이후의 모든 질문은 원본 파일이 아니라 그 압축된 그래프에 던지자는 발상입니다.
README의 한 문장이 사용법을 요약합니다.
Type
/graphifyin your AI coding assistant and it maps your entire project — code, docs, PDFs, images, videos — into a knowledge graph you can query instead of grepping through files.
이 글은 graphify 저장소(Python 라이브러리 + 26개 플랫폼용 스킬)를 직접 읽고, "지식 그래프로 코드베이스를 매핑한다"는 표현이 마케팅 수사인지 실제 구현인지를 검증하는 코드 리뷰입니다. 결론부터 말하면, graphify의 파이프라인은 7개의 독립 함수로 또렷하게 구현돼 있고, 핵심 가치인 "질의"는 LLM 호출 없이 결정론적으로 동작합니다. graphify는 Y Combinator S26 배치 프로젝트이며 PyPI에는 graphifyy(y 두 개)라는 이름으로 배포됩니다(CLI 명령은 graphify).
graphify란 무엇인가
graphify는 한 문장으로 "코드베이스(와 그 주변 문서)를 NetworkX 그래프로 추출해, AI 어시스턴트가 파일을 읽는 대신 그래프를 질의하도록 만드는 메모리 레이어"입니다. ARCHITECTURE.md는 스스로의 정체성을 이렇게 규정합니다. graphify는 Python 라이브러리를 백엔드로 둔 코딩 어시스턴트 스킬이며, 스킬이 라이브러리를 오케스트레이션하지만 라이브러리는 단독으로도 쓸 수 있습니다.
/graphify . 한 줄을 실행하면 graphify-out/ 디렉터리에 세 개의 산출물이 생깁니다.
graphify-out/
├── graph.html 브라우저에서 노드 클릭·필터·검색
├── GRAPH_REPORT.md 핵심 개념·의외의 연결·추천 질문 요약
└── graph.json 전체 그래프 — 파일을 다시 읽지 않고 언제든 질의graph.json은 NetworkX의 node-link 포맷이고, 각 노드는 id/label/file_type/source_file을, 각 엣지는 source/target/relation/confidence(+ INFERRED일 때 confidence_score)를 가집니다.
7단계 파이프라인
graphify의 골격은 한 줄로 요약됩니다. ARCHITECTURE.md에 적힌 파이프라인은 다음과 같습니다.
detect() → extract() → build_graph() → cluster() → analyze() → report() → export()각 단계는 자신만의 모듈에 있는 단일 함수이고, 단계들은 평범한 Python 딕셔너리와 NetworkX 그래프로만 소통합니다. 공유 상태도, graphify-out/ 바깥의 부수효과도 없습니다. 이 "함수 하나 = 단계 하나" 구조 덕분에 각 모듈을 독립적으로 읽고 테스트할 수 있고, 실제로 저장소의 tests/는 모듈당 한 개의 테스트 파일을 두는 규칙을 따릅니다.
| 모듈 | 함수 | 역할 |
|---|---|---|
detect.py | collect_files / detect(root) | 디렉터리 → 필터링된 파일 목록(타입별) |
extract.py | extract(path) | 파일 → {nodes, edges} 딕셔너리 |
build.py | build_graph(extractions) | 추출 딕셔너리들 → nx.Graph |
cluster.py | cluster(G) | 그래프 → 노드별 community_id 부여 |
analyze.py | analyze(G) | 그래프 → god node·의외의 연결·추천 질문 |
report.py | render_report(...) | 그래프+분석 → GRAPH_REPORT.md |
export.py | export(...) | 그래프 → Obsidian vault·json·html·svg |
세 개의 패스: 무엇을 어떻게 추출하나
docs/how-it-works.md는 입력 파일을 성격에 따라 세 갈래로 나눠 처리한다고 설명합니다. 이 분리가 graphify의 비용 구조를 결정합니다.
첫째, Pass 1 — 코드 구조(무료, API 호출 없음)입니다. tree-sitter가 코드 파일을 로컬에서 파싱해 클래스·함수·import·호출 그래프·주석을 추출합니다. LLM이 전혀 개입하지 않습니다. 중요한 설계 결정이 하나 있는데, 코드 파일은 의미 추출 LLM으로 보내지 않습니다. 코퍼스가 코드 파일만으로 이뤄져 있으면 LLM을 쓰는 Pass 3은 통째로 생략됩니다. 의미 추출은 문서·논문· 이미지·전사본에만 쓰입니다.
둘째, Pass 2 — 영상·오디오(로컬, API 호출 없음)입니다. faster-whisper로 전사하며, 전사 프롬프트를 지금까지 만든 코드 그래프의 god node(가장 많이 연결된 개념)로 시딩해 도메인에 초점을 맞춥니다. 전사 결과는 캐시되어 재실행 시 건너뜁니다.
셋째, Pass 3 — 문서·논문·이미지(Claude 서브에이전트, 토큰 비용 발생)입니다. 마크다운·PDF· 이미지·전사본 위에서 LLM이 병렬로 돌며 각 서브에이전트가 파일 묶음을 읽고 노드·엣지·그룹 관계가 담긴 JSON 조각을 출력합니다. 조각들은 하나의 그래프로 병합됩니다.
핵심은, 돈이 드는 부분(LLM)이 비코드 자료에만 국한된다는 점입니다. 대부분의 코드 분석은 로컬·결정론·무료로 끝납니다.
코드 추출 심층: tree-sitter 2-pass와 신뢰도 라벨
extract.py는 graphify에서 가장 큰 모듈(약 1.3만 줄)이며, 파일 확장자를 키로 하는 디스패치 테이블(_get_extractor)로 언어별 추출기(extract_python, extract_js 등)를 고릅니다. 각 추출기는 tree-sitter AST를 순회하며 노드와 엣지를 만드는데, 동작은 두 패스로 나뉩니다.
1단계(파일 내부)에서는 같은 파일 안에서 해소되는 호출·import를 즉시 엣지로 만들고 confidence: EXTRACTED를 답니다. 한 파일 안에서 호출 대상을 찾지 못한 호출은 버리지 않고 raw_calls 목록에 적재합니다(호출자 노드 ID·호출 이름·위치 등).
2단계(파일 사이)에서는 전체 노드를 정규화된 라벨로 색인한 전역 인덱스를 만들고, raw_calls의 각 항목을 라벨 매칭으로 해소합니다. 이때 신뢰도 부여가 흥미롭습니다. 호출자 파일이 대상에 대한 명시적 imports 엣지를 가지면 EXTRACTED로 승격하고, 그렇지 않으면 INFERRED로 답니다. 같은 라벨이 두 개 이상의 파일에 존재하면(모호) 엣지를 만들지 않고, 언어 계열이 다른 교차 호출도 INFERRED에서는 만들지 않습니다. 이름만 같아서 생기는 오연결을 막으려는 장치입니다.
신뢰도 라벨은 세 가지이며, 이는 graphify 전체를 관통하는 개념입니다.
| 라벨 | 의미 |
|---|---|
EXTRACTED | 소스에 명시된 관계(import, 직접 호출). 항상 confidence 1.0 |
INFERRED | 합리적 추론. confidence_score(0.55~0.95)를 가짐 |
AMBIGUOUS | 불확실 — GRAPH_REPORT.md에서 사람 검토 대상으로 표시 |
docs/how-it-works.md는 INFERRED의 점수 루브릭까지 못 박습니다. 0.95(명시적 교차 참조, 유일한 후보)부터 0.55(추측성)까지 이산값으로 매깁니다. "찾은 것"과 "추측한 것"을 항상 구분해 두는 것이 graphify의 신뢰성 철학입니다.
그래프 구축과 중복 제거
build.py의 build_graph(내부적으로 build_from_json)는 추출 딕셔너리들을 하나의 NetworkX 그래프로 병합합니다. 어려움은 같은 개념이 여러 경로로 중복 등장한다는 점인데, 중복 제거가 세 겹으로 설계돼 있습니다. 파일 내부에서는 각 추출기가 seen_ids 집합으로 노드를 한 번만 방출하고, 파일 사이에서는 G.add_node()가 멱등이라 나중 노드가 속성을 덮어씁니다(AST 먼저, 의미 추출 나중 — 그래서 의미 라벨이 이깁니다). 그리고 스킬 레벨에서 캐시본과 신규 결과를 node["id"] 기준으로 한 번 더 병합합니다.
추론된 호출이 만든 "유령 노드(ghost node)"도 정리합니다. AST 출신 노드를 정본(canonical)으로 삼아 (파일명, 라벨) 키로 모으고, 비-AST 노드가 정본 쌍둥이를 가지면 그 엣지를 정본 ID로 재배선한 뒤 유령을 제거합니다. 다만 같은 키에 정본 후보가 둘 이상이면(모호) 건드리지 않습니다. 또한 pyproject.toml·go.mod·pom.xml 같은 매니페스트의 패키지는 이름당 하나의 정본 노드로 모아, 여러 매니페스트에서 참조돼도 단일 허브가 되도록 합니다.
커뮤니티 탐지와 분석: god node·의외의 연결·추천 질문
cluster.py는 Leiden 알고리즘으로 커뮤니티를 찾습니다. graspologic의 leiden을 random_seed=42로 호출하고, 사용할 수 없으면 NetworkX의 Louvain(seed=42)으로 폴백합니다. 임베딩이나 벡터 DB가 필요 없다는 점이 핵심인데, Claude가 추출한 semantically_similar_to 엣지가 이미 그래프 안에 있으므로 그래프 구조 자체가 유사도 신호가 됩니다. 너무 큰 커뮤니티는 다시 쪼개고, 내부 엣지 밀도(cohesion)가 낮은 커뮤니티도 재분할하며, 마지막에 크기·라벨 순으로 안정적으로 재인덱싱해 실행마다 같은 커뮤니티 ID가 나오게 합니다.
analyze.py는 세 가지를 뽑습니다. god node는 연결 수(degree) 기준 상위 노드로, 파일 허브나 str·int·Mock 같은 잡음 라벨은 제외합니다. 의외의 연결(surprising connections)은 교차 파일 엣지에 복합 점수를 매겨 고릅니다. 신뢰도(AMBIGUOUS가 가장 의외), 파일 타입 교차 (코드↔논문), 저장소 교차, 커뮤니티 교차, 의미 유사도, 주변부→허브 도달에 각각 가산점을 주되, INFERRED 코드→문서나 언어 교차 같은 약한 신호에는 구조 보너스를 억제합니다. 추천 질문(suggested questions)은 다섯 유형으로 생성합니다. 모호한 엣지의 정체를 묻는 질문, 매개 중심성(betweenness)이 높은 다리 노드에 대한 질문, INFERRED가 많은 god node의 검증 질문, 고립 노드 질문, 응집도 낮은 커뮤니티의 분할 여부 질문입니다.
캐시와 증분 업데이트
재실행 비용을 줄이는 장치가 cache.py입니다. 모든 파일을 내용 해시로 지문화하는데, file_hash는 파일 내용(마크다운은 YAML 프론트매터 제외)과 루트 기준 상대 경로를 이어 SHA256을 계산합니다. 크기·mtime이 그대로면 읽기 자체를 건너뛰는 stat 기반 빠른 경로도 있습니다. 캐시는 AST용(cache/ast/v{버전}/)과 의미 추출용(cache/semantic/)으로 나뉘며, 저장 시에는 상대 경로로 적어 다른 머신·체크아웃 위치에서도 이식 가능합니다. 변경되지 않은 파일은 추출을 통째로 건너뛰므로, 첫 실행만 비용을 치르고 이후 질의는 압축된 그래프만 읽습니다. docs/how-it-works.md는 Karpathy 저장소 + 논문 5편 + 이미지 4장(총 52파일) 혼합 코퍼스에서 질의당 토큰이 원본을 직접 읽을 때보다 71.5배 적었다고 보고하며, 절감은 코퍼스가 커질수록 복리로 커진다고 설명합니다.
그래프를 질의하기: query와 MCP 서버
graphify에서 가장 인상적인 부분은 질의 경로가 LLM 없이 결정론적이라는 점입니다. graphify query "show the auth flow" 또는 MCP 서버의 query_graph 도구는 serve.py의 _query_graph_text로 흐르는데, 자연어 질문을 다음 단계로 서브그래프로 바꿉니다.
먼저 질문을 토큰으로 쪼개고(중국어는 jieba로 분절), 각 노드에 IDF 가중 점수를 매깁니다. 정확 일치에는 큰 보너스(1000), 접두 일치(100), 부분 문자열(1), 소스 파일명 일치(0.5)를 줍니다. 그다음 최대 3개의 시드 노드를 고르되, 점수가 최상위 대비 20% 이상 떨어지면 시딩을 멈춰 "error"·"exception" 같은 잡음어가 시드를 훔치지 못하게 합니다. 질문 키워드("call", "field", "parameter" 등)에서 컨텍스트 필터를 추론해 엣지를 좁히고, BFS 또는 DFS로 시드 주변을 탐색하되 고차수 허브(p99)는 시드가 아닌 한 확장을 막습니다. 마지막으로 토큰 예산 안에서 서브그래프를 텍스트로 렌더링합니다. 즉 질의 자체는 빠르고 무료이며, 비용은 첫 추출 때 이미 치른 것입니다.
serve.py는 이 그래프를 MCP 서버로도 노출합니다. 등록되는 도구는 query_graph, get_node, get_neighbors, get_community, god_nodes, graph_stats, shortest_path, 그리고 GitHub 연동인 list_prs/get_pr_impact/triage_prs입니다. 전송은 기본 stdio(개발자별 로컬 프로세스)와 HTTP Streamable(Starlette + uvicorn, 팀이 단일 URL을 공유) 두 가지를 지원하며, 그래프 파일의 mtime/크기가 바뀌면 핫 리로드합니다. HTTP로 외부에 노출할 때는 --host 0.0.0.0과 --api-key를 함께 쓰도록 안내합니다.
LLM 백엔드와 보안
llm.py의 BACKENDS 딕셔너리는 다양한 백엔드를 정의합니다. claude(Anthropic), claude-cli(Claude Code CLI), openai(및 OpenAI 호환), azure, gemini, deepseek, kimi, ollama(로컬), bedrock(AWS IAM)이며, ~/.graphify/providers.json으로 커스텀 공급자도 추가할 수 있습니다. 모델은 GRAPHIFY_<BACKEND>_MODEL 환경변수가 백엔드 기본값을 덮어쓰는 우선순위를 따릅니다. 의미 추출 시스템 프롬프트가 앞서 본 세 단계 신뢰도 루브릭을 모델에게 지시하는 단일 소스입니다. 다만 이 백엔드들은 헤드리스/CI 추출(graphify extract)에만 필요하고, IDE 안에서 /graphify 스킬로 돌릴 때는 IDE 세션의 모델 API를 쓰므로 별도 키가 필요 없습니다.
보안은 security.py에 모입니다. 외부 입력은 모두 이 모듈을 통과합니다. URL은 validate_url로 http/https만 허용하고 file:// 리다이렉트를 막으며, 가져온 콘텐츠는 크기 상한·타임아웃이 걸린 safe_fetch로 받습니다. 그래프 파일 경로는 validate_graph_path로 반드시 graphify-out/ 안으로 해소되게 강제하고, 노드 라벨은 sanitize_label로 제어문자 제거·256자 제한·HTML 이스케이프를 거칩니다. 파일 수집 단계(detect.py)에서도 .env·.pem·id_rsa나 secret· token·credential 같은 민감 파일과 .ssh·.aws 같은 디렉터리를 건너뜁니다. 전체 위협 모델은 SECURITY.md에 정리돼 있습니다.
설치와 사용
설치는 PyPI 패키지(graphifyy) 설치 후 스킬을 등록하는 두 단계입니다.
uv tool install graphifyy # 또는 pipx install graphifyy
graphify install # 어시스턴트에 /graphify 스킬 등록이후 AI 어시스턴트에서 /graphify .를 입력하면 됩니다. graphify install은 Claude Code, Codex, OpenCode, Cursor, Gemini CLI, Copilot, Aider, OpenClaw, Hermes 등 README에 나열된 20여 개 플랫폼에 스킬과 항상 적용되는 안내(AGENTS.md/규칙 파일)를 심습니다. PDF·Office·영상· MCP·각종 LLM 백엔드는 graphifyy[pdf]처럼 옵션 extra로 필요한 것만 설치합니다. 자주 쓰는 명령은 다음과 같습니다.
/graphify . # 현재 폴더 그래프 생성
/graphify . --update # 변경된 파일만 재추출
/graphify . --cluster-only # 재추출 없이 클러스터링만
/graphify . --wiki # 그래프에서 마크다운 위키 생성
graphify query "what connects auth to the database?"
graphify path "UserService" "DatabasePool"
graphify explain "RateLimiter"
graphify add https://arxiv.org/abs/1706.03762 # 논문을 가져와 그래프에 추가
graphify hook install # git 커밋마다 자동 재빌드(AST, 무료)
graphify prs --triage # PR 리뷰 큐를 그래프 영향 기준으로 정렬graphify-out/는 팀이 git에 커밋하도록 설계됐습니다. 한 명이 그래프를 만들어 커밋하면 나머지는 pull만으로 같은 지도를 얻고, graphify hook install이 커밋마다 그래프를 AST 수준으로 재빌드하며, graph.json용 git merge driver까지 등록해 두 사람이 병렬로 커밋해도 충돌 마커 없이 합집합 병합됩니다.
한계와 주의점
코드를 읽으며 드러난 경계는 다음과 같습니다. 첫째, 토큰 절감 효과는 코퍼스 크기에 비례합니다. docs/how-it-works.md는 6개 파일짜리 httpx 합성 라이브러리에서는 절감이 약 1배에 그친다고 정직하게 밝힙니다. 이미 컨텍스트 창에 들어가는 작은 프로젝트에서 그래프의 가치는 압축이 아니라 구조적 명료성입니다. 둘째, 의미 추출(Pass 3)은 LLM에 의존하므로 그 부분의 정밀도는 모델 품질에 좌우되며, graphify는 이를 신뢰도 라벨과 추천 검토 질문으로 보완할 뿐 완전히 제거하지는 않습니다. INFERRED·AMBIGUOUS 엣지는 본질적으로 "검토가 필요한 추측"입니다. 셋째, 파일 수집은 민감 파일을 차단하지만, HTTP MCP 서버를 0.0.0.0으로 노출하는 경우처럼 운영자가 직접 외부에 여는 시나리오에서는 --api-key를 함께 거는 책임이 사용자에게 있습니다. 넷째, 코드 추출의 교차 파일 호출 해소는 라벨 매칭에 기반하므로, 모호한 이름이나 언어 경계에서는 의도적으로 엣지를 만들지 않습니다. 재현성 측면에서는 저장소의 worked/ 폴더가 실제 입력과 산출물 (GRAPH_REPORT.md, graph.json)을 함께 담고 있어 누구나 결과를 직접 검증할 수 있습니다.
결론
graphify는 "코드베이스를 지식 그래프로 만든다"는 약속을 구체적인 공학으로 풀어낸 도구입니다. 핵심을 정리하면 다음과 같습니다.
- 명료한 파이프라인:
detect → extract → build → cluster → analyze → report → export, 각 단계는 부수효과 없는 단일 함수이고 딕셔너리·NetworkX 그래프로만 소통합니다. - 비용 분리: 코드는 tree-sitter로 로컬·무료·결정론적으로 추출하고, LLM은 문서·논문·이미지에만 씁니다. 코드만 있는 코퍼스는 LLM 패스를 통째로 건너뜁니다.
- 신뢰도 우선: 모든 관계에 EXTRACTED/INFERRED/AMBIGUOUS 라벨을 달아 "찾은 것"과 "추측한 것"을 구분하고, 추측은 점수와 검토 질문으로 노출합니다.
- 결정론적 질의:
query와 MCP 도구는 IDF 점수·시드 선택·BFS/DFS로 서브그래프를 뽑습니다. 질의에는 LLM이 없어 빠르고 무료이며, 비용은 첫 추출 때 이미 치른 것입니다. - 팀 친화:
graphify-out/을 커밋하고, 커밋 훅으로 자동 재빌드하며,graph.json은 merge driver로 충돌 없이 합쳐집니다.
grep과 RAG가 매 질문마다 코드를 재발견한다면, graphify는 그 발견을 한 번 그래프로 굳혀 두고 이후의 질문이 그 자산 위에서 값싸게 굴러가게 합니다. 신뢰도 라벨과 결정론적 질의 경로는 "AI가 코드베이스를 어떻게 기억할 것인가"라는 질문에 대한 graphify의 분명한 입장입니다.
이 글에서 다루지 못한 부분
분량상 다음 모듈은 본문에서 깊게 다루지 못했습니다. callflow_html.py/tree_html.py(Mermaid 아키텍처·콜플로우 및 D3 트리 HTML 생성), prs.py(GitHub PR 대시보드·트리아지), watch.py(폴더 감시 자동 재빌드), scip_ingest.py/mcp_ingest.py/manifest_ingest.py(SCIP 인덱스·MCP 설정· 매니페스트 수집), pg_introspect.py/cargo_introspect.py(라이브 PostgreSQL·Cargo 인트로스펙션), wiki.py(그래프→마크다운 위키), semantic_cleanup.py·dedup.py의 후처리 세부.