Compare commits
12 Commits
enhancemen
...
enhancemen
Author | SHA1 | Date | |
---|---|---|---|
93311a9d9b | |||
704030230f | |||
e394cb7ddb | |||
a0e4fb01af | |||
eb9190efa1 | |||
8cc37d6f59 | |||
6cc9fe3d06 | |||
719f3a9dd8 | |||
71efd84113 | |||
25e46a99fd | |||
87f978e816 | |||
b3e79ed677 |
51
README.md
@ -9,18 +9,40 @@
|
|||||||
|
|
||||||
[](https://github.com/Codium-ai/pr-agent/blob/main/LICENSE)
|
[](https://github.com/Codium-ai/pr-agent/blob/main/LICENSE)
|
||||||
[](https://discord.com/channels/1057273017547378788/1126104260430528613)
|
[](https://discord.com/channels/1057273017547378788/1126104260430528613)
|
||||||
|
|
||||||
CodiumAI `pr-agent` is an open-source tool aiming to help developers review PRs faster and more efficiently. It automatically analyzes the PR, provides feedback and suggestions, and can answer free-text questions.
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
<div align="left">
|
||||||
|
|
||||||
|
CodiumAI `pr-agent` is an open-source tool aiming to help developers review PRs faster and more efficiently. It automatically analyzes the PR and can provide several types of feedback:
|
||||||
|
|
||||||
|
**Auto-Description**: Automatically generating PR description - name, type, summary, and code walkthrough.
|
||||||
|
\
|
||||||
|
**PR Review**: Feedback about the PR main theme, type, relevant tests, security issues, focused, and various suggestions for the PR content.
|
||||||
|
\
|
||||||
|
**Question Answering**: Answering free-text questions about the PR.
|
||||||
|
\
|
||||||
|
**Code Suggestion**: Committable code suggestions for improving the PR.
|
||||||
|
|
||||||
|
Example results:
|
||||||
|
</div>
|
||||||
|
<div align="center">
|
||||||
|
<p float="center">
|
||||||
|
<img src="./pics/pr_reviewer_1.png" width="800">
|
||||||
|
</p>
|
||||||
|
<p float="center">
|
||||||
|
<img src="./pics/pr_code_suggestions.png" width="800">
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div align="left">
|
||||||
|
|
||||||
- [Live demo](#live-demo)
|
- [Live demo](#live-demo)
|
||||||
- [Quickstart](#Quickstart)
|
- [Overview](#overview)
|
||||||
|
- [Quickstart](#quickstart)
|
||||||
- [Usage and tools](#usage-and-tools)
|
- [Usage and tools](#usage-and-tools)
|
||||||
- [Configuration](#Configuration)
|
- [Configuration](#configuration)
|
||||||
- [How it works](#how-it-works)
|
- [How it works](#how-it-works)
|
||||||
- [Roadmap](#roadmap)
|
- [Roadmap](#roadmap)
|
||||||
- [Similar projects](#similar-projects)
|
- [Similar projects](#similar-projects)
|
||||||
|
</div>
|
||||||
|
|
||||||
## Live demo
|
## Live demo
|
||||||
|
|
||||||
@ -31,6 +53,25 @@ Experience GPT-4 powered PR review on your public GitHub repository with our hos
|
|||||||
To set up your own pr-agent, see the [Quickstart](#Quickstart) section
|
To set up your own pr-agent, see the [Quickstart](#Quickstart) section
|
||||||
|
|
||||||
---
|
---
|
||||||
|
## Overview
|
||||||
|
`pr-agent` offers extensive pull request functionalities across various git providers:
|
||||||
|
| | | Github | Gitlab | Bitbucket |
|
||||||
|
|-------|---------------------------------------------|--------|--------|-----------|
|
||||||
|
| TOOLS | Review | ✓ | ✓ | ✓ |
|
||||||
|
| | ⮑ Inline review | ✓ | ✓ | |
|
||||||
|
| | Ask | ✓ | ✓ | |
|
||||||
|
| | Auto-Description | ✓ | | |
|
||||||
|
| | Improve Code | ✓ | | |
|
||||||
|
| | | | | |
|
||||||
|
| USAGE | CLI | ✓ | ✓ | ✓ |
|
||||||
|
| | Tagging bot | ✓ | ✓ | |
|
||||||
|
| | Actions | ✓ | | |
|
||||||
|
| | | | | |
|
||||||
|
| CORE | PR compression | ✓ | ✓ | ✓ |
|
||||||
|
| | Repo language prioritization | ✓ | ✓ | ✓ |
|
||||||
|
| | Adaptive and token-aware<br />file patch fitting | ✓ | ✓ | ✓ |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Quickstart
|
## Quickstart
|
||||||
|
|
||||||
|
BIN
pics/Icon-7.png
Before Width: | Height: | Size: 100 KiB |
Before Width: | Height: | Size: 102 KiB |
BIN
pics/main_pic_4_tools.gif
Normal file
After Width: | Height: | Size: 260 KiB |
BIN
pics/pr_auto_description.png
Normal file
After Width: | Height: | Size: 335 KiB |
BIN
pics/pr_code_suggestions.png
Normal file
After Width: | Height: | Size: 193 KiB |
Before Width: | Height: | Size: 137 KiB After Width: | Height: | Size: 161 KiB |
Before Width: | Height: | Size: 267 KiB |
BIN
pics/pr_reviewer_1.png
Normal file
After Width: | Height: | Size: 185 KiB |
BIN
pics/pr_reviewer_2.png
Normal file
After Width: | Height: | Size: 162 KiB |
Before Width: | Height: | Size: 42 KiB |
@ -1,5 +1,7 @@
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
|
from pr_agent.tools.pr_code_suggestions import PRCodeSuggestions
|
||||||
|
from pr_agent.tools.pr_description import PRDescription
|
||||||
from pr_agent.tools.pr_questions import PRQuestions
|
from pr_agent.tools.pr_questions import PRQuestions
|
||||||
from pr_agent.tools.pr_reviewer import PRReviewer
|
from pr_agent.tools.pr_reviewer import PRReviewer
|
||||||
|
|
||||||
@ -8,17 +10,20 @@ class PRAgent:
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def handle_request(self, pr_url, request):
|
async def handle_request(self, pr_url, request) -> bool:
|
||||||
if 'please review' in request.lower() or 'review' == request.lower().strip() or len(request) == 0:
|
if any(cmd in request for cmd in ["/review", "/review_pr"]):
|
||||||
reviewer = PRReviewer(pr_url)
|
await PRReviewer(pr_url).review()
|
||||||
await reviewer.review()
|
elif any(cmd in request for cmd in ["/describe", "/describe_pr"]):
|
||||||
|
await PRDescription(pr_url).describe()
|
||||||
|
elif any(cmd in request for cmd in ["/improve", "/improve_code"]):
|
||||||
|
await PRCodeSuggestions(pr_url).suggest()
|
||||||
|
elif any(cmd in request for cmd in ["/ask", "/ask_question"]):
|
||||||
|
pattern = r'(/ask|/ask_question)\s*(.*)'
|
||||||
|
matches = re.findall(pattern, request, re.IGNORECASE)
|
||||||
|
if matches:
|
||||||
|
question = matches[0][1]
|
||||||
|
await PRQuestions(pr_url, question).answer()
|
||||||
else:
|
else:
|
||||||
if "please answer" in request.lower():
|
return False
|
||||||
question = re.split(r'(?i)please answer', request)[1].strip()
|
|
||||||
elif request.lower().strip().startswith("answer"):
|
return True
|
||||||
question = re.split(r'(?i)answer', request)[1].strip()
|
|
||||||
else:
|
|
||||||
question = request
|
|
||||||
answerer = PRQuestions(pr_url, question)
|
|
||||||
await answerer.answer()
|
|
||||||
|
@ -10,29 +10,48 @@ from pr_agent.tools.pr_reviewer import PRReviewer
|
|||||||
|
|
||||||
|
|
||||||
def run():
|
def run():
|
||||||
parser = argparse.ArgumentParser(description='AI based pull request analyzer')
|
parser = argparse.ArgumentParser(description='AI based pull request analyzer', usage="""\
|
||||||
|
Usage: cli.py --pr-url <URL on supported git hosting service> <command> [<args>].
|
||||||
|
|
||||||
|
Supported commands:
|
||||||
|
review / review_pr - Add a review that includes a summary of the PR and specific suggestions for improvement.
|
||||||
|
ask / ask_question [question] - Ask a question about the PR.
|
||||||
|
describe / describe_pr - Modify the PR title and description based on the PR's contents.
|
||||||
|
improve / improve_code - Suggest improvements to the code in the PR as pull request comments ready to commit.
|
||||||
|
""")
|
||||||
parser.add_argument('--pr_url', type=str, help='The URL of the PR to review', required=True)
|
parser.add_argument('--pr_url', type=str, help='The URL of the PR to review', required=True)
|
||||||
parser.add_argument('--question', type=str, help='Optional question to ask', required=False)
|
parser.add_argument('command', type=str, help='The', choices=['review', 'review_pr',
|
||||||
parser.add_argument('--pr_description', action='store_true', required=False)
|
'ask', 'ask_question',
|
||||||
parser.add_argument('--pr_code_suggestions', action='store_true', required=False)
|
'describe', 'describe_pr',
|
||||||
|
'improve', 'improve_code'], default='review')
|
||||||
|
parser.add_argument('rest', nargs=argparse.REMAINDER, default=[])
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
logging.basicConfig(level=os.environ.get("LOGLEVEL", "INFO"))
|
logging.basicConfig(level=os.environ.get("LOGLEVEL", "INFO"))
|
||||||
if args.question:
|
command = args.command.lower()
|
||||||
print(f"Question: {args.question} about PR {args.pr_url}")
|
if command in ['ask', 'ask_question']:
|
||||||
reviewer = PRQuestions(args.pr_url, args.question)
|
question = ' '.join(args.rest).strip()
|
||||||
|
if len(question) == 0:
|
||||||
|
print("Please specify a question")
|
||||||
|
parser.print_help()
|
||||||
|
return
|
||||||
|
print(f"Question: {question} about PR {args.pr_url}")
|
||||||
|
reviewer = PRQuestions(args.pr_url, question)
|
||||||
asyncio.run(reviewer.answer())
|
asyncio.run(reviewer.answer())
|
||||||
elif args.pr_description:
|
elif command in ['describe', 'describe_pr']:
|
||||||
print(f"PR description: {args.pr_url}")
|
print(f"PR description: {args.pr_url}")
|
||||||
reviewer = PRDescription(args.pr_url)
|
reviewer = PRDescription(args.pr_url)
|
||||||
asyncio.run(reviewer.describe())
|
asyncio.run(reviewer.describe())
|
||||||
elif args.pr_code_suggestions:
|
elif command in ['improve', 'improve_code']:
|
||||||
print(f"PR code suggestions: {args.pr_url}")
|
print(f"PR code suggestions: {args.pr_url}")
|
||||||
reviewer = PRCodeSuggestions(args.pr_url)
|
reviewer = PRCodeSuggestions(args.pr_url)
|
||||||
asyncio.run(reviewer.suggest())
|
asyncio.run(reviewer.suggest())
|
||||||
else:
|
elif command in ['review', 'review_pr']:
|
||||||
print(f"Reviewing PR: {args.pr_url}")
|
print(f"Reviewing PR: {args.pr_url}")
|
||||||
reviewer = PRReviewer(args.pr_url, cli_mode=True)
|
reviewer = PRReviewer(args.pr_url, cli_mode=True)
|
||||||
asyncio.run(reviewer.review())
|
asyncio.run(reviewer.review())
|
||||||
|
else:
|
||||||
|
print(f"Unknown command: {command}")
|
||||||
|
parser.print_help()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
@ -56,7 +56,7 @@ async def handle_request(body):
|
|||||||
api_url = pull_request.get("url", None)
|
api_url = pull_request.get("url", None)
|
||||||
if api_url is None:
|
if api_url is None:
|
||||||
return {}
|
return {}
|
||||||
await agent.handle_request(api_url, "please review")
|
await agent.handle_request(api_url, "/review")
|
||||||
else:
|
else:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
@ -31,6 +31,7 @@ async def polling_loop():
|
|||||||
last_modified = [None]
|
last_modified = [None]
|
||||||
git_provider = get_git_provider()()
|
git_provider = get_git_provider()()
|
||||||
user_id = git_provider.get_user_id()
|
user_id = git_provider.get_user_id()
|
||||||
|
agent = PRAgent()
|
||||||
try:
|
try:
|
||||||
deployment_type = settings.github.deployment_type
|
deployment_type = settings.github.deployment_type
|
||||||
token = settings.github.user_token
|
token = settings.github.user_token
|
||||||
@ -90,19 +91,8 @@ async def polling_loop():
|
|||||||
continue
|
continue
|
||||||
rest_of_comment = comment_body.split(user_tag)[1].strip()
|
rest_of_comment = comment_body.split(user_tag)[1].strip()
|
||||||
|
|
||||||
if any(cmd in rest_of_comment for cmd in ["/review", "/review_pr"]):
|
success = await agent.handle_request(pr_url, rest_of_comment)
|
||||||
await PRReviewer(pr_url).review()
|
if not success:
|
||||||
elif any(cmd in rest_of_comment for cmd in ["/describe", "/describe_pr"]):
|
|
||||||
await PRDescription(pr_url).describe()
|
|
||||||
elif any(cmd in rest_of_comment for cmd in ["/improve", "/improve_code"]):
|
|
||||||
await PRCodeSuggestions(pr_url).suggest()
|
|
||||||
elif any(cmd in rest_of_comment for cmd in ["/ask", "/ask_question"]):
|
|
||||||
pattern = r'(/ask|/ask_question)\s*(.*)'
|
|
||||||
matches = re.findall(pattern, rest_of_comment, re.IGNORECASE)
|
|
||||||
if matches:
|
|
||||||
question = matches[0][1]
|
|
||||||
await PRQuestions(pr_url, question).answer()
|
|
||||||
else:
|
|
||||||
git_provider.set_pr(pr_url)
|
git_provider.set_pr(pr_url)
|
||||||
git_provider.publish_comment("### How to user PR-Agent\n" +
|
git_provider.publish_comment("### How to user PR-Agent\n" +
|
||||||
bot_help_text(user_id))
|
bot_help_text(user_id))
|
||||||
|