From f1bbd7ab9f30c7721cc2f7ba4d414e81c8822fbd Mon Sep 17 00:00:00 2001 From: Vic <125237471+vicsanity623@users.noreply.github.com> Date: Wed, 11 Mar 2026 06:55:16 -0700 Subject: [PATCH 1/6] Update autoreviewer.py --- src/pyob/autoreviewer.py | 204 ++++++++++----------------------------- 1 file changed, 50 insertions(+), 154 deletions(-) diff --git a/src/pyob/autoreviewer.py b/src/pyob/autoreviewer.py index 6d084ea..3545b45 100644 --- a/src/pyob/autoreviewer.py +++ b/src/pyob/autoreviewer.py @@ -198,42 +198,59 @@ def get_valid_edit( attempts: int = int(0) use_ollama = False + is_cloud = os.environ.get("GITHUB_ACTIONS") == "true" or os.environ.get("CI") == "true" or "GITHUB_RUN_ID" in os.environ + while True: key = None now = time.time() available_keys = [ k for k, cooldown in self.key_cooldowns.items() if now > cooldown ] + if not available_keys: - if not use_ollama: - logger.warning( - "🚫 All Gemini keys are currently rate-limited. Falling back to Local Ollama." - ) - use_ollama = True + if is_cloud: + logger.warning("☁️ Cloud environment: Gemini keys exhausted/limited. Sleeping 60s for refill...") + time.sleep(60) + attempts += 1 + continue + else: + if not use_ollama: + logger.warning("🚫 Gemini rate-limited. Falling back to Local Ollama.") + use_ollama = True else: use_ollama = False key = available_keys[attempts % len(available_keys)] logger.info( f"\n[Attempting Gemini API Key {attempts % len(available_keys) + 1}/{len(available_keys)} Available]" ) + if use_ollama: logger.info("\n[Attempting Local Ollama]") + response_text = self._stream_single_llm( prompt, key=key, context=display_name ) + + if "ERROR_CODE_413" in response_text: + logger.warning("⚠️ GitHub Models context too large (413). Sleeping 60s...") + time.sleep(60) + attempts += 1 + continue + if response_text.startswith("ERROR_CODE_429"): if key: - logger.warning( - "⚠️ Key hit a 429 rate limit. Putting it in a 20-minute timeout." - ) + logger.warning("⚠️ Key hit a 429 rate limit. Timeout 20m.") self.key_cooldowns[key] = time.time() + 1200 + time.sleep(60) attempts += 1 continue + if response_text.startswith("ERROR_CODE_") or not response_text.strip(): - logger.warning("⚠️ API Error or Empty Response. Rotating...") + logger.warning("⚠️ API Error or Empty Response. Backing off 60s...") time.sleep(60) attempts += 1 continue + new_code, explanation, edit_success = self.apply_xml_edits( source_code, response_text ) @@ -247,49 +264,34 @@ def get_valid_edit( if not require_edit and ai_approved_code: if edit_count > 0: - logger.info( - "🤖 AI stated the code looks good, but hallucinated empty blocks. Ignoring them." - ) + logger.info("🤖 AI stated the code looks good, but hallucinated empty blocks. Ignoring them.") return source_code, explanation, response_text + if edit_count > 0 and not edit_success: - logger.warning( - f"⚠️ Partial edit failure in {display_name} ({edit_count} blocks found, but some missed targets). Auto-regenerating..." - ) - time.sleep(60) + logger.warning(f"⚠️ Partial edit failure in {display_name}. Auto-regenerating...") + time.sleep(30) attempts += 1 continue + if require_edit and new_code == source_code: logger.warning("Search block mismatch. Rotating...") - time.sleep(60) + time.sleep(30) attempts += 1 continue + if not require_edit and new_code == source_code: - lower_exp = explanation.lower() - if ( - "no fixes needed" in lower_exp - or "looks good" in lower_exp - or "no changes needed" in lower_exp - ): + if ai_approved_code: return new_code, explanation, response_text else: - if edit_count > 0: - logger.warning( - f"⚠️ AI generated {edit_count} blocks, but text failed to match. Rotating..." - ) - else: - logger.warning( - f"⚠️ AI provided no edit and didn't state: [{display_name}] looks good. Rotating..." - ) - time.sleep(60) + logger.warning(f"⚠️ AI provided no edit and no approval. Rotating...") + time.sleep(30) attempts += 1 continue + if new_code != source_code: print("\n" + "=" * 50) print(f"💡 AI Proposed Edit Ready for: [{display_name}]") print("=" * 50) - print( - f"AI Thought: {explanation[:400]}{'...' if len(explanation) > 400 else ''}" - ) diff_lines = list( difflib.unified_diff( source_code.splitlines(keepends=True), @@ -298,138 +300,32 @@ def get_valid_edit( tofile="Proposed", ) ) - print("\nProposed Changes:\n") for line in diff_lines[2:22]: clean_line = line.rstrip() - if clean_line.startswith("+"): - print(f"\033[92m{clean_line}\033[0m") - elif clean_line.startswith("-"): - print(f"\033[91m{clean_line}\033[0m") - elif clean_line.startswith("@@"): - print(f"\033[94m{clean_line}\033[0m") - else: - print(clean_line) - if len(diff_lines) > 22: - print(f"\033[93m... and {len(diff_lines) - 22} more lines.\033[0m") + if clean_line.startswith("+"): print(f"\033[92m{clean_line}\033[0m") + elif clean_line.startswith("-"): print(f"\033[91m{clean_line}\033[0m") + elif clean_line.startswith("@@"): print(f"\033[94m{clean_line}\033[0m") + else: print(clean_line) + user_choice = self.get_user_approval( - "Hit ENTER to APPLY, type 'FULL_DIFF' to view full diff, 'EDIT_CODE' to refine code manually, 'EDIT_XML' to refine AI XML, 'REGENERATE' to retry AI, or 'SKIP' to cancel.", + "Hit ENTER to APPLY, type 'FULL_DIFF', 'EDIT_CODE', 'EDIT_XML', 'REGENERATE', or 'SKIP'.", timeout=220, ) + if user_choice == "SKIP": - logger.info("AI proposed edit skipped by user.") return source_code, "Edit skipped by user.", "" elif user_choice == "REGENERATE": - logger.info("Regenerating AI edit...") attempts += 1 continue elif user_choice == "EDIT_XML": - logger.info( - "Opening AI XML response in editor for manual refinement..." - ) - response_text = self._edit_prompt_with_external_editor( - response_text - ) - new_code, explanation, _ = self.apply_xml_edits( - source_code, response_text - ) - if new_code == source_code: - logger.warning( - "Edited XML failed to match code. Skipping edit." - ) - return source_code, "Edit failed after manual refinement.", "" + response_text = self._edit_prompt_with_external_editor(response_text) + new_code, explanation, _ = self.apply_xml_edits(source_code, response_text) return new_code, explanation, response_text elif user_choice == "EDIT_CODE": - logger.info( - "Opening proposed code in editor for manual refinement..." - ) - file_ext = ( - os.path.splitext(target_filepath)[1] - if target_filepath - else ".py" - ) - edited_code = self._launch_external_code_editor( - new_code, file_suffix=file_ext - ) - if edited_code == new_code: - logger.info( - "No changes made in external editor. Proceeding with original AI proposal." - ) - return new_code, explanation, response_text - else: - logger.info( - "User manually refined code. Applying refined code." - ) - return ( - edited_code, - explanation + " (User refined code manually)", - response_text, - ) - elif user_choice == "FULL_DIFF": - full_diff_text = "".join(diff_lines) - try: - pager_cmd = os.environ.get("PAGER", "less -R").split() - if sys.platform == "win32": - pager_cmd = ["more"] - process = subprocess.Popen( - pager_cmd, - stdin=subprocess.PIPE, - stdout=sys.stdout, - stderr=sys.stderr, - text=True, - ) - process.communicate(input=full_diff_text) - except FileNotFoundError: - for line in diff_lines: - clean_line = line.rstrip() - if clean_line.startswith("+"): - print(f"\033[92m{clean_line}\033[0m") - elif clean_line.startswith("-"): - print(f"\033[91m{clean_line}\033[0m") - elif clean_line.startswith("@@"): - print(f"\033[94m{clean_line}\033[0m") - else: - print(clean_line) - user_choice_after_diff = self.get_user_approval( - "Hit ENTER to APPLY, type 'EDIT_CODE' to refine code manually, 'EDIT_XML' to refine AI XML, 'REGENERATE' to retry AI, or 'SKIP' to cancel.", - timeout=220, - ) - if user_choice_after_diff == "SKIP": - return source_code, "Edit skipped by user.", "" - elif user_choice_after_diff == "REGENERATE": - attempts += 1 - continue - elif user_choice_after_diff == "EDIT_XML": - response_text = self._edit_prompt_with_external_editor( - response_text - ) - new_code, explanation, _ = self.apply_xml_edits( - source_code, response_text - ) - if new_code == source_code: - return ( - source_code, - "Edit failed after manual refinement.", - "", - ) - return new_code, explanation, response_text - elif user_choice_after_diff == "EDIT_CODE": - file_ext = ( - os.path.splitext(target_filepath)[1] - if target_filepath - else ".py" - ) - edited_code = self._launch_external_code_editor( - new_code, file_suffix=file_ext - ) - if edited_code == new_code: - return new_code, explanation, response_text - else: - return ( - edited_code, - explanation + " (User refined code manually)", - response_text, - ) - return new_code, explanation, response_text + file_ext = os.path.splitext(target_filepath)[1] if target_filepath else ".py" + edited_code = self._launch_external_code_editor(new_code, file_suffix=file_ext) + return edited_code, explanation + " (User refined code manually)", response_text + return new_code, explanation, response_text def scan_directory(self) -> list[str]: From 58d282fd975957ebaad9dca57e90acb98246090e Mon Sep 17 00:00:00 2001 From: Vic <125237471+vicsanity623@users.noreply.github.com> Date: Wed, 11 Mar 2026 06:57:14 -0700 Subject: [PATCH 2/6] Update autoreviewer.py --- src/pyob/autoreviewer.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/pyob/autoreviewer.py b/src/pyob/autoreviewer.py index 3545b45..a2c1c26 100644 --- a/src/pyob/autoreviewer.py +++ b/src/pyob/autoreviewer.py @@ -302,9 +302,12 @@ def get_valid_edit( ) for line in diff_lines[2:22]: clean_line = line.rstrip() - if clean_line.startswith("+"): print(f"\033[92m{clean_line}\033[0m") - elif clean_line.startswith("-"): print(f"\033[91m{clean_line}\033[0m") - elif clean_line.startswith("@@"): print(f"\033[94m{clean_line}\033[0m") + if clean_line.startswith("+"): + print(f"\033[92m{clean_line}\033[0m") + elif clean_line.startswith("-"): + print(f"\033[91m{clean_line}\033[0m") + elif clean_line.startswith("@@"): + print(f"\033[94m{clean_line}\033[0m") else: print(clean_line) user_choice = self.get_user_approval( From a56c1532f51fff7d0ba3de9cac13bc6eade064a8 Mon Sep 17 00:00:00 2001 From: Vic <125237471+vicsanity623@users.noreply.github.com> Date: Wed, 11 Mar 2026 06:59:30 -0700 Subject: [PATCH 3/6] Update autoreviewer.py --- src/pyob/autoreviewer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pyob/autoreviewer.py b/src/pyob/autoreviewer.py index a2c1c26..5493fd0 100644 --- a/src/pyob/autoreviewer.py +++ b/src/pyob/autoreviewer.py @@ -308,7 +308,8 @@ def get_valid_edit( print(f"\033[91m{clean_line}\033[0m") elif clean_line.startswith("@@"): print(f"\033[94m{clean_line}\033[0m") - else: print(clean_line) + else: + print(clean_line) user_choice = self.get_user_approval( "Hit ENTER to APPLY, type 'FULL_DIFF', 'EDIT_CODE', 'EDIT_XML', 'REGENERATE', or 'SKIP'.", From 1c774becd178a570ee22b5a2474ac994164d9ec4 Mon Sep 17 00:00:00 2001 From: vicsanity623 <125237471+vicsanity623@users.noreply.github.com> Date: Wed, 11 Mar 2026 13:59:59 +0000 Subject: [PATCH 4/6] =?UTF-8?q?=F0=9F=AA=84=20PyOB:=20Automated=20Lint=20&?= =?UTF-8?q?=20Format=20Fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pyob/autoreviewer.py | 68 ++++++++++++++++++++++++++++------------ 1 file changed, 48 insertions(+), 20 deletions(-) diff --git a/src/pyob/autoreviewer.py b/src/pyob/autoreviewer.py index 5493fd0..ed4841a 100644 --- a/src/pyob/autoreviewer.py +++ b/src/pyob/autoreviewer.py @@ -198,7 +198,11 @@ def get_valid_edit( attempts: int = int(0) use_ollama = False - is_cloud = os.environ.get("GITHUB_ACTIONS") == "true" or os.environ.get("CI") == "true" or "GITHUB_RUN_ID" in os.environ + is_cloud = ( + os.environ.get("GITHUB_ACTIONS") == "true" + or os.environ.get("CI") == "true" + or "GITHUB_RUN_ID" in os.environ + ) while True: key = None @@ -209,13 +213,17 @@ def get_valid_edit( if not available_keys: if is_cloud: - logger.warning("☁️ Cloud environment: Gemini keys exhausted/limited. Sleeping 60s for refill...") + logger.warning( + "☁️ Cloud environment: Gemini keys exhausted/limited. Sleeping 60s for refill..." + ) time.sleep(60) attempts += 1 continue else: if not use_ollama: - logger.warning("🚫 Gemini rate-limited. Falling back to Local Ollama.") + logger.warning( + "🚫 Gemini rate-limited. Falling back to Local Ollama." + ) use_ollama = True else: use_ollama = False @@ -232,11 +240,13 @@ def get_valid_edit( ) if "ERROR_CODE_413" in response_text: - logger.warning("⚠️ GitHub Models context too large (413). Sleeping 60s...") + logger.warning( + "⚠️ GitHub Models context too large (413). Sleeping 60s..." + ) time.sleep(60) attempts += 1 continue - + if response_text.startswith("ERROR_CODE_429"): if key: logger.warning("⚠️ Key hit a 429 rate limit. Timeout 20m.") @@ -244,7 +254,7 @@ def get_valid_edit( time.sleep(60) attempts += 1 continue - + if response_text.startswith("ERROR_CODE_") or not response_text.strip(): logger.warning("⚠️ API Error or Empty Response. Backing off 60s...") time.sleep(60) @@ -264,26 +274,30 @@ def get_valid_edit( if not require_edit and ai_approved_code: if edit_count > 0: - logger.info("🤖 AI stated the code looks good, but hallucinated empty blocks. Ignoring them.") + logger.info( + "🤖 AI stated the code looks good, but hallucinated empty blocks. Ignoring them." + ) return source_code, explanation, response_text - + if edit_count > 0 and not edit_success: - logger.warning(f"⚠️ Partial edit failure in {display_name}. Auto-regenerating...") + logger.warning( + f"⚠️ Partial edit failure in {display_name}. Auto-regenerating..." + ) time.sleep(30) attempts += 1 continue - + if require_edit and new_code == source_code: logger.warning("Search block mismatch. Rotating...") time.sleep(30) attempts += 1 continue - + if not require_edit and new_code == source_code: if ai_approved_code: return new_code, explanation, response_text else: - logger.warning(f"⚠️ AI provided no edit and no approval. Rotating...") + logger.warning("⚠️ AI provided no edit and no approval. Rotating...") time.sleep(30) attempts += 1 continue @@ -310,26 +324,40 @@ def get_valid_edit( print(f"\033[94m{clean_line}\033[0m") else: print(clean_line) - + user_choice = self.get_user_approval( "Hit ENTER to APPLY, type 'FULL_DIFF', 'EDIT_CODE', 'EDIT_XML', 'REGENERATE', or 'SKIP'.", timeout=220, ) - + if user_choice == "SKIP": return source_code, "Edit skipped by user.", "" elif user_choice == "REGENERATE": attempts += 1 continue elif user_choice == "EDIT_XML": - response_text = self._edit_prompt_with_external_editor(response_text) - new_code, explanation, _ = self.apply_xml_edits(source_code, response_text) + response_text = self._edit_prompt_with_external_editor( + response_text + ) + new_code, explanation, _ = self.apply_xml_edits( + source_code, response_text + ) return new_code, explanation, response_text elif user_choice == "EDIT_CODE": - file_ext = os.path.splitext(target_filepath)[1] if target_filepath else ".py" - edited_code = self._launch_external_code_editor(new_code, file_suffix=file_ext) - return edited_code, explanation + " (User refined code manually)", response_text - + file_ext = ( + os.path.splitext(target_filepath)[1] + if target_filepath + else ".py" + ) + edited_code = self._launch_external_code_editor( + new_code, file_suffix=file_ext + ) + return ( + edited_code, + explanation + " (User refined code manually)", + response_text, + ) + return new_code, explanation, response_text def scan_directory(self) -> list[str]: From fe85851d91f96b986b1aa0ddc4686e5174d7b34e Mon Sep 17 00:00:00 2001 From: Vic <125237471+vicsanity623@users.noreply.github.com> Date: Wed, 11 Mar 2026 07:01:40 -0700 Subject: [PATCH 5/6] Update models.py --- src/pyob/models.py | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/src/pyob/models.py b/src/pyob/models.py index 3ed05b7..7311409 100644 --- a/src/pyob/models.py +++ b/src/pyob/models.py @@ -215,21 +215,14 @@ def on_chunk(): # Immediately intercept 413, pause 60s, and force Gemini usage so outer loops don't panic if response_text and "413" in response_text: first_chunk_received[0] = True - sys.stdout.write("\r\033[K") - sys.stdout.flush() - logger.warning( - "\n⚠️ Payload too large for GitHub Models (413). Sleeping 60s, then pivoting to Gemini..." - ) + logger.warning("\n⚠️ Payload too large. Sleeping 60s, then pivoting to Gemini...") time.sleep(60) - gemini_keys = [ - k.strip() - for k in os.environ.get("PYOB_GEMINI_KEYS", "").split(",") - if k.strip() - ] + gemini_keys = [k.strip() for k in os.environ.get("PYOB_GEMINI_KEYS", "").split(",") if k.strip()] if gemini_keys: - response_text = stream_gemini(prompt, gemini_keys[0], on_chunk) + # Return a specific signal string so the caller knows it worked + return stream_gemini(prompt, gemini_keys[0], on_chunk) else: - response_text = "ERROR_CODE_413_NO_GEMINI_FALLBACK" + return "ERROR_CODE_413_NO_GEMINI_FALLBACK" # Force mandatory sleep if ANY cloud error escapes, breaking infinite loop triggers if response_text and response_text.startswith("ERROR_CODE_"): From cd8fba96ae4d18b629f74c2e24a824903911cff7 Mon Sep 17 00:00:00 2001 From: vicsanity623 <125237471+vicsanity623@users.noreply.github.com> Date: Wed, 11 Mar 2026 14:02:06 +0000 Subject: [PATCH 6/6] =?UTF-8?q?=F0=9F=AA=84=20PyOB:=20Automated=20Lint=20&?= =?UTF-8?q?=20Format=20Fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pyob/models.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/pyob/models.py b/src/pyob/models.py index 7311409..e25c071 100644 --- a/src/pyob/models.py +++ b/src/pyob/models.py @@ -215,9 +215,15 @@ def on_chunk(): # Immediately intercept 413, pause 60s, and force Gemini usage so outer loops don't panic if response_text and "413" in response_text: first_chunk_received[0] = True - logger.warning("\n⚠️ Payload too large. Sleeping 60s, then pivoting to Gemini...") + logger.warning( + "\n⚠️ Payload too large. Sleeping 60s, then pivoting to Gemini..." + ) time.sleep(60) - gemini_keys = [k.strip() for k in os.environ.get("PYOB_GEMINI_KEYS", "").split(",") if k.strip()] + gemini_keys = [ + k.strip() + for k in os.environ.get("PYOB_GEMINI_KEYS", "").split(",") + if k.strip() + ] if gemini_keys: # Return a specific signal string so the caller knows it worked return stream_gemini(prompt, gemini_keys[0], on_chunk)