From d9d43d4b5e02b77bf5345966291449f5dbf1d10d Mon Sep 17 00:00:00 2001 From: spenceryonce Date: Fri, 14 Apr 2023 09:28:34 -0500 Subject: [PATCH 1/3] added better error handling moved openai key to a function call for easier calling --- wolverine.py | 122 ++++++++++++++++++++++++++++----------------------- 1 file changed, 68 insertions(+), 54 deletions(-) diff --git a/wolverine.py b/wolverine.py index 42f7ff8..a05b2e4 100644 --- a/wolverine.py +++ b/wolverine.py @@ -5,15 +5,15 @@ import shutil import subprocess import sys - import openai from termcolor import cprint -# Set up the OpenAI API -with open("openai_key.txt") as f: - openai.api_key = f.read().strip() - +# Load the OpenAI API key +def load_openai_key(): + with open("openai_key.txt") as f: + return f.read().strip() +# Run the provided script and return the output and return code def run_script(script_name, script_args): script_args = [str(arg) for arg in script_args] try: @@ -24,8 +24,8 @@ def run_script(script_name, script_args): return e.output.decode("utf-8"), e.returncode return result.decode("utf-8"), 0 - -def send_error_to_gpt(file_path, args, error_message, model): +# Send the error to GPT and receive suggestions +def send_error_to_gpt(file_path, args, error_message, model, prompt_length_limit=4096): with open(file_path, "r") as f: file_lines = f.readlines() @@ -50,7 +50,9 @@ def send_error_to_gpt(file_path, args, error_message, model): "exact format as described above." ) - # print(prompt) + # Truncate the prompt if it exceeds the limit + if len(prompt) > prompt_length_limit: + prompt = prompt[:prompt_length_limit] response = openai.ChatCompletion.create( model=model, @@ -65,45 +67,50 @@ def send_error_to_gpt(file_path, args, error_message, model): return response.choices[0].message.content.strip() - +# Apply the changes suggested by GPT def apply_changes(file_path, changes_json): - with open(file_path, "r") as f: - original_file_lines = f.readlines() - - changes = json.loads(changes_json) - - # Filter out explanation elements - operation_changes = [change for change in changes if "operation" in change] - explanations = [ - change["explanation"] for change in changes if "explanation" in change - ] - - # Sort the changes in reverse line order - operation_changes.sort(key=lambda x: x["line"], reverse=True) - - file_lines = original_file_lines.copy() - for change in operation_changes: - operation = change["operation"] - line = change["line"] - content = change["content"] - - if operation == "Replace": - file_lines[line - 1] = content + "\n" - elif operation == "Delete": - del file_lines[line - 1] - elif operation == "InsertAfter": - file_lines.insert(line, content + "\n") - - with open(file_path, "w") as f: - f.writelines(file_lines) - - # Print explanations - cprint("Explanations:", "blue") - for explanation in explanations: - cprint(f"- {explanation}", "blue") - - # Show the diff - print("\nChanges:") + try: + with open(file_path, "r") as f: + original_file_lines = f.readlines() + + changes = json.loads(changes_json) + + operation_changes = [change for change in changes if "operation" in change] + explanations = [ + change["explanation"] for change in changes if "explanation" in change + ] + + operation_changes.sort(key=lambda x: x["line"], reverse=True) + + file_lines = original_file_lines.copy() + for change in operation_changes: + operation = change["operation"] + line = change["line"] + content = change["content"] + + if operation == "Replace": + file_lines[line - 1] = content + "\n" + elif operation == "Delete": + del file_lines[line - 1] + elif operation == "InsertAfter": + file_lines.insert(line, content + "\n") + + with open(file_path, "w") as f: + f.writelines(file_lines) + + # Print explanations + cprint("Explanations:", "blue") + for explanation in explanations: + cprint(f"- {explanation}", "blue") + + # Show the diff + print("\nChanges:") + print_diff(original_file_lines, file_lines) + except Exception as e: + raise Exception(f"Failed to apply changes: {str(e)}") + +# Print the differences between two file contents +def print_diff(original_file_lines, file_lines): diff = difflib.unified_diff(original_file_lines, file_lines, lineterm="") for line in diff: if line.startswith("+"): @@ -113,17 +120,17 @@ def apply_changes(file_path, changes_json): else: print(line, end="") - def main(script_name, *script_args, revert=False, model="gpt-4"): + openai.api_key = load_openai_key() + if revert: backup_file = script_name + ".bak" if os.path.exists(backup_file): shutil.copy(backup_file, script_name) print(f"Reverted changes to {script_name}") - sys.exit(0) + return else: - print(f"No backup file found for {script_name}") - sys.exit(1) + raise Exception(f"No backup file found for {script_name}") # Make a backup of the original script shutil.copy(script_name, script_name + ".bak") @@ -145,9 +152,16 @@ def main(script_name, *script_args, revert=False, model="gpt-4"): error_message=output, model=model, ) - apply_changes(script_name, json_response) - cprint("Changes applied. Rerunning...", "blue") - + try: + apply_changes(script_name, json_response) + cprint("Changes applied. Rerunning...", "blue") + except Exception as e: + raise Exception(f"Failed to fix the script: {str(e)}") if __name__ == "__main__": - fire.Fire(main) + try: + fire.Fire(main) + except Exception as e: + print(str(e)) + sys.exit(1) + From 270ac7af27c8ad8f6067012306525f938ea9c98f Mon Sep 17 00:00:00 2001 From: spenceryonce Date: Fri, 14 Apr 2023 09:58:10 -0500 Subject: [PATCH 2/3] added interactive mode + CLI flag allows for accepting changes of individual suggestions --- wolverine.py | 57 ++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 51 insertions(+), 6 deletions(-) diff --git a/wolverine.py b/wolverine.py index a05b2e4..b4df33a 100644 --- a/wolverine.py +++ b/wolverine.py @@ -108,6 +108,37 @@ def apply_changes(file_path, changes_json): print_diff(original_file_lines, file_lines) except Exception as e: raise Exception(f"Failed to apply changes: {str(e)}") + +# Apply a single change suggested by GPT interactively +def apply_change_interactive(file_path, change): + with open(file_path, "r") as f: + original_file_lines = f.readlines() + + operation = change["operation"] + line = change["line"] + content = change["content"] + + file_lines = original_file_lines.copy() + if operation == "Replace": + file_lines[line - 1] = content + "\n" + elif operation == "Delete": + del file_lines[line - 1] + elif operation == "InsertAfter": + file_lines.insert(line, content + "\n") + + print("\nSuggested change:") + print_diff(original_file_lines, file_lines) + + while True: + decision = input("Do you want to apply this change? (y/n): ").lower() + if decision == "y": + with open(file_path, "w") as f: + f.writelines(file_lines) + return True + elif decision == "n": + return False + else: + print("Invalid input. Please enter 'y' or 'n'.") # Print the differences between two file contents def print_diff(original_file_lines, file_lines): @@ -120,7 +151,7 @@ def print_diff(original_file_lines, file_lines): else: print(line, end="") -def main(script_name, *script_args, revert=False, model="gpt-4"): +def main(script_name, *script_args, revert=False, model="gpt-4", interactive=False): openai.api_key = load_openai_key() if revert: @@ -152,11 +183,25 @@ def main(script_name, *script_args, revert=False, model="gpt-4"): error_message=output, model=model, ) - try: - apply_changes(script_name, json_response) - cprint("Changes applied. Rerunning...", "blue") - except Exception as e: - raise Exception(f"Failed to fix the script: {str(e)}") + if interactive: + changes = json.loads(json_response) + operation_changes = [change for change in changes if "operation" in change] + explanations = [ + change["explanation"] for change in changes if "explanation" in change + ] + + for change in operation_changes: + if apply_change_interactive(script_name, change): + cprint("Change applied.", "green") + else: + cprint("Change rejected.", "red") + cprint("Finished applying changes. Rerunning...", "blue") + else: + try: + apply_changes(script_name, json_response) + cprint("Changes applied. Rerunning...", "blue") + except Exception as e: + raise Exception(f"Failed to fix the script: {str(e)}") if __name__ == "__main__": try: From fa84b1e7de060d05f2113e17b2fc4fed1717f1f4 Mon Sep 17 00:00:00 2001 From: spenceryonce Date: Fri, 14 Apr 2023 10:20:55 -0500 Subject: [PATCH 3/3] Added Proper Logging all commands are logged in a log file with nicely formatted context and datetime --- .gitignore | 1 + wolverine.py | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/.gitignore b/.gitignore index 673fe09..d7e2948 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ venv openai_key.txt +wolverine.log \ No newline at end of file diff --git a/wolverine.py b/wolverine.py index b4df33a..3d405bb 100644 --- a/wolverine.py +++ b/wolverine.py @@ -6,12 +6,22 @@ import subprocess import sys import openai +import logging from termcolor import cprint # Load the OpenAI API key def load_openai_key(): with open("openai_key.txt") as f: return f.read().strip() + +# Set up logging +def configure_logging(): + logging.basicConfig( + filename="wolverine.log", + format="%(asctime)s - %(levelname)s - %(message)s", + level=logging.INFO, + ) + # Run the provided script and return the output and return code def run_script(script_name, script_args): @@ -134,8 +144,10 @@ def apply_change_interactive(file_path, change): if decision == "y": with open(file_path, "w") as f: f.writelines(file_lines) + logging.info(f"Applied change: {change}") return True elif decision == "n": + logging.info(f"Rejected change: {change}") return False else: print("Invalid input. Please enter 'y' or 'n'.") @@ -172,9 +184,11 @@ def main(script_name, *script_args, revert=False, model="gpt-4", interactive=Fal if returncode == 0: cprint("Script ran successfully.", "blue") print("Output:", output) + logging.info("Script ran successfully.") break else: cprint("Script crashed. Trying to fix...", "blue") + logging.error(f"Script crashed with return code {returncode}.") print("Output:", output) json_response = send_error_to_gpt( @@ -196,17 +210,21 @@ def main(script_name, *script_args, revert=False, model="gpt-4", interactive=Fal else: cprint("Change rejected.", "red") cprint("Finished applying changes. Rerunning...", "blue") + logging.info("Finished applying changes in interactive mode.") else: try: apply_changes(script_name, json_response) cprint("Changes applied. Rerunning...", "blue") + logging.info("Changes applied.") except Exception as e: raise Exception(f"Failed to fix the script: {str(e)}") if __name__ == "__main__": + configure_logging() try: fire.Fire(main) except Exception as e: print(str(e)) + logging.error(f"Error: {str(e)}") sys.exit(1)