feat: Planner/Reviewer 에이전트 모드 + 직접 파일 접근
This commit is contained in:
@@ -117,31 +117,33 @@ class TaskPipeline:
|
||||
# 프로젝트 파일 읽기 (공용)
|
||||
# ──────────────────────────────────────────
|
||||
|
||||
def _read_recent_files(self, cutoff_seconds: int = 600) -> list[str]:
|
||||
"""최근 변경된 프로젝트 파일 읽기."""
|
||||
def _read_project_files(self) -> list[str]:
|
||||
"""프로젝트의 모든 텍스트 파일 읽기."""
|
||||
import os
|
||||
import time as _time
|
||||
|
||||
recent_files = []
|
||||
cutoff = _time.time() - cutoff_seconds
|
||||
project_files = []
|
||||
project_root = Path(self.project_path)
|
||||
skip_dirs = {".git", "__pycache__", "node_modules", ".venv", "venv"}
|
||||
skip_dirs = {".git", "__pycache__", "node_modules", ".venv", "venv", "dist", "build"}
|
||||
binary_exts = {".png", ".jpg", ".jpeg", ".gif", ".ico", ".woff", ".woff2", ".ttf",
|
||||
".eot", ".mp3", ".mp4", ".zip", ".tar", ".gz", ".exe", ".dll",
|
||||
".so", ".pyc", ".pyo", ".db", ".sqlite"}
|
||||
|
||||
for root, dirs, files in os.walk(self.project_path):
|
||||
dirs[:] = [d for d in dirs if d not in skip_dirs]
|
||||
for fname in files:
|
||||
fpath = Path(root) / fname
|
||||
if fpath.suffix.lower() in binary_exts:
|
||||
continue
|
||||
try:
|
||||
if fpath.stat().st_mtime > cutoff:
|
||||
rel = fpath.relative_to(project_root)
|
||||
content = fpath.read_text(encoding="utf-8", errors="replace")
|
||||
recent_files.append(
|
||||
f"### {rel}\n```\n{content}\n```"
|
||||
)
|
||||
rel = fpath.relative_to(project_root)
|
||||
content = fpath.read_text(encoding="utf-8", errors="replace")
|
||||
project_files.append(
|
||||
f"### {rel}\n```\n{content}\n```"
|
||||
)
|
||||
except (OSError, UnicodeDecodeError):
|
||||
continue
|
||||
|
||||
return recent_files
|
||||
return project_files
|
||||
|
||||
# ──────────────────────────────────────────
|
||||
# Planner 자가 검증 (오케스트레이션)
|
||||
@@ -151,21 +153,10 @@ class TaskPipeline:
|
||||
self, user_request: str, plan: dict,
|
||||
code_outputs: list[str],
|
||||
) -> dict:
|
||||
"""Planner가 자기 계획의 달성 여부를 검증.
|
||||
"""Planner가 자기 계획의 달성 여부를 에이전트 모드로 검증.
|
||||
|
||||
실제 파일을 읽어서 계획이 충족됐는지 판단합니다.
|
||||
미달이면 추가 태스크를 생성합니다.
|
||||
|
||||
Returns:
|
||||
{
|
||||
"satisfied": bool,
|
||||
"feedback": "미충족 사유",
|
||||
"additional_tasks": [...] (satisfied=false일 때)
|
||||
}
|
||||
프로젝트 디렉토리에서 직접 파일을 읽어서 계획 충족 여부를 판단합니다.
|
||||
"""
|
||||
recent_files = self._read_recent_files()
|
||||
files_section = "\n\n".join(recent_files) if recent_files else "(파일 없음)"
|
||||
|
||||
agent_reports = "\n".join(
|
||||
f"--- Agent {i+1} ---\n{output}"
|
||||
for i, output in enumerate(code_outputs)
|
||||
@@ -175,12 +166,12 @@ class TaskPipeline:
|
||||
f"## 원래 사용자 요청\n{user_request}\n\n"
|
||||
f"## 내가 세운 계획\n{json.dumps(plan, ensure_ascii=False, indent=2)}\n\n"
|
||||
f"## 에이전트 보고\n{agent_reports}\n\n"
|
||||
f"## 현재 프로젝트 파일\n{files_section}\n\n"
|
||||
f"## 판단 요청\n"
|
||||
f"위 계획이 충족되었는지 판단하세요.\n"
|
||||
f"현재 디렉토리의 프로젝트 파일을 직접 읽어서 계획이 충족되었는지 확인하세요.\n"
|
||||
f"필요한 파일만 선택적으로 읽으세요.\n\n"
|
||||
f"충족되었으면 satisfied=true.\n"
|
||||
f"미충족이면 satisfied=false + 부족한 부분을 해결할 추가 태스크를 생성하세요.\n\n"
|
||||
f"JSON 형식:\n"
|
||||
f"반드시 아래 JSON만 출력하세요:\n"
|
||||
f"```json\n"
|
||||
f'{{\n'
|
||||
f' "satisfied": true|false,\n'
|
||||
@@ -192,7 +183,9 @@ class TaskPipeline:
|
||||
f"```"
|
||||
)
|
||||
|
||||
response = await self.gemini.call("planner", prompt, timeout=180)
|
||||
response = await self.gemini.call_agent(
|
||||
"planner", prompt, cwd=self.project_path, timeout=180,
|
||||
)
|
||||
self._log("planner_verify", user_request, response)
|
||||
|
||||
result = self._extract_json(response)
|
||||
@@ -203,7 +196,7 @@ class TaskPipeline:
|
||||
# ──────────────────────────────────────────
|
||||
|
||||
async def batch_review(self, tasks: list[dict], code_outputs: list[str]) -> dict:
|
||||
"""에이전트가 생성/수정한 실제 파일을 리뷰."""
|
||||
"""에이전트 모드로 프로젝트 파일을 직접 읽어 리뷰."""
|
||||
task_summaries = []
|
||||
for i, task in enumerate(tasks):
|
||||
title = task.get("title", task.get("description", f"Task {i+1}"))
|
||||
@@ -213,26 +206,17 @@ class TaskPipeline:
|
||||
for i, output in enumerate(code_outputs):
|
||||
agent_reports.append(f"--- Agent {i+1} 보고 ---\n{output}")
|
||||
|
||||
recent_files = self._read_recent_files()
|
||||
|
||||
if not recent_files:
|
||||
return {
|
||||
"passed": True,
|
||||
"summary": "파일 변경 없음 또는 삭제 작업 - 자동 통과",
|
||||
"issues": [],
|
||||
}
|
||||
|
||||
files_section = "\n\n".join(recent_files)
|
||||
|
||||
prompt = (
|
||||
f"## 요청된 태스크\n{chr(10).join(task_summaries)}\n\n"
|
||||
f"## 에이전트 보고\n{chr(10).join(agent_reports)}\n\n"
|
||||
f"## 실제 생성/수정된 파일\n{files_section}\n\n"
|
||||
f"위 파일들이 태스크 요구사항을 충족하는지 리뷰하세요."
|
||||
f"현재 디렉토리의 프로젝트 파일을 직접 읽어서 리뷰하세요.\n"
|
||||
f"필요한 파일만 선택적으로 확인하세요."
|
||||
)
|
||||
|
||||
response = await self.gemini.call("reviewer", prompt, timeout=180)
|
||||
self._log("batch_review", f"{len(tasks)} tasks, {len(recent_files)} files", response)
|
||||
response = await self.gemini.call_agent(
|
||||
"reviewer", prompt, cwd=self.project_path, timeout=180,
|
||||
)
|
||||
self._log("batch_review", f"{len(tasks)} tasks", response)
|
||||
|
||||
review = self._extract_json(response)
|
||||
return review or {"passed": True, "summary": response, "raw": response}
|
||||
|
||||
Reference in New Issue
Block a user