├── .gitignore ├── LICENSE ├── README.md ├── commit-message-generator.py └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control 110 | .pdm.toml 111 | .pdm-python 112 | .pdm-build/ 113 | 114 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 115 | __pypackages__/ 116 | 117 | # Celery stuff 118 | celerybeat-schedule 119 | celerybeat.pid 120 | 121 | # SageMath parsed files 122 | *.sage.py 123 | 124 | # Environments 125 | .env 126 | .venv 127 | env/ 128 | venv/ 129 | ENV/ 130 | env.bak/ 131 | venv.bak/ 132 | 133 | # Spyder project settings 134 | .spyderproject 135 | .spyproject 136 | 137 | # Rope project settings 138 | .ropeproject 139 | 140 | # mkdocs documentation 141 | /site 142 | 143 | # mypy 144 | .mypy_cache/ 145 | .dmypy.json 146 | dmypy.json 147 | 148 | # Pyre type checker 149 | .pyre/ 150 | 151 | # pytype static type analyzer 152 | .pytype/ 153 | 154 | # Cython debug symbols 155 | cython_debug/ 156 | 157 | # PyCharm 158 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 159 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 160 | # and can be added to the global gitignore or merged into this file. For a more nuclear 161 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 162 | #.idea/ 163 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License Copyright (c) 2024 Danilo Poccia 2 | 3 | Permission is hereby granted, free 4 | of charge, to any person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, copy, modify, merge, 7 | publish, distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to the 9 | following conditions: 10 | 11 | The above copyright notice and this permission notice 12 | (including the next paragraph) shall be included in all copies or substantial 13 | portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO 18 | EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR 19 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🤖 AI-Powered Git Commit Message Generator 2 | 3 | Elevate your Git workflow with intelligent, context-aware commit messages generated by an AI language model. 4 | 5 | ## 🌟 Features 6 | 7 | - **AI-Powered**: Leverages AI models via Amazon Bedrock for high-quality commit message generation. 8 | - **Context-Aware**: Analyzes file changes, diffs, and commit history for comprehensive understanding. 9 | - **Smart Formatting**: Generates commit messages following best practices, including summary lines and detailed explanations. 10 | - **Interactive**: Allows user review and approval of generated commit messages. 11 | - **Git-Integrated**: Seamlessly works with your existing Git workflow. 12 | - **Customizable**: Supports debug mode for detailed logging and troubleshooting. 13 | 14 | ## 🚀 Quick Start 15 | 16 | 1. **Clone the repository:** 17 | ``` 18 | git clone https://github.com/yourusername/commit-message-generator.git 19 | cd commit-message-generator 20 | ``` 21 | 22 | 2. **Install dependencies:** 23 | ``` 24 | pip install -r requirements.txt 25 | ``` 26 | 27 | 3. **Set up AWS credentials:** 28 | Ensure your AWS credentials are configured with access to Amazon Bedrock. 29 | 30 | 4. **Add the script to your PATH:** 31 | ``` 32 | export PATH="$PATH:/path/to/commit-message-generator" 33 | ``` 34 | 35 | 5. **Run the script from within your repository:** 36 | ``` 37 | commit-message-generator.py 38 | ``` 39 | 40 | 5. **Review and approve** the generated commit message. 41 | 42 | ## 🛠️ Usage 43 | 44 | After staging your changes in Git, run the script (it should be in your `PATH`): 45 | 46 | ``` 47 | commit-message-generator.py [options] 48 | ``` 49 | 50 | ### Command Line Options 51 | 52 | - `--debug`: Enable detailed debug logging 53 | - `--include-content`: Include file content in the analysis (disabled by default) 54 | - `--include-history`: Include file commit history in the analysis (disabled by default) 55 | - `--context-lines N`: Number of context lines to show in git diff (default: 3) 56 | 57 | Example: 58 | ```bash 59 | # Run with debug logging and include file history 60 | commit-message-generator.py --debug --include-history 61 | 62 | # Include both content and history, with 5 lines of context 63 | commit-message-generator.py --include-content --include-history --context-lines 5 64 | ``` 65 | 66 | The script will: 67 | 1. Analyze staged changes 68 | 2. Generate a commit message using an AI model 69 | 3. Display the message for your review 70 | 4. Commit the changes if you approve 71 | 72 | ## 🧠 How It Works 73 | 74 | 1. **File Analysis**: Gathers information about staged files, including content, history, and diffs. 75 | 2. **Prompt Generation**: Creates a detailed XML-formatted prompt, including file changes and commit context. 76 | 3. **AI Processing**: Sends the prompt to Amazon Bedrock for intelligent analysis. 77 | 4. **Message Generation**: The model generates a contextually relevant and well-formatted commit message. 78 | 5. **User Interaction**: Presents the generated message for user review and approval. 79 | 6. **Git Integration**: Commits the changes using the approved message. 80 | 81 | ## 📝 Sample Output 82 | 83 | ```sh 84 | % git status 85 | On branch main 86 | Changes not staged for commit: 87 | (use "git add ..." to update what will be committed) 88 | (use "git restore ..." to discard changes in working directory) 89 | modified: commit-message-generator.py 90 | 91 | no changes added to commit (use "git add" and/or "git commit -a") 92 | % git add . 93 | % commit-message-generator.py 94 | 2024-10-23 16:50:40,625 - INFO - Found 1 staged files 95 | 2024-10-23 16:50:40,648 - INFO - Found 0 non-staged changes 96 | 2024-10-23 16:50:48,496 - INFO - 97 | Suggested commit message: 98 | -------------------------------------------------- 99 | Add prompt truncation and move XML order for better performance 100 | 101 | - Introduce MAX_PROMPT_SIZE constant to limit prompt size 102 | - Truncate prompt if it exceeds maximum size and log a warning 103 | - Reorder XML tags to send diffs before content for efficiency 104 | 105 | This commit optimizes the commit message generator by: 106 | 107 | - Adding a maximum prompt size limit to avoid sending excessively large prompts to the model 108 | - Truncating prompts that exceed the size limit and logging a warning 109 | - Reordering the XML tags to send file diffs before content, allowing the model to process diffs first for better performance 110 | 111 | These changes help improve the efficiency and reliability of the commit message generation process, especially for repositories with large files or many changes. 112 | -------------------------------------------------- 113 | 114 | Would you like to use this commit message? (y/n): y 115 | 2024-10-23 16:50:56,662 - INFO - Successfully committed changes: [main 98971fb] Add prompt truncation and move XML order for better performance 116 | 1 file changed, 10 insertions(+), 2 deletions(-) 117 | 118 | Changes committed successfully! 119 | ``` 120 | 121 | ## 🔧 Configuration 122 | 123 | The script uses the following configuration: 124 | 125 | - **AWS Region**: Set to `us-east-1` by default. Modify the `AWS_REGION` variable in the script if needed. 126 | - **Anthropic's Claude Model**: Uses Anthropic Claude 3.5 Sonnet (`anthropic.claude-3-sonnet-20240229-v1:0`). Update the `MODEL_ID` to use a different model/version. 127 | 128 | ## 🚨 Error Handling 129 | 130 | The script includes robust error handling: 131 | 132 | - Checks if the current directory is a Git repository 133 | - Validates the presence of staged changes 134 | - Handles file reading errors gracefully 135 | - Manages API call failures 136 | 137 | If you encounter issues, run the script with the `--debug` flag for more detailed logging. 138 | 139 | ## 🤝 Contributing 140 | 141 | Contributions are welcome! Please ensure your code adheres to the existing style and includes appropriate tests. 142 | 143 | ## 📄 License 144 | 145 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 146 | 147 | --- 148 | 149 | Happy committing! 🎉 Let AI supercharge your Git workflow! 150 | -------------------------------------------------------------------------------- /commit-message-generator.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | 3 | from typing import List, Dict, Optional 4 | import subprocess 5 | import logging 6 | from pathlib import Path 7 | from xml.sax.saxutils import escape 8 | from dataclasses import dataclass 9 | import time 10 | 11 | import boto3 12 | 13 | AWS_REGION = 'us-east-1' 14 | 15 | MODEL_ID = 'us.anthropic.claude-3-sonnet-20240229-v1:0' 16 | 17 | bedrock_runtime = boto3.client( 18 | service_name='bedrock-runtime', 19 | region_name=AWS_REGION 20 | ) 21 | 22 | 23 | # Set up logging 24 | logging.basicConfig( 25 | level=logging.INFO, 26 | format='%(asctime)s - %(levelname)s - %(message)s' 27 | ) 28 | logger = logging.getLogger(__name__) 29 | 30 | # Add this constant near the top of the file, after other imports and constants 31 | MAX_PROMPT_SIZE = 14000 # Adjust this value as needed 32 | 33 | @dataclass 34 | class FileInfo: 35 | path: str 36 | content: str 37 | history: str 38 | diff: str 39 | 40 | @dataclass 41 | class CommitOptions: 42 | include_content: bool 43 | include_history: bool 44 | context_lines: int 45 | debug: bool 46 | 47 | def setup_logging(debug: bool = False) -> None: 48 | """Configure logging level and format.""" 49 | level = logging.DEBUG if debug else logging.INFO 50 | logging.getLogger().setLevel(level) 51 | 52 | def escape_xml_content(content: str) -> str: 53 | """Escape special characters for XML content.""" 54 | return escape(content, {'"': '"', "'": '''}) 55 | 56 | def create_xml_element(tag: str, content: str, attributes: Dict[str, str] = None) -> str: 57 | """Create an XML element with optional attributes.""" 58 | attrs = '' if attributes is None else ' ' + ' '.join(f'{k}="{v}"' for k, v in attributes.items()) 59 | return f"<{tag}{attrs}>{escape_xml_content(content)}" 60 | 61 | def get_staged_files() -> List[str]: 62 | """Get list of staged files.""" 63 | try: 64 | # First, get the list of all changes 65 | result = subprocess.run( 66 | ['git', 'status', '--porcelain'], 67 | capture_output=True, 68 | text=True, 69 | check=True 70 | ) 71 | # Filter for staged files (those starting with 'A', 'M', 'R', or 'D') 72 | files = list(set([line.split()[-1] for line in result.stdout.splitlines() 73 | if line.startswith(('A', 'M', 'R', 'D'))])) 74 | logger.info(f"Found {len(files)} staged files") 75 | return files 76 | except subprocess.CalledProcessError as e: 77 | logger.error(f"Failed to get staged files: {e.stderr}") 78 | return [] 79 | 80 | def get_file_content(file_path: str) -> str: 81 | """Get the current content of a file.""" 82 | try: 83 | path = Path(file_path) 84 | if not path.exists(): 85 | logger.warning(f"File not found: {file_path}") 86 | return "" 87 | 88 | # Should handle different encodings and binary files 89 | try: 90 | with open(path, 'r', encoding='utf-8') as f: 91 | content = f.read() 92 | except UnicodeDecodeError: 93 | logger.warning(f"Could not read {file_path} as UTF-8, skipping content") 94 | return "" 95 | 96 | logger.debug(f"Read {len(content)} characters from {file_path}") 97 | return content 98 | except Exception as e: 99 | logger.error(f"Error reading file {file_path}: {str(e)}") 100 | return "" 101 | 102 | def get_staged_diff(file_path: Optional[str] = None, context_lines: int = 3) -> str: 103 | """Get the diff of staged changes for a specific file or all files.""" 104 | try: 105 | cmd = ['git', 'diff', '--staged', f'-U{context_lines}'] 106 | if file_path: 107 | cmd.append(file_path) 108 | 109 | result = subprocess.run( 110 | cmd, 111 | capture_output=True, 112 | text=True, 113 | check=True 114 | ) 115 | return result.stdout 116 | except subprocess.CalledProcessError as e: 117 | logger.error(f"Error getting git diff: {e.stderr}") 118 | return f"Error: Failed to get git diff: {e.stderr}" 119 | 120 | def get_file_history(file_path: str, max_entries: int = 5) -> str: 121 | """Get the git history for a file.""" 122 | try: 123 | result = subprocess.run( 124 | ['git', 'log', f'-{max_entries}', '-p', file_path], 125 | capture_output=True, 126 | text=True, 127 | check=True 128 | ) 129 | return result.stdout 130 | except subprocess.CalledProcessError as e: 131 | if "does not have any commits yet" in e.stderr or "no such path" in e.stderr: 132 | logger.info(f"No commit history for {file_path} (new file or repository)") 133 | return "New file" 134 | logger.error(f"Error getting file history for {file_path}: {e.stderr}") 135 | return "" 136 | 137 | def get_file_info(file_path: str) -> FileInfo: 138 | """Collect all information about a file.""" 139 | return FileInfo( 140 | path=file_path, 141 | content=get_file_content(file_path), 142 | history=get_file_history(file_path), 143 | diff=get_staged_diff(file_path) 144 | ) 145 | 146 | def format_file_info_xml(file_info: FileInfo, options: CommitOptions) -> str: 147 | """Format file information as XML.""" 148 | status = "new" if file_info.history == "New file" else "modified" 149 | xml = f""" 150 | {escape_xml_content(file_info.diff)}""" 151 | 152 | if options.include_history: 153 | xml += f"\n {escape_xml_content(file_info.history[:500])}" 154 | if options.include_content: 155 | xml += f"\n {escape_xml_content(file_info.content[:1000])}" 156 | 157 | xml += "\n" 158 | return xml 159 | 160 | def invoke_text_model(prompt: str, max_retries: int = 3) -> Optional[str]: 161 | """Invoke a text model via Amazon Bedrock Converse API.""" 162 | for attempt in range(max_retries): 163 | try: 164 | response = bedrock_runtime.converse( 165 | modelId=MODEL_ID, 166 | messages=[ 167 | { 168 | "role": "user", 169 | "content": [ { "text": prompt } ] 170 | } 171 | ] 172 | ) 173 | 174 | return response['output']['message']['content'][0]['text'] 175 | 176 | except boto3.client('bedrock-runtime').exceptions.ThrottlingException: 177 | if attempt == max_retries - 1: 178 | logger.error("Max retries reached for rate limit") 179 | return None 180 | time.sleep(2 ** attempt) # Exponential backoff 181 | except Exception as e: 182 | logger.error(f"Error invoking model: {str(e)}") 183 | return None 184 | 185 | def generate_commit_prompt(files_info: List[FileInfo], options: CommitOptions) -> str: 186 | """Generate a prompt to create a commit message.""" 187 | files_xml = "\n".join(format_file_info_xml(info, options) for info in files_info) 188 | 189 | is_initial_commit = all(not info.history for info in files_info) 190 | 191 | if is_initial_commit: 192 | context = "This is the initial commit in the repository." 193 | else: 194 | context = "This is a subsequent commit in the repository." 195 | 196 | prompt = f""" 197 | 198 | You are an expert developer reviewing code changes for a commit message. 199 | {context} 200 | Based on the following file changes, generate a clear and informative git commit message following these guidelines: 201 | - Start with a concise summary line (max 50 chars) 202 | - Add detailed explanation after a blank line 203 | - Use bullet points for multiple changes 204 | - Mention the purpose of each file being added or modified 205 | - Highlight any important setup, configuration, or architectural changes 206 | - For subsequent commits, focus on what has changed since the last commit 207 | 208 | 209 | 210 | {files_xml} 211 | 212 | 213 | 214 | Please generate a commit message that best describes these changes.""" 215 | 216 | if len(prompt) > MAX_PROMPT_SIZE: 217 | truncation_message = f"\n\nNote: The prompt has been truncated due to size limitations. Some file information may have been omitted." 218 | prompt = prompt[:MAX_PROMPT_SIZE - len(truncation_message)] + truncation_message 219 | logger.warning(f"Prompt exceeded maximum size and was truncated to {MAX_PROMPT_SIZE} characters.") 220 | 221 | return prompt 222 | 223 | def commit_changes(message: str) -> bool: 224 | """Commit changes with the given message.""" 225 | if not message or message.isspace(): 226 | logger.error("Empty commit message provided") 227 | return False 228 | 229 | try: 230 | result = subprocess.run( 231 | ['git', 'commit', '-m', message], 232 | capture_output=True, 233 | text=True, 234 | check=True 235 | ) 236 | logger.info(f"Successfully committed changes: {result.stdout}") 237 | return True 238 | except subprocess.CalledProcessError as e: 239 | logger.error(f"Failed to commit changes: {e.stderr}") 240 | return False 241 | 242 | def is_git_repository() -> bool: 243 | """Check if the current directory is a Git repository.""" 244 | try: 245 | subprocess.run(['git', 'rev-parse', '--is-inside-work-tree'], 246 | capture_output=True, check=True) 247 | return True 248 | except subprocess.CalledProcessError: 249 | return False 250 | 251 | def get_non_staged_changes() -> List[str]: 252 | """Get list of non-staged changes.""" 253 | try: 254 | result = subprocess.run( 255 | ['git', 'status', '--porcelain'], 256 | capture_output=True, 257 | text=True, 258 | check=True 259 | ) 260 | # Filter for non-staged files (those starting with ' M', '??', ' D', etc.) 261 | files = [line.split()[-1] for line in result.stdout.splitlines() 262 | if not line.startswith(('A', 'M', 'R', 'D'))] 263 | logger.info(f"Found {len(files)} non-staged changes") 264 | return files 265 | except subprocess.CalledProcessError as e: 266 | logger.error(f"Failed to get non-staged changes: {e.stderr}") 267 | return [] 268 | 269 | def prompt_for_override() -> bool: 270 | """Prompt the user to override the non-staged changes warning.""" 271 | response = input("There are non-staged changes. Do you want to continue anyway? (y/n): ").lower() 272 | return response == 'y' 273 | 274 | def main(options: CommitOptions) -> None: 275 | """Main function to generate git commit message.""" 276 | setup_logging(options.debug) 277 | 278 | # Check if the current directory is a Git repository 279 | if not is_git_repository(): 280 | logger.error("This script must be run in a Git repository.") 281 | return 282 | 283 | # Get staged files 284 | staged_files = get_staged_files() 285 | 286 | # Check for non-staged changes 287 | non_staged_changes = get_non_staged_changes() 288 | 289 | if not staged_files and not non_staged_changes: 290 | logger.info("No changes found in the repository.") 291 | return 292 | 293 | if not staged_files: 294 | logger.error("No staged changes found.") 295 | if non_staged_changes: 296 | print("There are non-staged changes in the repository:") 297 | for file in non_staged_changes: 298 | print(f" - {file}") 299 | print("Please stage your changes before running this script.") 300 | return 301 | 302 | if non_staged_changes: 303 | logger.warning("There are non-staged changes in the repository.") 304 | print("Non-staged files:") 305 | for file in non_staged_changes: 306 | print(f" - {file}") 307 | 308 | if not prompt_for_override(): 309 | logger.info("Operation cancelled by user.") 310 | return 311 | 312 | try: 313 | # Collect information about each file 314 | files_info = [get_file_info(file_path) for file_path in staged_files] 315 | 316 | # Generate prompt 317 | prompt = generate_commit_prompt(files_info, options) 318 | 319 | # Get commit message 320 | commit_message = invoke_text_model(prompt) 321 | 322 | if commit_message: 323 | logger.info("\nSuggested commit message:") 324 | print("-" * 50) 325 | print(commit_message) 326 | print("-" * 50) 327 | 328 | if input("\nWould you like to use this commit message? (y/n): ").lower() == 'y': 329 | if commit_changes(commit_message): 330 | print("Changes committed successfully!") 331 | else: 332 | print("Failed to commit changes. Please check the logs for details.") 333 | else: 334 | logger.error("Failed to generate commit message.") 335 | 336 | except Exception as e: 337 | logger.error(f"An error occurred: {str(e)}") 338 | if options.debug: 339 | raise 340 | 341 | if __name__ == "__main__": 342 | import argparse 343 | parser = argparse.ArgumentParser(description='Generate git commit messages using an AI model.') 344 | parser.add_argument('--debug', action='store_true', help='Enable debug logging') 345 | parser.add_argument('--include-content', action='store_true', 346 | help='Include file content in the prompt (disabled by default)') 347 | parser.add_argument('--include-history', action='store_true', 348 | help='Include file history in the prompt (disabled by default)') 349 | parser.add_argument('--context-lines', type=int, default=3, 350 | help='Number of context lines to show in git diff (default: 3)') 351 | args = parser.parse_args() 352 | 353 | options = CommitOptions( 354 | include_content=args.include_content, 355 | include_history=args.include_history, 356 | context_lines=args.context_lines, 357 | debug=args.debug 358 | ) 359 | 360 | main(options) 361 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | boto3 --------------------------------------------------------------------------------