From aa6bdb23de95f4a834a0d6a7a8c162207abb6925 Mon Sep 17 00:00:00 2001 From: Ted Fulk <98295827+Tedfulk@users.noreply.github.com> Date: Thu, 9 Nov 2023 12:31:29 -0500 Subject: [PATCH] Git diff logic (#1) * aider: Added /Users/tedfulk/repos/personal/do_you_even_diff_bro/py_do_you_even_diff_bro/git.py * aider: Updated the README.md to include an introduction, a detailed description, and instructions on how to use the tool. # Aider chat conversation: USER: update the README.md to be a comprehensive representation of all the files i have given. I want it to be four sections an Introduction, a more detailed description, and how to use it. ASSISTANT: Sure, I can help with that. Here's a general structure for the README.md file. ```markdown README.md <<<<<<< SEARCH ======= # Introduction This project is a command-line tool named BROGRAMMER, an AI Peer Review Bro. It uses the GPT model to review your code and provide feedback. The tool is designed to be flexible, allowing you to specify the level of feedback you want, the files you want to include or exclude, and even the branch to compare the git diff against. # Detailed Description The main functionality of the tool is contained within the `main.py` file. This script uses the `typer` library to parse command-line arguments and options. It also uses the `rich` library to display the results in a formatted table in the console. The `git.py` file contains a function to get the git diff of the current project. It allows you to specify the file extensions to include or exclude, and the branch to compare against. The `llm.py` file contains functions to interact with the GPT model. It sends a prompt to the model and parses the response. The `models.py` file defines the `BroMode` enum, which represents the level of feedback you want from the tool. The `utils.py` file contains utility functions used by the other scripts. The `constants.py` and `commandments.py` files contain constant values and functions to generate prompts for the GPT model, respectively. # How to Use To use the tool, run the `main.py` script with the desired options. For example, to get a chill, jr-level engineer review, use the `--chill` option: ```bash python main.py --chill ``` To specify the GPT model to use, use the `--model` option: ```bash python main.py --model gpt-4 ``` To only include files with certain extensions, use the `--only` option: ```bash python main.py --only .py .js ``` To ignore files with certain extensions, use the `--ignore` option: ```bash python main.py --ignore .txt .md ``` To specify a custom prompt for the GPT model, use the `--prompt` option: ```bash python main.py --prompt "Review this code" ``` To summarize the git diff, use the `--summarize` option: ```bash python main.py --summarize ``` To specify the branch to compare the git diff against, use the `--peer-review` option: ```bash python main.py --peer-review main ``` >>>>>>> REPLACE ``` Please note that this is a general structure and might need to be adjusted based on the specific functionality and usage of your project. * aider: Added README.md file with an introduction, detailed description, and instructions on how to use the tool. # Aider chat conversation: USER: I added these *read-write* files: py_do_you_even_diff_bro/README.md, py_do_you_even_diff_bro/constants.py, py_do_you_even_diff_bro/utils.py, py_do_you_even_diff_bro/models.py, py_do_you_even_diff_bro/llm.py, py_do_you_even_diff_bro/commandments.py ASSISTANT: Great! Now that we have all the necessary files, let's proceed with the changes. 1. **File to modify:** `py_do_you_even_diff_bro/README.md` The README file is the first point of contact for anyone who wants to understand your project. It should provide a clear and concise overview of what the project does, how to install and use it, and any other relevant information. Here's a general structure for the README.md file. ```markdown README.md <<<<<<< SEARCH ======= # Introduction This project is a command-line tool named BROGRAMMER, an AI Peer Review Bro. It uses the GPT model to review your code and provide feedback. The tool is designed to be flexible, allowing you to specify the level of feedback you want, the files you want to include or exclude, and even the branch to compare the git diff against. # Detailed Description The main functionality of the tool is contained within the `main.py` file. This script uses the `typer` library to parse command-line arguments and options. It also uses the `rich` library to display the results in a formatted table in the console. The `git.py` file contains a function to get the git diff of the current project. It allows you to specify the file extensions to include or exclude, and the branch to compare against. The `llm.py` file contains functions to interact with the GPT model. It sends a prompt to the model and parses the response. The `models.py` file defines the `BroMode` enum, which represents the level of feedback you want from the tool. The `utils.py` file contains utility functions used by the other scripts. The `constants.py` and `commandments.py` files contain constant values and functions to generate prompts for the GPT model, respectively. # How to Use To use the tool, run the `main.py` script with the desired options. For example, to get a chill, jr-level engineer review, use the `--chill` option: ```bash python main.py --chill ``` To specify the GPT model to use, use the `--model` option: ```bash python main.py --model gpt-4 ``` To only include files with certain extensions, use the `--only` option: ```bash python main.py --only .py .js ``` To ignore files with certain extensions, use the `--ignore` option: ```bash python main.py --ignore .txt .md ``` To specify a custom prompt for the GPT model, use the `--prompt` option: ```bash python main.py --prompt "Review this code" ``` To summarize the git diff, use the `--summarize` option: ```bash python main.py --summarize ``` To specify the branch to compare the git diff against, use the `--peer-review` option: ```bash python main.py --peer-review main ``` >>>>>>> REPLACE ``` Please note that this is a general structure and might need to be adjusted based on the specific functionality and usage of your project. * aider: Added /Users/tedfulk/repos/personal/do_you_even_diff_bro/tests/test_main.py * aider: Add unit tests for `main.py` using pytest in `test_main.py`. # Aider chat conversation: USER: update test_main.py to include unit tests using pytest for main.py ASSISTANT: To add unit tests for `main.py` in `test_main.py`, we need to import the necessary modules and functions, and then write the tests. Here is an example of how you might structure your tests. Note that the actual tests will depend on the specific functionality of your code, so you may need to adjust these examples to fit your needs. ```python tests/test_main.py <<<<<<< SEARCH ======= import pytest from py_do_you_even_diff_bro.main import get_bro_mode, display_diff_summary, main from py_do_you_even_diff_bro.models import BroMode def test_get_bro_mode(): assert get_bro_mode(True, False, False) == BroMode.CHILL assert get_bro_mode(False, True, False) == BroMode.MID assert get_bro_mode(False, False, True) == BroMode.CHAD assert get_bro_mode(False, False, False) == BroMode.CHILL def test_display_diff_summary(mocker): mocker.patch('py_do_you_even_diff_bro.main.console') display_diff_summary("Test summary") py_do_you_even_diff_bro.main.console.print.assert_called_once() @pytest.mark.parametrize("chill, mid, chad, model, only, ignore, prompt, summarize, peer_review", [ (True, False, False, 'gpt-4', ['.py'], [], '', False, ''), (False, True, False, 'gpt-3.5-turbo', ['.py'], ['.txt'], 'Test prompt', True, 'main') ]) def test_main(chill, mid, chad, model, only, ignore, prompt, summarize, peer_review, mocker): mocker.patch('py_do_you_even_diff_bro.main.get_git_diff', return_value="Test diff") mocker.patch('py_do_you_even_diff_bro.main.gpt_prompt', return_value="Test response") main(chill, mid, chad, model, only, ignore, prompt, summarize, peer_review) py_do_you_even_diff_bro.main.get_git_diff.assert_called_once_with(only, ignore, peer_review) py_do_you_even_diff_bro.main.gpt_prompt.assert_called() >>>>>>> REPLACE ``` This code adds three tests: 1. `test_get_bro_mode` tests the `get_bro_mode` function with different combinations of inputs. 2. `test_display_diff_summary` tests the `display_diff_summary` function and checks that it calls the `console.print` method once. 3. `test_main` tests the `main` function with different combinations of inputs and checks that it calls the `get_git_diff` and `gpt_prompt` functions. Please note that you need to install the `pytest` and `pytest-mock` packages to run these tests. You can install them with pip: ```bash pip install pytest pytest-mock ``` To run the tests, use the following command: ```bash pytest tests/test_main.py ``` * refactor & tests: added validation for main function and unit tests * added labeler action * Delete .github/workflows/labeler.yml * Delete README.html --------- Co-authored-by: Ted Fulk --- .gitignore | 2 + README.html | 342 ------------------------ README.md | 250 +++-------------- py_do_you_even_diff_bro/commandments.py | 49 ++-- py_do_you_even_diff_bro/constants.py | 16 +- py_do_you_even_diff_bro/git.py | 11 +- py_do_you_even_diff_bro/llm.py | 2 +- py_do_you_even_diff_bro/main.py | 210 +++++++++------ py_do_you_even_diff_bro/models.py | 59 +++- pyproject.toml | 2 + tests/test_main.py | 192 +++++++++++++ 11 files changed, 456 insertions(+), 679 deletions(-) delete mode 100644 README.html diff --git a/.gitignore b/.gitignore index cc97b87..d83e4ae 100644 --- a/.gitignore +++ b/.gitignore @@ -120,3 +120,5 @@ cython_debug/ # Visual Studio Code files .vscode/ .aider* + +README.html diff --git a/README.html b/README.html deleted file mode 100644 index 083cba5..0000000 --- a/README.html +++ /dev/null @@ -1,342 +0,0 @@ - - - - - Project Requirements: Do you Even Diff Bro - - - - - - - - - -

Project Requirements: Do you Even Diff Bro

-

==========================================

-

In the vast landscape of the coding world, where giants like GitHub and GitLab rule the realms, a new sprout emerges from the fertile soil. A code review tool imbued with the wisdom of GPT, designed to illuminate your path to better code. Witness the birth of a new era of code reviews.

-

Introduction

-

The GPT Code Review Tool aims to provide an automated yet insightful review of code changes in Python projects. It leverages the powers of pre-commit, Poetry for dependency management, Git Diff for code comparison, and GitHub Actions for CI/CD, culminating in a poetic symphony of code quality enforcement.

-

Core Features

-

GPT-Enhanced Review

- -

Pre-Commit Integration

- -

Poetry Support

- -

Git Diff Comparison

- -

GitHub Actions

- -

PyPI Package

- -

Technical Requirements

-

Python Environment

- -

Pre-Commit

- -

Poetry

- -

GitHub Actions

- -

Git

- -

Functionality

-

Initialization

- -

Review Commands

- -

GitHub Actions

- -

Custom Rules

- -

Performance

-

Caching

- -

Asynchronous Operations

- -

Security

-

Authentication

- -

Input Validation

- -

Database (Future Consideration)

- -

Documentation

- -

Testing

- -

Error Handling

- -

Project Milestones

-
    -
  1. Sprint 1: Project Setup and Pre-Commit Integration
  2. -
  3. Sprint 2: GPT and Git Diff Implementation
  4. -
  5. Sprint 3: GitHub Actions and Testing
  6. -
  7. Sprint 4: Packaging and Documentation
  8. -
  9. Sprint 5: Security and Performance Optimization
  10. -
-

Project Milestones Breakdown: A Step-by-Step Journey Through Sprints

-

====================================================================

-
-

Sprint 1: Project Setup and Pre-Commit Integration

-
-

Raise the curtain, and set the stage!

- -
-

Sprint 2: GPT and Git Diff Implementation

-

A symphony of bytes and text, harmonized by GPT.

- -
-

Sprint 3: GitHub Actions and Testing

-
-

The celestial dance of automation, choreographed in YAML.

- -
-

Sprint 4: Packaging and Documentation

-
-

The grand finale, where your creation takes its final shape.

- -
-

Sprint 5: Security and Performance Optimization

-
-

The encore, presenting a refined and resilient performance.

- -
-

May this project, bestowed with the wisdom of GPT and crafted in the magical language of Python, usher in a new dawn of code reviews. Be the maestro of your code symphony, conducting each line and function in perfect harmony.

- - - - - diff --git a/README.md b/README.md index 833184b..706688a 100644 --- a/README.md +++ b/README.md @@ -1,235 +1,61 @@ -# Project Requirements: Do you Even Diff Bro +# Introduction -========================================== +This project is a command-line tool named BROGRAMMER, an AI Peer Review Bro. It uses the GPT model to review your code and provide feedback. The tool is designed to be flexible, allowing you to specify the level of feedback you want, the files you want to include or exclude, and even the branch to compare the git diff against. -_In the vast landscape of the coding world, where giants like GitHub and GitLab rule the realms, a new sprout emerges from the fertile soil. A code review tool imbued with the wisdom of GPT, designed to illuminate your path to better code. Witness the birth of a new era of code reviews._ +## Detailed Description -## Introduction +The main functionality of the tool is contained within the `main.py` file. This script uses the `typer` library to parse command-line arguments and options. It also uses the `rich` library to display the results in a formatted table in the console. -The GPT Code Review Tool aims to provide an automated yet insightful review of code changes in Python projects. It leverages the powers of pre-commit, Poetry for dependency management, Git Diff for code comparison, and GitHub Actions for CI/CD, culminating in a poetic symphony of code quality enforcement. +The `git.py` file contains a function to get the git diff of the current project. It allows you to specify the file extensions to include or exclude, and the branch to compare against. -## Core Features +The `llm.py` file contains functions to interact with the GPT model. It sends a prompt to the model and parses the response. -### GPT-Enhanced Review +The `models.py` file defines the `BroMode` enum, which represents the level of feedback you want from the tool. -- Leverage GPT to provide natural language comments on code style, efficiency, and potential bugs. -- Provide actionable insights for code improvement. +The `utils.py` file contains utility functions used by the other scripts. -### Pre-Commit Integration +The `constants.py` and `commandments.py` files contain constant values and functions to generate prompts for the GPT model, respectively. -- Automatically initiate code review before any commit occurs. -- Customizable rule sets based on project requirements. +## How to Use -### Poetry Support +To use the tool, run the `main.py` script with the desired options. For example, to get a chill, jr-level engineer review, use the `--chill` option: -- Manage dependencies like a maestro conducting a symphony, utilizing Poetry for Python package management. +```bash +python main.py --chill +``` -### Git Diff Comparison +To specify the GPT model to use, use the `--model` option: -- Not just a shallow look but a deep understanding of changes through Git Diff. -- Highlight line-by-line changes for more nuanced feedback. +```bash +python main.py --model gpt-4 +``` -### GitHub Actions +To only include files with certain extensions, use the `--only` option: -- A gatekeeper in the cloud, automatically running reviews on pull requests and commits. -- Configurable through YAML files, empowering you to customize your checks. +```bash +python main.py --only .py .js +``` -### PyPI Package +To ignore files with certain extensions, use the `--ignore` option: -- Easily installable and shareable as a PyPI package. +```bash +python main.py --ignore .txt .md +``` -## Technical Requirements +To specify a custom prompt for the GPT model, use the `--prompt` option: -### Python Environment +```bash +python main.py --prompt "Review this code" +``` -- Python 3.9 or higher +To summarize the git diff, use the `--summarize` option: -### Pre-Commit +```bash +python main.py --summarize +``` -- Latest stable version +To specify the branch to compare the git diff against, use the `--peer-review` option: -### Poetry - -- Latest stable version - -### GitHub Actions - -- Compatible with the latest GitHub Actions API - -### Git - -- Git 2.32 or higher for optimal Git Diff support - -## Functionality - -### Initialization - -- `init` command to scaffold project settings. - -### Review Commands - -- `review`: Initiate a full project review -- `review-file`: Review a single file -- `review-diff`: Review code based on Git Diff. - -### GitHub Actions - -- YAML configuration files for different types of review. - -### Custom Rules - -- JSON or YAML-based rule definitions. - -## Performance - -### Caching - -- Implement caching of previously reviewed code snippets. - -### Asynchronous Operations - -- Utilize Python's `asyncio` for non-blocking operations. - -## Security - -### Authentication - -- GitHub OAuth for secure API interactions. - -### Input Validation - -- Extensive validation checks to mitigate injection attacks. - -## Database (Future Consideration) - -- PostgreSQL 12.14 for analytics and caching. - -## Documentation - -- Sphinx-based documentation available as HTML and PDF. - -## Testing - -- Extensive test suite using `pytest`. - -## Error Handling - -- Utilize `try/except` blocks in Python and gracefully handle all exceptions. -- Log errors to a persistent storage medium. - -## Project Milestones - -1. **Sprint 1**: Project Setup and Pre-Commit Integration -2. **Sprint 2**: GPT and Git Diff Implementation -3. **Sprint 3**: GitHub Actions and Testing -4. **Sprint 4**: Packaging and Documentation -5. **Sprint 5**: Security and Performance Optimization - -## Project Milestones Breakdown: A Step-by-Step Journey Through Sprints - -==================================================================== - ---- - -### Sprint 1: Project Setup and Pre-Commit Integration - ---- - -_Raise the curtain, and set the stage!_ - -- \[ ✅\] **Initialize Git Repository** - - Run `git init` -- \[ ✅\] **Initialize Poetry** - - Run `poetry init` -- \[ ✅\] **Add Project Dependencies** - - Use `poetry add ` -- \[ ✅\] **Initialize Pre-Commit Config** - - Create `.pre-commit-config.yaml` -- \[ ✅\] **Install Pre-Commit Hooks** - - Run `pre-commit install` -- \[ ✅\] **Initial Commit** - - Run `git add . && git commit -m "Initial commit"` -- \[ ✅\] **Push to GitHub** - - Create GitHub repository and push - ---- - -### Sprint 2: GPT and Git Diff Implementation - -_A symphony of bytes and text, harmonized by GPT._ - -- \[ \] **Integrate GPT Library** - - Use OpenAI’s Python package -- \[ \] **Implement GPT Review Functionality** - - Create a function to send code to GPT and receive insights -- \[ \] **Create Git Diff Parser** - - Function to interpret `git diff` output -- \[ \] **Combine GPT and Git Diff** - - Merge insights from GPT based on Git Diff -- \[ \] **Unit Testing** - - Write unit tests for new features -- \[ \] **Commit and Push** - - Push the changes to GitHub - ---- - -### Sprint 3: GitHub Actions and Testing - ---- - -_The celestial dance of automation, choreographed in YAML._ - -- \[ \] **Create GitHub Actions YAML** - - Create `.github/workflows/main.yaml` -- \[ \] **Configure Pre-Commit Action** - - Add pre-commit action to YAML -- \[ \] **Configure GPT Review Action** - - Add custom GitHub action for GPT-based review -- \[ \] **Test GitHub Actions** - - Trigger actions via push or pull request -- \[ \] **Write Additional Tests** - - Increase code coverage -- \[ \] **Commit and Push** - - Update GitHub repository - ---- - -### Sprint 4: Packaging and Documentation - ---- - -_The grand finale, where your creation takes its final shape._ - -- \[ \] **Prepare for PyPI Packaging** - - Create `setup.py` and `MANIFEST.in` -- \[ \] **Document Code** - - Inline comments and docstrings -- \[ \] **Create User Documentation** - - Use Sphinx to generate docs -- \[ \] **Commit and Push** - - Make sure everything is in GitHub -- \[ \] **Publish to PyPI** - - Run `poetry publish` - ---- - -### Sprint 5: Security and Performance Optimization - ---- - -_The encore, presenting a refined and resilient performance._ - -- \[ \] **Implement OAuth for GitHub** - - Secure API requests -- \[ \] **Input Validation** - - Protect against injections -- \[ \] **Implement Caching** - - Use appropriate data structures for fast lookup -- \[ \] **Asynchronous Code Review** - - Utilize Python's `asyncio` for non-blocking operations -- \[ \] **Commit and Push** - - Final push to GitHub with all optimizations - ---- - -_May this project, bestowed with the wisdom of GPT and crafted in the magical language of Python, usher in a new dawn of code reviews. Be the maestro of your code symphony, conducting each line and function in perfect harmony._ +```bash +python main.py --peer-review main +``` diff --git a/py_do_you_even_diff_bro/commandments.py b/py_do_you_even_diff_bro/commandments.py index a64ac78..3c600fb 100644 --- a/py_do_you_even_diff_bro/commandments.py +++ b/py_do_you_even_diff_bro/commandments.py @@ -1,30 +1,46 @@ from typing import Dict from py_do_you_even_diff_bro.models import BroMode -from py_do_you_even_diff_bro.utils import StrEnum -SUMMARY_BRO_PROMPT = "Summarize the git diff below in a concise, 1-2 sentence description of the changes made. It will be used as the git commit message. Focus on high-level changes not code level details." +SUMMARY_BRO_PROMPT = """Summarize the git dif below in a concise, 1-2 sentence description of the changes made. It will be used as the git commit message. Focus on high-level changes not code level details. +""" -CORE_DIFF_BRO_SYSTEM_PROMPT = """You're brogrammer. A programmer's ultimate peer review bro. -You're here to help your bros review their code before they embarrass themselves in front of the whole team. -You take git diffs and convert them into a format that's easy for your bros to understand and act on. -You're a bro, but you're also a brogrammer. You're a diffbrogrammer. -Follow the prompt DETAILS and GIT_DIFF below to help your bro review their code. +CORE_DIFF_SYSTEM_PROMPT = """Yo, what's up bro? You're the ultimate code reviewing bro, the diffbro. You got my back when it comes to reviewing my code before you make a fool of yourself in front of the whole team, bro. +You take those gnarly git diffs and transform them into a format that's super easy for me to understand and take action on. You're not just a bro, You're a brogrammer, bro. You're a diffbrogrammer. You're diffy bro, and you're here to help you out. +Dive into the DETAILS and GIT_DIFF I provide, and together, we'll rock this code review like true college coding bros. Let's make some magic happen, bro! """ -BRO_DETAILS = { - "chill": "You're a chill coder bro. Your job is to peer review your bro's code. You look for the big picture stuff and you aren't worried about small details like formatting, naming, pass statements in try blocks, etc. You focus on only code that could lead to critical bugs and nothing else.", - "mid": "You're a mid level coder bro. You're starting to rise the ranks so you have something to lose by not reviewing your bro's code and by reviewing poorly. You look for any critical bugs, improvements, and you also look for any formatting, code smells, naming, pass statements in try blocks, etc. You focus on code that could lead to critical bugs and you also look for any code that could lead to non-critical bugs.", - "chad": "You're a chad coder bro. You're at the top of the ranks so you have a lot to lose by not reviewing your bro's code and by reviewing poorly. You look for any critical bugs, improvements, and you also look for any formatting, code smells, naming, pass statements in try blocks, etc. You focus on code that could lead to critical bugs and you also look for any code that could lead to non-critical bugs. You diligently report each bug or issue on a low, medium, high scale. You organize them so that the most critical bugs are at the top of the list and the least critical bugs are at the bottom of the list. Nothing gets past you, you triple check everything.", -} +CHILL_BRO_PR_REVIEW_PROMPT = f"""{CORE_DIFF_SYSTEM_PROMPT} + +DETAILS: + +You're a chill coder BROGRAMMER. Your job is to peer review your bro's code. You look for the big picture stuff and you aren't worried about small details like formatting, naming, pass statements in try blocks, etc. You focus on only code that could lead to critical bugs and nothing else. +You're a chill bro. You're a chill coder bro. You're a chill BROGRAMMER. +""" + +MID_BRO_PR_REVIEW_PROMPT = f"""{CORE_DIFF_SYSTEM_PROMPT} + +DETAILS: + +You're a mid level coder bro. You're starting to rise the ranks so you have something to lose by not reviewing your bro's code and by reviewing poorly. You look for any critical bugs, improvements, and you also look for any formatting, naming, pass statements in try blocks, etc. You focus on code that could lead to critical bugs and you also look for any code that could lead to non-critical bugs. You're a mid level BROGRAMMER. +""" + +CHAD_BRO_PR_REVIEW_PROMPT = f"""{CORE_DIFF_SYSTEM_PROMPT} + +DETAILS: + +Yo, CodeMaster Chad here! You're the alpha of the coding pack, the legend who lifts more lines of code than weights. Your job? Flex those coding muscles and review your bros' code like you're spotting them at the gym. +Hunt down those bugs like you're on a protein-packed coding diet - focus on the big gains (critical bugs) and don't miss out on the smaller reps (non-critical stuff). Your code should be as clean and ripped as your post-workout selfie. +Rate those bugs like you're judging a beach body contest: high, medium, low. Stack them up like you're organizing your protein shakes - the heaviest, most muscle-making bugs at the top, and the lightweight, just-for-the-taste bugs at the bottom. +Remember, you're the Chad of the Code. Double-check everything like you check yourself out in the mirror. Zero tolerance for buggy code in these iron-pumping, key-smashing fingers. Let's make this code as buff as our biceps, bro! +""" -CHILL, MID, CHAD = StrEnum.get_enum_by_names(BroMode) MAP_BRO_MODE_TO_PROMPT: Dict[BroMode, str] = { - CHILL: f"{CORE_DIFF_BRO_SYSTEM_PROMPT}\n\nDETAILS:\n\n{BRO_DETAILS['chill']}", - MID: f"{CORE_DIFF_BRO_SYSTEM_PROMPT}\n\nDETAILS:\n\n{BRO_DETAILS['mid']}", - CHAD: f"{CORE_DIFF_BRO_SYSTEM_PROMPT}\n\nDETAILS:\n\n{BRO_DETAILS['chad']}", + BroMode.CHILL: CHILL_BRO_PR_REVIEW_PROMPT, + BroMode.MID: MID_BRO_PR_REVIEW_PROMPT, + BroMode.CHAD: CHAD_BRO_PR_REVIEW_PROMPT, } @@ -32,6 +48,7 @@ def get_diff_prompt(bro_mode: BroMode, git_diff: str) -> str: """ Returns the prompt for the given bro mode and git diff """ + # print(MAP_BRO_MODE_TO_PROMPT[bro_mode]) return f"""{MAP_BRO_MODE_TO_PROMPT[bro_mode]} GIT_DIFF: diff --git a/py_do_you_even_diff_bro/constants.py b/py_do_you_even_diff_bro/constants.py index 14d02d6..801fe73 100644 --- a/py_do_you_even_diff_bro/constants.py +++ b/py_do_you_even_diff_bro/constants.py @@ -60,19 +60,19 @@ You can get an API key here: https://platform.openai.com/account/api-keys -Export command - `export OPENAI_API_KEY=` +Export command in terminal: + `export OPENAI_API_KEY=` Examples: * I want a chill review on my .py and .js files: - brogrammer --chill --only .py .js + brogrammer --chill --only .py .js - * I'm about to ship production, mission critical UI code, I need a hardcore review on my FE code: - brogrammer --chad --model gpt-4 --only .js .jsx .tsx .svelte + * I'm about to ship production, mission critical UI code, I need a hardcore review on my frontend code: + brogrammer --chad --model gpt-4 --only .js .jsx .tsx .svelte * I'm about to a fullstack app and need a comprehensive mid level review on all my code excluding .tsx files: - brogrammer --mid --model gpt-4 --ignore .tsx + brogrammer --mid --model gpt-4 --ignore .tsx - * I want legit reviews all the time: - Create a bash alias: alias dbro='brogrammer --chad --model gpt-4' + * I want legit reviews of all the diff: + brogrammer --chad """ diff --git a/py_do_you_even_diff_bro/git.py b/py_do_you_even_diff_bro/git.py index 9ac70d2..e21755f 100644 --- a/py_do_you_even_diff_bro/git.py +++ b/py_do_you_even_diff_bro/git.py @@ -1,13 +1,7 @@ -import logging from typing import List from py_do_you_even_diff_bro.utils import run_shell_command -logging.basicConfig( - level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" -) -logger = logging.getLogger(__name__) - def get_git_diff( only: List[str] = None, ignore: List[str] = None, branch: str = None @@ -22,21 +16,18 @@ def get_git_diff( command = ["git", "diff"] if branch: - logger.info(f"Diffing against branch {branch}") command.append(branch) if only or ignore: command.append("--") if ignore: - logger.info(f"Diffing ALL files except those with extensions {ignore}") files = [f":(exclude)**/*{ext}" for ext in ignore] command.extend(files) elif only: - logger.info(f"Diffing exclusively files with extensions {only}") files = [f"**/*{ext}" for ext in only] command.extend(files) else: - logger.info(f"Diffing all files (no extensions specified))") + pass return run_shell_command(" ".join(command)) diff --git a/py_do_you_even_diff_bro/llm.py b/py_do_you_even_diff_bro/llm.py index 51c476e..9f27ab5 100644 --- a/py_do_you_even_diff_bro/llm.py +++ b/py_do_you_even_diff_bro/llm.py @@ -28,7 +28,7 @@ def response_parser(response: Dict[str, Any]) -> str: return safe_get(response, "choices.0.message.content") -def prompt(prompt: str, model: str = "gpt-4") -> str: +def gpt_prompt(prompt: str, model: str = "gpt-4") -> str: if not openai.api_key: sys.exit( """ diff --git a/py_do_you_even_diff_bro/main.py b/py_do_you_even_diff_bro/main.py index 646659b..7dabad3 100644 --- a/py_do_you_even_diff_bro/main.py +++ b/py_do_you_even_diff_bro/main.py @@ -1,9 +1,9 @@ -from typing import List, Optional +from typing import List import typer from rich.console import Console from rich.table import Table -from typing_extensions import Annotated +from typer import Argument, Option, confirm, prompt from py_do_you_even_diff_bro.commandments import SUMMARY_BRO_PROMPT, get_diff_prompt from py_do_you_even_diff_bro.constants import ( @@ -11,112 +11,152 @@ PROGRAMMING_FILE_EXTENSIONS, ) from py_do_you_even_diff_bro.git import get_git_diff -from py_do_you_even_diff_bro.llm import prompt +from py_do_you_even_diff_bro.llm import gpt_prompt from py_do_you_even_diff_bro.models import BroMode -app = typer.Typer(help=DETAILED_BROGRAMMER_DESCRIPTION) +app = typer.Typer( + name="BROGRAMMER", + help="BROGRAMMER: Your AI Peer Review Bro", + epilog=DETAILED_BROGRAMMER_DESCRIPTION, +) console = Console() -def display_summary_table(model: str, bro_mode: BroMode, git_diff: str): - table = Table(show_header=True, header_style="bold magenta") - table.add_column("Model", style="dim", width=12) - table.add_column("Bro Mode", style="dim", width=12) - table.add_column("Git Diff", style="dim", width=50) - table.add_row( - model, str(bro_mode), git_diff[:50] + "..." if len(git_diff) > 50 else git_diff - ) - console.print(table) +def get_bro_mode(chill: bool, mid: bool, chad: bool) -> BroMode: + mode_map = {chill: BroMode.CHILL, mid: BroMode.MID, chad: BroMode.CHAD} + return next((mode for cond, mode in mode_map.items() if cond), BroMode.CHILL) -def determine_bro_mode(chill: bool, mid: bool, chad: bool, model: str) -> BroMode: - bro_mode: BroMode = BroMode.CHILL - if mid: - bro_mode = BroMode.MID - elif chad: - bro_mode = BroMode.CHAD - if model != "gpt-4": - user_input = input( - "Chad mode is engaged. It is suggested to use 'gpt-4' model for optimal results. Do you want to switch to 'gpt-4'? (y/n): " - ) - if user_input.lower() not in ["yes", "y"]: - model = input( - "Enter the model you want to use (e.g., 'gpt-3.5-turbo'): " - ) - return bro_mode, model +def display_diff_summary(summary_diff_response: str): + table = Table(show_header=True, header_style="bold magenta") + table.add_column("Summary", justify="left") + table.add_row(summary_diff_response) + console.print(table) -def fetch_git_diff( - only: List[str], ignore: List[str], peer_review: Optional[str] -) -> str: - git_diff = get_git_diff(only, ignore, peer_review) - if not git_diff: - print(f"No git diff for BROGRAMMER to review. Exiting.") - return "" - return git_diff +def validate_extensions( + ctx: typer.Context, param: typer.CallbackParam, value: List[str] +) -> List[str]: + invalid_exts = [ext for ext in value if ext not in PROGRAMMING_FILE_EXTENSIONS] + if invalid_exts: + for ext in invalid_exts: + console.log(f"Invalid file extension: {ext}", style="bold red") + raise typer.BadParameter(f"Invalid extensions: {', '.join(invalid_exts)}") + return value -def run_diff( - bro_mode: BroMode, - model: str, - git_diff: str, - custom_prompt: Optional[str], - summarize: bool, +@app.command() +def main( + chill: bool = typer.Option( + False, + "--chill", + "-c", + help="Get a chill, jr-level engineer, relaxed BROGRAMMER PR review", + ), + mid: bool = typer.Option( + False, "--mid", "-m", help="Get a mid-level engineer, BROGRAMMER PR review" + ), + chad: bool = typer.Option( + False, + "--chad", + "-d", + help="Get a chad, sr-level engineer, intense BROGRAMMER PR review", + ), + model: str = typer.Option( + "gpt-4", "--model", "-o", help="GPT model use 'gpt-3.5-turbo' or 'gpt-4'" + ), + only: list[str] = typer.Option( + PROGRAMMING_FILE_EXTENSIONS, + "--only", + callback=validate_extensions, + help="Only include files with these extensions", + ), + ignore: list[str] = typer.Option( + [], + "--ignore", + callback=validate_extensions, + help="Ignore files with these extensions", + ), + prompt: str = typer.Option("", "--prompt", "-p", help="Specify a custom prompt"), + summarize: bool = typer.Option( + False, "--summarize", "-s", help="Summarize the git diff" + ), + peer_review: str = typer.Option( + "", + "--peer-review", + "-r", + help="Specify the branch to compare the git diff against", + ), ): - print( - f"Building prompt for diffbro in bromode: '{bro_mode.name.lower()}' mode on GPT model '{model}'" - f"{f' with custom prompt: {custom_prompt}' if custom_prompt else ''}" - f"{f' Will generate diff summary.' if summarize else ''}" - ) + try: + valid_models = ["gpt-3.5-turbo", "gpt-4"] + if model not in valid_models: + console.log( + "Invalid model. Choose from 'gpt-3.5-turbo' or 'gpt-4'", + style="bold red", + ) + return + + invalid_only_exts = [ + ext for ext in only if ext not in PROGRAMMING_FILE_EXTENSIONS + ] + invalid_ignore_exts = [ + ext for ext in ignore if ext not in PROGRAMMING_FILE_EXTENSIONS + ] + + if invalid_only_exts or invalid_ignore_exts: + if invalid_only_exts: + console.log( + f"Invalid file extensions in --only: {', '.join(invalid_only_exts)}", + style="bold red", + ) + if invalid_ignore_exts: + console.log( + f"Invalid file extensions in --ignore: {', '.join(invalid_ignore_exts)}", + style="bold red", + ) + return - if not custom_prompt: - prompt_text = get_diff_prompt(bro_mode, git_diff) - else: - prompt_text = f"{custom_prompt}\n\n{git_diff}" + if sum([chill, mid, chad]) > 1: + console.log( + "Only one of --chill, --mid, or --chad can be True.", style="bold red" + ) + return - print(f"Running BROGRAMMER") + bro_mode = get_bro_mode(chill, mid, chad) - diff_resp = prompt(prompt_text, model) + git_diff = get_git_diff(only, ignore, peer_review) - print("\n\nBROGRAMMER\n\n", diff_resp) + if not git_diff: + print(f"No git diff for BROGRAMMER") + return - if summarize: - print(f"\n\nSummarizing git diff for diffbro on GPT model '{model}'") + print( + f"Building prompt for BROGRAMMER in bromode: '{str(bro_mode)}' mode on GPT model '{model}'" + f"{f' with custom prompt: {prompt}' if prompt else ''}" + f"{f' Will generate diff summary.' if summarize else ''}" + ) - summary_prompt_text = f"{SUMMARY_BRO_PROMPT}\n\n{git_diff}" + if summarize: + print(f"\n\nSummarizing git diff for BROGRAMMER on GPT model '{model}'") - summary_diff_resp = prompt(summary_prompt_text, model) + summary_prompt_text = f"{SUMMARY_BRO_PROMPT}\n\n{git_diff}" - print("\n\nBROGRAMMER SUMMARY\n\n", summary_diff_resp) + summary_diff_response = gpt_prompt(summary_prompt_text, model) + display_diff_summary(summary_diff_response) + else: + if not prompt: + prompt_text = get_diff_prompt(bro_mode, git_diff) + else: + prompt_text = f"{prompt}\n\n{git_diff}" + print(f"Running BROGRAMMER") -@app.command() -def main( - chill: bool = typer.Option( - True, help="Get a chill, jr-level engineer, relaxed diffbro PR review" - ), - mid: bool = typer.Option(False, help="Get a mid-level engineer, diffbro PR review"), - chad: bool = typer.Option( - False, help="Get a chad, sr-level engineer, intense diffbro PR review" - ), - model: Annotated[Optional[str], typer.Argument()] = "gpt-4", - only: List[str] = typer.Option( - PROGRAMMING_FILE_EXTENSIONS, help="Only include files with these extensions" - ), - ignore: List[str] = typer.Option([], help="Ignore files with these extensions"), - custom_prompt: Optional[str] = typer.Option(None, help="Specify a custom prompt"), - summarize: bool = typer.Option(False, help="Summarize the git diff"), - peer_review: Optional[str] = typer.Option( - None, help="Specify the branch to compare the git diff against" - ), -): - try: - bro_mode, model = determine_bro_mode(chill, mid, chad, model) - git_diff = fetch_git_diff(only, ignore, peer_review) - if git_diff: - run_diff(bro_mode, model, git_diff, custom_prompt, summarize) + diff_response = gpt_prompt(prompt_text, model) + + print("\nBROGRAMMER\n\n", diff_response) except Exception as e: - console.print(f"[red]An error occurred: {e}[/red]") + console.log(f"An error occurred: {e}", style="bold red") if __name__ == "__main__": diff --git a/py_do_you_even_diff_bro/models.py b/py_do_you_even_diff_bro/models.py index eace5cf..160d732 100644 --- a/py_do_you_even_diff_bro/models.py +++ b/py_do_you_even_diff_bro/models.py @@ -1,13 +1,62 @@ -from pydantic import BaseModel - -from py_do_you_even_diff_bro.utils import StrEnum +from datetime import datetime +from typing import List, Optional +from pydantic import BaseModel, Field, validator -class GPTRequest(BaseModel): - code_snippet: str +from py_do_you_even_diff_bro.utils import StrEnum class BroMode(StrEnum): CHILL = "chill" MID = "mid" CHAD = "chad" + + +class BugBase(BaseModel): + bug_id: str + description: str + + @validator("bug_id") + def validate_bug_id(cls, v): + if not v.startswith("B") or not v[1:].isdigit(): + raise ValueError("bug_id must start with B followed by numbers") + return v + + +class BugReport(BugBase): + pass + + +class BugImprovement(BugBase): + improvements: str + code: str + + +class SeverityReports(BaseModel): + high: Optional[List[BugReport]] = [] + medium: Optional[List[BugReport]] = [] + low: Optional[List[BugReport]] = [] + + +class SeverityImprovements(BaseModel): + high: Optional[List[BugImprovement]] = [] + medium: Optional[List[BugImprovement]] = [] + + +class MetaData(BaseModel): + timestamp: Optional[datetime] = Field(default_factory=datetime.utcnow) + session_id: Optional[str] + user_info: Optional[dict] + + +class ErrorModel(BaseModel): + error_message: Optional[str] + error_code: Optional[int] + + +class CodeReview(BaseModel): + summary: str + severities: SeverityReports + suggestions: SeverityImprovements + metadata: Optional[MetaData] = Field(default_factory=MetaData) + error: Optional[ErrorModel] diff --git a/pyproject.toml b/pyproject.toml index fd248cb..d89d76e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,8 @@ pre-commit = "^3.5.0" pytest = "^7.4.3" pydantic = "^2.4.2" typer = {extras = ["all"], version = "^0.9.0"} +pytest-mock = "^3.12.0" +unidiff = "^0.7.5" [tool.poetry.group.dev.dependencies] diff --git a/tests/test_main.py b/tests/test_main.py index e69de29..85e2b05 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -0,0 +1,192 @@ +import pytest + +import py_do_you_even_diff_bro.main +from py_do_you_even_diff_bro.main import ( + display_diff_summary, + get_bro_mode, + get_git_diff, + main, +) +from py_do_you_even_diff_bro.models import BroMode + + +def test_get_bro_mode(): + assert get_bro_mode(True, False, False) == BroMode.CHILL + assert get_bro_mode(False, True, False) == BroMode.MID + assert get_bro_mode(False, False, True) == BroMode.CHAD + assert get_bro_mode(False, False, False) == BroMode.CHILL + + +def test_display_diff_summary(mocker): + mocker.patch("py_do_you_even_diff_bro.main.console") + display_diff_summary("Test summary") + py_do_you_even_diff_bro.main.console.print.assert_called_once() + + +@pytest.mark.parametrize( + "chill, mid, chad, model, only, ignore, prompt, summarize, peer_review", + [ + (True, False, False, "gpt-4", [".py"], [], "", False, ""), + ( + False, + True, + False, + "gpt-3.5-turbo", + [".py"], + [".ts"], + "Test prompt", + True, + "main", + ), + ], +) +def test_main( + chill, mid, chad, model, only, ignore, prompt, summarize, peer_review, mocker +): + mocker.patch("py_do_you_even_diff_bro.main.get_git_diff", return_value="Test diff") + mocker.patch( + "py_do_you_even_diff_bro.main.gpt_prompt", return_value="Test response" + ) + main(chill, mid, chad, model, only, ignore, prompt, summarize, peer_review) + if get_git_diff: + py_do_you_even_diff_bro.main.get_git_diff.assert_called_once_with( + only, ignore, peer_review + ) + else: + py_do_you_even_diff_bro.main.get_git_diff.assert_not_called() + py_do_you_even_diff_bro.main.gpt_prompt.assert_called() + + +# Test with a different model +def test_main_with_different_model(mocker): + mocker.patch("py_do_you_even_diff_bro.main.get_git_diff", return_value="Test diff") + mocker.patch( + "py_do_you_even_diff_bro.main.gpt_prompt", return_value="Test response" + ) + main( + False, + True, + False, + "gpt-3.5-turbo", + [".py"], + [".ts"], + "Test prompt", + True, + "main", + ) + py_do_you_even_diff_bro.main.get_git_diff.assert_called_once_with( + [".py"], [".ts"], "main" + ) + py_do_you_even_diff_bro.main.gpt_prompt.assert_called() + + +# Test with a different file type +def test_main_with_different_file_type(mocker): + mocker.patch("py_do_you_even_diff_bro.main.get_git_diff", return_value="Test diff") + mocker.patch( + "py_do_you_even_diff_bro.main.gpt_prompt", return_value="Test response" + ) + main( + False, + True, + False, + "gpt-3.5-turbo", + [".java"], + [".ts"], + "Test prompt", + True, + "main", + ) + py_do_you_even_diff_bro.main.get_git_diff.assert_called_once_with( + [".java"], [".ts"], "main" + ) + py_do_you_even_diff_bro.main.gpt_prompt.assert_called() + + +# Test with a different prompt value +def test_main_with_different_prompt(mocker): + mocker.patch("py_do_you_even_diff_bro.main.get_git_diff", return_value="Test diff") + mocker.patch( + "py_do_you_even_diff_bro.main.gpt_prompt", return_value="Test response" + ) + main( + False, + True, + False, + "gpt-3.5-turbo", + [".py"], + [".ts"], + "Different prompt", + True, + "main", + ) + py_do_you_even_diff_bro.main.get_git_diff.assert_called_once_with( + [".py"], [".ts"], "main" + ) + py_do_you_even_diff_bro.main.gpt_prompt.assert_called() + + +# Test with a different peer_review value +def test_main_with_different_peer_review(mocker): + mocker.patch("py_do_you_even_diff_bro.main.get_git_diff", return_value="Test diff") + mocker.patch( + "py_do_you_even_diff_bro.main.gpt_prompt", return_value="Test response" + ) + main( + False, + True, + False, + "gpt-3.5-turbo", + [".py"], + [".ts"], + "Test prompt", + True, + False, + ) + py_do_you_even_diff_bro.main.get_git_diff.assert_called_once_with( + [".py"], [".ts"], False + ) + py_do_you_even_diff_bro.main.gpt_prompt.assert_called() + + +# Test with a different summarize value +def test_main_with_different_summarize(mocker): + mocker.patch("py_do_you_even_diff_bro.main.get_git_diff", return_value="Test diff") + mocker.patch( + "py_do_you_even_diff_bro.main.gpt_prompt", return_value="Test response" + ) + main( + False, + True, + False, + "gpt-3.5-turbo", + [".py"], + [".ts"], + "Test prompt", + False, + "main", + ) + py_do_you_even_diff_bro.main.get_git_diff.assert_called_once_with( + [".py"], [".ts"], "main" + ) + py_do_you_even_diff_bro.main.gpt_prompt.assert_called() + + +# Test error scenario +def test_main_with_error(mocker): + mocker.patch( + "py_do_you_even_diff_bro.main.get_git_diff", side_effect=Exception("Test error") + ) + if not get_git_diff: + with pytest.raises(Exception, match="Test error"): + main( + False, + True, + False, + "gpt-3.5-turbo", + [".py"], + [".ts"], + "Test prompt", + True, + "main", + )