90 lines
3.0 KiB
Python
90 lines
3.0 KiB
Python
"""Context Manager — 관련 파일 선별 + 토큰 예산 제어.
|
|
|
|
Gemini CLI Context Rot 해결의 핵심.
|
|
태스크에 필요한 파일만 골라 토큰 예산 내에서 컨텍스트를 구성.
|
|
"""
|
|
|
|
from pathlib import Path
|
|
from core.project_indexer import ProjectIndex
|
|
|
|
|
|
# 대략적 토큰 추정: 1 토큰 ≈ 4 bytes (영문), 2 bytes (한글)
|
|
BYTES_PER_TOKEN = 3
|
|
|
|
|
|
class ContextManager:
|
|
"""태스크별 컨텍스트를 생성합니다."""
|
|
|
|
def __init__(self, index: ProjectIndex, token_budget: int = 50_000):
|
|
self.index = index
|
|
self.token_budget = token_budget
|
|
|
|
def gather(self, task: str, max_files: int = 15) -> str:
|
|
"""태스크에 필요한 파일만 선별하여 컨텍스트 생성."""
|
|
# 1. 태스크에서 관련 파일 찾기
|
|
relevant = self.index.find_relevant(task)
|
|
|
|
# 2. import 관계로 확장
|
|
if relevant:
|
|
expanded = self.index.expand_dependencies(relevant[:5], depth=1)
|
|
else:
|
|
expanded = []
|
|
|
|
# 3. 관련 파일을 우선순위 순으로 정렬 (원래 순서 유지)
|
|
ordered = []
|
|
seen = set()
|
|
for f in relevant + expanded:
|
|
if f not in seen:
|
|
ordered.append(f)
|
|
seen.add(f)
|
|
|
|
# 4. 토큰 예산 내에서 파일 포함
|
|
context_parts = []
|
|
total_tokens = 0
|
|
files_included = 0
|
|
|
|
# 프로젝트 구조 요약 항상 포함
|
|
structure = self.index.get_structure_summary()
|
|
structure_tokens = len(structure.encode("utf-8")) // BYTES_PER_TOKEN
|
|
context_parts.append(f"=== PROJECT STRUCTURE ===\n{structure}")
|
|
total_tokens += structure_tokens
|
|
|
|
for fpath in ordered[:max_files]:
|
|
info = self.index.files.get(fpath)
|
|
if not info:
|
|
continue
|
|
|
|
file_tokens = info.size // BYTES_PER_TOKEN
|
|
if total_tokens + file_tokens > self.token_budget:
|
|
context_parts.append(
|
|
f"\n=== SKIPPED: {fpath} ({info.line_count}L, budget exceeded) ==="
|
|
)
|
|
continue
|
|
|
|
try:
|
|
abs_path = self.index.project_path / fpath
|
|
content = abs_path.read_text(encoding="utf-8", errors="ignore")
|
|
except Exception:
|
|
continue
|
|
|
|
context_parts.append(
|
|
f"\n=== FILE: {fpath} ({info.line_count}L) ===\n{content}"
|
|
)
|
|
total_tokens += file_tokens
|
|
files_included += 1
|
|
|
|
context_parts.append(
|
|
f"\n=== CONTEXT SUMMARY: {files_included} files, ~{total_tokens} tokens ==="
|
|
)
|
|
|
|
return "\n".join(context_parts)
|
|
|
|
def gather_for_review(self, original: str, modified: str, task: str) -> str:
|
|
"""리뷰용 컨텍스트: 원본 + 수정본 + 관련 타입."""
|
|
parts = [
|
|
f"=== TASK: {task} ===",
|
|
f"\n=== ORIGINAL ===\n{original}",
|
|
f"\n=== MODIFIED ===\n{modified}",
|
|
]
|
|
return "\n".join(parts)
|