141 lines
4.9 KiB
Python
141 lines
4.9 KiB
Python
"""Docs Manager — 작업 기록 + Wiki 문서 관리.
|
|
|
|
모든 작업은 docs/에 기록되며, Gemini 호출 시
|
|
문서 경로와 기존 문서 목록을 프롬프트에 주입합니다.
|
|
"""
|
|
|
|
import logging
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
|
|
logger = logging.getLogger("variet.docs")
|
|
|
|
|
|
class DocsManager:
|
|
"""프로젝트 문서 관리자."""
|
|
|
|
def __init__(self, project_path: str, docs_subpath: str = "docs/wiki"):
|
|
self.project_path = Path(project_path)
|
|
self.docs_path = self.project_path / docs_subpath
|
|
self.sessions_path = self.project_path / "docs" / "sessions"
|
|
self.changelog_path = self.project_path / "docs" / "changelog.md"
|
|
|
|
# 디렉토리 생성
|
|
self.docs_path.mkdir(parents=True, exist_ok=True)
|
|
self.sessions_path.mkdir(parents=True, exist_ok=True)
|
|
|
|
def get_docs_index(self) -> str:
|
|
"""docs/wiki 내 문서 목록을 문자열로 반환 (프롬프트 주입용)."""
|
|
if not self.docs_path.exists():
|
|
return "문서 없음"
|
|
|
|
files = sorted(self.docs_path.glob("*.md"))
|
|
if not files:
|
|
return "문서 없음"
|
|
|
|
lines = ["=== PROJECT DOCS ==="]
|
|
for f in files:
|
|
size = f.stat().st_size
|
|
lines.append(f" - {f.name} ({size}B)")
|
|
lines.append(f"경로: {self.docs_path}")
|
|
lines.append("=== END DOCS ===")
|
|
|
|
return "\n".join(lines)
|
|
|
|
def get_doc_content(self, filename: str) -> str:
|
|
"""특정 문서 내용 반환."""
|
|
path = self.docs_path / filename
|
|
if path.exists():
|
|
return path.read_text(encoding="utf-8", errors="ignore")
|
|
return ""
|
|
|
|
def get_all_docs_content(self, max_total_bytes: int = 30000) -> str:
|
|
"""전체 문서 내용 반환 (예산 내에서)."""
|
|
files = sorted(self.docs_path.glob("*.md"))
|
|
parts = ["=== PROJECT KNOWLEDGE BASE ===\n"]
|
|
total = 0
|
|
|
|
for f in files:
|
|
content = f.read_text(encoding="utf-8", errors="ignore")
|
|
if total + len(content.encode("utf-8")) > max_total_bytes:
|
|
parts.append(f"\n--- {f.name} (예산 초과, 생략) ---")
|
|
continue
|
|
parts.append(f"\n--- {f.name} ---\n{content}")
|
|
total += len(content.encode("utf-8"))
|
|
|
|
parts.append("\n=== END KNOWLEDGE BASE ===")
|
|
return "\n".join(parts)
|
|
|
|
def record_session(self, request: str, summary: dict, plan: dict = None) -> str:
|
|
"""작업 세션을 기록."""
|
|
try:
|
|
self.sessions_path.mkdir(parents=True, exist_ok=True)
|
|
now = datetime.now()
|
|
filename = f"{now.strftime('%Y-%m-%d_%H%M%S')}.md"
|
|
filepath = self.sessions_path / filename
|
|
|
|
lines = [
|
|
f"# 작업 기록 - {now.strftime('%Y-%m-%d %H:%M')}",
|
|
"",
|
|
f"## 요청",
|
|
f"{request}",
|
|
"",
|
|
]
|
|
|
|
if plan:
|
|
lines.extend([
|
|
f"## 계획",
|
|
f"{plan.get('summary', str(plan)[:300])}",
|
|
"",
|
|
])
|
|
|
|
if isinstance(summary, dict):
|
|
lines.extend([
|
|
f"## 결과",
|
|
f"{summary.get('summary', str(summary)[:300])}",
|
|
"",
|
|
])
|
|
|
|
changes = summary.get("changes", [])
|
|
if changes:
|
|
lines.append("## 변경 파일")
|
|
for c in changes:
|
|
if isinstance(c, dict):
|
|
lines.append(f"- `{c.get('file', '?')}` - {c.get('description', '')}")
|
|
else:
|
|
lines.append(f"- {c}")
|
|
lines.append("")
|
|
|
|
warnings = summary.get("warnings", [])
|
|
if warnings:
|
|
lines.append("## 주의사항")
|
|
for w in warnings:
|
|
lines.append(f"- {w}")
|
|
lines.append("")
|
|
|
|
filepath.write_text("\n".join(lines), encoding="utf-8")
|
|
logger.info(f"세션 기록: {filepath}")
|
|
return str(filepath)
|
|
except Exception as e:
|
|
logger.warning(f"세션 기록 실패 (무시): {e}")
|
|
return ""
|
|
|
|
def append_changelog(self, entry: str):
|
|
"""Changelog에 항목 추가."""
|
|
now = datetime.now().strftime("%Y-%m-%d %H:%M")
|
|
line = f"\n- [{now}] {entry}"
|
|
|
|
if self.changelog_path.exists():
|
|
content = self.changelog_path.read_text(encoding="utf-8")
|
|
else:
|
|
content = "# Changelog\n"
|
|
|
|
content += line
|
|
self.changelog_path.write_text(content, encoding="utf-8")
|
|
|
|
def update_wiki(self, filename: str, content: str):
|
|
"""Wiki 문서 생성/수정."""
|
|
path = self.docs_path / filename
|
|
path.write_text(content, encoding="utf-8")
|
|
logger.info(f"Wiki 업데이트: {path}")
|