8 Commits

Author SHA1 Message Date
1396f4b305 Updated readme
Some checks failed
Code review / review (pull_request) Failing after 10s
Build and publish / Build Container (push) Successful in 1m2s
2025-12-27 19:58:21 -05:00
5d3e15f0f5 Added review
All checks were successful
Build and publish / Build Container (push) Successful in 1m1s
2025-12-27 19:53:48 -05:00
c27802ca44 Working AI
All checks were successful
Build and publish / Build Container (push) Successful in 1m1s
2025-12-27 17:54:03 -05:00
2b11979b66 Update 2025-12-27 15:21:57 -05:00
45d43c0977 Updated logo
All checks were successful
Build and publish / Build Container (push) Successful in 1m1s
2025-12-27 14:18:34 -05:00
1b4fd05d65 Fixed build
All checks were successful
Build and publish / Build Container (push) Successful in 1m7s
2025-12-27 14:17:05 -05:00
d6857bd8e3 Fixed build
Some checks failed
Build and publish / Build Container (push) Failing after 3s
2025-12-27 14:14:01 -05:00
6cc3d5eb00 Finished?
Some checks failed
Build and publish / Build Container (push) Failing after 3s
2025-12-27 14:11:40 -05:00
9 changed files with 2001 additions and 128 deletions

11
.env Normal file
View File

@@ -0,0 +1,11 @@
; DO NOT MODIFY! this is an example environment file
; Create a copy called .env.local with the needed settings
GIT_HOST=
GIT_OWNER=
GIT_REPO=
GIT_TOKEN=
PULL_REQUEST=
AI_HOST=
AI_MODEL=
AI_TOKEN=

44
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,44 @@
name: Build and publish
run-name: Build and publish
on:
push:
jobs:
container:
name: Build Container
runs-on: ubuntu-latest
container: docker
steps:
- name: Build Container
run: |
git clone -b "${{github.ref_name}}" "$(echo ${{github.server_url}}/${{github.repository}}.git | sed s%://%://${{github.token}}@% )" .
DOCKER_HUB=$([ -n "${{secrets.DOCKER_HUB_USER}}" ] && [ -n "${{secrets.DOCKER_HUB_TOKEN}}" ] && [ -n "${{secrets.DOCKER_HUB_IMAGE}}" ] && echo "true" || echo "false")
REGISTRY="$(echo "${{github.server_url}}" | sed -E 's|https?://||')"
VERSION="$(cat package.json | grep version | grep -Eo '[0-9][[:alnum:]\.\/\-]+')"
docker login -u "${{github.repository_owner}}" -p "${{secrets.DEPLOY_TOKEN}}" "$REGISTRY"
if [ "$DOCKER_HUB" = "true" ]; then docker login -u "${{secrets.DOCKER_HUB_USER}}" -p "${{secrets.DOCKER_HUB_TOKEN}}" docker.io; fi
docker build -t "$REGISTRY/${{github.repository}}:${{github.ref_name}}" .
docker push "$REGISTRY/${{github.repository}}:${{github.ref_name}}"
if [ "$DOCKER_HUB" = "true" ]; then
docker tag "$REGISTRY/${{github.repository}}:${{github.ref_name}}" "docker.io/${{secrets.DOCKER_HUB_IMAGE}}:${{github.ref_name}}"
docker push "docker.io/${{secrets.DOCKER_HUB_IMAGE}}:${{github.ref_name}}"
fi
if [ "${{github.ref_name}}" = "master" ]; then
docker tag "$REGISTRY/${{github.repository}}:${{github.ref_name}}" "$REGISTRY/${{github.repository}}:$VERSION"
docker push "$REGISTRY/${{github.repository}}:$VERSION"
if [ "$DOCKER_HUB" = "true" ]; then
docker tag "$REGISTRY/${{github.repository}}:${{github.ref_name}}" "docker.io/${{secrets.DOCKER_HUB_IMAGE}}:$VERSION"
docker push "docker.io/${{secrets.DOCKER_HUB_IMAGE}}:$VERSION"
fi
docker tag "$REGISTRY/${{github.repository}}:${{github.ref_name}}" "$REGISTRY/${{github.repository}}:latest"
docker push "$REGISTRY/${{github.repository}}:latest"
if [ "$DOCKER_HUB" = "true" ]; then
docker tag "$REGISTRY/${{github.repository}}:${{github.ref_name}}" "docker.io/${{secrets.DOCKER_HUB_IMAGE}}:latest"
docker push "docker.io/${{secrets.DOCKER_HUB_IMAGE}}:latest"
fi
fi

32
.github/workflows/review.yml vendored Normal file
View File

@@ -0,0 +1,32 @@
name: Code review
on:
pull_request:
types: [opened, synchronize, reopened]
jobs:
review:
runs-on: ubuntu-latest
steps:
- name: Checkout PR
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha }}
- name: Fetch base branch
run: |
git fetch origin ${{ github.event.pull_request.base.ref }}
git branch -r
- name: Run AI Review
uses: docker://git.zakscode.com/zakscode/ai-reporter:latest
env:
AI_HOST: anthropic
AI_MODEL: claude-sonnet-4-5
AI_TOKEN: ${{ secrets.ANTHROPIC_TOKEN }}
GIT_HOST: ${{ github.server_url }}
GIT_OWNER: ${{ github.repository_owner }}
GIT_REPO: ${{ github.event.repository.name }}
GIT_TOKEN: ${{ secrets.ASSISTANT_TOKEN }}
GIT_BRANCH: origin/${{ github.event.pull_request.base.ref }}
PULL_REQUEST: ${{ github.event.pull_request.number }}

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
.env.local

7
Dockerfile Normal file
View File

@@ -0,0 +1,7 @@
FROM node:22
COPY . /ai
RUN cd /ai && npm ci && mkdir -p /github/workspace
WORKDIR /github/workspace
ENTRYPOINT ["npx", "--yes", "/ai", "review", "/github/workspace"]

138
README.md
View File

@@ -1,96 +1,48 @@
<!-- Header -->
<div id="top" align="center">
<br />
<!-- Logo -->
<img src="https://git.zakscode.com/repo-avatars/2b4ee6ba1f2e2618bf7694e4a52fb56d1d0ea6abafa2dcbe496ab786b86d5a76" alt="Logo" width="200" height="200">
<img src="https://git.zakscode.com/repo-avatars/d2c56ffd0220751c2f4a9f6fc1e8125a9a63aeed452ae4f4ab696c084330faa2" alt="Logo" width="200" height="200">
<!-- Title -->
### Template
### AI Reviewer
<!-- Description -->
Simple repository template
Automated AI-powered code review for pull requests 🤖
<!-- Repo badges -->
[![Version](https://img.shields.io/badge/dynamic/json.svg?label=Version&style=for-the-badge&url=https://git.zakscode.com/api/v1/repos/ztimson/template/tags&query=$[0].name)](https://git.zakscode.com/ztimson/template/tags)
[![Pull Requests](https://img.shields.io/badge/dynamic/json.svg?label=Pull%20Requests&style=for-the-badge&url=https://git.zakscode.com/api/v1/repos/ztimson/template&query=open_pr_counter)](https://git.zakscode.com/ztimson/template/pulls)
[![Issues](https://img.shields.io/badge/dynamic/json.svg?label=Issues&style=for-the-badge&url=https://git.zakscode.com/api/v1/repos/ztimson/template&query=open_issues_count)](https://git.zakscode.com/ztimson/template/issues)
[![Version](https://img.shields.io/badge/dynamic/json.svg?label=Version&style=for-the-badge&url=https://git.zakscode.com/api/v1/repos/ztimson/ai-reviewer/tags&query=$[0].name)](https://git.zakscode.com/ztimson/ai-reviewer/tags)
[![Pull Requests](https://img.shields.io/badge/dynamic/json.svg?label=Pull%20Requests&style=for-the-badge&url=https://git.zakscode.com/api/v1/repos/ztimson/ai-reviewer&query=open_pr_counter)](https://git.zakscode.com/ztimson/ai-reviewer/pulls)
[![Issues](https://img.shields.io/badge/dynamic/json.svg?label=Issues&style=for-the-badge&url=https://git.zakscode.com/api/v1/repos/ztimson/ai-reviewer&query=open_issues_count)](https://git.zakscode.com/ztimson/ai-reviewer/issues)
<!-- Links -->
---
<!-- Links -->
---
<div>
<a href="https://git.zakscode.com/ztimson/template/wiki" target="_blank">Documentation</a>
• <a href="https://git.zakscode.com/ztimson/template/releases" target="_blank">Release Notes</a>
• <a href="https://git.zakscode.com/ztimson/template/issues/new?template=.github%2fissue_template%2fbug.md" target="_blank">Report a Bug</a>
• <a href="https://git.zakscode.com/ztimson/template/issues/new?template=.github%2fissue_template%2fenhancement.md" target="_blank">Request a Feature</a>
<a href="https://git.zakscode.com/ztimson/ai-reviewer/releases" target="_blank">Release Notes</a>
• <a href="https://git.zakscode.com/ztimson/ai-reviewer/issues/new?template=.github%2fissue_template%2fbug.md" target="_blank">Report a Bug</a>
• <a href="https://git.zakscode.com/ztimson/ai-reviewer/issues/new?template=.github%2fissue_template%2fenhancement.md" target="_blank">Request a Feature</a>
</div>
---
</div>
## Table of Contents
- [Template](#top)
- [About](#about)
- [Demo](#demo)
- [Built With](#built-with)
- [Setup](#setup)
- [Production](#production)
- [Development](#development)
- [License](#license)
- [AI Reviewer](#top)
- [About](#about)
- [Built With](#built-with)
- [Setup](#setup)
- [Production](#production)
- [License](#license)
## About
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
### Demo
Website: https://git.zakscode.com
Automated code reviewer that uses AI to analyze git diffs and provide inline comments on pull requests. Supports Anthropic, OpenAI, and Ollama models with tool-based reviewing for precise feedback.
### Built With
[![Angular](https://img.shields.io/badge/Angular-DD0031?style=for-the-badge&logo=angular)](https://angular.io/)
[![Android](https://img.shields.io/badge/android-34A853?style=for-the-badge&logo=android&logoColor=ffffff)](https://www.android.com/)
[![Arduino](https://img.shields.io/badge/Arduino-00878F?style=for-the-badge&logo=arduino&logoColor=white)](https://www.arduino.cc/)
[![Bootstrap](https://img.shields.io/badge/Bootstrap-563D7C?style=for-the-badge&logo=bootstrap&logoColor=white)](https://getbootstrap.com)
[![C](https://img.shields.io/badge/C-A8B9CC?style=for-the-badge&logo=c&logoColor=ffffff)](https://en.cppreference.com/w/c/language)
[![C++](https://img.shields.io/badge/C%2B%2B-00599C?style=for-the-badge&logo=cplusplus)](https://cplusplus.com/)
[![C#](https://img.shields.io/badge/C%23-239120?style=for-the-badge&logo=csharp)](https://dotnet.microsoft.com/)
[![CSS](https://img.shields.io/badge/CSS-1572B6?style=for-the-badge&logo=css3)](https://www.w3.org/Style/CSS/Overview.en.html)
[![Django](https://img.shields.io/badge/django-0C4B33?style=for-the-badge&logo=django)](https://www.djangoproject.com/)
[![Docker](https://img.shields.io/badge/Docker-384d54?style=for-the-badge&logo=docker)](https://docker.com/)
[![Electron](https://img.shields.io/badge/Electron-47848F?style=for-the-badge&logo=electron&logoColor=white)](https://www.electronjs.org/)
[![Firebase](https://img.shields.io/badge/Firebase-FFFFFF?style=for-the-badge&logo=firebase)](https://firebase.google.com/)
[![Go](https://img.shields.io/badge/Go-00ADD8?style=for-the-badge&logo=go&logoColor=ffffff)](https://go.dev/)
[![GraphQL](https://img.shields.io/badge/GraphQL-E10098?style=for-the-badge&logo=graphql)](https://graphql.org/)
[![HTML](https://img.shields.io/badge/HTML-FFFFFF?style=for-the-badge&logo=html5)](https://developer.mozilla.org/en-US/docs/Glossary/HTML)
[![Java](https://img.shields.io/badge/Java-5382A1?style=for-the-badge&logo=coffeescript&logoColor=F8981D)](https://java.com/)
[![JavaScript](https://img.shields.io/badge/JavaScript-000000?style=for-the-badge&logo=javascript)](https://javascript.com/)
[![JQuery](https://img.shields.io/badge/jQuery-0769AD?style=for-the-badge&logo=jquery)](https://jquery.com )
[![Laravel](https://img.shields.io/badge/Laravel-6C6C6C?style=for-the-badge&logo=laravel)](https://laravel.com)
[![Linux](https://img.shields.io/badge/Linux-eeeeee?style=for-the-badge&logo=linux&logoColor=000000)](https://www.linux.org/)
[![Momentum](https://img.shields.io/badge/Momentum-000000?style=for-the-badge&logo=data:image/svg%2bxml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pgo8IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDIwMDEwOTA0Ly9FTiIgImh0dHA6Ly93d3cudzMub3JnL1RSLzIwMDEvUkVDLVNWRy0yMDAxMDkwNC9EVEQvc3ZnMTAuZHRkIj4KPHN2ZyB2ZXJzaW9uPSIxLjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjI1MHB0IiBoZWlnaHQ9IjI1MHB0IiB2aWV3Qm94PSIwIDAgMjUwIDI1MCIgIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaWRZTWlkIG1lZXQiPgoJPHN0eWxlPgoJCUBtZWRpYSAocHJlZmVycy1jb2xvci1zY2hlbWU6IGxpZ2h0KSB7CgkJZyB7IGZpbGw6ICMwMDAwMDA7IH0KCQl9CgkJQG1lZGlhIChwcmVmZXJzLWNvbG9yLXNjaGVtZTogZGFyaykgewoJCWcgeyBmaWxsOiAjZmZmZmZmOyB9CgkJfQoJPC9zdHlsZT4KCTxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsMjUwKSBzY2FsZSgwLjEsLTAuMSkiPgoJCTxwYXRoIGQ9Ik0xMTY1IDIyOTkgYy0yNDUgLTE4IC00NzAgLTEyMyAtNjUxIC0zMDMgLTExNyAtMTE4IC0yMDAgLTI1MCAtMjUyIC00MDEgLTcxIC0yMDkgLTcxIC00NjggLTEgLTY3OSAzNyAtMTA4IDExOCAtMjU0IDE3OCAtMzE5IGwyNSAtMjggLTEzMiAtMTMyIC0xMzIgLTEzMiA1MiAtNTMgNTMgLTUyIDEzMiAxMzIgMTMyIDEzMiAyOCAtMjUgYzQxIC0zOCAxMDkgLTgyIDE4OCAtMTIxIDE1MyAtNzcgMjkyIC0xMDkgNDcwIC0xMDkgMjkxIDAgNTM2IDEwMSA3NDEgMzA1IDE1MyAxNTQgMjQ0IDMyNCAyODkgNTQwIDIxIDEwMyAyMSAyOTkgLTEgNDAyIC0zMiAxNTkgLTEwNSAzMjQgLTE4NyA0MjYgbC00OSA2MSAxMjIgMTIyIDEyMyAxMjMgLTIyIDMxIGMtMTIgMTYgLTM2IDQwIC01MiA1MiBsLTMxIDIyIC0xMjEgLTEyMSAtMTIyIC0xMjEgLTYwIDQ2IGMtODEgNjEgLTI2MSAxNDcgLTM2NSAxNzMgLTEwNCAyNyAtMjM1IDM3IC0zNTUgMjl6IG0yNzQgLTE2NCBjMTAyIC0yMSAyMTcgLTY4IDI5NyAtMTIxIDEwOSAtNzIgMTA2IC02NiA0OSAtMTI0IGwtNTAgLTUwIC01MCA5IGMtODMgMTYgLTE2OSAtNSAtMzA1IC03NCBsLTc1IC0zOCA0NSAtMzggYzI1IC0yMiA1MyAtNDIgNjMgLTQ1IDExIC0zIDQ1IDYgNzcgMjAgNjggMzEgMTM1IDQ1IDE3MCAzNiAyMiAtNSAyNSAtMTIgMjggLTU2IDEyIC0yMDQgLTM2MCAtNjQ1IC02NzQgLTgwMCAtMTA3IC01MyAtMTgwIC02NiAtMjA4IC0zOCAtMjcgMjcgLTE4IDExMyAyMiAxOTggMTggMzcgMjkgNzMgMjUgODAgLTE0IDI0IC03NiA5NiAtODIgOTYgLTEyIDAgLTc4IC0xMzIgLTEwMSAtMTk5IC0xNiAtNDkgLTIxIC04NCAtMTkgLTE0OCBsMyAtODQgLTQxIC00MSBjLTUwIC01MCAtNTIgLTQ5IC0xMTkgNTggLTU1IDg4IC05OCAxOTYgLTExOSAyOTQgLTE5IDkxIC0xOSAyNzkgMCAzNjkgNzIgMzQzIDM0NyA2MjEgNjg4IDY5NSA4NSAxOSAyODkgMTkgMzc2IDF6IG01NjkgLTM5MyBjNjMgLTk1IDEwNSAtMTk1IDEyOCAtMzA1IDE4IC04OSAxOCAtMjc3IC0xIC0zNjcgLTczIC0zNDYgLTM0OSAtNjIyIC02OTUgLTY5NSAtOTEgLTE5IC0yNzggLTE5IC0zNjggMCAtMTMwIDI3IC0yNzggOTUgLTM2NyAxNjkgbC0zMCAyNSA0NSA0NSBjNDQgNDUgNDUgNDYgMTEwIDQ2IDE3MyAwIDM5MiAxMjQgNjI1IDM1NSAxMzAgMTI5IDIwNCAyMjIgMjc4IDM1MCA2MyAxMDkgODkgMTg1IDk0IDI4MCBsNSA4MCA1MSA1MyBjMjggMjggNTUgNTIgNTkgNTIgNSAwIDM1IC00MCA2NiAtODh6Ii8+CgkJPHBhdGggZD0iTTc1MiAxODM5IGMtMTk1IC05NyAtOTMgLTQzMyAyMzggLTc5MSA3MSAtNzcgNzIgLTc4IDEwMCAtNjMgMTUgOCA0MiAyNiA2MCA0MSBsMzEgMjcgLTY5IDcxIGMtMTc2IDE4MSAtMjk3IDM3NCAtMzE3IDUwOCAtMTQgOTIgMzIgMTEwIDE1OCA2NSAxMjAgLTQ0IDI2OCAtMTQ3IDQwOSAtMjg3IGw4OCAtODcgMTkgMjEgYzEwIDEyIDI4IDM2IDM5IDU1IGwyMSAzNCAtMjYgMzEgYy00NyA1NyAtMTk3IDE4NiAtMjg1IDI0NyAtMTk2IDEzNCAtMzYxIDE4MCAtNDY2IDEyOHoiLz4KCQk8cGF0aCBkPSJNMTAxMiAxNDk3IGMtMjkgLTI5IC01MiAtNTggLTUyIC02NiAwIC0xMyA1NCAtOTYgNjggLTEwNSA1IC0zIDQ0IDMwIDg2IDczIGw3OCA3OSAtNTMgMzYgYy0zMCAyMCAtNTggMzYgLTY0IDM2IC02IDAgLTM0IC0yNCAtNjMgLTUzeiIvPgoJCTxwYXRoIGQ9Ik0xNjcyIDExMzkgYy00NiAtNTYgLTQ5IC02OSAtMjggLTEwMiAyMyAtMzUgNTYgLTE0MyA1NiAtMTgxIDAgLTc4IC01OSAtODMgLTIwNiAtMjAgbC04MyAzNiAtNTEgLTQ0IGMtMjcgLTI0IC01MCAtNDYgLTUwIC01MCAwIC0xNCAxODYgLTk5IDI1MiAtMTE0IDIwMCAtNDkgMzE3IDc4IDI2MyAyODYgLTE2IDYzIC03MCAxODYgLTk3IDIyMyAtMTMgMTcgLTE3IDE0IC01NiAtMzR6Ii8+Cgk8L2c+Cjwvc3ZnPgo=)](https://git.zakscode.com/ztimson/momentum)
[![MongoDB](https://img.shields.io/badge/mongodb-000000?style=for-the-badge&logo=mongodb)](https://www.mongodb.com/)
[![MySQL](https://img.shields.io/badge/MySQL-4479A1?style=for-the-badge&logo=mysql&logoColor=ffffff)](https://www.mysql.com/)
[![Nest](https://img.shields.io/badge/nestjs-E0234E?style=for-the-badge&logo=nestjs)](https://nestjs.com/)
[![.NET](https://img.shields.io/badge/.NET-512BD4?style=for-the-badge&logo=dotnet)](https://dotnet.microsoft.com/)
[![Next](https://img.shields.io/badge/next.js-000000?style=for-the-badge&logo=nextdotjs)](https://nextjs.org/)
[![NGINX](https://img.shields.io/badge/NGINX-009639?style=for-the-badge&logo=nginx)](https://www.nginx.com/)
[![Node](https://img.shields.io/badge/Node.js-000000?style=for-the-badge&logo=nodedotjs)](https://nodejs.org/)
[![p5.js](https://img.shields.io/badge/p5.js-ed225d?style=for-the-badge&logo=p5dotjs&logoColor=white)](https://p5js.org/)
[![PHP](https://img.shields.io/badge/PHP-474A8A?style=for-the-badge&logo=php&logoColor=white)](https://www.php.net/)
[![PostgreSQL](https://img.shields.io/badge/PostgreSQl-212121?style=for-the-badge&logo=postgresql)](https://www.postgresql.org/)
[![Python](https://img.shields.io/badge/Python-FFD43B?style=for-the-badge&logo=python)](https://www.python.org/)
[![React](https://img.shields.io/badge/React-20232A?style=for-the-badge&logo=react)](https://reactjs.org/)
[![Redis](https://img.shields.io/badge/Redis-ffffff?style=for-the-badge&logo=redis)](https://redis.com/)
[![SASS](https://img.shields.io/badge/SASS-CC6699?style=for-the-badge&logo=sass&logoColor=ffffff)](https://sass-lang.com/)
[![Shell](https://img.shields.io/badge/Shell-000000?style=for-the-badge&logo=windowsterminal&logoColor=00ff00)](https://en.wikipedia.org/wiki/Shell_script)
[![SQL Server](https://img.shields.io/badge/SQL%20Server-CC2927?style=for-the-badge&logo=microsoftsqlserver)](https://www.microsoft.com/en-ca/sql-server)
[![SQLite](https://img.shields.io/badge/SQLITE-003B57?style=for-the-badge&logo=sqlite)](https://www.sqlite.org/index.html)
[![Svelte](https://img.shields.io/badge/Svelte-4A4A55?style=for-the-badge&logo=svelte)](https://svelte.dev/)
[![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?style=for-the-badge&logo=typescript&logoColor=white)](https://typescriptlang.org/)
[![Windows](https://img.shields.io/badge/Windows-0078D4?style=for-the-badge&logo=windows)](https://microsoft.com/windows)
[![Vite](https://img.shields.io/badge/vite-1b1b1b?style=for-the-badge&logo=vite)](https://vitejs.dev/)
[![Vue](https://img.shields.io/badge/Vue.js-35495E?style=for-the-badge&logo=vuedotjs)](https://vuejs.org/)
## Setup
@@ -101,12 +53,44 @@ Website: https://git.zakscode.com
</h3>
</summary>
#### Prerequisites
- [Docker](https://docs.docker.com/install/)
#### Instructions
1. Run the docker image: `docker run -p 80:80 git.zakscode.com/ztimson/template:latest`
2. Open [http://localhost](http://localhost)
1. Add the following git action:
```yaml
name: Code review
on:
pull_request:
types: [opened, synchronize, reopened]
jobs:
review:
runs-on: ubuntu-latest
steps:
- name: Checkout PR
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha }}
- name: Fetch base branch
run: |
git fetch origin ${{ github.event.pull_request.base.ref }}
git branch -r
- name: Run AI Review
uses: docker://git.zakscode.com/zakscode/ai-reporter:latest
env:
AI_HOST: anthropic
AI_MODEL: claude-sonnet-4-5
AI_TOKEN: ${{ secrets.ANTHROPIC_TOKEN }}
GIT_HOST: ${{ github.server_url }}
GIT_OWNER: ${{ github.repository_owner }}
GIT_REPO: ${{ github.event.repository.name }}
GIT_TOKEN: ${{ secrets.ASSISTANT_TOKEN }}
GIT_BRANCH: origin/${{ github.event.pull_request.base.ref }}
PULL_REQUEST: ${{ github.event.pull_request.number }}
```
</details>
<details>
@@ -120,13 +104,13 @@ Website: https://git.zakscode.com
- [Node.js](https://nodejs.org/en/download)
#### Instructions
1. Install the dependencies: `npm install`
2. Start the Angular server: `npm run start`
3. Open [http://localhost:4200](http://localhost:4200)
1. Install the dependencies: `npm i`
2. Build library: `npm run review`
</details>
## License
Copyright © 2023 Zakary Timson | All Rights Reserved | Available under MIT Licensing
Copyright © 2025 Zakary Timson | All Rights Reserved | Available under MIT Licensing
See the [license](./LICENSE) for more information.

1757
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -9,8 +9,9 @@
"license": "ISC",
"description": "",
"dependencies": {
"@ztimson/ai-utils": "0.2.3",
"@ztimson/ai-utils": "^0.2.4",
"@ztimson/node-utils": "^1.0.7",
"@ztimson/utils": "^0.28.3",
"@ztimson/node-utils": "^1.0.4"
"dotenv": "^17.2.3"
}
}

View File

@@ -1,58 +1,94 @@
/**
* Variables:
* HOST - AI provider (ollama/anthropic/openai)
* MODEL - AI model name
* TOKEN - Access token (Omit with ollama)
* ROOT - Path to project
*/
import {Ai} from '@ztimson/ai-utils';
import {$} from '@ztimson/node-utils';
import * as os from 'node:os';
import * as path from 'node:path';
import * as fs from 'node:fs';
import * as dotenv from 'dotenv';
const gitDiff = await $`git diff HEAD^..HEAD`;
const root = process.env['ROOT'] || process.cwd(),
host = process.env['HOST'],
model = process.env['MODEL'],
token = process.env['TOKEN'];
dotenv.config();
dotenv.config({path: '.env.local', override: true});
let options = {ollama: {model, host}};
if(host === 'anthropic') options = {anthropic: {model, token}};
else if(host === 'openai') options = {openAi: {model, token}};
(async () => {
const root = process.argv[2] || process.cwd(),
branch = process.env['GIT_BRANCH'] || await $`cd ${root} && git symbolic-ref refs/remotes/origin/HEAD`,
git = process.env['GIT_HOST'],
owner = process.env['GIT_OWNER'],
repo = process.env['GIT_REPO'],
auth = process.env['GIT_TOKEN'],
pr = process.env['PULL_REQUEST'],
host = process.env['AI_HOST'],
model = process.env['AI_MODEL'],
token = process.env['AI_TOKEN'];
const ai = new Ai({
...options,
model: [host, model],
path: process.env['path'] || os.tmpdir(),
system: 'You are a code reviewer, use the following git diff to find any bugs and recommend changes using the `recommend` tool.',
tools: [{
name: 'read_file',
description: 'Read contents of a file',
args: {
path: {type: 'string', description: 'Path to file relative to project root'}
},
fn: (args) => fs.readFileSync(path.join(root, args.path), 'utf-8')
}, {
name: 'get_diff',
description: 'Check a file for differences using git',
args: {
path: {type: 'string', description: 'Path to file relative to project root'}
},
fn: async (args) => await $`git diff HEAD^..HEAD -- ${path.join(root, args.path)}`
}, {
name: 'recommend',
description: 'Make review recommendation',
args: {
file: {type: 'string', description: 'File path'},
line: {type: 'number', description: 'Line number', optional: true},
comment: {type: 'string', description: 'Review comment'}
},
fn: (args) => {
console.log(`💬 ${args.file}${args.line ? `:${args.line}` : ''} - ${args.comment}`);
// TODO: Add fetch to gitea to add comment to PR
}
}]
});
const comments = [];
const commit = await $`cd ${root} && git log -1 --pretty=format:%H`;
const gitDiff = await $`cd ${root} && git diff ${branch}`;
await ai.language.ask(gitDiff);
console.log(`Reviewing: ${root}\n`);
let options = {ollama: {model, host}};
if(host === 'anthropic') options = {anthropic: {model, token}};
else if(host === 'openai') options = {openAi: {model, token}};
const ai = new Ai({
...options,
model: [host, model],
path: process.env['path'] || os.tmpdir(),
system: `You are a code reviewer. Analyze the git diff and use the \`recommend\` tool for EACH issue you find. You must call \`recommend\` exactly once for every bug or improvement opportunity. After making all recommendations, provide a brief bullet point summary in markdown.`,
tools: [{
name: 'read_file',
description: 'Read contents of a file',
args: {
path: {type: 'string', description: 'Path to file relative to project root'}
},
fn: (args) => fs.readFileSync(path.join(root, args.path), 'utf-8')
}, {
name: 'get_diff',
description: 'Check a file for differences using git',
args: {
path: {type: 'string', description: 'Path to file relative to project root'}
},
fn: async (args) => await $`git diff HEAD^..HEAD -- ${path.join(root, args.path)}`
}, {
name: 'recommend',
description: 'REQUIRED: Call this once for every bug, improvement, or concern identified in the review.',
args: {
file: {type: 'string', description: 'File path'},
line: {type: 'number', description: 'Line number in new file'},
comment: {type: 'string', description: 'Review comment explaining the issue'}
},
fn: (args) => {
comments.push({
path: args.file,
new_position: args.line,
body: args.comment,
});
return 'Comment recorded, continue reviewing';
}
}]
});
if(!gitDiff) {
console.warn('No diff found');
return process.exit();
}
const messages = await ai.language.ask(gitDiff);
const summary = messages.pop().content;
if(git) {
const res = await fetch(`${git}/api/v1/repos/${owner}/${repo}/pulls/${pr}/reviews`, {
method: 'POST',
headers: {
'Authorization': `token ${auth}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
body: summary,
commit_id: commit,
event: 'COMMENT',
comments,
})
});
if(!res.ok) throw new Error(`${res.status} ${await res.text()}`);
}
console.log(comments.map(c => `${c.path}${c.new_position ? `:${c.new_position}` : ''}\n${c.body}`).join('\n\n') + '\n\n' + summary);
})();