kopia lustrzana https://github.com/biobootloader/wolverine
Merge ea7d6fa2b1
into 742aaaf9d1
commit
4c28632510
|
@ -1,4 +1,5 @@
|
||||||
venv
|
openai_key.txt
|
||||||
|
wolverine.log
|
||||||
.venv
|
.venv
|
||||||
.env
|
.env
|
||||||
env/
|
env/
|
||||||
|
|
158
wolverine.py
158
wolverine.py
|
@ -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")
|
||||||
|
@ -19,8 +21,17 @@ 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 = [
|
||||||
{
|
{
|
||||||
|
@ -107,47 +122,85 @@ 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:
|
||||||
|
original_file_lines = f.readlines()
|
||||||
|
|
||||||
|
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)}")
|
||||||
|
|
||||||
|
# Apply a single change suggested by GPT interactively
|
||||||
|
def apply_change_interactive(file_path, change):
|
||||||
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 = change["operation"]
|
||||||
operation_changes = [change for change in changes if "operation" in change]
|
line = change["line"]
|
||||||
explanations = [
|
content = change["content"]
|
||||||
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()
|
file_lines = original_file_lines.copy()
|
||||||
for change in operation_changes:
|
if operation == "Replace":
|
||||||
operation = change["operation"]
|
file_lines[line - 1] = content + "\n"
|
||||||
line = change["line"]
|
elif operation == "Delete":
|
||||||
content = change["content"]
|
del file_lines[line - 1]
|
||||||
|
elif operation == "InsertAfter":
|
||||||
|
file_lines.insert(line, content + "\n")
|
||||||
|
|
||||||
if operation == "Replace":
|
print("\nSuggested change:")
|
||||||
file_lines[line - 1] = content + "\n"
|
print_diff(original_file_lines, file_lines)
|
||||||
elif operation == "Delete":
|
|
||||||
del file_lines[line - 1]
|
|
||||||
elif operation == "InsertAfter":
|
|
||||||
file_lines.insert(line, content + "\n")
|
|
||||||
|
|
||||||
with open(file_path, "w") as f:
|
while True:
|
||||||
f.writelines(file_lines)
|
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 explanations
|
# Print the differences between two file contents
|
||||||
cprint("Explanations:", "blue")
|
def print_diff(original_file_lines, file_lines):
|
||||||
for explanation in explanations:
|
|
||||||
cprint(f"- {explanation}", "blue")
|
|
||||||
|
|
||||||
# Show the diff
|
|
||||||
print("\nChanges:")
|
|
||||||
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
|
||||||
|
]
|
||||||
|
|
||||||
apply_changes(script_name, json_response)
|
for change in operation_changes:
|
||||||
cprint("Changes applied. Rerunning...", "blue")
|
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)
|
||||||
|
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__":
|
||||||
fire.Fire(main)
|
configure_logging()
|
||||||
|
try:
|
||||||
|
fire.Fire(main)
|
||||||
|
except Exception as e:
|
||||||
|
print(str(e))
|
||||||
|
logging.error(f"Error: {str(e)}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
Ładowanie…
Reference in New Issue