├── .gitignore ├── README.md ├── examples └── import_indent_bug.py ├── requirements.txt └── swept.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | __pycache__/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Japeeti 2 | AI sends pull requests for features you request in natural language 3 | 4 | - Create frontend website for marketing 5 | - Semantic search for what files to update and recursive update 6 | - Slackbot that takes requests from users and responds with PR links 7 | - Github api to retrieve PR comments and what file, line num it correspond to 8 | 9 | 10 | ## Usage 11 | ``` 12 | python swept.py -h 13 | usage: swept.py [-h] -f FILE -i INSTRUCTION [-r REPO] [-d] [-pr] 14 | 15 | Edit a section of code, PR with changes. 16 | 17 | optional arguments: 18 | -h, --help show this help message and exit 19 | -f FILE, --file FILE location of a file 20 | -i INSTRUCTION, --instruction INSTRUCTION 21 | instruction on how to edit the file 22 | -r REPO, --repo REPO location to git repo 23 | -d, --diff show diff 24 | -pr, --pull-request add change, commit, push and raise a PR 25 | 26 | ``` 27 | 28 | ## Example 29 | 30 | ``` 31 | OPENAI_KEY="" GH_TOKEN="" python swept.py -f examples/import_indent_bug.py -i "Rewrite the given code and fix any bugs in the program." -d --pr 32 | ``` 33 | Here is a real PR opened by this above command: https://github.com/keerthanpg/SwePT/pull/8 34 | 35 | 36 | ``` 37 | OPENAI_KEY="" GH_TOKEN="" python swept.py 38 | -f /Users/keerthanapg/robotics_transformer/tokenizers/image_tokenizer.py -i "Rewrite the given code by making the __call__ function always use token learner and remove use_token_learner and self._use_token_learner variable" -r "/Users/keerthanapg/robotics_transformer" -d -pr 39 | ``` 40 | Here is a more complex PR opened on an open source repo: https://github.com/keerthanpg/robotics_transformer/pull/5/commits/20dda2730774a414cfd6a59e59f8870c83ce6307 -------------------------------------------------------------------------------- /examples/import_indent_bug.py: -------------------------------------------------------------------------------- 1 | import Random 2 | a = random.randint(1,12) 3 | b = random.randint(1,12) 4 | for i in range(10): 5 | question = "What is "+a+" x "+b+"? " 6 | answer = input(question) 7 | if answer =a*b 8 | print (Well done!) 9 | else: 10 | print("No.") -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | GitPython 2 | openai 3 | -------------------------------------------------------------------------------- /swept.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import json 3 | import openai 4 | import os 5 | import random 6 | import requests 7 | 8 | from git import Repo 9 | from pathlib import Path 10 | from typing import Union, Dict 11 | 12 | ALLOWED_FILE_EXT = [".py"] 13 | openai.api_key = os.getenv('OPENAI_KEY', "") 14 | gh_token = os.getenv('GH_TOKEN', "") 15 | 16 | 17 | def get_edits_for_instruction(code: str, instruction: str) -> str: 18 | response = openai.Edit.create( 19 | model="code-davinci-edit-001", 20 | input=code, 21 | instruction=instruction, 22 | temperature=0.1, 23 | top_p=1.0, 24 | ) 25 | code = response["choices"][0]["text"] 26 | return code 27 | 28 | 29 | def edit_file(file: Union[str, Path], instruction: str) -> bool: 30 | if isinstance(file, str): 31 | file = Path(file) 32 | with open(file) as fob: 33 | file_contents = fob.read() 34 | is_modified = False 35 | modified_code = get_edits_for_instruction(file_contents, instruction) 36 | 37 | if modified_code != file_contents: # any more checks? 38 | with open(file, "w") as fob: 39 | fob.write(modified_code) 40 | is_modified = True 41 | else: 42 | print("The code was not modified!") 43 | return is_modified 44 | 45 | 46 | def display_diff(repo: Repo, file: Path) -> None: 47 | print("\n\n-------------------Diff--------------------") 48 | print(repo.git.diff([str(file)])) 49 | 50 | 51 | def generate_summary(prompt: str) -> str: 52 | prompt = 'Generate a pull request branch name, commit message, pr heading and pr body for instruction:' + prompt 53 | response = openai.Completion.create( 54 | model="text-davinci-003", 55 | prompt=prompt, 56 | temperature=0.7, 57 | max_tokens=1024, 58 | top_p=1.0, 59 | frequency_penalty=0.0, 60 | presence_penalty=0.0, 61 | stop=["\"\"\""] 62 | ) 63 | return response["choices"][0]["text"] 64 | 65 | 66 | def get_meta_info(instruction: str) -> Dict[str, str]: 67 | summary = generate_summary(instruction).strip().replace("\n\n", "\n") 68 | summary_list = [x.split(":")[-1].strip() for x in summary.split("\n")] 69 | 70 | if len(summary_list) != 4: 71 | summary_list = ['new-edit-branch', 'add edit based on instruction', 'Instruction based edit', "Instruction: " + instruction] 72 | 73 | res = { 74 | "branch": f"{summary_list[0]}-{random.randint(0, 1000)}", 75 | "commit_message": summary_list[1], 76 | "pr_title": summary_list[2], 77 | "pr_body": summary_list[3], 78 | } 79 | return res 80 | 81 | 82 | if __name__ == "__main__": 83 | parser = argparse.ArgumentParser(description='Edit a section of code, PR with changes.') 84 | parser.add_argument('-f', '--file', help='location of a file', required=True) 85 | parser.add_argument('-i', '--instruction', help='instruction on how to edit the file', type=str, required=True) 86 | parser.add_argument('-r', '--repo', help='location to git repo', type=str, default='./') 87 | parser.add_argument('-d', '--diff', help='show diff', action='store_true') 88 | parser.add_argument('-pr', '--pull-request', help='add change, commit, push and raise a PR', action='store_true') 89 | args = parser.parse_args() 90 | 91 | file = Path(args.file) 92 | repo_loc = Path(args.repo) 93 | instruction = args.instruction.strip() 94 | repo = Repo(repo_loc) 95 | 96 | assert file.exists() and file.is_file(), "File does not exist!" 97 | assert file.suffix in ALLOWED_FILE_EXT, "Filetype not supported" 98 | assert len(instruction) > 0, "Instruction not valid" 99 | assert not repo.bare, "Repo is bare!" 100 | 101 | is_modified = edit_file(file, instruction) 102 | 103 | if args.diff and is_modified: 104 | display_diff(repo, file) 105 | 106 | if args.pull_request and is_modified: 107 | print("\n\n-------------------PR--------------------") 108 | meta = get_meta_info(instruction) 109 | print("Meta:", meta) 110 | current_branch = repo.active_branch.name 111 | print(f"0. Creating new-branch: {meta['branch']} (current-branch: {current_branch})") 112 | repo.git.checkout("-b", meta['branch']) 113 | 114 | print(f"1. Adding file: {file}") 115 | repo.git.add(str(file)) 116 | 117 | print(f"2. Committing changes with message: {meta['commit_message']}") 118 | commit_output = repo.git.commit(m=meta['commit_message']) 119 | 120 | print("3. Push changes to remote (GitHub)") 121 | push_output = repo.git.push('--set-upstream', repo.remote().name, meta['branch']) 122 | 123 | print("4. Create a PR on GitHub") 124 | if gh_token == "": 125 | print("No GitHub token provided, cannot raise PR!") 126 | else: 127 | remote_url = repo.remotes.origin.url.replace(":", "/").replace(".git", "") 128 | repo_owner = remote_url.split("/")[-2] 129 | repo_name = remote_url.split("/")[-1] 130 | url = f"https://api.github.com/repos/{repo_owner}/{repo_name}/pulls" 131 | data = { 132 | "title": meta['pr_title'], 133 | "body": meta['pr_body'], 134 | "head": meta['branch'], 135 | "base": current_branch 136 | } 137 | headers = { 138 | "Accept": "application/vnd.github+json", 139 | "Authorization": f"Bearer {gh_token}", 140 | "X-GitHub-Api-Version": "2022-11-28" 141 | } 142 | response = requests.post(url=url, data=json.dumps(data), headers=headers) 143 | 144 | if response.status_code == 201: 145 | resp_json = response.json() 146 | print(f"PR Created: {resp_json['html_url']}") 147 | 148 | print("5. Checking out current-branch, deleting local new-branch") 149 | repo.git.checkout(current_branch) 150 | repo.git.branch("-D", meta['branch']) 151 | --------------------------------------------------------------------------------