spenceryonce 2023-04-14 18:37:52 -05:00 zatwierdzone przez GitHub
commit 4c28632510
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
2 zmienionych plików z 120 dodań i 41 usunięć

3
.gitignore vendored
Wyświetl plik

@ -1,4 +1,5 @@
venv openai_key.txt
wolverine.log
.venv .venv
.env .env
env/ env/

Wyświetl plik

@ -6,10 +6,12 @@ import shutil
import subprocess import subprocess
import sys import sys
import openai import openai
import logging
from termcolor import cprint from termcolor import cprint
from dotenv import load_dotenv from dotenv import load_dotenv
# Set up the OpenAI API # Set up the OpenAI API
load_dotenv() load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY") openai.api_key = os.getenv("OPENAI_API_KEY")
@ -20,7 +22,16 @@ DEFAULT_MODEL = os.environ.get("DEFAULT_MODEL", "gpt-4")
with open("prompt.txt") as f: with open("prompt.txt") as f:
SYSTEM_PROMPT = f.read() SYSTEM_PROMPT = f.read()
# 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): def run_script(script_name, script_args):
script_args = [str(arg) for arg in script_args] script_args = [str(arg) for arg in script_args]
try: try:
@ -73,8 +84,8 @@ def json_validated_response(model, messages):
raise e raise e
return json_response return json_response
# Send the error to GPT and receive suggestions
def send_error_to_gpt(file_path, args, error_message, model=DEFAULT_MODEL): def send_error_to_gpt(file_path, args, error_message, model=DEFAULT_MODEL, prompt_length_limit=4096):
with open(file_path, "r") as f: with open(file_path, "r") as f:
file_lines = f.readlines() file_lines = f.readlines()
@ -94,6 +105,10 @@ def send_error_to_gpt(file_path, args, error_message, model=DEFAULT_MODEL):
"exact format as described above." "exact format as described above."
) )
# Truncate the prompt if it exceeds the limit
if len(prompt) > prompt_length_limit:
prompt = prompt[:prompt_length_limit]
# print(prompt) # print(prompt)
messages = [ messages = [
{ {
@ -108,21 +123,20 @@ def send_error_to_gpt(file_path, args, error_message, model=DEFAULT_MODEL):
return json_validated_response(model, messages) return json_validated_response(model, messages)
# Apply the changes suggested by GPT
def apply_changes(file_path, changes: list): def apply_changes(file_path, changes: list):
""" """
Pass changes as loaded json (list of dicts) Pass changes as loaded json (list of dicts)
""" """
try:
with open(file_path, "r") as f: with open(file_path, "r") as f:
original_file_lines = f.readlines() original_file_lines = f.readlines()
# Filter out explanation elements
operation_changes = [change for change in changes if "operation" in change] operation_changes = [change for change in changes if "operation" in change]
explanations = [ explanations = [
change["explanation"] for change in changes if "explanation" in change 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) operation_changes.sort(key=lambda x: x["line"], reverse=True)
file_lines = original_file_lines.copy() file_lines = original_file_lines.copy()
@ -148,6 +162,45 @@ def apply_changes(file_path, changes: list):
# Show the diff # Show the diff
print("\nChanges:") print("\nChanges:")
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)
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'.")
# 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="") diff = difflib.unified_diff(original_file_lines, file_lines, lineterm="")
for line in diff: for line in diff:
if line.startswith("+"): if line.startswith("+"):
@ -157,17 +210,15 @@ def apply_changes(file_path, changes: list):
else: else:
print(line, end="") print(line, end="")
def main(script_name, *script_args, revert=False, model=DEFAULT_MODEL, interactive=False):
def main(script_name, *script_args, revert=False, model=DEFAULT_MODEL):
if revert: if revert:
backup_file = script_name + ".bak" backup_file = script_name + ".bak"
if os.path.exists(backup_file): if os.path.exists(backup_file):
shutil.copy(backup_file, script_name) shutil.copy(backup_file, script_name)
print(f"Reverted changes to {script_name}") print(f"Reverted changes to {script_name}")
sys.exit(0) return
else: else:
print(f"No backup file found for {script_name}") raise Exception(f"No backup file found for {script_name}")
sys.exit(1)
# Make a backup of the original script # Make a backup of the original script
shutil.copy(script_name, script_name + ".bak") shutil.copy(script_name, script_name + ".bak")
@ -178,9 +229,11 @@ def main(script_name, *script_args, revert=False, model=DEFAULT_MODEL):
if returncode == 0: if returncode == 0:
cprint("Script ran successfully.", "blue") cprint("Script ran successfully.", "blue")
print("Output:", output) print("Output:", output)
logging.info("Script ran successfully.")
break break
else: else:
cprint("Script crashed. Trying to fix...", "blue") cprint("Script crashed. Trying to fix...", "blue")
logging.error(f"Script crashed with return code {returncode}.")
print("Output:", output) print("Output:", output)
json_response = send_error_to_gpt( json_response = send_error_to_gpt(
@ -189,10 +242,35 @@ def main(script_name, *script_args, revert=False, model=DEFAULT_MODEL):
error_message=output, error_message=output,
model=model, model=model,
) )
if interactive:
changes = 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")
logging.info("Finished applying changes in interactive mode.")
else:
try:
apply_changes(script_name, json_response) apply_changes(script_name, json_response)
cprint("Changes applied. Rerunning...", "blue") 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__": if __name__ == "__main__":
configure_logging()
try:
fire.Fire(main) fire.Fire(main)
except Exception as e:
print(str(e))
logging.error(f"Error: {str(e)}")
sys.exit(1)