diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..e69de29 diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md new file mode 100644 index 0000000..17d239b --- /dev/null +++ b/DEVELOPMENT.md @@ -0,0 +1,127 @@ +# CodeScribe Agent - Development Setup + +## Development Mode (No NGROK Required!) + +For local development, you can use **Smee.io** instead of NGROK. This is much simpler and doesn't require installing additional software. + +### Quick Setup Steps: + +1. **Install dependencies:** + + ```bash + npm install + ``` + +2. **Get your Groq API key** from [https://console.groq.com/keys](https://console.groq.com/keys) + +3. **Create a Smee channel** by visiting [https://smee.io/](https://smee.io/) and clicking "Start a new channel" + +4. **Create a GitHub App:** + + - Go to [https://github.com/settings/apps](https://github.com/settings/apps) + - Click "New GitHub App" + - **Webhook URL**: Use your Smee.io URL (e.g., `https://smee.io/abc123`) + - **Webhook Secret**: Create any random string + - **Permissions**: Grant read & write for: + - Pull Requests + - Repository Contents + - Issues + - Commit Statuses + - **Events**: Subscribe to: + - Pull Request + - Pull Request Review + - Pull Request Review Comment + +5. **Create your `.env` file:** + + ```env + GITHUB_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY----- + + -----END RSA PRIVATE KEY-----" + GITHUB_APP_ID= + GITHUB_WEBHOOK_SECRET= + GROQ_API_KEY= + + # Development settings + NODE_ENV=development + LOG_LEVEL=DEBUG + + # Agent configuration (optional) + ENABLE_AGENTIC_REVIEW=true + ENABLE_SECURITY_AGENT=true + ENABLE_PERFORMANCE_AGENT=true + ENABLE_CODE_QUALITY_AGENT=true + ENABLE_DOCUMENTATION_AGENT=true + ENABLE_TESTING_AGENT=true + ``` + +6. **Start the development server:** + + ```bash + npm run dev + ``` + +7. **Start Smee client** (in another terminal): + ```bash + npx smee -u -p 3000 + ``` + +That's it! Now when you create pull requests in repositories where your GitHub App is installed, the agent will automatically review them. + +## Alternative: Traditional NGROK Setup + +If you prefer NGROK: + +1. Download NGROK from [https://ngrok.com/download](https://ngrok.com/download) +2. Run: `ngrok http 3000` +3. Use the NGROK URL + `/api/review` as your webhook URL +4. Run: `npm start` + +## Configuration Options + +You can customize the agent behavior using environment variables: + +- `ENABLE_AGENTIC_REVIEW=true/false` - Enable/disable the new multi-agent review system +- `ENABLE_SECURITY_AGENT=true/false` - Enable/disable security-focused reviews +- `ENABLE_PERFORMANCE_AGENT=true/false` - Enable/disable performance reviews +- `ENABLE_CODE_QUALITY_AGENT=true/false` - Enable/disable code quality reviews +- `ENABLE_DOCUMENTATION_AGENT=true/false` - Enable/disable documentation reviews +- `ENABLE_TESTING_AGENT=true/false` - Enable/disable testing reviews +- `LOG_LEVEL=DEBUG/INFO/WARN/ERROR` - Set logging level +- `MAX_FILES_PER_REVIEW=20` - Maximum files to review in one PR +- `TEMPERATURE=0.1` - LLM temperature for responses + +## Testing + +Create a test pull request in a repository where your GitHub App is installed. The agent will: + +1. ๐Ÿ” Analyze all changed files +2. ๐Ÿš€ Run multiple specialized AI agents (security, performance, code quality, documentation, testing) +3. ๐Ÿ“ Generate a comprehensive review with specific suggestions +4. ๐Ÿ’ฌ Post the review as a GitHub comment + +## Features Added + +### โœจ Multi-Agent Review System + +- **Security Agent**: Identifies vulnerabilities, injection attacks, auth issues +- **Performance Agent**: Finds bottlenecks, inefficient algorithms, memory issues +- **Code Quality Agent**: Reviews readability, naming, SOLID principles +- **Documentation Agent**: Suggests missing docs, comments, examples +- **Testing Agent**: Identifies missing tests, edge cases + +### ๐Ÿ Python Parser Support + +- Complete Python code analysis +- Function and class detection +- Basic syntax validation +- Indentation checking + +### ๐Ÿ› ๏ธ Enhanced Infrastructure + +- Improved error handling with retries +- Configurable logging system +- Environment-based configuration +- Better fallback mechanisms + +The agent is now much more robust and provides detailed, actionable feedback! diff --git a/package-lock.json b/package-lock.json index f60579a..f5965fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -687,9 +687,10 @@ } }, "node_modules/@octokit/app": { - "version": "14.0.1", - "resolved": "https://registry.npmjs.org/@octokit/app/-/app-14.0.1.tgz", - "integrity": "sha512-4opdXcWBVhzd6FOxlaxDKXXqi9Vz2hsDSWQGNo49HbYFAX11UqMpksMjEdfvHy0x19Pse8Nvn+R6inNb/V398w==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@octokit/app/-/app-14.1.0.tgz", + "integrity": "sha512-g3uEsGOQCBl1+W1rgfwoRFUIR6PtvB2T1E4RpygeUU5LrLvlOqcxrt5lfykIeRpUPpupreGJUYl70fqMDXdTpw==", + "license": "MIT", "dependencies": { "@octokit/auth-app": "^6.0.0", "@octokit/auth-unauthenticated": "^5.0.0", @@ -697,40 +698,57 @@ "@octokit/oauth-app": "^6.0.0", "@octokit/plugin-paginate-rest": "^9.0.0", "@octokit/types": "^12.0.0", - "@octokit/webhooks": "^12.0.1" + "@octokit/webhooks": "^12.0.4" }, "engines": { "node": ">= 18" } }, "node_modules/@octokit/auth-app": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-6.0.1.tgz", - "integrity": "sha512-tjCD4nzQNZgmLH62+PSnTF6eGerisFgV4v6euhqJik6yWV96e1ZiiGj+NXIqbgnpjLmtnBqVUrNyGKu3DoGEGA==", + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-6.1.3.tgz", + "integrity": "sha512-dcaiteA6Y/beAlDLZOPNReN3FGHu+pARD6OHfh3T9f3EO09++ec+5wt3KtGGSSs2Mp5tI8fQwdMOEnrzBLfgUA==", + "license": "MIT", "dependencies": { - "@octokit/auth-oauth-app": "^7.0.0", - "@octokit/auth-oauth-user": "^4.0.0", - "@octokit/request": "^8.0.2", - "@octokit/request-error": "^5.0.0", - "@octokit/types": "^12.0.0", + "@octokit/auth-oauth-app": "^7.1.0", + "@octokit/auth-oauth-user": "^4.1.0", + "@octokit/request": "^8.3.1", + "@octokit/request-error": "^5.1.0", + "@octokit/types": "^13.1.0", "deprecation": "^2.3.1", - "lru-cache": "^10.0.0", - "universal-github-app-jwt": "^1.1.1", + "lru-cache": "npm:@wolfy1339/lru-cache@^11.0.2-patch.1", + "universal-github-app-jwt": "^1.1.2", "universal-user-agent": "^6.0.0" }, "engines": { "node": ">= 18" } }, + "node_modules/@octokit/auth-app/node_modules/@octokit/openapi-types": { + "version": "22.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz", + "integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==", + "license": "MIT" + }, + "node_modules/@octokit/auth-app/node_modules/@octokit/types": { + "version": "13.6.2", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.2.tgz", + "integrity": "sha512-WpbZfZUcZU77DrSW4wbsSgTPfKcp286q3ItaIgvSbBpZJlu6mnYXAkjZz6LVZPXkEvLIM8McanyZejKTYUHipA==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^22.2.0" + } + }, "node_modules/@octokit/auth-oauth-app": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-7.0.1.tgz", - "integrity": "sha512-RE0KK0DCjCHXHlQBoubwlLijXEKfhMhKm9gO56xYvFmP1QTMb+vvwRPmQLLx0V+5AvV9N9I3lr1WyTzwL3rMDg==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-7.1.0.tgz", + "integrity": "sha512-w+SyJN/b0l/HEb4EOPRudo7uUOSW51jcK1jwLa+4r7PA8FPFpoxEnHBHMITqCsc/3Vo2qqFjgQfz/xUUvsSQnA==", + "license": "MIT", "dependencies": { - "@octokit/auth-oauth-device": "^6.0.0", - "@octokit/auth-oauth-user": "^4.0.0", - "@octokit/request": "^8.0.2", - "@octokit/types": "^12.0.0", + "@octokit/auth-oauth-device": "^6.1.0", + "@octokit/auth-oauth-user": "^4.1.0", + "@octokit/request": "^8.3.1", + "@octokit/types": "^13.0.0", "@types/btoa-lite": "^1.0.0", "btoa-lite": "^1.0.0", "universal-user-agent": "^6.0.0" @@ -739,29 +757,61 @@ "node": ">= 18" } }, + "node_modules/@octokit/auth-oauth-app/node_modules/@octokit/openapi-types": { + "version": "22.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz", + "integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==", + "license": "MIT" + }, + "node_modules/@octokit/auth-oauth-app/node_modules/@octokit/types": { + "version": "13.6.2", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.2.tgz", + "integrity": "sha512-WpbZfZUcZU77DrSW4wbsSgTPfKcp286q3ItaIgvSbBpZJlu6mnYXAkjZz6LVZPXkEvLIM8McanyZejKTYUHipA==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^22.2.0" + } + }, "node_modules/@octokit/auth-oauth-device": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-6.0.1.tgz", - "integrity": "sha512-yxU0rkL65QkjbqQedgVx3gmW7YM5fF+r5uaSj9tM/cQGVqloXcqP2xK90eTyYvl29arFVCW8Vz4H/t47mL0ELw==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-6.1.0.tgz", + "integrity": "sha512-FNQ7cb8kASufd6Ej4gnJ3f1QB5vJitkoV1O0/g6e6lUsQ7+VsSNRHRmFScN2tV4IgKA12frrr/cegUs0t+0/Lw==", + "license": "MIT", "dependencies": { - "@octokit/oauth-methods": "^4.0.0", - "@octokit/request": "^8.0.0", - "@octokit/types": "^12.0.0", + "@octokit/oauth-methods": "^4.1.0", + "@octokit/request": "^8.3.1", + "@octokit/types": "^13.0.0", "universal-user-agent": "^6.0.0" }, "engines": { "node": ">= 18" } }, + "node_modules/@octokit/auth-oauth-device/node_modules/@octokit/openapi-types": { + "version": "22.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz", + "integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==", + "license": "MIT" + }, + "node_modules/@octokit/auth-oauth-device/node_modules/@octokit/types": { + "version": "13.6.2", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.2.tgz", + "integrity": "sha512-WpbZfZUcZU77DrSW4wbsSgTPfKcp286q3ItaIgvSbBpZJlu6mnYXAkjZz6LVZPXkEvLIM8McanyZejKTYUHipA==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^22.2.0" + } + }, "node_modules/@octokit/auth-oauth-user": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-4.0.1.tgz", - "integrity": "sha512-N94wWW09d0hleCnrO5wt5MxekatqEJ4zf+1vSe8MKMrhZ7gAXKFOKrDEZW2INltvBWJCyDUELgGRv8gfErH1Iw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-4.1.0.tgz", + "integrity": "sha512-FrEp8mtFuS/BrJyjpur+4GARteUCrPeR/tZJzD8YourzoVhRics7u7we/aDcKv+yywRNwNi/P4fRi631rG/OyQ==", + "license": "MIT", "dependencies": { - "@octokit/auth-oauth-device": "^6.0.0", - "@octokit/oauth-methods": "^4.0.0", - "@octokit/request": "^8.0.2", - "@octokit/types": "^12.0.0", + "@octokit/auth-oauth-device": "^6.1.0", + "@octokit/oauth-methods": "^4.1.0", + "@octokit/request": "^8.3.1", + "@octokit/types": "^13.0.0", "btoa-lite": "^1.0.0", "universal-user-agent": "^6.0.0" }, @@ -769,6 +819,21 @@ "node": ">= 18" } }, + "node_modules/@octokit/auth-oauth-user/node_modules/@octokit/openapi-types": { + "version": "22.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz", + "integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==", + "license": "MIT" + }, + "node_modules/@octokit/auth-oauth-user/node_modules/@octokit/types": { + "version": "13.6.2", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.2.tgz", + "integrity": "sha512-WpbZfZUcZU77DrSW4wbsSgTPfKcp286q3ItaIgvSbBpZJlu6mnYXAkjZz6LVZPXkEvLIM8McanyZejKTYUHipA==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^22.2.0" + } + }, "node_modules/@octokit/auth-token": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz", @@ -781,6 +846,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/@octokit/auth-unauthenticated/-/auth-unauthenticated-5.0.1.tgz", "integrity": "sha512-oxeWzmBFxWd+XolxKTc4zr+h3mt+yofn4r7OfoIkR/Cj/o70eEGmPsFbueyJE2iBAGpjgTnEOKM3pnuEGVmiqg==", + "license": "MIT", "dependencies": { "@octokit/request-error": "^5.0.0", "@octokit/types": "^12.0.0" @@ -833,9 +899,10 @@ } }, "node_modules/@octokit/oauth-app": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@octokit/oauth-app/-/oauth-app-6.0.0.tgz", - "integrity": "sha512-bNMkS+vJ6oz2hCyraT9ZfTpAQ8dZNqJJQVNaKjPLx4ue5RZiFdU1YWXguOPR8AaSHS+lKe+lR3abn2siGd+zow==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@octokit/oauth-app/-/oauth-app-6.1.0.tgz", + "integrity": "sha512-nIn/8eUJ/BKUVzxUXd5vpzl1rwaVxMyYbQkNZjHrF7Vk/yu98/YDF/N2KeWO7uZ0g3b5EyiFXFkZI8rJ+DH1/g==", + "license": "MIT", "dependencies": { "@octokit/auth-oauth-app": "^7.0.0", "@octokit/auth-oauth-user": "^4.0.0", @@ -854,19 +921,21 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-6.0.2.tgz", "integrity": "sha512-CdoJukjXXxqLNK4y/VOiVzQVjibqoj/xHgInekviUJV73y/BSIcwvJ/4aNHPBPKcPWFnd4/lO9uqRV65jXhcLA==", + "license": "MIT", "engines": { "node": ">= 18" } }, "node_modules/@octokit/oauth-methods": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@octokit/oauth-methods/-/oauth-methods-4.0.0.tgz", - "integrity": "sha512-dqy7BZLfLbi3/8X8xPKUKZclMEK9vN3fK5WF3ortRvtplQTszFvdAGbTo71gGLO+4ZxspNiLjnqdd64Chklf7w==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@octokit/oauth-methods/-/oauth-methods-4.1.0.tgz", + "integrity": "sha512-4tuKnCRecJ6CG6gr0XcEXdZtkTDbfbnD5oaHBmLERTjTMZNi2CbfEHZxPU41xXLDG4DfKf+sonu00zvKI9NSbw==", + "license": "MIT", "dependencies": { "@octokit/oauth-authorization-url": "^6.0.2", - "@octokit/request": "^8.0.2", - "@octokit/request-error": "^5.0.0", - "@octokit/types": "^11.0.0", + "@octokit/request": "^8.3.1", + "@octokit/request-error": "^5.1.0", + "@octokit/types": "^13.0.0", "btoa-lite": "^1.0.0" }, "engines": { @@ -874,16 +943,18 @@ } }, "node_modules/@octokit/oauth-methods/node_modules/@octokit/openapi-types": { - "version": "18.1.1", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-18.1.1.tgz", - "integrity": "sha512-VRaeH8nCDtF5aXWnjPuEMIYf1itK/s3JYyJcWFJT8X9pSNnBtriDf7wlEWsGuhPLl4QIH4xM8fqTXDwJ3Mu6sw==" + "version": "22.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz", + "integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==", + "license": "MIT" }, "node_modules/@octokit/oauth-methods/node_modules/@octokit/types": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-11.1.0.tgz", - "integrity": "sha512-Fz0+7GyLm/bHt8fwEqgvRBWwIV1S6wRRyq+V6exRKLVWaKGsuy6H9QFYeBVDV7rK6fO3XwHgQOPxv+cLj2zpXQ==", + "version": "13.6.2", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.2.tgz", + "integrity": "sha512-WpbZfZUcZU77DrSW4wbsSgTPfKcp286q3ItaIgvSbBpZJlu6mnYXAkjZz6LVZPXkEvLIM8McanyZejKTYUHipA==", + "license": "MIT", "dependencies": { - "@octokit/openapi-types": "^18.0.0" + "@octokit/openapi-types": "^22.2.0" } }, "node_modules/@octokit/openapi-types": { @@ -973,14 +1044,14 @@ } }, "node_modules/@octokit/request": { - "version": "8.1.3", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.1.3.tgz", - "integrity": "sha512-iUvXP4QmysS8kyE/a4AGwR0A+tHDVxgW6TmPd2ci8/Xc8KjlBtTKSDpZlUT5Y4S4Nu+eM8LvbOYjVAp/sz3Gpg==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.4.0.tgz", + "integrity": "sha512-9Bb014e+m2TgBeEJGEbdplMVWwPmL1FPtggHQRkV+WVsMggPtEkLKPlcVYm/o8xKLkpJ7B+6N8WfQMtDLX2Dpw==", + "license": "MIT", "dependencies": { - "@octokit/endpoint": "^9.0.0", - "@octokit/request-error": "^5.0.0", - "@octokit/types": "^12.0.0", - "is-plain-object": "^5.0.0", + "@octokit/endpoint": "^9.0.1", + "@octokit/request-error": "^5.1.0", + "@octokit/types": "^13.1.0", "universal-user-agent": "^6.0.0" }, "engines": { @@ -988,11 +1059,12 @@ } }, "node_modules/@octokit/request-error": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.0.1.tgz", - "integrity": "sha512-X7pnyTMV7MgtGmiXBwmO6M5kIPrntOXdyKZLigNfQWSEQzVxR4a4vo49vJjTWX70mPndj8KhfT4Dx+2Ng3vnBQ==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.1.0.tgz", + "integrity": "sha512-GETXfE05J0+7H2STzekpKObFe765O5dlAKUTLNGeH+x47z7JjXHfsHKo5z21D/o/IOZTUEI6nyWyR+bZVP/n5Q==", + "license": "MIT", "dependencies": { - "@octokit/types": "^12.0.0", + "@octokit/types": "^13.1.0", "deprecation": "^2.0.0", "once": "^1.4.0" }, @@ -1000,6 +1072,36 @@ "node": ">= 18" } }, + "node_modules/@octokit/request-error/node_modules/@octokit/openapi-types": { + "version": "22.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz", + "integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==", + "license": "MIT" + }, + "node_modules/@octokit/request-error/node_modules/@octokit/types": { + "version": "13.6.2", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.2.tgz", + "integrity": "sha512-WpbZfZUcZU77DrSW4wbsSgTPfKcp286q3ItaIgvSbBpZJlu6mnYXAkjZz6LVZPXkEvLIM8McanyZejKTYUHipA==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^22.2.0" + } + }, + "node_modules/@octokit/request/node_modules/@octokit/openapi-types": { + "version": "22.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz", + "integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==", + "license": "MIT" + }, + "node_modules/@octokit/request/node_modules/@octokit/types": { + "version": "13.6.2", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.2.tgz", + "integrity": "sha512-WpbZfZUcZU77DrSW4wbsSgTPfKcp286q3ItaIgvSbBpZJlu6mnYXAkjZz6LVZPXkEvLIM8McanyZejKTYUHipA==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^22.2.0" + } + }, "node_modules/@octokit/rest": { "version": "20.0.2", "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-20.0.2.tgz", @@ -1023,13 +1125,14 @@ } }, "node_modules/@octokit/webhooks": { - "version": "12.0.3", - "resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-12.0.3.tgz", - "integrity": "sha512-8iG+/yza7hwz1RrQ7i7uGpK2/tuItZxZq1aTmeg2TNp2xTUB8F8lZF/FcZvyyAxT8tpDMF74TjFGCDACkf1kAQ==", + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-12.3.1.tgz", + "integrity": "sha512-BVwtWE3rRXB9IugmQTfKspqjNa8q+ab73ddkV9k1Zok3XbuOxJUi4lTYk5zBZDhfWb/Y2H+RO9Iggm25gsqeow==", + "license": "MIT", "dependencies": { "@octokit/request-error": "^5.0.0", - "@octokit/webhooks-methods": "^4.0.0", - "@octokit/webhooks-types": "7.1.0", + "@octokit/webhooks-methods": "^4.1.0", + "@octokit/webhooks-types": "7.6.1", "aggregate-error": "^3.1.0" }, "engines": { @@ -1043,22 +1146,25 @@ "deprecated": "Use @octokit/webhooks-types, @octokit/webhooks-schemas, or @octokit/webhooks-examples instead. See https://github.com/octokit/webhooks/issues/447" }, "node_modules/@octokit/webhooks-methods": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@octokit/webhooks-methods/-/webhooks-methods-4.0.0.tgz", - "integrity": "sha512-M8mwmTXp+VeolOS/kfRvsDdW+IO0qJ8kYodM/sAysk093q6ApgmBXwK1ZlUvAwXVrp/YVHp6aArj4auAxUAOFw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@octokit/webhooks-methods/-/webhooks-methods-4.1.0.tgz", + "integrity": "sha512-zoQyKw8h9STNPqtm28UGOYFE7O6D4Il8VJwhAtMHFt2C4L0VQT1qGKLeefUOqHNs1mNRYSadVv7x0z8U2yyeWQ==", + "license": "MIT", "engines": { "node": ">= 18" } }, "node_modules/@octokit/webhooks-types": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@octokit/webhooks-types/-/webhooks-types-7.1.0.tgz", - "integrity": "sha512-y92CpG4kFFtBBjni8LHoV12IegJ+KFxLgKRengrVjKmGE5XMeCuGvlfRe75lTRrgXaG6XIWJlFpIDTlkoJsU8w==" + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/@octokit/webhooks-types/-/webhooks-types-7.6.1.tgz", + "integrity": "sha512-S8u2cJzklBC0FgTwWVLaM8tMrDuDMVE4xiTK4EYXM9GntyvrdbSoxqDQa+Fh57CCNApyIpyeqPhhFEmHPfrXgw==", + "license": "MIT" }, "node_modules/@types/aws-lambda": { - "version": "8.10.124", - "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.124.tgz", - "integrity": "sha512-PHqK0SuAkFS3tZjceqRXecxxrWIN3VqTicuialtK2wZmvBy7H9WGc3u3+wOgaZB7N8SpSXDpWk9qa7eorpTStg==" + "version": "8.10.146", + "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.146.tgz", + "integrity": "sha512-3BaDXYTh0e6UCJYL/jwV/3+GRslSc08toAiZSmleYtkAUyV5rtvdPYxrG/88uqvTuT6sb27WE9OS90ZNTIuQ0g==", + "license": "MIT" }, "node_modules/@types/babel__traverse": { "version": "7.20.3", @@ -1070,9 +1176,10 @@ } }, "node_modules/@types/btoa-lite": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/btoa-lite/-/btoa-lite-1.0.0.tgz", - "integrity": "sha512-wJsiX1tosQ+J5+bY5LrSahHxr2wT+uME5UDwdN1kg4frt40euqA+wzECkmq4t5QbveHiJepfdThgQrPw6KiSlg==" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/btoa-lite/-/btoa-lite-1.0.2.tgz", + "integrity": "sha512-ZYbcE2x7yrvNFJiU7xJGrpF/ihpkM7zKgw8bha3LNJSesvTtUNxbpzaT7WXBIryf6jovisrxTBvymxMeLLj1Mg==", + "license": "MIT" }, "node_modules/@types/diff": { "version": "5.0.7", @@ -1081,9 +1188,10 @@ "dev": true }, "node_modules/@types/jsonwebtoken": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", - "integrity": "sha512-b0jGiOgHtZ2jqdPgPnP6WLCXZk1T8p06A/vPGzUvxpFGgKMbjXJDjC5m52ErqBnIuWZFgGoIJyRdeG5AyreJjA==", + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.7.tgz", + "integrity": "sha512-ugo316mmTYBl2g81zDFnZ7cfxlut3o+/EQdaP7J8QN2kY6lJ22hmQYCK5EHcJHbrW+dkCGSCPgbG8JtYj6qSrg==", + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -1137,6 +1245,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "license": "MIT", "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" @@ -1199,12 +1308,14 @@ "node_modules/btoa-lite": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/btoa-lite/-/btoa-lite-1.0.0.tgz", - "integrity": "sha512-gvW7InbIyF8AicrqWoptdW08pUxuhq8BEgowNajy9RhiE86fmGAGl+bLKo6oB8QP0CkqHLowfN0oJdKC/J6LbA==" + "integrity": "sha512-gvW7InbIyF8AicrqWoptdW08pUxuhq8BEgowNajy9RhiE86fmGAGl+bLKo6oB8QP0CkqHLowfN0oJdKC/J6LbA==", + "license": "MIT" }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" }, "node_modules/call-bind": { "version": "1.0.2", @@ -1235,6 +1346,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "license": "MIT", "engines": { "node": ">=6" } @@ -1359,6 +1471,7 @@ "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", "dependencies": { "safe-buffer": "^5.0.1" } @@ -1638,6 +1751,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "license": "MIT", "engines": { "node": ">=8" } @@ -1676,6 +1790,7 @@ "version": "9.0.2", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "license": "MIT", "dependencies": { "jws": "^3.2.2", "lodash.includes": "^4.3.0", @@ -1697,6 +1812,7 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "license": "MIT", "dependencies": { "buffer-equal-constant-time": "1.0.1", "ecdsa-sig-formatter": "1.0.11", @@ -1707,6 +1823,7 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", "dependencies": { "jwa": "^1.4.1", "safe-buffer": "^5.0.1" @@ -1715,44 +1832,53 @@ "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" }, "node_modules/lodash.isboolean": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" }, "node_modules/lodash.isinteger": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" }, "node_modules/lodash.isnumber": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" }, "node_modules/lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" }, "node_modules/lodash.isstring": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" }, "node_modules/lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" }, "node_modules/lru-cache": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.1.tgz", - "integrity": "sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==", + "name": "@wolfy1339/lru-cache", + "version": "11.0.2-patch.1", + "resolved": "https://registry.npmjs.org/@wolfy1339/lru-cache/-/lru-cache-11.0.2-patch.1.tgz", + "integrity": "sha512-BgYZfL2ADCXKOw2wJtkM3slhHotawWkgIRRxq4wEybnZQPjvAp71SPX35xepMykTw8gXlzWcWPTY31hlbnRsDA==", + "license": "ISC", "engines": { - "node": "14 || >=16.14" + "node": "18 >=18.20 || 20 || >=22" } }, "node_modules/methods": { @@ -1863,23 +1989,69 @@ } }, "node_modules/octokit": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/octokit/-/octokit-3.1.1.tgz", - "integrity": "sha512-AKJs5XYs7iAh7bskkYpxhUIpsYZdLqjnlnqrN5s9FFZuJ/a6ATUHivGpUKDpGB/xa+LGDtG9Lu8bOCfPM84vHQ==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/octokit/-/octokit-3.2.1.tgz", + "integrity": "sha512-u+XuSejhe3NdIvty3Jod00JvTdAE/0/+XbhIDhefHbu+2OcTRHd80aCiH6TX19ZybJmwPQBKFQmHGxp0i9mJrg==", + "license": "MIT", "dependencies": { - "@octokit/app": "^14.0.0", + "@octokit/app": "^14.0.2", "@octokit/core": "^5.0.0", "@octokit/oauth-app": "^6.0.0", "@octokit/plugin-paginate-graphql": "^4.0.0", - "@octokit/plugin-paginate-rest": "^9.0.0", - "@octokit/plugin-rest-endpoint-methods": "^10.0.0", + "@octokit/plugin-paginate-rest": "11.3.1", + "@octokit/plugin-rest-endpoint-methods": "13.2.2", "@octokit/plugin-retry": "^6.0.0", "@octokit/plugin-throttling": "^8.0.0", "@octokit/request-error": "^5.0.0", - "@octokit/types": "^12.0.0" + "@octokit/types": "^13.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/octokit/node_modules/@octokit/openapi-types": { + "version": "22.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz", + "integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==", + "license": "MIT" + }, + "node_modules/octokit/node_modules/@octokit/plugin-paginate-rest": { + "version": "11.3.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.3.1.tgz", + "integrity": "sha512-ryqobs26cLtM1kQxqeZui4v8FeznirUsksiA+RYemMPJ7Micju0WSkv50dBksTuZks9O5cg4wp+t8fZ/cLY56g==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.5.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "5" + } + }, + "node_modules/octokit/node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-13.2.2.tgz", + "integrity": "sha512-EI7kXWidkt3Xlok5uN43suK99VWqc8OaIMktY9d9+RNKl69juoTyxmLoWPIZgJYzi41qj/9zU7G/ljnNOJ5AFA==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.5.0" }, "engines": { "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "^5" + } + }, + "node_modules/octokit/node_modules/@octokit/types": { + "version": "13.6.2", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.2.tgz", + "integrity": "sha512-WpbZfZUcZU77DrSW4wbsSgTPfKcp286q3ItaIgvSbBpZJlu6mnYXAkjZz6LVZPXkEvLIM8McanyZejKTYUHipA==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^22.2.0" } }, "node_modules/on-finished": { @@ -2153,12 +2325,13 @@ "license": "MIT" }, "node_modules/universal-github-app-jwt": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/universal-github-app-jwt/-/universal-github-app-jwt-1.1.1.tgz", - "integrity": "sha512-G33RTLrIBMFmlDV4u4CBF7dh71eWwykck4XgaxaIVeZKOYZRAAxvcGMRFTUclVY6xoUPQvO4Ne5wKGxYm/Yy9w==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/universal-github-app-jwt/-/universal-github-app-jwt-1.2.0.tgz", + "integrity": "sha512-dncpMpnsKBk0eetwfN8D8OUHGfiDhhJ+mtsbMl+7PfW7mYjiH8LIcqRmYMtzYLgSh47HjfdBtrBwIQ/gizKR3g==", + "license": "MIT", "dependencies": { "@types/jsonwebtoken": "^9.0.0", - "jsonwebtoken": "^9.0.0" + "jsonwebtoken": "^9.0.2" } }, "node_modules/universal-user-agent": { @@ -2589,9 +2762,9 @@ } }, "@octokit/app": { - "version": "14.0.1", - "resolved": "https://registry.npmjs.org/@octokit/app/-/app-14.0.1.tgz", - "integrity": "sha512-4opdXcWBVhzd6FOxlaxDKXXqi9Vz2hsDSWQGNo49HbYFAX11UqMpksMjEdfvHy0x19Pse8Nvn+R6inNb/V398w==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@octokit/app/-/app-14.1.0.tgz", + "integrity": "sha512-g3uEsGOQCBl1+W1rgfwoRFUIR6PtvB2T1E4RpygeUU5LrLvlOqcxrt5lfykIeRpUPpupreGJUYl70fqMDXdTpw==", "requires": { "@octokit/auth-app": "^6.0.0", "@octokit/auth-unauthenticated": "^5.0.0", @@ -2599,61 +2772,121 @@ "@octokit/oauth-app": "^6.0.0", "@octokit/plugin-paginate-rest": "^9.0.0", "@octokit/types": "^12.0.0", - "@octokit/webhooks": "^12.0.1" + "@octokit/webhooks": "^12.0.4" } }, "@octokit/auth-app": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-6.0.1.tgz", - "integrity": "sha512-tjCD4nzQNZgmLH62+PSnTF6eGerisFgV4v6euhqJik6yWV96e1ZiiGj+NXIqbgnpjLmtnBqVUrNyGKu3DoGEGA==", - "requires": { - "@octokit/auth-oauth-app": "^7.0.0", - "@octokit/auth-oauth-user": "^4.0.0", - "@octokit/request": "^8.0.2", - "@octokit/request-error": "^5.0.0", - "@octokit/types": "^12.0.0", + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-6.1.3.tgz", + "integrity": "sha512-dcaiteA6Y/beAlDLZOPNReN3FGHu+pARD6OHfh3T9f3EO09++ec+5wt3KtGGSSs2Mp5tI8fQwdMOEnrzBLfgUA==", + "requires": { + "@octokit/auth-oauth-app": "^7.1.0", + "@octokit/auth-oauth-user": "^4.1.0", + "@octokit/request": "^8.3.1", + "@octokit/request-error": "^5.1.0", + "@octokit/types": "^13.1.0", "deprecation": "^2.3.1", - "lru-cache": "^10.0.0", - "universal-github-app-jwt": "^1.1.1", + "lru-cache": "npm:@wolfy1339/lru-cache@^11.0.2-patch.1", + "universal-github-app-jwt": "^1.1.2", "universal-user-agent": "^6.0.0" + }, + "dependencies": { + "@octokit/openapi-types": { + "version": "22.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz", + "integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==" + }, + "@octokit/types": { + "version": "13.6.2", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.2.tgz", + "integrity": "sha512-WpbZfZUcZU77DrSW4wbsSgTPfKcp286q3ItaIgvSbBpZJlu6mnYXAkjZz6LVZPXkEvLIM8McanyZejKTYUHipA==", + "requires": { + "@octokit/openapi-types": "^22.2.0" + } + } } }, "@octokit/auth-oauth-app": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-7.0.1.tgz", - "integrity": "sha512-RE0KK0DCjCHXHlQBoubwlLijXEKfhMhKm9gO56xYvFmP1QTMb+vvwRPmQLLx0V+5AvV9N9I3lr1WyTzwL3rMDg==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-7.1.0.tgz", + "integrity": "sha512-w+SyJN/b0l/HEb4EOPRudo7uUOSW51jcK1jwLa+4r7PA8FPFpoxEnHBHMITqCsc/3Vo2qqFjgQfz/xUUvsSQnA==", "requires": { - "@octokit/auth-oauth-device": "^6.0.0", - "@octokit/auth-oauth-user": "^4.0.0", - "@octokit/request": "^8.0.2", - "@octokit/types": "^12.0.0", + "@octokit/auth-oauth-device": "^6.1.0", + "@octokit/auth-oauth-user": "^4.1.0", + "@octokit/request": "^8.3.1", + "@octokit/types": "^13.0.0", "@types/btoa-lite": "^1.0.0", "btoa-lite": "^1.0.0", "universal-user-agent": "^6.0.0" + }, + "dependencies": { + "@octokit/openapi-types": { + "version": "22.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz", + "integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==" + }, + "@octokit/types": { + "version": "13.6.2", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.2.tgz", + "integrity": "sha512-WpbZfZUcZU77DrSW4wbsSgTPfKcp286q3ItaIgvSbBpZJlu6mnYXAkjZz6LVZPXkEvLIM8McanyZejKTYUHipA==", + "requires": { + "@octokit/openapi-types": "^22.2.0" + } + } } }, "@octokit/auth-oauth-device": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-6.0.1.tgz", - "integrity": "sha512-yxU0rkL65QkjbqQedgVx3gmW7YM5fF+r5uaSj9tM/cQGVqloXcqP2xK90eTyYvl29arFVCW8Vz4H/t47mL0ELw==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-6.1.0.tgz", + "integrity": "sha512-FNQ7cb8kASufd6Ej4gnJ3f1QB5vJitkoV1O0/g6e6lUsQ7+VsSNRHRmFScN2tV4IgKA12frrr/cegUs0t+0/Lw==", "requires": { - "@octokit/oauth-methods": "^4.0.0", - "@octokit/request": "^8.0.0", - "@octokit/types": "^12.0.0", + "@octokit/oauth-methods": "^4.1.0", + "@octokit/request": "^8.3.1", + "@octokit/types": "^13.0.0", "universal-user-agent": "^6.0.0" + }, + "dependencies": { + "@octokit/openapi-types": { + "version": "22.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz", + "integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==" + }, + "@octokit/types": { + "version": "13.6.2", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.2.tgz", + "integrity": "sha512-WpbZfZUcZU77DrSW4wbsSgTPfKcp286q3ItaIgvSbBpZJlu6mnYXAkjZz6LVZPXkEvLIM8McanyZejKTYUHipA==", + "requires": { + "@octokit/openapi-types": "^22.2.0" + } + } } }, "@octokit/auth-oauth-user": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-4.0.1.tgz", - "integrity": "sha512-N94wWW09d0hleCnrO5wt5MxekatqEJ4zf+1vSe8MKMrhZ7gAXKFOKrDEZW2INltvBWJCyDUELgGRv8gfErH1Iw==", - "requires": { - "@octokit/auth-oauth-device": "^6.0.0", - "@octokit/oauth-methods": "^4.0.0", - "@octokit/request": "^8.0.2", - "@octokit/types": "^12.0.0", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-4.1.0.tgz", + "integrity": "sha512-FrEp8mtFuS/BrJyjpur+4GARteUCrPeR/tZJzD8YourzoVhRics7u7we/aDcKv+yywRNwNi/P4fRi631rG/OyQ==", + "requires": { + "@octokit/auth-oauth-device": "^6.1.0", + "@octokit/oauth-methods": "^4.1.0", + "@octokit/request": "^8.3.1", + "@octokit/types": "^13.0.0", "btoa-lite": "^1.0.0", "universal-user-agent": "^6.0.0" + }, + "dependencies": { + "@octokit/openapi-types": { + "version": "22.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz", + "integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==" + }, + "@octokit/types": { + "version": "13.6.2", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.2.tgz", + "integrity": "sha512-WpbZfZUcZU77DrSW4wbsSgTPfKcp286q3ItaIgvSbBpZJlu6mnYXAkjZz6LVZPXkEvLIM8McanyZejKTYUHipA==", + "requires": { + "@octokit/openapi-types": "^22.2.0" + } + } } }, "@octokit/auth-token": { @@ -2705,9 +2938,9 @@ } }, "@octokit/oauth-app": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@octokit/oauth-app/-/oauth-app-6.0.0.tgz", - "integrity": "sha512-bNMkS+vJ6oz2hCyraT9ZfTpAQ8dZNqJJQVNaKjPLx4ue5RZiFdU1YWXguOPR8AaSHS+lKe+lR3abn2siGd+zow==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@octokit/oauth-app/-/oauth-app-6.1.0.tgz", + "integrity": "sha512-nIn/8eUJ/BKUVzxUXd5vpzl1rwaVxMyYbQkNZjHrF7Vk/yu98/YDF/N2KeWO7uZ0g3b5EyiFXFkZI8rJ+DH1/g==", "requires": { "@octokit/auth-oauth-app": "^7.0.0", "@octokit/auth-oauth-user": "^4.0.0", @@ -2725,28 +2958,28 @@ "integrity": "sha512-CdoJukjXXxqLNK4y/VOiVzQVjibqoj/xHgInekviUJV73y/BSIcwvJ/4aNHPBPKcPWFnd4/lO9uqRV65jXhcLA==" }, "@octokit/oauth-methods": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@octokit/oauth-methods/-/oauth-methods-4.0.0.tgz", - "integrity": "sha512-dqy7BZLfLbi3/8X8xPKUKZclMEK9vN3fK5WF3ortRvtplQTszFvdAGbTo71gGLO+4ZxspNiLjnqdd64Chklf7w==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@octokit/oauth-methods/-/oauth-methods-4.1.0.tgz", + "integrity": "sha512-4tuKnCRecJ6CG6gr0XcEXdZtkTDbfbnD5oaHBmLERTjTMZNi2CbfEHZxPU41xXLDG4DfKf+sonu00zvKI9NSbw==", "requires": { "@octokit/oauth-authorization-url": "^6.0.2", - "@octokit/request": "^8.0.2", - "@octokit/request-error": "^5.0.0", - "@octokit/types": "^11.0.0", + "@octokit/request": "^8.3.1", + "@octokit/request-error": "^5.1.0", + "@octokit/types": "^13.0.0", "btoa-lite": "^1.0.0" }, "dependencies": { "@octokit/openapi-types": { - "version": "18.1.1", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-18.1.1.tgz", - "integrity": "sha512-VRaeH8nCDtF5aXWnjPuEMIYf1itK/s3JYyJcWFJT8X9pSNnBtriDf7wlEWsGuhPLl4QIH4xM8fqTXDwJ3Mu6sw==" + "version": "22.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz", + "integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==" }, "@octokit/types": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-11.1.0.tgz", - "integrity": "sha512-Fz0+7GyLm/bHt8fwEqgvRBWwIV1S6wRRyq+V6exRKLVWaKGsuy6H9QFYeBVDV7rK6fO3XwHgQOPxv+cLj2zpXQ==", + "version": "13.6.2", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.2.tgz", + "integrity": "sha512-WpbZfZUcZU77DrSW4wbsSgTPfKcp286q3ItaIgvSbBpZJlu6mnYXAkjZz6LVZPXkEvLIM8McanyZejKTYUHipA==", "requires": { - "@octokit/openapi-types": "^18.0.0" + "@octokit/openapi-types": "^22.2.0" } } } @@ -2804,25 +3037,54 @@ } }, "@octokit/request": { - "version": "8.1.3", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.1.3.tgz", - "integrity": "sha512-iUvXP4QmysS8kyE/a4AGwR0A+tHDVxgW6TmPd2ci8/Xc8KjlBtTKSDpZlUT5Y4S4Nu+eM8LvbOYjVAp/sz3Gpg==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.4.0.tgz", + "integrity": "sha512-9Bb014e+m2TgBeEJGEbdplMVWwPmL1FPtggHQRkV+WVsMggPtEkLKPlcVYm/o8xKLkpJ7B+6N8WfQMtDLX2Dpw==", "requires": { - "@octokit/endpoint": "^9.0.0", - "@octokit/request-error": "^5.0.0", - "@octokit/types": "^12.0.0", - "is-plain-object": "^5.0.0", + "@octokit/endpoint": "^9.0.1", + "@octokit/request-error": "^5.1.0", + "@octokit/types": "^13.1.0", "universal-user-agent": "^6.0.0" + }, + "dependencies": { + "@octokit/openapi-types": { + "version": "22.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz", + "integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==" + }, + "@octokit/types": { + "version": "13.6.2", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.2.tgz", + "integrity": "sha512-WpbZfZUcZU77DrSW4wbsSgTPfKcp286q3ItaIgvSbBpZJlu6mnYXAkjZz6LVZPXkEvLIM8McanyZejKTYUHipA==", + "requires": { + "@octokit/openapi-types": "^22.2.0" + } + } } }, "@octokit/request-error": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.0.1.tgz", - "integrity": "sha512-X7pnyTMV7MgtGmiXBwmO6M5kIPrntOXdyKZLigNfQWSEQzVxR4a4vo49vJjTWX70mPndj8KhfT4Dx+2Ng3vnBQ==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.1.0.tgz", + "integrity": "sha512-GETXfE05J0+7H2STzekpKObFe765O5dlAKUTLNGeH+x47z7JjXHfsHKo5z21D/o/IOZTUEI6nyWyR+bZVP/n5Q==", "requires": { - "@octokit/types": "^12.0.0", + "@octokit/types": "^13.1.0", "deprecation": "^2.0.0", "once": "^1.4.0" + }, + "dependencies": { + "@octokit/openapi-types": { + "version": "22.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz", + "integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==" + }, + "@octokit/types": { + "version": "13.6.2", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.2.tgz", + "integrity": "sha512-WpbZfZUcZU77DrSW4wbsSgTPfKcp286q3ItaIgvSbBpZJlu6mnYXAkjZz6LVZPXkEvLIM8McanyZejKTYUHipA==", + "requires": { + "@octokit/openapi-types": "^22.2.0" + } + } } }, "@octokit/rest": { @@ -2845,13 +3107,13 @@ } }, "@octokit/webhooks": { - "version": "12.0.3", - "resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-12.0.3.tgz", - "integrity": "sha512-8iG+/yza7hwz1RrQ7i7uGpK2/tuItZxZq1aTmeg2TNp2xTUB8F8lZF/FcZvyyAxT8tpDMF74TjFGCDACkf1kAQ==", + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-12.3.1.tgz", + "integrity": "sha512-BVwtWE3rRXB9IugmQTfKspqjNa8q+ab73ddkV9k1Zok3XbuOxJUi4lTYk5zBZDhfWb/Y2H+RO9Iggm25gsqeow==", "requires": { "@octokit/request-error": "^5.0.0", - "@octokit/webhooks-methods": "^4.0.0", - "@octokit/webhooks-types": "7.1.0", + "@octokit/webhooks-methods": "^4.1.0", + "@octokit/webhooks-types": "7.6.1", "aggregate-error": "^3.1.0" } }, @@ -2861,19 +3123,19 @@ "integrity": "sha512-do4Z1r2OVhuI0ihJhQ8Hg+yPWnBYEBNuFNCrvtPKoYT1w81jD7pBXgGe86lYuuNirkDHb0Nxt+zt4O5GiFJfgA==" }, "@octokit/webhooks-methods": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@octokit/webhooks-methods/-/webhooks-methods-4.0.0.tgz", - "integrity": "sha512-M8mwmTXp+VeolOS/kfRvsDdW+IO0qJ8kYodM/sAysk093q6ApgmBXwK1ZlUvAwXVrp/YVHp6aArj4auAxUAOFw==" + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@octokit/webhooks-methods/-/webhooks-methods-4.1.0.tgz", + "integrity": "sha512-zoQyKw8h9STNPqtm28UGOYFE7O6D4Il8VJwhAtMHFt2C4L0VQT1qGKLeefUOqHNs1mNRYSadVv7x0z8U2yyeWQ==" }, "@octokit/webhooks-types": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@octokit/webhooks-types/-/webhooks-types-7.1.0.tgz", - "integrity": "sha512-y92CpG4kFFtBBjni8LHoV12IegJ+KFxLgKRengrVjKmGE5XMeCuGvlfRe75lTRrgXaG6XIWJlFpIDTlkoJsU8w==" + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/@octokit/webhooks-types/-/webhooks-types-7.6.1.tgz", + "integrity": "sha512-S8u2cJzklBC0FgTwWVLaM8tMrDuDMVE4xiTK4EYXM9GntyvrdbSoxqDQa+Fh57CCNApyIpyeqPhhFEmHPfrXgw==" }, "@types/aws-lambda": { - "version": "8.10.124", - "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.124.tgz", - "integrity": "sha512-PHqK0SuAkFS3tZjceqRXecxxrWIN3VqTicuialtK2wZmvBy7H9WGc3u3+wOgaZB7N8SpSXDpWk9qa7eorpTStg==" + "version": "8.10.146", + "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.146.tgz", + "integrity": "sha512-3BaDXYTh0e6UCJYL/jwV/3+GRslSc08toAiZSmleYtkAUyV5rtvdPYxrG/88uqvTuT6sb27WE9OS90ZNTIuQ0g==" }, "@types/babel__traverse": { "version": "7.20.3", @@ -2885,9 +3147,9 @@ } }, "@types/btoa-lite": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/btoa-lite/-/btoa-lite-1.0.0.tgz", - "integrity": "sha512-wJsiX1tosQ+J5+bY5LrSahHxr2wT+uME5UDwdN1kg4frt40euqA+wzECkmq4t5QbveHiJepfdThgQrPw6KiSlg==" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/btoa-lite/-/btoa-lite-1.0.2.tgz", + "integrity": "sha512-ZYbcE2x7yrvNFJiU7xJGrpF/ihpkM7zKgw8bha3LNJSesvTtUNxbpzaT7WXBIryf6jovisrxTBvymxMeLLj1Mg==" }, "@types/diff": { "version": "5.0.7", @@ -2896,9 +3158,9 @@ "dev": true }, "@types/jsonwebtoken": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", - "integrity": "sha512-b0jGiOgHtZ2jqdPgPnP6WLCXZk1T8p06A/vPGzUvxpFGgKMbjXJDjC5m52ErqBnIuWZFgGoIJyRdeG5AyreJjA==", + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.7.tgz", + "integrity": "sha512-ugo316mmTYBl2g81zDFnZ7cfxlut3o+/EQdaP7J8QN2kY6lJ22hmQYCK5EHcJHbrW+dkCGSCPgbG8JtYj6qSrg==", "requires": { "@types/node": "*" } @@ -3435,9 +3697,9 @@ "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" }, "lru-cache": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.1.tgz", - "integrity": "sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==" + "version": "npm:@wolfy1339/lru-cache@11.0.2-patch.1", + "resolved": "https://registry.npmjs.org/@wolfy1339/lru-cache/-/lru-cache-11.0.2-patch.1.tgz", + "integrity": "sha512-BgYZfL2ADCXKOw2wJtkM3slhHotawWkgIRRxq4wEybnZQPjvAp71SPX35xepMykTw8gXlzWcWPTY31hlbnRsDA==" }, "methods": { "version": "1.1.2", @@ -3502,20 +3764,51 @@ "dev": true }, "octokit": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/octokit/-/octokit-3.1.1.tgz", - "integrity": "sha512-AKJs5XYs7iAh7bskkYpxhUIpsYZdLqjnlnqrN5s9FFZuJ/a6ATUHivGpUKDpGB/xa+LGDtG9Lu8bOCfPM84vHQ==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/octokit/-/octokit-3.2.1.tgz", + "integrity": "sha512-u+XuSejhe3NdIvty3Jod00JvTdAE/0/+XbhIDhefHbu+2OcTRHd80aCiH6TX19ZybJmwPQBKFQmHGxp0i9mJrg==", "requires": { - "@octokit/app": "^14.0.0", + "@octokit/app": "^14.0.2", "@octokit/core": "^5.0.0", "@octokit/oauth-app": "^6.0.0", "@octokit/plugin-paginate-graphql": "^4.0.0", - "@octokit/plugin-paginate-rest": "^9.0.0", - "@octokit/plugin-rest-endpoint-methods": "^10.0.0", + "@octokit/plugin-paginate-rest": "11.3.1", + "@octokit/plugin-rest-endpoint-methods": "13.2.2", "@octokit/plugin-retry": "^6.0.0", "@octokit/plugin-throttling": "^8.0.0", "@octokit/request-error": "^5.0.0", - "@octokit/types": "^12.0.0" + "@octokit/types": "^13.0.0" + }, + "dependencies": { + "@octokit/openapi-types": { + "version": "22.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz", + "integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==" + }, + "@octokit/plugin-paginate-rest": { + "version": "11.3.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.3.1.tgz", + "integrity": "sha512-ryqobs26cLtM1kQxqeZui4v8FeznirUsksiA+RYemMPJ7Micju0WSkv50dBksTuZks9O5cg4wp+t8fZ/cLY56g==", + "requires": { + "@octokit/types": "^13.5.0" + } + }, + "@octokit/plugin-rest-endpoint-methods": { + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-13.2.2.tgz", + "integrity": "sha512-EI7kXWidkt3Xlok5uN43suK99VWqc8OaIMktY9d9+RNKl69juoTyxmLoWPIZgJYzi41qj/9zU7G/ljnNOJ5AFA==", + "requires": { + "@octokit/types": "^13.5.0" + } + }, + "@octokit/types": { + "version": "13.6.2", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.2.tgz", + "integrity": "sha512-WpbZfZUcZU77DrSW4wbsSgTPfKcp286q3ItaIgvSbBpZJlu6mnYXAkjZz6LVZPXkEvLIM8McanyZejKTYUHipA==", + "requires": { + "@octokit/openapi-types": "^22.2.0" + } + } } }, "on-finished": { @@ -3710,12 +4003,12 @@ "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, "universal-github-app-jwt": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/universal-github-app-jwt/-/universal-github-app-jwt-1.1.1.tgz", - "integrity": "sha512-G33RTLrIBMFmlDV4u4CBF7dh71eWwykck4XgaxaIVeZKOYZRAAxvcGMRFTUclVY6xoUPQvO4Ne5wKGxYm/Yy9w==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/universal-github-app-jwt/-/universal-github-app-jwt-1.2.0.tgz", + "integrity": "sha512-dncpMpnsKBk0eetwfN8D8OUHGfiDhhJ+mtsbMl+7PfW7mYjiH8LIcqRmYMtzYLgSh47HjfdBtrBwIQ/gizKR3g==", "requires": { "@types/jsonwebtoken": "^9.0.0", - "jsonwebtoken": "^9.0.0" + "jsonwebtoken": "^9.0.2" } }, "universal-user-agent": { diff --git a/package.json b/package.json index fb08515..fa22fd8 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,10 @@ "main": "src/app.ts", "license": "ISC", "scripts": { - "start": "tsx src/app.ts" + "start": "tsx src/app.ts", + "dev": "tsx --watch src/app.ts", + "build": "tsc", + "test": "echo \"Error: no test specified\" && exit 1" }, "dependencies": { "@babel/parser": "^7.23.0", diff --git a/src/app.ts b/src/app.ts index 450ae71..a6764bd 100644 --- a/src/app.ts +++ b/src/app.ts @@ -7,6 +7,8 @@ import { Review } from "./constants"; import { env } from "./env"; import { processPullRequest } from "./review-agent"; import { applyReview } from "./reviews"; +import { logger } from "./utils/logger"; +import { config } from "./utils/config"; // This creates a new instance of the Octokit App class. const reviewApp = new App({ @@ -19,6 +21,11 @@ const reviewApp = new App({ const getChangesPerFile = async (payload: WebhookEventMap["pull_request"]) => { try { + logger.debug("Fetching changed files for PR", { + repo: payload.repository.full_name, + pr: payload.pull_request.number, + }); + const octokit = await reviewApp.getInstallationOctokit( payload.installation.id ); @@ -27,10 +34,18 @@ const getChangesPerFile = async (payload: WebhookEventMap["pull_request"]) => { repo: payload.repository.name, pull_number: payload.pull_request.number, }); - console.dir({ files }, { depth: null }); + + logger.info( + `Found ${files.length} changed files in PR #${payload.pull_request.number}` + ); + logger.debug( + "Changed files:", + files.map((f) => f.filename) + ); + return files; - } catch (exc) { - console.log("exc"); + } catch (error) { + logger.error("Failed to fetch changed files:", error); return []; } }; @@ -43,28 +58,54 @@ async function handlePullRequestOpened({ octokit: Octokit; payload: WebhookEventMap["pull_request"]; }) { - console.log( - `Received a pull request event for #${payload.pull_request.number}` - ); - // const reposWithInlineEnabled = new Set([601904706, 701925328]); - // const canInlineSuggest = reposWithInlineEnabled.has(payload.repository.id); + const prNumber = payload.pull_request.number; + const repoFullName = payload.repository.full_name; + + logger.info(`๐Ÿ” Processing PR #${prNumber} in ${repoFullName}`); + + const cfg = config.get(); + try { - console.log("pr info", { + logger.debug("Repository info:", { id: payload.repository.id, fullName: payload.repository.full_name, url: payload.repository.html_url, }); + const files = await getChangesPerFile(payload); + + if (files.length === 0) { + logger.warn(`No files found for PR #${prNumber}, skipping review`); + return; + } + + if (files.length > cfg.maxFilesPerReview) { + logger.warn( + `PR #${prNumber} has ${files.length} files, which exceeds the limit of ${cfg.maxFilesPerReview}. Some files may not be reviewed.` + ); + } + + logger.info( + `๐Ÿš€ Starting ${ + cfg.enableAgenticReview ? "agentic" : "traditional" + } review process...` + ); + const review: Review = await processPullRequest( octokit, payload, files, true ); - await applyReview({ octokit, payload, review }); - console.log("Review Submitted"); - } catch (exc) { - console.log(exc); + + if (review.review) { + await applyReview({ octokit, payload, review }); + logger.success(`โœ… Review completed and posted for PR #${prNumber}`); + } else { + logger.warn(`No review generated for PR #${prNumber}`); + } + } catch (error) { + logger.error(`โŒ Failed to process PR #${prNumber}:`, error); } } @@ -72,7 +113,7 @@ async function handlePullRequestOpened({ //@ts-ignore reviewApp.webhooks.on("pull_request.opened", handlePullRequestOpened); -const port = process.env.PORT || 3000; +const port = process.env.PORT || 3001; const reviewWebhook = `/api/review`; const reviewMiddleware = createNodeMiddleware(reviewApp.webhooks, { @@ -90,6 +131,12 @@ const server = http.createServer((req, res) => { // This creates a Node.js server that listens for incoming HTTP requests (including webhook payloads from GitHub) on the specified port. When the server receives a request, it executes the `middleware` function that you defined earlier. Once the server is running, it logs messages to the console to indicate that it is listening. server.listen(port, () => { - console.log(`Server is listening for events.`); - console.log("Press Ctrl + C to quit."); + logger.success(`๐Ÿš€ CodeScribe Agent is running on port ${port}`); + logger.info(`๐Ÿ“ก Webhook endpoint: http://localhost:${port}${reviewWebhook}`); + logger.info( + `๐Ÿค– Agentic review: ${ + config.get().enableAgenticReview ? "ENABLED" : "DISABLED" + }` + ); + logger.info("Press Ctrl + C to quit."); }); diff --git a/src/constants.ts b/src/constants.ts index 14c7de1..e7fe207 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,5 +1,6 @@ import { Node } from "@babel/traverse"; import { JavascriptParser } from "./context/language/javascript-parser"; +import { PythonParser } from "./context/language/python-parser"; import { ChatCompletionMessageParam } from "groq-sdk/resources/chat/completions"; export interface PRFile { @@ -107,6 +108,8 @@ const EXTENSIONS_TO_PARSERS: Map = new Map([ ["tsx", new JavascriptParser()], ["js", new JavascriptParser()], ["jsx", new JavascriptParser()], + ["py", new PythonParser()], + ["pyw", new PythonParser()], ]); export const getParserForExtension = (filename: string) => { diff --git a/src/context/language/python-parser.ts b/src/context/language/python-parser.ts index 845e90b..0093229 100644 --- a/src/context/language/python-parser.ts +++ b/src/context/language/python-parser.ts @@ -1,15 +1,225 @@ import { AbstractParser, EnclosingContext } from "../../constants"; + +interface PythonContext { + type: "function" | "class" | "method"; + name: string; + startLine: number; + endLine: number; + indentLevel: number; +} + export class PythonParser implements AbstractParser { findEnclosingContext( file: string, lineStart: number, lineEnd: number ): EnclosingContext { - // TODO: Implement this method for Python - return null; + const lines = file.split("\n"); + const contexts: PythonContext[] = []; + + // Parse the file to find all function and class definitions + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const trimmedLine = line.trim(); + const indentLevel = line.length - line.trimStart().length; + + // Match class definitions + const classMatch = trimmedLine.match(/^class\s+(\w+).*:/); + if (classMatch) { + const context: PythonContext = { + type: "class", + name: classMatch[1], + startLine: i + 1, // 1-based line numbers + endLine: this.findBlockEnd(lines, i, indentLevel), + indentLevel, + }; + contexts.push(context); + } + + // Match function/method definitions + const funcMatch = trimmedLine.match(/^def\s+(\w+).*:/); + if (funcMatch) { + const context: PythonContext = { + type: "function", + name: funcMatch[1], + startLine: i + 1, // 1-based line numbers + endLine: this.findBlockEnd(lines, i, indentLevel), + indentLevel, + }; + contexts.push(context); + } + } + + // Find the most specific (deepest nested) context that contains our lines + let bestContext: PythonContext | null = null; + let bestSpecificity = -1; + + for (const context of contexts) { + if (context.startLine <= lineStart && lineEnd <= context.endLine) { + // This context contains our target lines + const specificity = context.indentLevel; // Deeper indentation = more specific + if (specificity > bestSpecificity) { + bestSpecificity = specificity; + bestContext = context; + } + } + } + + if (bestContext) { + // Create a mock node object similar to what Babel would return + const mockNode = { + type: + bestContext.type === "class" + ? "ClassDeclaration" + : "FunctionDeclaration", + loc: { + start: { line: bestContext.startLine, column: 0 }, + end: { line: bestContext.endLine, column: 0 }, + }, + name: bestContext.name, + pythonType: bestContext.type, + }; + + return { + enclosingContext: mockNode as any, + }; + } + + return { + enclosingContext: null, + }; + } + + private findBlockEnd( + lines: string[], + startIndex: number, + baseIndent: number + ): number { + let endLine = startIndex + 1; + + for (let i = startIndex + 1; i < lines.length; i++) { + const line = lines[i]; + const trimmedLine = line.trim(); + + // Skip empty lines and comments + if (trimmedLine === "" || trimmedLine.startsWith("#")) { + endLine = i + 1; + continue; + } + + const lineIndent = line.length - line.trimStart().length; + + // If we find a line with same or less indentation than the definition, + // and it's not empty, the block has ended + if (lineIndent <= baseIndent) { + break; + } + + endLine = i + 1; + } + + return endLine; } + dryRun(file: string): { valid: boolean; error: string } { - // TODO: Implement this method for Python - return { valid: false, error: "Not implemented yet" }; + try { + // Basic Python syntax validation using compile() + // This is a simple approach without requiring Python runtime + + // Check for basic syntax issues + const issues: string[] = []; + const lines = file.split("\n"); + + let indentStack: number[] = [0]; + let inMultilineString = false; + let multilineStringChar = ""; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const lineNum = i + 1; + + // Handle multiline strings + if (inMultilineString) { + if (line.includes(multilineStringChar.repeat(3))) { + inMultilineString = false; + multilineStringChar = ""; + } + continue; + } + + // Check for start of multiline strings + if (line.includes('"""') || line.includes("'''")) { + multilineStringChar = line.includes('"""') ? '"' : "'"; + inMultilineString = true; + continue; + } + + const trimmedLine = line.trim(); + + // Skip empty lines and comments + if (trimmedLine === "" || trimmedLine.startsWith("#")) { + continue; + } + + // Check indentation consistency + const currentIndent = line.length - line.trimStart().length; + + // Basic indentation validation + if (line.trimStart() !== line && currentIndent % 4 !== 0) { + // Warning for non-standard indentation (not an error) + } + + // Check for basic syntax patterns + if (trimmedLine.endsWith(":")) { + // This should increase indentation level + const expectedIndent = currentIndent + 4; + indentStack.push(expectedIndent); + } else if (currentIndent < indentStack[indentStack.length - 1]) { + // Dedent - pop from stack + while ( + indentStack.length > 1 && + currentIndent < indentStack[indentStack.length - 1] + ) { + indentStack.pop(); + } + } + + // Check for common syntax errors + if ( + trimmedLine.match( + /^\s*(def|class|if|elif|else|for|while|try|except|finally|with)\s*[^:]*$/ + ) + ) { + issues.push(`Line ${lineNum}: Missing colon after statement`); + } + + // Check for incorrect operators + if (trimmedLine.includes("=") && !trimmedLine.match(/[=!<>]=|==/)) { + const beforeEquals = trimmedLine.split("=")[0].trim(); + if (beforeEquals.match(/^\s*(if|elif|while|assert)\s/)) { + issues.push( + `Line ${lineNum}: Use '==' for comparison, not '=' in conditional` + ); + } + } + } + + if (issues.length > 0) { + return { + valid: false, + error: issues.join("; "), + }; + } + + return { + valid: true, + error: "", + }; + } catch (error) { + return { + valid: false, + error: `Syntax validation error: ${error.message || error}`, + }; + } } } diff --git a/src/llms/agent-chat.ts b/src/llms/agent-chat.ts new file mode 100644 index 0000000..b19e0ff --- /dev/null +++ b/src/llms/agent-chat.ts @@ -0,0 +1,334 @@ +import { ChatCompletionMessageParam } from "groq-sdk/resources/chat/completions"; +import { generateChatCompletion } from "./chat"; +import { logger } from "../utils/logger"; +import { config } from "../utils/config"; + +export interface AgentReviewResult { + type: + | "security" + | "performance" + | "code_quality" + | "documentation" + | "testing"; + feedback: string; + suggestions: Array<{ + line_start?: number; + line_end?: number; + file: string; + issue: string; + suggestion: string; + severity: "low" | "medium" | "high" | "critical"; + }>; +} + +export class ReviewAgent { + /** + * Security-focused review agent + */ + static async securityReview(diff: string): Promise { + const messages: ChatCompletionMessageParam[] = [ + { + role: "system", + content: `You are a security expert reviewing code changes. Focus on: +- SQL injection vulnerabilities +- XSS vulnerabilities +- Authentication/authorization issues +- Data validation problems +- Sensitive data exposure +- Insecure dependencies +- CORS misconfigurations + +Respond in JSON format with: +{ + "feedback": "Overall security assessment", + "suggestions": [ + { + "file": "filename", + "line_start": number, + "line_end": number, + "issue": "Security issue description", + "suggestion": "How to fix it", + "severity": "low|medium|high|critical" + } + ] +}`, + }, + { + role: "user", + content: `Review this code diff for security vulnerabilities:\n\n${diff}`, + }, + ]; + + const response = await generateChatCompletion({ messages }); + try { + const parsed = JSON.parse(response.content || "{}"); + return { + type: "security", + feedback: parsed.feedback || "No security issues detected.", + suggestions: parsed.suggestions || [], + }; + } catch (error) { + return { + type: "security", + feedback: response.content || "Security review completed.", + suggestions: [], + }; + } + } + + /** + * Performance-focused review agent + */ + static async performanceReview(diff: string): Promise { + const messages: ChatCompletionMessageParam[] = [ + { + role: "system", + content: `You are a performance expert reviewing code changes. Focus on: +- Algorithm efficiency (O(n) complexity) +- Database query optimization +- Memory usage patterns +- Caching opportunities +- Unnecessary loops or iterations +- Blocking operations +- Resource leaks + +Respond in JSON format with: +{ + "feedback": "Overall performance assessment", + "suggestions": [ + { + "file": "filename", + "line_start": number, + "line_end": number, + "issue": "Performance issue description", + "suggestion": "How to optimize it", + "severity": "low|medium|high|critical" + } + ] +}`, + }, + { + role: "user", + content: `Review this code diff for performance issues:\n\n${diff}`, + }, + ]; + + const response = await generateChatCompletion({ messages }); + try { + const parsed = JSON.parse(response.content || "{}"); + return { + type: "performance", + feedback: parsed.feedback || "No performance issues detected.", + suggestions: parsed.suggestions || [], + }; + } catch (error) { + return { + type: "performance", + feedback: response.content || "Performance review completed.", + suggestions: [], + }; + } + } + + /** + * Code quality-focused review agent + */ + static async codeQualityReview(diff: string): Promise { + const messages: ChatCompletionMessageParam[] = [ + { + role: "system", + content: `You are a code quality expert reviewing code changes. Focus on: +- Code readability and clarity +- Naming conventions +- Function/method length and complexity +- DRY principle violations +- SOLID principles +- Error handling +- Code organization and structure +- TypeScript/type safety + +Respond in JSON format with: +{ + "feedback": "Overall code quality assessment", + "suggestions": [ + { + "file": "filename", + "line_start": number, + "line_end": number, + "issue": "Code quality issue description", + "suggestion": "How to improve it", + "severity": "low|medium|high|critical" + } + ] +}`, + }, + { + role: "user", + content: `Review this code diff for code quality issues:\n\n${diff}`, + }, + ]; + + const response = await generateChatCompletion({ messages }); + try { + const parsed = JSON.parse(response.content || "{}"); + return { + type: "code_quality", + feedback: parsed.feedback || "Code quality looks good.", + suggestions: parsed.suggestions || [], + }; + } catch (error) { + return { + type: "code_quality", + feedback: response.content || "Code quality review completed.", + suggestions: [], + }; + } + } + + /** + * Documentation-focused review agent + */ + static async documentationReview(diff: string): Promise { + const messages: ChatCompletionMessageParam[] = [ + { + role: "system", + content: `You are a documentation expert reviewing code changes. Focus on: +- Missing function/method documentation +- Incomplete or unclear comments +- README updates needed +- API documentation +- Type definitions documentation +- Example usage +- Changelog updates + +Respond in JSON format with: +{ + "feedback": "Overall documentation assessment", + "suggestions": [ + { + "file": "filename", + "line_start": number, + "line_end": number, + "issue": "Documentation issue description", + "suggestion": "What documentation to add", + "severity": "low|medium|high|critical" + } + ] +}`, + }, + { + role: "user", + content: `Review this code diff for documentation needs:\n\n${diff}`, + }, + ]; + + const response = await generateChatCompletion({ messages }); + try { + const parsed = JSON.parse(response.content || "{}"); + return { + type: "documentation", + feedback: parsed.feedback || "Documentation looks adequate.", + suggestions: parsed.suggestions || [], + }; + } catch (error) { + return { + type: "documentation", + feedback: response.content || "Documentation review completed.", + suggestions: [], + }; + } + } + + /** + * Testing-focused review agent + */ + static async testingReview(diff: string): Promise { + const messages: ChatCompletionMessageParam[] = [ + { + role: "system", + content: `You are a testing expert reviewing code changes. Focus on: +- Missing unit tests for new functions +- Integration test coverage +- Edge case testing +- Error condition testing +- Mock usage +- Test organization +- Test naming conventions + +Respond in JSON format with: +{ + "feedback": "Overall testing assessment", + "suggestions": [ + { + "file": "filename", + "line_start": number, + "line_end": number, + "issue": "Testing issue description", + "suggestion": "What tests to add", + "severity": "low|medium|high|critical" + } + ] +}`, + }, + { + role: "user", + content: `Review this code diff for testing needs:\n\n${diff}`, + }, + ]; + + const response = await generateChatCompletion({ messages }); + try { + const parsed = JSON.parse(response.content || "{}"); + return { + type: "testing", + feedback: parsed.feedback || "Testing coverage looks adequate.", + suggestions: parsed.suggestions || [], + }; + } catch (error) { + return { + type: "testing", + feedback: response.content || "Testing review completed.", + suggestions: [], + }; + } + } + + /** + * Orchestrator that runs all agents and combines results + */ + static async comprehensiveReview(diff: string): Promise { + logger.agent("Starting comprehensive agentic review..."); + + const cfg = config.get(); + const reviewPromises: Promise[] = []; + + // Add enabled agents to the review process + if (cfg.enableSecurityAgent) { + reviewPromises.push(this.securityReview(diff)); + } + if (cfg.enablePerformanceAgent) { + reviewPromises.push(this.performanceReview(diff)); + } + if (cfg.enableCodeQualityAgent) { + reviewPromises.push(this.codeQualityReview(diff)); + } + if (cfg.enableDocumentationAgent) { + reviewPromises.push(this.documentationReview(diff)); + } + if (cfg.enableTestingAgent) { + reviewPromises.push(this.testingReview(diff)); + } + + try { + const results = await Promise.all(reviewPromises); + logger.success( + `All ${results.length} agent reviews completed successfully` + ); + return results; + } catch (error) { + logger.error("Error in agent reviews:", error); + // Return partial results or fallback + return []; + } + } +} diff --git a/src/llms/chat.ts b/src/llms/chat.ts index 452fe59..59501dd 100644 --- a/src/llms/chat.ts +++ b/src/llms/chat.ts @@ -1,13 +1,73 @@ -import { ChatCompletionCreateParamsNonStreaming } from "groq-sdk/resources/chat/completions"; +import { + ChatCompletionCreateParamsNonStreaming, + ChatCompletionMessage, +} from "groq-sdk/resources/chat/completions"; import { groq, GROQ_MODEL } from "./groq"; +import { logger } from "../utils/logger"; +import { config } from "../utils/config"; export const generateChatCompletion = async ( - options: Omit -) => { - const response = await groq.chat.completions.create({ - model: GROQ_MODEL, - temperature: 0, - ...options, - }); - return response.choices[0].message; + options: Omit, + retryCount = 0 +): Promise => { + const cfg = config.get(); + + try { + logger.debug( + `Making chat completion request (attempt ${retryCount + 1}/${ + cfg.retryAttempts + 1 + })` + ); + + const response = await groq.chat.completions.create({ + model: cfg.primaryModel as any, + temperature: cfg.temperature, + ...options, + }); + + if (!response.choices || response.choices.length === 0) { + throw new Error("No choices returned from chat completion"); + } + + const message = response.choices[0].message; + if (!message.content) { + logger.warn("Chat completion returned empty content"); + } + + logger.debug("Chat completion successful"); + return message; + } catch (error) { + logger.error(`Chat completion error (attempt ${retryCount + 1}):`, error); + + // Rate limiting or temporary errors - retry with exponential backoff + if ( + retryCount < cfg.retryAttempts && + (error.status === 429 || + error.status >= 500 || + error.code === "ECONNRESET") + ) { + const delay = Math.pow(2, retryCount) * 1000; // Exponential backoff + logger.info(`Retrying chat completion in ${delay}ms...`); + + await new Promise((resolve) => setTimeout(resolve, delay)); + return generateChatCompletion(options, retryCount + 1); + } + + // Try fallback model if available + if (cfg.fallbackModel && retryCount === 0) { + logger.info(`Trying fallback model: ${cfg.fallbackModel}`); + try { + const fallbackResponse = await groq.chat.completions.create({ + model: cfg.fallbackModel as any, + temperature: cfg.temperature, + ...options, + }); + return fallbackResponse.choices[0].message; + } catch (fallbackError) { + logger.error("Fallback model also failed:", fallbackError); + } + } + + throw error; + } }; diff --git a/src/llms/groq.ts b/src/llms/groq.ts index c98725c..1d501e8 100644 --- a/src/llms/groq.ts +++ b/src/llms/groq.ts @@ -8,4 +8,4 @@ export const groq = new Groq({ export type GroqChatModel = ChatCompletionCreateParamsBase["model"]; -export const GROQ_MODEL: GroqChatModel = "mixtral-8x7b-32768"; +export const GROQ_MODEL: GroqChatModel = "llama-3.3-70b-versatile"; diff --git a/src/prompts.ts b/src/prompts.ts index 42a907e..26c9564 100644 --- a/src/prompts.ts +++ b/src/prompts.ts @@ -8,7 +8,9 @@ import { import { GROQ_MODEL, type GroqChatModel } from "./llms/groq"; const ModelsToTokenLimits: Record = { - "mixtral-8x7b-32768": 32768, + "llama-3.3-70b-versatile": 32768, + "llama-3.1-70b-versatile": 32768, // Deprecated but keeping for type compatibility + "mixtral-8x7b-32768": 32768, // Deprecated but keeping for type compatibility "gemma-7b-it": 32768, "llama3-70b-8192": 8192, "llama3-8b-8192": 8192, diff --git a/src/review-agent.ts b/src/review-agent.ts index 31a6afe..ad5f99d 100644 --- a/src/review-agent.ts +++ b/src/review-agent.ts @@ -12,6 +12,7 @@ import type { } from "./constants"; import { PRSuggestionImpl } from "./data/PRSuggestionImpl"; import { generateChatCompletion } from "./llms/chat"; +import { ReviewAgent, AgentReviewResult } from "./llms/agent-chat"; import { PR_SUGGESTION_TEMPLATE, buildPatchPrompt, @@ -26,6 +27,8 @@ import { getInlineFixPrompt, } from "./prompts/inline-prompt"; import { getGitFile } from "./reviews"; +import { logger } from "./utils/logger"; +import { config } from "./utils/config"; export const reviewDiff = async (messages: ChatCompletionMessageParam[]) => { const message = await generateChatCompletion({ @@ -81,7 +84,9 @@ const filterFile = (file: PRFile) => { } const extension = splitFilename.pop()?.toLowerCase(); if (extension && extensionsToIgnore.has(extension)) { - console.log(`Filtering out file with ignored extension: ${file.filename} (.${extension})`); + console.log( + `Filtering out file with ignored extension: ${file.filename} (.${extension})` + ); return false; } return true; @@ -507,6 +512,140 @@ const reviewChangesRetry = async (files: PRFile[], builders: Builders[]) => { throw new Error("All convoBuilders failed."); }; +/** + * Enhanced agentic review process that uses multiple specialized agents + */ +const performAgenticReview = async ( + files: PRFile[], + owner: string, + repoName: string +): Promise => { + try { + logger.agent("Starting agentic review process..."); + + // Generate unified diff for all files + const patches = files.map((file) => buildPatchPrompt(file)); + const unifiedDiff = patches.join("\n"); + + logger.debug( + `Generated unified diff with ${unifiedDiff.length} characters` + ); + + // Run comprehensive agent-based review + const agentResults = await ReviewAgent.comprehensiveReview(unifiedDiff); + + // Combine all agent feedback into a cohesive review + let reviewComment = "## ๐Ÿค– AI-Powered Code Review\n\n"; + reviewComment += + "This pull request has been analyzed by multiple specialized AI agents. Here's what they found:\n\n"; + + const allSuggestions: any[] = []; + + for (const result of agentResults) { + const emoji = + { + security: "๐Ÿ”’", + performance: "โšก", + code_quality: "โœจ", + documentation: "๐Ÿ“", + testing: "๐Ÿงช", + }[result.type] || "๐Ÿ”"; + + reviewComment += `### ${emoji} ${result.type + .replace("_", " ") + .toUpperCase()} Analysis\n`; + reviewComment += `${result.feedback}\n\n`; + + if (result.suggestions.length > 0) { + reviewComment += "**Specific Issues Found:**\n"; + result.suggestions.forEach((suggestion, index) => { + const severity = suggestion.severity || "medium"; + const severityEmoji = + { + critical: "๐Ÿšจ", + high: "๐Ÿ”ด", + medium: "๐ŸŸก", + low: "๐ŸŸข", + }[severity] || "๐Ÿ”"; + + reviewComment += `${index + 1}. ${severityEmoji} **${ + suggestion.file + }** `; + if (suggestion.line_start) { + reviewComment += `(Line ${suggestion.line_start}`; + if ( + suggestion.line_end && + suggestion.line_end !== suggestion.line_start + ) { + reviewComment += `-${suggestion.line_end}`; + } + reviewComment += `) `; + } + reviewComment += `- ${suggestion.issue}\n`; + reviewComment += ` *Suggested fix:* ${suggestion.suggestion}\n\n`; + + // Convert to structured comment format for inline suggestions + allSuggestions.push( + new PRSuggestionImpl( + `${result.type}: ${suggestion.issue}`, + result.type, + suggestion.suggestion, + suggestion.suggestion, // Using suggestion as code for now + suggestion.file + ) + ); + }); + reviewComment += "\n"; + } + } + + // Add summary + const totalIssues = agentResults.reduce( + (sum, result) => sum + result.suggestions.length, + 0 + ); + const criticalIssues = agentResults.reduce( + (sum, result) => + sum + + result.suggestions.filter((s) => s.severity === "critical").length, + 0 + ); + + reviewComment += "---\n\n"; + reviewComment += `**Summary**: Found ${totalIssues} total issues`; + if (criticalIssues > 0) { + reviewComment += `, including ${criticalIssues} critical issues that should be addressed immediately`; + } + reviewComment += ".\n\n"; + reviewComment += + "This review was generated using multiple AI agents specializing in security, performance, code quality, documentation, and testing."; + + const cfg = config.get(); + if (reviewComment.length > cfg.maxCommentLength) { + logger.warn( + `Review comment truncated from ${reviewComment.length} to ${cfg.maxCommentLength} characters` + ); + reviewComment = + reviewComment.substring(0, cfg.maxCommentLength - 100) + + "\n\n...(truncated)"; + } + + logger.success(`Generated agentic review with ${totalIssues} issues found`); + + return { + comment: reviewComment, + structuredComments: allSuggestions, + }; + } catch (error) { + logger.error("Error in agentic review:", error); + // Fallback to traditional review + return { + comment: "โŒ Agentic review failed, falling back to standard review.", + structuredComments: [], + }; + } +}; + export const processPullRequest = async ( octokit: Octokit, payload: WebhookEventMap["pull_request"], @@ -517,7 +656,9 @@ export const processPullRequest = async ( const filteredFiles = files.filter((file) => filterFile(file)); console.dir({ filteredFiles }, { depth: null }); if (filteredFiles.length == 0) { - console.log("Nothing to comment on, all files were filtered out. The PR Agent does not support the following file types: pdf, png, jpg, jpeg, gif, mp4, mp3, md, json, env, toml, svg, package-lock.json, yarn.lock, .gitignore, package.json, tsconfig.json, poetry.lock, readme.md"); + console.log( + "Nothing to comment on, all files were filtered out. The PR Agent does not support the following file types: pdf, png, jpg, jpeg, gif, mp4, mp3, md, json, env, toml, svg, package-lock.json, yarn.lock, .gitignore, package.json, tsconfig.json, poetry.lock, readme.md" + ); return { review: null, suggestions: [], @@ -530,23 +671,21 @@ export const processPullRequest = async ( ); const owner = payload.repository.owner.login; const repoName = payload.repository.name; - const curriedXMLResponseBuilder = curriedXmlResponseBuilder(owner, repoName); - if (includeSuggestions) { - const reviewComments = await reviewChangesRetry(filteredFiles, [ - { - convoBuilder: getXMLReviewPrompt, - responseBuilder: curriedXMLResponseBuilder, - }, - { - convoBuilder: getReviewPrompt, - responseBuilder: basicResponseBuilder, - }, - ]); + + // Use agentic review as the primary method + try { + console.log("๐Ÿš€ Attempting agentic review..."); + const agenticReview = await performAgenticReview( + filteredFiles, + owner, + repoName + ); + let inlineComments: CodeSuggestion[] = []; - if (reviewComments.structuredComments.length > 0) { - console.log("STARTING INLINE COMMENT PROCESSING"); + if (includeSuggestions && agenticReview.structuredComments.length > 0) { + console.log("STARTING AGENTIC INLINE COMMENT PROCESSING"); inlineComments = await Promise.all( - reviewComments.structuredComments.map((suggestion) => { + agenticReview.structuredComments.map((suggestion) => { // find relevant file const file = files.find( (file) => file.filename === suggestion.filename @@ -561,13 +700,24 @@ export const processPullRequest = async ( const filteredInlineComments = inlineComments.filter( (comment) => comment !== null ); + return { - review: reviewComments, + review: agenticReview, suggestions: filteredInlineComments, }; - } else { - const [review] = await Promise.all([ - reviewChangesRetry(filteredFiles, [ + } catch (error) { + console.error( + "โŒ Agentic review failed, falling back to traditional review:", + error + ); + + // Fallback to traditional review + const curriedXMLResponseBuilder = curriedXmlResponseBuilder( + owner, + repoName + ); + if (includeSuggestions) { + const reviewComments = await reviewChangesRetry(filteredFiles, [ { convoBuilder: getXMLReviewPrompt, responseBuilder: curriedXMLResponseBuilder, @@ -576,12 +726,48 @@ export const processPullRequest = async ( convoBuilder: getReviewPrompt, responseBuilder: basicResponseBuilder, }, - ]), - ]); - - return { - review, - suggestions: [], - }; + ]); + let inlineComments: CodeSuggestion[] = []; + if (reviewComments.structuredComments.length > 0) { + console.log("STARTING INLINE COMMENT PROCESSING"); + inlineComments = await Promise.all( + reviewComments.structuredComments.map((suggestion) => { + // find relevant file + const file = files.find( + (file) => file.filename === suggestion.filename + ); + if (file == null) { + return null; + } + return generateInlineComments(suggestion, file); + }) + ); + } + const filteredInlineComments = inlineComments.filter( + (comment) => comment !== null + ); + return { + review: reviewComments, + suggestions: filteredInlineComments, + }; + } else { + const [review] = await Promise.all([ + reviewChangesRetry(filteredFiles, [ + { + convoBuilder: getXMLReviewPrompt, + responseBuilder: curriedXMLResponseBuilder, + }, + { + convoBuilder: getReviewPrompt, + responseBuilder: basicResponseBuilder, + }, + ]), + ]); + + return { + review, + suggestions: [], + }; + } } }; diff --git a/src/utils/config.ts b/src/utils/config.ts new file mode 100644 index 0000000..6f460b9 --- /dev/null +++ b/src/utils/config.ts @@ -0,0 +1,132 @@ +import { env } from "../env"; + +export interface AgentConfig { + // Review settings + enableAgenticReview: boolean; + enableSecurityAgent: boolean; + enablePerformanceAgent: boolean; + enableCodeQualityAgent: boolean; + enableDocumentationAgent: boolean; + enableTestingAgent: boolean; + + // Processing settings + maxFilesPerReview: number; + maxTokensPerRequest: number; + retryAttempts: number; + + // Model settings + primaryModel: string; + fallbackModel?: string; + temperature: number; + + // GitHub settings + maxCommentLength: number; + createIssueLinks: boolean; +} + +const DEFAULT_CONFIG: AgentConfig = { + // Review settings + enableAgenticReview: true, + enableSecurityAgent: true, + enablePerformanceAgent: true, + enableCodeQualityAgent: true, + enableDocumentationAgent: true, + enableTestingAgent: true, + + // Processing settings + maxFilesPerReview: 20, + maxTokensPerRequest: 30000, // Conservative limit for Groq + retryAttempts: 3, + + // Model settings + primaryModel: "llama-3.3-70b-versatile", + temperature: 0.1, + + // GitHub settings + maxCommentLength: 8000, // GitHub has a limit around 8192 characters + createIssueLinks: true, +}; + +class ConfigManager { + private config: AgentConfig; + + constructor() { + this.config = { ...DEFAULT_CONFIG }; + this.loadFromEnvironment(); + } + + private loadFromEnvironment() { + // Load configuration from environment variables + if (process.env.ENABLE_AGENTIC_REVIEW !== undefined) { + this.config.enableAgenticReview = + process.env.ENABLE_AGENTIC_REVIEW === "true"; + } + + if (process.env.MAX_FILES_PER_REVIEW) { + this.config.maxFilesPerReview = parseInt( + process.env.MAX_FILES_PER_REVIEW, + 10 + ); + } + + if (process.env.MAX_TOKENS_PER_REQUEST) { + this.config.maxTokensPerRequest = parseInt( + process.env.MAX_TOKENS_PER_REQUEST, + 10 + ); + } + + if (process.env.RETRY_ATTEMPTS) { + this.config.retryAttempts = parseInt(process.env.RETRY_ATTEMPTS, 10); + } + + if (process.env.PRIMARY_MODEL) { + this.config.primaryModel = process.env.PRIMARY_MODEL; + } + + if (process.env.TEMPERATURE) { + this.config.temperature = parseFloat(process.env.TEMPERATURE); + } + + // Agent-specific toggles + if (process.env.ENABLE_SECURITY_AGENT !== undefined) { + this.config.enableSecurityAgent = + process.env.ENABLE_SECURITY_AGENT === "true"; + } + + if (process.env.ENABLE_PERFORMANCE_AGENT !== undefined) { + this.config.enablePerformanceAgent = + process.env.ENABLE_PERFORMANCE_AGENT === "true"; + } + + if (process.env.ENABLE_CODE_QUALITY_AGENT !== undefined) { + this.config.enableCodeQualityAgent = + process.env.ENABLE_CODE_QUALITY_AGENT === "true"; + } + + if (process.env.ENABLE_DOCUMENTATION_AGENT !== undefined) { + this.config.enableDocumentationAgent = + process.env.ENABLE_DOCUMENTATION_AGENT === "true"; + } + + if (process.env.ENABLE_TESTING_AGENT !== undefined) { + this.config.enableTestingAgent = + process.env.ENABLE_TESTING_AGENT === "true"; + } + } + + get(): AgentConfig { + return { ...this.config }; + } + + update(updates: Partial) { + this.config = { ...this.config, ...updates }; + } + + reset() { + this.config = { ...DEFAULT_CONFIG }; + this.loadFromEnvironment(); + } +} + +export const config = new ConfigManager(); diff --git a/src/utils/logger.ts b/src/utils/logger.ts new file mode 100644 index 0000000..9b8a5a5 --- /dev/null +++ b/src/utils/logger.ts @@ -0,0 +1,86 @@ +import chalk from "chalk"; + +export enum LogLevel { + DEBUG = 0, + INFO = 1, + WARN = 2, + ERROR = 3, +} + +class Logger { + private level: LogLevel = LogLevel.INFO; + + setLevel(level: LogLevel) { + this.level = level; + } + + private shouldLog(level: LogLevel): boolean { + return level >= this.level; + } + + debug(message: string, ...args: any[]) { + if (this.shouldLog(LogLevel.DEBUG)) { + console.log( + chalk.gray(`[DEBUG] ${new Date().toISOString()}: ${message}`), + ...args + ); + } + } + + info(message: string, ...args: any[]) { + if (this.shouldLog(LogLevel.INFO)) { + console.log( + chalk.blue(`[INFO] ${new Date().toISOString()}: ${message}`), + ...args + ); + } + } + + warn(message: string, ...args: any[]) { + if (this.shouldLog(LogLevel.WARN)) { + console.warn( + chalk.yellow(`[WARN] ${new Date().toISOString()}: ${message}`), + ...args + ); + } + } + + error(message: string, error?: any, ...args: any[]) { + if (this.shouldLog(LogLevel.ERROR)) { + console.error( + chalk.red(`[ERROR] ${new Date().toISOString()}: ${message}`), + error, + ...args + ); + } + } + + success(message: string, ...args: any[]) { + if (this.shouldLog(LogLevel.INFO)) { + console.log( + chalk.green(`[SUCCESS] ${new Date().toISOString()}: ${message}`), + ...args + ); + } + } + + agent(message: string, ...args: any[]) { + if (this.shouldLog(LogLevel.INFO)) { + console.log( + chalk.magenta(`[AGENT] ${new Date().toISOString()}: ${message}`), + ...args + ); + } + } +} + +export const logger = new Logger(); + +// Set log level from environment variable +if (process.env.LOG_LEVEL) { + const level = + LogLevel[process.env.LOG_LEVEL.toUpperCase() as keyof typeof LogLevel]; + if (level !== undefined) { + logger.setLevel(level); + } +} diff --git a/test/ai-review-page.ts b/test/ai-review-page.ts new file mode 100644 index 0000000..e69de29 diff --git a/test/api-client.ts b/test/api-client.ts new file mode 100644 index 0000000..7130f57 --- /dev/null +++ b/test/api-client.ts @@ -0,0 +1,76 @@ +/** + * API utilities with various issues for testing + */ + +interface User { + id: number; + name: string; + email: string; +} + +export class APIClient { + private baseUrl: string; + private apiKey: string; + + constructor(baseUrl: string, apiKey: string) { + this.baseUrl = baseUrl; + this.apiKey = apiKey; // Should be marked as readonly + } + + // Missing error handling and return type annotation + async getUser(id) { + const response = await fetch(`${this.baseUrl}/users/${id}`, { + headers: { + Authorization: `Bearer ${this.apiKey}`, + "Content-Type": "application/json", + }, + }); + return response.json(); // No error checking + } + + // Performance issue: not using proper HTTP methods + async updateUser(user: User): Promise { + // Should use PUT/PATCH, not POST + const response = await fetch(`${this.baseUrl}/users/${user.id}`, { + method: "POST", + headers: { + Authorization: `Bearer ${this.apiKey}`, + "Content-Type": "application/json", + }, + body: JSON.stringify(user), + }); + + if (!response.ok) { + throw new Error("Failed to update user"); // Generic error message + } + + return response.json(); + } + + // Missing JSDoc documentation + async deleteUser(id: number): Promise { + await fetch(`${this.baseUrl}/users/${id}`, { + method: "DELETE", + headers: { + Authorization: `Bearer ${this.apiKey}`, + }, + }); + // No error handling at all + } + + // Security issue: logging sensitive data + async createUser(userData: Partial): Promise { + console.log("Creating user with data:", userData); // Could log sensitive info + + const response = await fetch(`${this.baseUrl}/users`, { + method: "POST", + headers: { + Authorization: `Bearer ${this.apiKey}`, + "Content-Type": "application/json", + }, + body: JSON.stringify(userData), + }); + + return response.json(); + } +} diff --git a/test/data-service.ts b/test/data-service.ts new file mode 100644 index 0000000..3479420 --- /dev/null +++ b/test/data-service.ts @@ -0,0 +1,119 @@ +// Additional TypeScript file with more issues for comprehensive testing +export interface ApiResponse { + data: T; + success: boolean; + message?: string; +} + +export class DataService { + private apiKey: string; + private baseUrl: string; + + constructor(apiKey: string, baseUrl: string) { + this.apiKey = apiKey; // Security: API key should be marked as readonly + this.baseUrl = baseUrl; + } + + // Missing proper error handling and return type issues + async fetchUser(id: number) { + // Should specify return type + const response = await fetch(`${this.baseUrl}/users/${id}`, { + headers: { + Authorization: `Bearer ${this.apiKey}`, // Security: exposing API key in logs + "Content-Type": "application/json", + }, + }); + + return response.json(); // No error checking or type validation + } + + // Type safety issues with generics + async updateResource(endpoint: string, data: any): Promise { + // Using 'any' defeats purpose of generics + const response = await fetch(`${this.baseUrl}${endpoint}`, { + method: "PUT", + headers: { + Authorization: `Bearer ${this.apiKey}`, + "Content-Type": "application/json", + }, + body: JSON.stringify(data), + }); + + if (!response.ok) { + throw new Error("Request failed"); // Generic error message + } + + return response.json() as T; // Type assertion without validation + } + + // Performance issue: no caching, repeated API calls + async getUserPermissions(userId: number): Promise { + const user = await this.fetchUser(userId); // Could cache this + const permissions = await fetch( + `${this.baseUrl}/users/${userId}/permissions` + ); + const roles = await fetch(`${this.baseUrl}/users/${userId}/roles`); // Multiple sequential calls + + // No proper error handling for failed requests + return [...(await permissions.json()), ...(await roles.json())]; + } +} + +// Enum issues +enum UserRole { + ADMIN, // No explicit values - fragile to reordering + MODERATOR, + USER, + GUEST, +} + +// Interface with optional properties but no validation +export interface UserSettings { + theme?: "dark" | "light"; + notifications?: boolean; + language?: string; // Should be union type of supported languages + customFields?: { [key: string]: any }; // Using any and index signature +} + +// Class with inheritance issues +export abstract class BaseEntity { + protected id: number; // Should be readonly + protected createdAt: Date; + + constructor(id: number) { + this.id = id; + this.createdAt = new Date(); + } + + // Missing implementation details + abstract validate(): boolean; + + // Public method accessing protected field + getId(): number { + return this.id; // Direct access without validation + } +} + +export class AdminUser extends BaseEntity { + private permissions: UserRole[]; + + constructor(id: number, permissions: UserRole[]) { + super(id); + this.permissions = permissions; // No validation of permissions + } + + validate(): boolean { + return this.permissions.length > 0; // Weak validation + } + + // Security issue: privilege escalation potential + addPermission(role: UserRole): void { + this.permissions.push(role); // No access control check + } + + // Type coercion issue + hasPermission(role: string): boolean { + // Should accept UserRole, not string + return this.permissions.includes(role as unknown as UserRole); // Unsafe type assertion + } +} diff --git a/test/page-layout.ts b/test/page-layout.ts new file mode 100644 index 0000000..e69de29 diff --git a/test/password-layout.ts b/test/password-layout.ts new file mode 100644 index 0000000..e69de29 diff --git a/test/password-manager.py b/test/password-manager.py new file mode 100644 index 0000000..cc71395 --- /dev/null +++ b/test/password-manager.py @@ -0,0 +1,45 @@ +# Password utilities with security issues +import hashlib +import random +import string + +class PasswordManager: + def __init__(self): + self.passwords = {} + + def generate_password(self, length=8): + # Weak password generation + chars = string.ascii_letters + string.digits + password = ''.join(random.choice(chars) for _ in range(length)) + return password + + def hash_password(self, password): + # Using MD5 - insecure hashing algorithm! + return hashlib.md5(password.encode()).hexdigest() + + def store_password(self, username, password): + # No input validation + hashed = self.hash_password(password) + self.passwords[username] = hashed + + def verify_password(self, username, password): + if username in self.passwords: + return self.passwords[username] == self.hash_password(password) + return False + + def get_all_passwords(self): + # Security issue: exposing all password hashes + return self.passwords + + def backup_passwords(self, filename): + # No error handling, could fail silently + with open(filename, 'w') as f: + for username, password_hash in self.passwords.items(): + f.write(f"{username}:{password_hash}\n") + + def load_passwords(self, filename): + # No validation of file content + with open(filename, 'r') as f: + for line in f: + username, password_hash = line.strip().split(':') + self.passwords[username] = password_hash \ No newline at end of file diff --git a/test/user-manager.ts b/test/user-manager.ts new file mode 100644 index 0000000..dddf16b --- /dev/null +++ b/test/user-manager.ts @@ -0,0 +1,152 @@ +// Test file to demonstrate various code quality issues in TypeScript +// This will trigger multiple AI agents for TS-specific analysis + +interface User { + id: number; + firstName: string; + lastName: string; + email: string; + password: string; // Security issue: plain text password! + address: string; + phoneNumber: string; + dateOfBirth: Date; + preferences: any; // Should be more specific type +} + +interface DatabaseConnection { + execute(query: string): Promise; // Should have proper return types +} + +interface UserPreferences { + theme?: string; + notifications?: boolean; + language?: string; +} + +class TestUserManager { + private users: User[]; // Good: private field + private db_connection: DatabaseConnection | null; // Bad naming convention + + constructor() { + this.users = []; + this.db_connection = null; + } + + // Security issue: SQL injection vulnerability + async getUserById(id: number): Promise { + const query = `SELECT * FROM users WHERE id = ${id}`; // Still vulnerable to SQL injection + if (!this.db_connection) { + throw new Error("Database connection not initialized"); // At least some error handling + } + const result = await this.db_connection.execute(query); + return result; // Should validate and type the result properly + } + + // Performance issue: inefficient search - O(n) complexity + findUserByEmail(email: string): User | null { + for (let i = 0; i < this.users.length; i++) { + if (this.users[i].email === email) { + return this.users[i]; + } + } + return null; // Could use Map for O(1) lookup + } + + // Code quality issues: long parameter list, poor validation + createUser( + firstName: string, + lastName: string, + email: string, + password: string, + address: string, + phoneNumber: string, + dateOfBirth: Date, + preferences: UserPreferences + ): User { + // No input validation! + const user: User = { + id: Math.random(), // Poor ID generation - not unique, not secure + firstName, + lastName, + email, + password, // Security issue: storing plain text password + address, + phoneNumber, + dateOfBirth, + preferences: preferences as any, // Type assertion without validation + }; + this.users.push(user); + return user; + } + + // Missing documentation, minimal error handling + deleteUser(id: number): boolean { + const initialLength = this.users.length; + this.users = this.users.filter((user) => user.id !== id); + return this.users.length < initialLength; // At least return success status + } + + // Performance issue: sequential API calls instead of batching + async notifyAllUsers(message: string): Promise { + for (const user of this.users) { + try { + await fetch(`/api/notify/${user.id}`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ message }), + }); // Should batch these requests for better performance + } catch (error) { + console.error(`Failed to notify user ${user.id}:`, error); // Logging sensitive user ID + } + } + } + + // Security and type safety issues + updateUserEmail(userId: number, newEmail: string): void { + const user = this.users.find((u) => u.id == userId); // Type coercion issue: == instead of === + if (user) { + // At least added null check + user.email = newEmail; // No email format validation + } + // Should throw error if user not found + } + + // Missing proper error handling and return types + async batchUpdateUsers(updates: Partial[]): Promise { + const results: User[] = []; + + for (const update of updates) { + if (!update.id) continue; // Minimal validation + + const userIndex = this.users.findIndex((u) => u.id === update.id); + if (userIndex !== -1) { + // Object spread without validation - could overwrite critical fields + this.users[userIndex] = { ...this.users[userIndex], ...update }; + results.push(this.users[userIndex]); + } + } + + return results; // No database persistence + } + + // Security issue: exposing internal data without proper access control + getAllUsers(): User[] { + return this.users; // Returns reference to internal array - should return copy + } + + // Type safety issue: using 'any' return type + getUserStats(): any { + return { + totalUsers: this.users.length, + activeUsers: this.users.filter((u) => u.preferences).length, // Weak activity check + averageAge: + this.users.reduce((sum, user) => { + const age = + new Date().getFullYear() - new Date(user.dateOfBirth).getFullYear(); + return sum + age; + }, 0) / this.users.length || 0, + }; + } +}