Spaces:
Sleeping
Sleeping
update nodes with changes lost during refactoring
Browse files- src/langgraph_logic/nodes.py +251 -57
src/langgraph_logic/nodes.py
CHANGED
|
@@ -36,39 +36,85 @@ logging.basicConfig(
|
|
| 36 |
# --- GitHub API Functions ---
|
| 37 |
|
| 38 |
def fetch_pr_code_changes(repo_name: str, pr_id: int) -> Tuple[Optional[str], Optional[Dict[str, str]], Optional[str], Optional[str]]:
|
| 39 |
-
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
github_token = git_hub_token
|
|
|
|
| 41 |
if not github_token:
|
| 42 |
-
|
|
|
|
| 43 |
|
| 44 |
try:
|
| 45 |
g = Github(github_token)
|
| 46 |
repo = g.get_repo(repo_name)
|
| 47 |
pull_request = repo.get_pull(pr_id)
|
|
|
|
|
|
|
| 48 |
head_commit_sha = pull_request.head.sha
|
|
|
|
| 49 |
|
|
|
|
|
|
|
|
|
|
| 50 |
patch_url = pull_request.patch_url
|
| 51 |
headers = {"Authorization": f"token {github_token}"}
|
| 52 |
raw_diff_content = requests.get(patch_url, headers=headers).text
|
| 53 |
|
|
|
|
| 54 |
file_contents: Dict[str, str] = {}
|
| 55 |
for file in pull_request.get_files():
|
|
|
|
| 56 |
if file.status == 'deleted':
|
| 57 |
file_contents[file.filename] = "[FILE DELETED]"
|
| 58 |
continue
|
|
|
|
| 59 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
file_content_obj = repo.get_contents(file.filename, ref=pull_request.head.sha)
|
|
|
|
| 61 |
if isinstance(file_content_obj, list):
|
|
|
|
| 62 |
file_contents[file.filename] = "[DIRECTORY OR MULTIPLE FILES]"
|
| 63 |
continue
|
|
|
|
| 64 |
file_contents[file.filename] = file_content_obj.decoded_content.decode('utf-8')
|
|
|
|
| 65 |
except GithubException as e:
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
return None, None, None, error_msg
|
| 73 |
|
| 74 |
def post_review_comments_on_github(
|
|
@@ -151,29 +197,95 @@ def update_submitted_review_body(
|
|
| 151 |
# --- LLM and Parsing Functions ---
|
| 152 |
|
| 153 |
def generate_code_review_markdown(code_diff: str, file_contents: Dict[str, str]) -> str:
|
| 154 |
-
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 155 |
full_contents_str = ""
|
| 156 |
if file_contents:
|
| 157 |
for filename, content in file_contents.items():
|
|
|
|
| 158 |
full_contents_str += f"--- Full Content of {filename} ---\n```python\n{content}\n```\n\n"
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 164 |
review_chain = prompt | llm
|
|
|
|
|
|
|
| 165 |
try:
|
| 166 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 167 |
except Exception as e:
|
| 168 |
-
|
|
|
|
|
|
|
| 169 |
|
|
|
|
| 170 |
def _extract_suggestion(text: str) -> Tuple[Optional[str], str]:
|
|
|
|
| 171 |
suggestion_match = re.search(r"```suggestion\n([\s\S]*?)\n```", text, re.MULTILINE)
|
| 172 |
suggestion_code = suggestion_match.group(1).strip() if suggestion_match else None
|
|
|
|
|
|
|
| 173 |
cleaned_message = re.sub(r"```suggestion[\s\S]*?```", "", text).strip()
|
| 174 |
return suggestion_code, cleaned_message
|
| 175 |
|
|
|
|
| 176 |
def _parse_bullet_comments(text_block: str) -> List[ParsedComment]:
|
|
|
|
| 177 |
comments = []
|
| 178 |
comment_matches = re.finditer(r"(^ *[-*]\s*[\s\S]*?)(?=\n *[-*]\s*|\Z)", text_block, re.MULTILINE | re.DOTALL)
|
| 179 |
for cm in comment_matches:
|
|
@@ -290,72 +402,154 @@ def parse_llm_review_markdown(markdown_review: str) -> LLMReviewOutput:
|
|
| 290 |
|
| 291 |
# --- Graph Nodes ---
|
| 292 |
|
| 293 |
-
def code_retriever_node(state:
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 305 |
# --- DEBUG LOGGING START ---
|
| 306 |
print("\n" + "="*50)
|
| 307 |
print("--- RAW LLM MARKDOWN OUTPUT ---")
|
| 308 |
print(review_markdown)
|
| 309 |
print("="*50 + "\n")
|
| 310 |
# --- DEBUG LOGGING END ---
|
| 311 |
-
return state.model_copy(update={"review_status": "code_reviewed", "llm_markdown_review": review_markdown})
|
| 312 |
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 318 |
|
| 319 |
def post_code_review_node(state: PRReviewState) -> PRReviewState:
|
| 320 |
-
"""
|
| 321 |
-
|
|
|
|
|
|
|
| 322 |
if not state.parsed_llm_review_data:
|
| 323 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 324 |
try:
|
|
|
|
| 325 |
result = post_review_comments_on_github(
|
| 326 |
-
repo_name=
|
| 327 |
-
|
|
|
|
|
|
|
| 328 |
)
|
|
|
|
|
|
|
| 329 |
return state.model_copy(update={
|
| 330 |
-
"review_status": "initial_review_posted",
|
| 331 |
-
"
|
|
|
|
|
|
|
|
|
|
| 332 |
})
|
| 333 |
except Exception as e:
|
| 334 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 335 |
|
| 336 |
def update_review_body_based_on_human_input_node(state: PRReviewState) -> PRReviewState:
|
| 337 |
-
"""
|
| 338 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 339 |
if not state.require_human_approval:
|
|
|
|
|
|
|
| 340 |
return state
|
| 341 |
-
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 348 |
else:
|
| 349 |
-
|
|
|
|
| 350 |
|
| 351 |
try:
|
|
|
|
| 352 |
result = update_submitted_review_body(
|
| 353 |
-
repo_name=
|
| 354 |
-
|
|
|
|
|
|
|
|
|
|
| 355 |
)
|
|
|
|
|
|
|
| 356 |
return state.model_copy(update={
|
| 357 |
-
"review_status": "review_submitted",
|
| 358 |
-
"
|
|
|
|
|
|
|
|
|
|
| 359 |
})
|
| 360 |
except Exception as e:
|
| 361 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
# --- GitHub API Functions ---
|
| 37 |
|
| 38 |
def fetch_pr_code_changes(repo_name: str, pr_id: int) -> Tuple[Optional[str], Optional[Dict[str, str]], Optional[str], Optional[str]]:
|
| 39 |
+
"""
|
| 40 |
+
Fetches the raw diff content, the full contents of changed files,
|
| 41 |
+
and the head commit SHA for a given PR.
|
| 42 |
+
Args:
|
| 43 |
+
repo_name (str): The full name of the repository (e.g., "octocat/Spoon-Knife").
|
| 44 |
+
pr_id (int): The ID of the Pull Request.
|
| 45 |
+
Returns:
|
| 46 |
+
Tuple[Optional[str], Optional[Dict[str, str]], Optional[str], Optional[str]]:
|
| 47 |
+
- raw_diff_content (str or None): The raw diff content of the PR.
|
| 48 |
+
- file_contents (Dict[str, str] or None): Dictionary mapping filename to its full content (after changes).
|
| 49 |
+
- head_commit_sha (str or None): The SHA of the head commit of the PR.
|
| 50 |
+
- error_message (str or None): An error message if something went wrong.
|
| 51 |
+
"""
|
| 52 |
+
#github_token = os.getenv("GITHUB_TOKEN")
|
| 53 |
github_token = git_hub_token
|
| 54 |
+
|
| 55 |
if not github_token:
|
| 56 |
+
print("Error: GITHUB_TOKEN environment variable not set.")
|
| 57 |
+
return None, None, None, "GitHub token not found in environment variables."
|
| 58 |
|
| 59 |
try:
|
| 60 |
g = Github(github_token)
|
| 61 |
repo = g.get_repo(repo_name)
|
| 62 |
pull_request = repo.get_pull(pr_id)
|
| 63 |
+
|
| 64 |
+
# --- NEW: Get the head commit SHA ---
|
| 65 |
head_commit_sha = pull_request.head.sha
|
| 66 |
+
print(f"Fetched PR {pr_id} head commit SHA: {head_commit_sha}")
|
| 67 |
|
| 68 |
+
|
| 69 |
+
# 1. Fetch raw diff content (patch)
|
| 70 |
+
# Using requests directly for patch_url is good as PyGithub's get_patch() can sometimes be rate-limited differently
|
| 71 |
patch_url = pull_request.patch_url
|
| 72 |
headers = {"Authorization": f"token {github_token}"}
|
| 73 |
raw_diff_content = requests.get(patch_url, headers=headers).text
|
| 74 |
|
| 75 |
+
# 2. Fetch full content of changed files
|
| 76 |
file_contents: Dict[str, str] = {}
|
| 77 |
for file in pull_request.get_files():
|
| 78 |
+
# Skip files that were deleted, as their content cannot be retrieved from the current head.
|
| 79 |
if file.status == 'deleted':
|
| 80 |
file_contents[file.filename] = "[FILE DELETED]"
|
| 81 |
continue
|
| 82 |
+
|
| 83 |
try:
|
| 84 |
+
# We want the content *after* the change, which is from the PR's head branch.
|
| 85 |
+
# PyGithub's get_contents should be called with `ref` set to `pull_request.head.ref`
|
| 86 |
+
# or `pull_request.head.sha` for explicit content at the PR's head.
|
| 87 |
+
# Using pull_request.head.sha is more robust as ref might change.
|
| 88 |
file_content_obj = repo.get_contents(file.filename, ref=pull_request.head.sha)
|
| 89 |
+
|
| 90 |
if isinstance(file_content_obj, list):
|
| 91 |
+
print(f"Warning: '{file.filename}' is a directory or multiple files, skipping content retrieval for now.")
|
| 92 |
file_contents[file.filename] = "[DIRECTORY OR MULTIPLE FILES]"
|
| 93 |
continue
|
| 94 |
+
|
| 95 |
file_contents[file.filename] = file_content_obj.decoded_content.decode('utf-8')
|
| 96 |
+
|
| 97 |
except GithubException as e:
|
| 98 |
+
print(f"Warning: GitHub API error fetching content for {file.filename} (PR {pr_id}, Repo {repo_name}): {e.status} - {e.data.get('message', 'No message')}")
|
| 99 |
+
file_contents[file.filename] = f"[ERROR: Could not fetch content. Status: {e.status}, Message: {e.data.get('message', 'No message')}]"
|
| 100 |
+
except Exception as e:
|
| 101 |
+
print(f"Unexpected error fetching content for {file.filename} (PR {pr_id}, Repo {repo_name}): {e}")
|
| 102 |
+
file_contents[file.filename] = f"[ERROR: Unexpected error fetching content: {e}]"
|
| 103 |
+
|
| 104 |
+
# Return the new head_commit_sha along with existing returns
|
| 105 |
+
return raw_diff_content, file_contents, head_commit_sha, None # No error message if successful
|
| 106 |
+
|
| 107 |
+
except UnknownObjectException as e:
|
| 108 |
+
error_msg = f"GitHub object not found (repo or PR): {e.data.get('message', 'No message')}"
|
| 109 |
+
print(f"Error in fetch_pr_code_changes: {error_msg}")
|
| 110 |
+
return None, None, None, error_msg
|
| 111 |
+
except GithubException as e:
|
| 112 |
+
error_msg = f"GitHub API error for PR {pr_id} from {repo_name}: {e.status} - {e.data.get('message', 'No message')}"
|
| 113 |
+
print(f"Error in fetch_pr_code_changes: {error_msg}")
|
| 114 |
+
return None, None, None, error_msg
|
| 115 |
+
except Exception as e:
|
| 116 |
+
error_msg = f"An unexpected error occurred while fetching PR {pr_id} from {repo_name}: {e}"
|
| 117 |
+
print(f"Error in fetch_pr_code_changes: {error_msg}")
|
| 118 |
return None, None, None, error_msg
|
| 119 |
|
| 120 |
def post_review_comments_on_github(
|
|
|
|
| 197 |
# --- LLM and Parsing Functions ---
|
| 198 |
|
| 199 |
def generate_code_review_markdown(code_diff: str, file_contents: Dict[str, str]) -> str:
|
| 200 |
+
"""
|
| 201 |
+
Generates a detailed, human-readable code review in Markdown format from the LLM.
|
| 202 |
+
The prompt is designed to elicit structured Markdown output that can then be
|
| 203 |
+
parsed for GitHub PR comments, grouped by file and function.
|
| 204 |
+
Args:
|
| 205 |
+
code_diff (str): The string representation of the code diff.
|
| 206 |
+
file_contents (Dict[str, str]): A dictionary where keys are file paths
|
| 207 |
+
and values are their full content.
|
| 208 |
+
Returns:
|
| 209 |
+
str: A Markdown string representing the code review.
|
| 210 |
+
"""
|
| 211 |
+
|
| 212 |
+
# Prepare full contents context
|
| 213 |
full_contents_str = ""
|
| 214 |
if file_contents:
|
| 215 |
for filename, content in file_contents.items():
|
| 216 |
+
# Add a clear separator and Markdown code block for each file
|
| 217 |
full_contents_str += f"--- Full Content of {filename} ---\n```python\n{content}\n```\n\n"
|
| 218 |
+
else:
|
| 219 |
+
full_contents_str = "No full file contents provided for additional context."
|
| 220 |
+
|
| 221 |
+
# Construct the Prompt Template
|
| 222 |
+
prompt = ChatPromptTemplate.from_messages(
|
| 223 |
+
[
|
| 224 |
+
("system",
|
| 225 |
+
"You are an expert Senior Software Engineer and a meticulous code reviewer.\n"
|
| 226 |
+
"Your task is to review the provided code changes in a Pull Request.\n"
|
| 227 |
+
"Analyze the `code_diff` for potential bugs, performance issues, security vulnerabilities, code style violations, maintainability concerns, and missing tests or documentation.\n"
|
| 228 |
+
"Refer to the `full_file_contents` for additional context if the diff alone is insufficient to understand the changes or their implications.\n"
|
| 229 |
+
"Provide a comprehensive, actionable, and constructive review.\n"
|
| 230 |
+
"Format your review clearly using Markdown. Structure it with the following top-level sections:\n"
|
| 231 |
+
"1. **Overall Impression:** A brief summary of the PR's purpose and overall quality.\n"
|
| 232 |
+
"2. **Specific Observations and Suggestions:** Detailed feedback, grouped by file.\n"
|
| 233 |
+
" - Within each file's section, group related comments, ideally by function or logical block.\n"
|
| 234 |
+
" - For each observation/suggestion, include relevant line numbers from the *new* file for context (e.g., 'Line X-Y:').\n"
|
| 235 |
+
"3. **Potential Issues and Edge Cases:** Discuss any missed scenarios or potential problems.\n"
|
| 236 |
+
"4. **Security Implications:** Highlight any security concerns.\n"
|
| 237 |
+
"5. **Adherence to Best Practices (PEP 8):** Comment on style and best practice compliance.\n"
|
| 238 |
+
"6. **Performance Considerations:** Discuss performance aspects.\n"
|
| 239 |
+
"7. **Unit Testing Suggestions:** Recommend additional tests.\n"
|
| 240 |
+
"8. **Docstring/Comment Improvements:** Suggest documentation enhancements.\n"
|
| 241 |
+
"9. **Clarity and Conciseness:** Feedback on code readability.\n"
|
| 242 |
+
"10. **Summary:** A concise conclusion and recommended action (e.g., 'Approve', 'Request Changes', 'Comment').\n\n"
|
| 243 |
+
"For code suggestions, use GitHub's Markdown code block with 'suggestion' annotation, like this:\n"
|
| 244 |
+
"```suggestion\n"
|
| 245 |
+
"your_suggested_code_here\n"
|
| 246 |
+
"```\n"
|
| 247 |
+
"Ensure file paths are correctly formatted (e.g., `src/utils/data_processor.py`)."
|
| 248 |
+
),
|
| 249 |
+
("human",
|
| 250 |
+
"Here are the code changes (diff):\n"
|
| 251 |
+
"```diff\n"
|
| 252 |
+
"{code_diff}\n"
|
| 253 |
+
"```\n\n"
|
| 254 |
+
"Here are the full contents of the changed files (for additional context, use only if necessary to understand the diff):\n"
|
| 255 |
+
"{full_contents_context}\n\n"
|
| 256 |
+
"Please provide your structured code review in Markdown."
|
| 257 |
+
),
|
| 258 |
+
]
|
| 259 |
+
)
|
| 260 |
+
|
| 261 |
+
# Create the Chain
|
| 262 |
review_chain = prompt | llm
|
| 263 |
+
|
| 264 |
+
# Invoke the Chain
|
| 265 |
try:
|
| 266 |
+
review_markdown = review_chain.invoke({
|
| 267 |
+
"code_diff": code_diff,
|
| 268 |
+
"full_contents_context": full_contents_str
|
| 269 |
+
}).content # Access the content attribute for Chat model output
|
| 270 |
+
return review_markdown
|
| 271 |
except Exception as e:
|
| 272 |
+
print(f"Error generating code review: {e}")
|
| 273 |
+
return f"Error: Could not generate code review. {e}\n\n" \
|
| 274 |
+
f"Please check the LLM API call or token limits."
|
| 275 |
|
| 276 |
+
# Helper to extract suggestion block and clean message (No change needed)
|
| 277 |
def _extract_suggestion(text: str) -> Tuple[Optional[str], str]:
|
| 278 |
+
"""Helper to extract suggestion block and clean message."""
|
| 279 |
suggestion_match = re.search(r"```suggestion\n([\s\S]*?)\n```", text, re.MULTILINE)
|
| 280 |
suggestion_code = suggestion_match.group(1).strip() if suggestion_match else None
|
| 281 |
+
|
| 282 |
+
# Remove suggestion from the main message
|
| 283 |
cleaned_message = re.sub(r"```suggestion[\s\S]*?```", "", text).strip()
|
| 284 |
return suggestion_code, cleaned_message
|
| 285 |
|
| 286 |
+
# Helper to parse bullet-point comments (No change needed, already uses ParsedComment)
|
| 287 |
def _parse_bullet_comments(text_block: str) -> List[ParsedComment]:
|
| 288 |
+
"""Helper to parse bullet-point comments from a given text block."""
|
| 289 |
comments = []
|
| 290 |
comment_matches = re.finditer(r"(^ *[-*]\s*[\s\S]*?)(?=\n *[-*]\s*|\Z)", text_block, re.MULTILINE | re.DOTALL)
|
| 291 |
for cm in comment_matches:
|
|
|
|
| 402 |
|
| 403 |
# --- Graph Nodes ---
|
| 404 |
|
| 405 |
+
def code_retriever_node(state:PRReviewState):
|
| 406 |
+
repo_name = state.repo_name
|
| 407 |
+
pull_req_id = state.pr_id
|
| 408 |
+
|
| 409 |
+
print(f"code_retriever_node started")
|
| 410 |
+
print(f"repo_name :{repo_name}-------- pull_req_id:{pull_req_id}")
|
| 411 |
+
|
| 412 |
+
diff, contents,head_commit_sha, error = fetch_pr_code_changes(repo_name, pull_req_id)
|
| 413 |
+
|
| 414 |
+
# Don't forget to return an updated state, as nodes in LangGraph should always do
|
| 415 |
+
# For this simple example, we'll just return a copy with an updated status
|
| 416 |
+
updated_state = state.model_copy(update={
|
| 417 |
+
"review_status": "code_fetched", # Update status after retrieval logic
|
| 418 |
+
"code_diff": diff,
|
| 419 |
+
"file_contents": contents
|
| 420 |
+
})
|
| 421 |
+
return updated_state
|
| 422 |
+
|
| 423 |
+
def code_reviewer_node(state:PRReviewState):
|
| 424 |
+
code_diff = state.code_diff
|
| 425 |
+
file_contents = state.file_contents
|
| 426 |
+
|
| 427 |
+
print(f"code_reviewer_node started")
|
| 428 |
+
|
| 429 |
+
review_markdown = generate_code_review_markdown(code_diff, file_contents)
|
| 430 |
+
|
| 431 |
# --- DEBUG LOGGING START ---
|
| 432 |
print("\n" + "="*50)
|
| 433 |
print("--- RAW LLM MARKDOWN OUTPUT ---")
|
| 434 |
print(review_markdown)
|
| 435 |
print("="*50 + "\n")
|
| 436 |
# --- DEBUG LOGGING END ---
|
|
|
|
| 437 |
|
| 438 |
+
# Don't forget to return an updated state, as nodes in LangGraph should always do
|
| 439 |
+
# For this simple example, we'll just return a copy with an updated status
|
| 440 |
+
updated_state = state.model_copy(update={
|
| 441 |
+
"review_status": "code_reviewed", # Update status after retrieval logic
|
| 442 |
+
"llm_markdown_review":review_markdown,
|
| 443 |
+
})
|
| 444 |
+
return updated_state
|
| 445 |
+
|
| 446 |
+
def feedback_formatter_node(state: PRReviewState):
|
| 447 |
+
print(f"feedback_formatter_node started")
|
| 448 |
+
llm_markdown_review = state.llm_markdown_review
|
| 449 |
+
|
| 450 |
+
parsed_llm_review_data = parse_llm_review_markdown(llm_markdown_review)
|
| 451 |
+
|
| 452 |
+
# Don't forget to return an updated state, as nodes in LangGraph should always do
|
| 453 |
+
# For this simple example, we'll just return a copy with an updated status
|
| 454 |
+
updated_state = state.model_copy(update={
|
| 455 |
+
"review_status": "review_parsed" ,# Update status after retrieval logic
|
| 456 |
+
"parsed_llm_review_data":parsed_llm_review_data,
|
| 457 |
+
})
|
| 458 |
+
return updated_state
|
| 459 |
|
| 460 |
def post_code_review_node(state: PRReviewState) -> PRReviewState:
|
| 461 |
+
"""
|
| 462 |
+
Posts the LLM-generated review as a PENDING GitHub review.
|
| 463 |
+
"""
|
| 464 |
+
print("--- NODE: post_code_review_node ---")
|
| 465 |
if not state.parsed_llm_review_data:
|
| 466 |
+
raise ValueError("Cannot post pending review: parsed_llm_review_data is missing.")
|
| 467 |
+
|
| 468 |
+
repo_name = state.repo_name
|
| 469 |
+
pr_id = state.pr_id
|
| 470 |
+
parsed_llm_review_data = state.parsed_llm_review_data
|
| 471 |
+
|
| 472 |
try:
|
| 473 |
+
# Call the helper to post as PENDING
|
| 474 |
result = post_review_comments_on_github(
|
| 475 |
+
repo_name=repo_name,
|
| 476 |
+
pr_id=pr_id,
|
| 477 |
+
parsed_review_data=parsed_llm_review_data,
|
| 478 |
+
github_token=git_hub_token,
|
| 479 |
)
|
| 480 |
+
|
| 481 |
+
print(f"result from post_pending_review_node():result = {result}")
|
| 482 |
return state.model_copy(update={
|
| 483 |
+
"review_status": "initial_review_posted",
|
| 484 |
+
"original_review_id": result['review_id'],
|
| 485 |
+
"original_review_url": result['review_url'],
|
| 486 |
+
"main_comment_body": result['main_comment_body'],
|
| 487 |
+
"last_error": None # Clear previous errors
|
| 488 |
})
|
| 489 |
except Exception as e:
|
| 490 |
+
#logging.error(f"Error posting pending review: {e}")
|
| 491 |
+
print(f"Error posting pending review: {e}")
|
| 492 |
+
return state.model_copy(update={
|
| 493 |
+
"review_status": "error",
|
| 494 |
+
"last_error": f"Failed to post pending review: {e}"
|
| 495 |
+
})
|
| 496 |
+
|
| 497 |
|
| 498 |
def update_review_body_based_on_human_input_node(state: PRReviewState) -> PRReviewState:
|
| 499 |
+
"""
|
| 500 |
+
Posts the LLM-generated review as a PENDING GitHub review.
|
| 501 |
+
"""
|
| 502 |
+
print("--- NODE: update_review_body_based_on_human_input_node ---")
|
| 503 |
+
if not state.main_comment_body:
|
| 504 |
+
raise ValueError("Cannot update submitted review body: main_comment_body is missing.")
|
| 505 |
+
|
| 506 |
if not state.require_human_approval:
|
| 507 |
+
print("require_human_approval is False so exiting this function")
|
| 508 |
+
logging.info("require_human_approval is False so exiting this function")
|
| 509 |
return state
|
| 510 |
+
|
| 511 |
+
repo_name = state.repo_name
|
| 512 |
+
pr_id = state.pr_id
|
| 513 |
+
original_review_id = state.original_review_id
|
| 514 |
+
main_comment_body = state.main_comment_body
|
| 515 |
+
|
| 516 |
+
if state.human_approval_status is True:
|
| 517 |
+
if state.human_feedback_message is not None:
|
| 518 |
+
updated_review_body = f"""**Human Decision:** approved\n---\n\nHuman Feedback: {state.human_feedback_message}\n\n Please go ahead and incorporate these Automated Bots review comments\n\n{main_comment_body}"""
|
| 519 |
+
else:
|
| 520 |
+
updated_review_body = f"""**Human Decision:** approved\n---\n\n Please go ahead and incorporate these Automated Bots review comments\n\n{main_comment_body}"""
|
| 521 |
+
|
| 522 |
+
elif state.human_approval_status is False:
|
| 523 |
+
if state.human_feedback_message is not None:
|
| 524 |
+
updated_review_body = f"""**Human Decision:** Reject\n---\n\nHuman Feedback: {state.human_feedback_message}\n\n Please IGNORE these Automated Bots review comments and wait for new review comments from your team\n\n{main_comment_body}"""
|
| 525 |
+
else:
|
| 526 |
+
updated_review_body = f"""**Human Decision:** Reject\n---\n\nPlease IGNORE these Automated Bots review comments and wait for new review comments from your team\n\n{main_comment_body}"""
|
| 527 |
else:
|
| 528 |
+
return state
|
| 529 |
+
|
| 530 |
|
| 531 |
try:
|
| 532 |
+
# Call the helper to post as PENDING
|
| 533 |
result = update_submitted_review_body(
|
| 534 |
+
repo_name=repo_name,
|
| 535 |
+
pr_id=pr_id,
|
| 536 |
+
review_id = original_review_id,
|
| 537 |
+
new_body =updated_review_body,
|
| 538 |
+
github_token=git_hub_token
|
| 539 |
)
|
| 540 |
+
|
| 541 |
+
print(f"result from update_submitted_review_body_node():result = {result}")
|
| 542 |
return state.model_copy(update={
|
| 543 |
+
"review_status": "review_submitted",
|
| 544 |
+
"final_review_id": result['review_id'],
|
| 545 |
+
"final_review_url": result['review_url'],
|
| 546 |
+
"main_comment_body": result['updated_body'],
|
| 547 |
+
"last_error": None # Clear previous errors
|
| 548 |
})
|
| 549 |
except Exception as e:
|
| 550 |
+
#logging.error(f"Error posting pending review: {e}")
|
| 551 |
+
print(f"Error posting pending review: {e}")
|
| 552 |
+
return state.model_copy(update={
|
| 553 |
+
"review_status": "error",
|
| 554 |
+
"last_error": f"Failed to post pending review: {e}"
|
| 555 |
+
})
|