52 Commits

Author SHA1 Message Date
e3d38d2df8 Updated readme
All checks were successful
Publish Library / Build NPM Project (push) Successful in 5s
Publish Library / Tag Version (push) Successful in 5s
2026-01-14 16:00:07 -05:00
9ff7beee1e Fixed output
All checks were successful
Publish Library / Build NPM Project (push) Successful in 6s
Publish Library / Tag Version (push) Successful in 5s
2026-01-14 15:56:44 -05:00
8cfcb3f95c Added checks
All checks were successful
Publish Library / Build NPM Project (push) Successful in 4s
Publish Library / Tag Version (push) Successful in 16s
Code review / review (pull_request) Successful in 38s
2026-01-14 15:56:07 -05:00
e625782eec Removed --no-cache, we are already using latest tag
All checks were successful
Publish Library / Build NPM Project (push) Successful in 3s
Publish Library / Tag Version (push) Successful in 5s
Code review / review (pull_request) Successful in 40s
2026-01-14 15:47:54 -05:00
72ffe3dcc7 Added a release bot to create release notes from closed milestones
All checks were successful
Publish Library / Build NPM Project (push) Successful in 3s
Publish Library / Tag Version (push) Successful in 5s
2026-01-14 15:43:53 -05:00
1ab97c2676 Merge pull request 'review-labels' (#14) from review-labels into master
All checks were successful
Publish Library / Build NPM Project (push) Successful in 8s
Publish Library / Tag Version (push) Successful in 7s
Reviewed-on: #14
2026-01-14 14:06:21 -05:00
7447204351 Fixed more logic checks
Some checks failed
Publish Library / Build NPM Project (push) Successful in 4s
Code review / review (pull_request) Has been cancelled
Publish Library / Tag Version (push) Successful in 6s
2026-01-14 14:05:41 -05:00
3b01e1bfc1 Fixed label condition check 💀
All checks were successful
Publish Library / Build NPM Project (push) Successful in 9s
Publish Library / Tag Version (push) Successful in 23s
Code review / review (pull_request) Successful in 49s
2026-01-14 13:51:41 -05:00
1460c3a0ae Added PR info as context to AI
All checks were successful
Publish Library / Build NPM Project (push) Successful in 3s
Publish Library / Tag Version (push) Successful in 5s
Code review / review (pull_request) Successful in 46s
2026-01-14 13:44:31 -05:00
ebc3da8605 Merge branch 'master' of git.zakscode.com:ztimson/ai-agents into review-labels
All checks were successful
Publish Library / Build NPM Project (push) Successful in 3s
Publish Library / Tag Version (push) Successful in 5s
2026-01-14 13:39:50 -05:00
677f84c97a Added label to enable review bot on PRs
All checks were successful
Publish Library / Build NPM Project (push) Successful in 3s
Publish Library / Tag Version (push) Successful in 5s
2026-01-14 13:38:20 -05:00
f543e08e36 Merge pull request 'Added configurable labels to ticket refiner' (#13) from ticket-duplicates into master
All checks were successful
Publish Library / Build NPM Project (push) Successful in 8s
Publish Library / Tag Version (push) Successful in 5s
Reviewed-on: #13
2026-01-14 13:22:33 -05:00
5b9f8e0e13 Fixed minor bugs and typos
All checks were successful
Code review / review (pull_request) Successful in 51s
Publish Library / Build NPM Project (push) Successful in 4s
Publish Library / Tag Version (push) Successful in 5s
2026-01-14 13:14:03 -05:00
019b05105a Merge branch 'master' of git.zakscode.com:ztimson/ai-agents into ticket-duplicates
All checks were successful
Publish Library / Build NPM Project (push) Successful in 4s
Publish Library / Tag Version (push) Successful in 16s
Code review / review (pull_request) Successful in 51s
2026-01-14 13:11:07 -05:00
decd533e4e Added configurable labels to ticket refiner
All checks were successful
Publish Library / Build NPM Project (push) Successful in 5s
Publish Library / Tag Version (push) Successful in 7s
Code review / review (pull_request) Successful in 48s
2026-01-14 13:05:16 -05:00
7becf99be2 Update .github/issue_template/ai-refinement.md
All checks were successful
Publish Library / Build NPM Project (push) Successful in 8s
Publish Library / Tag Version (push) Successful in 5s
2026-01-14 12:11:07 -05:00
99a1e55471 Merge pull request 'Check for duplicates before adding tickets' (#12) from ticket-duplicates into master
All checks were successful
Publish Library / Build NPM Project (push) Successful in 6s
Publish Library / Tag Version (push) Successful in 7s
Reviewed-on: #12
2025-12-31 00:02:18 -05:00
23cb66544e Fixed more bugs
Some checks failed
Publish Library / Build NPM Project (push) Successful in 3s
Code review / review (pull_request) Has been cancelled
Publish Library / Tag Version (push) Successful in 7s
2025-12-31 00:01:55 -05:00
9e5372f37b Fixed more bugs
All checks were successful
Publish Library / Build NPM Project (push) Successful in 3s
Publish Library / Tag Version (push) Successful in 13s
Code review / review (pull_request) Successful in 58s
2025-12-30 23:55:21 -05:00
eb4486f196 Fixed bugs
All checks were successful
Publish Library / Build NPM Project (push) Successful in 3s
Publish Library / Tag Version (push) Successful in 15s
Code review / review (pull_request) Successful in 1m30s
2025-12-30 23:42:17 -05:00
f3df34ec47 Merge branch 'master' of git.zakscode.com:ztimson/ai-agents into ticket-duplicates
All checks were successful
Publish Library / Build NPM Project (push) Successful in 3s
Publish Library / Tag Version (push) Successful in 16s
Code review / review (pull_request) Successful in 48s
2025-12-30 23:34:56 -05:00
f2936ae4dc Fixed the review stage
All checks were successful
Publish Library / Build NPM Project (push) Successful in 6s
Publish Library / Tag Version (push) Successful in 4s
2025-12-30 23:34:06 -05:00
e62e11fb75 Merge branch 'master' of git.zakscode.com:ztimson/ai-agents into ticket-duplicates
Some checks failed
Code review / review (pull_request) Failing after 15s
Publish Library / Build NPM Project (push) Successful in 3s
Publish Library / Tag Version (push) Successful in 11s
2025-12-30 23:25:30 -05:00
604e04559b Fixed the review stage
All checks were successful
Publish Library / Build NPM Project (push) Successful in 5s
Publish Library / Tag Version (push) Successful in 4s
2025-12-30 23:25:15 -05:00
8add830d2b Check for duplicates before adding tickets
Some checks failed
Publish Library / Build NPM Project (push) Successful in 14s
Code review / review (pull_request) Failing after 28s
Publish Library / Tag Version (push) Successful in 27s
2025-12-30 23:17:45 -05:00
57bbc1fdb4 Fixed ticker refiner labels
All checks were successful
Publish Library / Build NPM Project (push) Successful in 7s
Publish Library / Tag Version (push) Successful in 9s
2025-12-30 20:33:19 -05:00
078892297e Fixing ticket refiner labels
All checks were successful
Publish Library / Build NPM Project (push) Successful in 6s
Publish Library / Tag Version (push) Successful in 4s
2025-12-30 19:35:27 -05:00
3c2d6f7824 bump 0.0.5
All checks were successful
Publish Library / Build NPM Project (push) Successful in 7s
Publish Library / Tag Version (push) Successful in 4s
2025-12-30 19:26:00 -05:00
2d9662e86d Ticket refining test
All checks were successful
Publish Library / Build NPM Project (push) Successful in 5s
Publish Library / Tag Version (push) Successful in 5s
2025-12-30 19:24:56 -05:00
91d22b8b16 Fixed ticket refining labels
All checks were successful
Publish Library / Build NPM Project (push) Successful in 5s
Publish Library / Tag Version (push) Successful in 4s
2025-12-30 19:22:23 -05:00
c7f8ffb32a Fixed ticket refining labels
All checks were successful
Publish Library / Build NPM Project (push) Successful in 5s
Publish Library / Tag Version (push) Successful in 4s
2025-12-30 19:09:38 -05:00
27fad6a3d3 Fixed ci/cd agents
All checks were successful
Publish Library / Build NPM Project (push) Successful in 7s
Publish Library / Tag Version (push) Successful in 6s
2025-12-30 16:01:58 -05:00
3daf5442d8 Merge remote-tracking branch 'origin/master'
All checks were successful
Publish Library / Build NPM Project (push) Successful in 7s
Publish Library / Tag Version (push) Successful in 6s
2025-12-30 15:44:14 -05:00
002e809ef6 Updated gitea comment retrieval 2025-12-30 15:43:55 -05:00
d1c0b7a872 Merge pull request 'Added ticket refinement bot' (#4) from refinement into master
All checks were successful
Publish Library / Build NPM Project (push) Successful in 8s
Publish Library / Tag Version (push) Successful in 5s
Reviewed-on: #4
2025-12-30 15:38:49 -05:00
1b026529fe Merge branch 'master' of git.zakscode.com:ztimson/ai-agents into refinement
All checks were successful
Publish Library / Build NPM Project (push) Successful in 4s
Publish Library / Tag Version (push) Successful in 16s
Code review / review (pull_request) Successful in 1m13s
# Conflicts:
#	src/review.mjs
2025-12-30 15:22:31 -05:00
af09bd0f53 Small issue refiner fixes
All checks were successful
Publish Library / Build NPM Project (push) Successful in 4s
Publish Library / Tag Version (push) Successful in 10s
Code review / review (pull_request) Successful in 1m1s
2025-12-30 15:18:02 -05:00
397c9aeb90 Dont make re-recommendations
All checks were successful
Publish Library / Build NPM Project (push) Successful in 7s
Publish Library / Tag Version (push) Successful in 6s
2025-12-30 14:53:35 -05:00
1f48b5a872 Fixed misc bugs
All checks were successful
Publish Library / Build NPM Project (push) Successful in 4s
Publish Library / Tag Version (push) Successful in 13s
Code review / review (pull_request) Successful in 1m0s
2025-12-30 14:15:40 -05:00
6bb78d0862 Added ticket refinement bot
All checks were successful
Publish Library / Build NPM Project (push) Successful in 4s
Publish Library / Tag Version (push) Successful in 6s
Code review / review (pull_request) Successful in 1m6s
2025-12-30 13:47:59 -05:00
73ad9293ae Fixed review?
Some checks failed
Code review / review (pull_request) Has been cancelled
Publish Library / Build NPM Project (push) Successful in 11s
Publish Library / Tag Version (push) Successful in 13s
2025-12-27 22:11:24 -05:00
4175bf363c Fixed publish
Some checks failed
Code review / review (pull_request) Failing after 4s
Publish Library / Build NPM Project (push) Successful in 6s
Publish Library / Tag Version (push) Successful in 5s
2025-12-27 22:06:33 -05:00
c0281cd57c Updated readme 2025-12-27 22:04:35 -05:00
a24961a55c Use npx instead
Some checks failed
Code review / review (pull_request) Failing after 4s
Publish Library / Build NPM Project (push) Failing after 20s
Publish Library / Tag Version (push) Has been skipped
2025-12-27 22:03:14 -05:00
e0f3a3cd82 Fixed review?
Some checks failed
Code review / review (pull_request) Failing after 10s
Build and publish / Build Container (push) Successful in 39s
2025-12-27 21:04:48 -05:00
6fccede7ba Fixed review?
Some checks failed
Build and publish / Build Container (push) Successful in 40s
Code review / review (pull_request) Failing after 3s
2025-12-27 20:59:12 -05:00
f00906045a Fixed review?
Some checks failed
Code review / review (pull_request) Failing after 19s
Build and publish / Build Container (push) Successful in 47s
2025-12-27 20:56:11 -05:00
bee0029469 Fixed review?
Some checks failed
Build and publish / Build Container (push) Successful in 49s
Code review / review (pull_request) Failing after 3s
2025-12-27 20:48:20 -05:00
1b3232b10c Fixed docker file
Some checks failed
Build and publish / Build Container (push) Successful in 1m0s
Code review / review (pull_request) Failing after 5s
2025-12-27 20:42:43 -05:00
40ade3fef1 Fixed review build
Some checks failed
Code review / review (pull_request) Failing after 26s
Build and publish / Build Container (push) Successful in 1m5s
2025-12-27 20:39:37 -05:00
f65fd15220 Fixed review build
Some checks failed
Code review / review (pull_request) Failing after 4s
Build and publish / Build Container (push) Successful in 1m3s
2025-12-27 20:37:33 -05:00
93e7be5280 Fixed review build 2025-12-27 20:37:12 -05:00
14 changed files with 530 additions and 143 deletions

19
.github/issue_template/ai-refinement.md vendored Normal file
View File

@@ -0,0 +1,19 @@
---
name: "AI refinement"
about: "Use AI to refine ticket"
ref: "develop"
labels:
- Review/AI
---
Describe your request:
What are you trying to do and what's happening?
How can it be fixed or improved?
Steps to reproduce?
Any other useful information? Logs, screenshots, steps to reproduce?

View File

@@ -9,7 +9,5 @@
## Checklist ## Checklist
<!-- Complete after creating PR --> <!-- Complete after creating PR -->
- [ ] Linked issues - [ ] Reviewed changes (or use `Review/AI` label)
- [ ] Reviewed changes
- [ ] Updated comments/documentation - [ ] Updated comments/documentation

View File

@@ -1,44 +1,45 @@
name: Build and publish name: Publish Library
run-name: Build and publish run-name: Publish Library
on: on:
push: push:
jobs: jobs:
container: build:
name: Build Container name: Build NPM Project
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: docker container: node:alpine
steps: steps:
- name: Build Container - name: Clone Repository
uses: ztimson/actions/clone@develop
- name: Publish Library
run: | 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 if [ "${{github.ref_name}}" = "master" ]; then
docker tag "$REGISTRY/${{github.repository}}:${{github.ref_name}}" "$REGISTRY/${{github.repository}}:$VERSION" REGISTRY="${{github.server_url}}/api/packages/${{github.repository_owner}}/npm/"
docker push "$REGISTRY/${{github.repository}}:$VERSION" npm set registry "$REGISTRY"
if [ "$DOCKER_HUB" = "true" ]; then npm set $(echo $REGISTRY | sed s%http:%% | sed s%https:%% ):_authToken "${{secrets.DEPLOY_TOKEN}}"
docker tag "$REGISTRY/${{github.repository}}:${{github.ref_name}}" "docker.io/${{secrets.DOCKER_HUB_IMAGE}}:$VERSION" npm publish || echo "Failed to publish"
docker push "docker.io/${{secrets.DOCKER_HUB_IMAGE}}:$VERSION"
fi
docker tag "$REGISTRY/${{github.repository}}:${{github.ref_name}}" "$REGISTRY/${{github.repository}}:latest" REGISTRY="https://registry.npmjs.org/"
docker push "$REGISTRY/${{github.repository}}:latest" npm set registry "$REGISTRY"
if [ "$DOCKER_HUB" = "true" ]; then npm set $(echo $REGISTRY | sed s%http:%% | sed s%https:%% ):_authToken "${{secrets.NPM_TOKEN}}"
docker tag "$REGISTRY/${{github.repository}}:${{github.ref_name}}" "docker.io/${{secrets.DOCKER_HUB_IMAGE}}:latest" npm publish || echo "Failed to publish"
docker push "docker.io/${{secrets.DOCKER_HUB_IMAGE}}:latest"
fi
fi fi
tag:
name: Tag Version
needs: build
if: ${{github.ref_name}} == 'release'
runs-on: ubuntu-latest
container: node:alpine
steps:
- name: Clone Repository
uses: ztimson/actions/clone@develop
- name: Get Version Number
run: echo "VERSION=$(cat package.json | grep version | grep -Eo ':.+' | grep -Eo '[[:alnum:]\.\/\-]+')" >> $GITHUB_ENV
- name: Tag Version
uses: ztimson/actions/tag@develop
with:
tag: ${{env.VERSION}}

29
.github/workflows/code-review.yml vendored Normal file
View File

@@ -0,0 +1,29 @@
name: Code review
on:
pull_request:
types: [opened, synchronize, reopened, labeled]
jobs:
review:
runs-on: ubuntu-latest
container: node:22
steps:
- name: Fetch code
run: |
git clone "$(echo ${{github.server_url}}/${{github.repository}}.git | sed s%://%://${{github.token}}@% )" .
git checkout ${{ github.event.pull_request.head.sha }}
git fetch origin ${{ github.event.pull_request.base.ref }}
- name: Create review
run: npx -y -p @ztimson/ai-agents@latest review $GITHUB_WORKSPACE
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 }}

27
.github/workflows/release-creator.yml vendored Normal file
View File

@@ -0,0 +1,27 @@
name: Release Bot
on:
milestone:
types: [closed]
jobs:
release:
runs-on: ubuntu-latest
container: node:22
steps:
- name: Checkout
run: |
git clone "$(echo ${{github.server_url}}/${{github.repository}}.git | sed s%://%://${{github.token}}@% )" .
git checkout ${{ github.event.repository.default_branch }}
- name: Create release
run: npx -y @ztimson/ai-agents@latest release
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 }}
MILESTONE: ${{ github.event.milestone.number }}

View File

@@ -1,32 +0,0 @@
name: Code review
on:
pull_request:
types: [opened, synchronize, reopened]
jobs:
review:
runs-on: ubuntu-latest
steps:
- name: Fetch code
run: |
git clone "$(echo ${{github.server_url}}/${{github.repository}}.git | sed s%://%://${{github.token}}@% )" .
git checkout ${{ github.event.pull_request.head.sha }}
git fetch origin ${{ github.event.pull_request.base.ref }}
- name: Run AI Review
run: |
REGISTRY="$(echo "${{github.server_url}}" | sed -E 's|https?://||')"
docker login -u "${{github.repository_owner}}" -p "${{secrets.DEPLOY_TOKEN}}" "$REGISTRY"
docker pull "$REGISTRY/zakscode/ai-reporter:latest"
docker run --rm \
-v $(pwd):/github/workspace \
-e AI_HOST=anthropic \
-e AI_MODEL=claude-sonnet-4-5 \
-e AI_TOKEN="${{ secrets.ANTHROPIC_TOKEN }}" \
-e GIT_HOST="${{ github.server_url }}" \
-e GIT_OWNER="${{ github.repository_owner }}" \
-e GIT_REPO="${{ github.event.repository.name }}" \
-e GIT_TOKEN="${{ secrets.ASSISTANT_TOKEN }}" \
-e GIT_BRANCH="origin/${{ github.event.pull_request.base.ref }}" \
-e PULL_REQUEST="${{ github.event.pull_request.number }}" \
"$REGISTRY/zakscode/ai-reporter:latest"

27
.github/workflows/ticket-refinement.yml vendored Normal file
View File

@@ -0,0 +1,27 @@
name: Ticket refinement
on:
issues:
types: [labeled]
jobs:
format:
runs-on: ubuntu-latest
container: node:22
steps:
- name: Fetch code
run: |
git clone "$(echo ${{github.server_url}}/${{github.repository}}.git | sed s%://%://${{github.token}}@% )" .
git checkout ${{ github.event.repository.default_branch }}
- name: Refine ticket
run: npx -y -p @ztimson/ai-agents@latest refine
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 }}
TICKET: ${{ github.event.issue.number }}

View File

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

View File

@@ -3,32 +3,32 @@
<br /> <br />
<!-- Logo --> <!-- Logo -->
<img src="https://git.zakscode.com/repo-avatars/d2c56ffd0220751c2f4a9f6fc1e8125a9a63aeed452ae4f4ab696c084330faa2" alt="Logo" width="200" height="200"> <img src="https://git.zakscode.com/repo-avatars/309c233243bcd1c1e9b3f359ec3f59769bb01b655e8ed7b32587781be4c8b21c" alt="Logo" width="200" height="200">
<!-- Title --> <!-- Title -->
### AI Reviewer ### AI Agents
<!-- Description --> <!-- Description -->
Automated AI-powered code review for pull requests 🤖 AI-powered Gitea agents for automating reviews and administration
<!-- Repo badges --> <!-- 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/ai-reviewer/tags&query=$[0].name)](https://git.zakscode.com/ztimson/ai-reviewer/tags) [![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-agents/tags&query=$[0].name)](https://git.zakscode.com/ztimson/ai-agents/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) [![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-agents&query=open_pr_counter)](https://git.zakscode.com/ztimson/ai-agents/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) [![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-agents&query=open_issues_count)](https://git.zakscode.com/ztimson/ai-agents/issues)
<!-- Links --> <!-- Links -->
--- ---
<div> <div>
<a href="https://git.zakscode.com/ztimson/ai-reviewer/releases" target="_blank">Release Notes</a> <a href="https://git.zakscode.com/ztimson/ai-agents/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-agents/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> • <a href="https://git.zakscode.com/ztimson/ai-agents/issues/new?template=.github%2fissue_template%2fenhancement.md" target="_blank">Request a Feature</a>
</div> </div>
--- ---
</div> </div>
## Table of Contents ## Table of Contents
- [AI Reviewer](#top) - [AI Agents](#top)
- [About](#about) - [About](#about)
- [Built With](#built-with) - [Built With](#built-with)
- [Setup](#setup) - [Setup](#setup)
@@ -37,7 +37,12 @@ Automated AI-powered code review for pull requests 🤖
## About ## About
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. AI-powered Gitea agents for automating administration of code repos:
- Code Review
- Release Notes
- Ticket Refinement
Only supports Gitea, copy the relevant `.github/workflows/______.yml` action to start using it
### Built With ### Built With
[![Docker](https://img.shields.io/badge/Docker-384d54?style=for-the-badge&logo=docker)](https://docker.com/) [![Docker](https://img.shields.io/badge/Docker-384d54?style=for-the-badge&logo=docker)](https://docker.com/)
@@ -53,42 +58,11 @@ Automated code reviewer that uses AI to analyze git diffs and provide inline com
</h3> </h3>
</summary> </summary>
#### Prerequisites
- [Node.js](https://nodejs.org/en/download)
#### Instructions #### Instructions
1. Add the following git action: 1. Run using npx: `npx -y @ztimson/ai-agents@latest review`
```yaml
name: Code review
on:
pull_request:
types: [opened, synchronize, reopened]
jobs:
review:
runs-on: ubuntu-latest
steps:
- name: Fetch code
run: |
git clone "$(echo ${{github.server_url}}/${{github.repository}}.git | sed s%://%://${{github.token}}@% )" .
git checkout ${{ github.event.pull_request.head.sha }}
git fetch origin ${{ github.event.pull_request.base.ref }}
- name: Run AI Review
run: |
REGISTRY="$(echo "${{github.server_url}}" | sed -E 's|https?://||')"
docker login -u "${{github.repository_owner}}" -p "${{secrets.DEPLOY_TOKEN}}" "$REGISTRY"
docker pull "$REGISTRY/zakscode/ai-reporter:latest"
docker run --rm \
-v $(pwd):/github/workspace \
-e AI_HOST=anthropic \
-e AI_MODEL=claude-sonnet-4-5 \
-e AI_TOKEN="${{ secrets.ANTHROPIC_TOKEN }}" \
-e GIT_HOST="${{ github.server_url }}" \
-e GIT_OWNER="${{ github.repository_owner }}" \
-e GIT_REPO="${{ github.event.repository.name }}" \
-e GIT_TOKEN="${{ secrets.ASSISTANT_TOKEN }}" \
-e GIT_BRANCH="origin/${{ github.event.pull_request.base.ref }}" \
-e PULL_REQUEST="${{ github.event.pull_request.number }}" \
"$REGISTRY/zakscode/ai-reporter:latest"
```
</details> </details>

View File

@@ -1,17 +1,23 @@
{ {
"name": "ai-reviewer", "name": "@ztimson/ai-agents",
"version": "0.0.0", "version": "0.1.3",
"scripts": { "description": "AI agents",
"review": "node src/review.mjs" "keywords": ["ai", "review"],
}, "author": "ztimson",
"keywords": [],
"author": "",
"license": "ISC", "license": "ISC",
"description": "", "type": "module",
"bin": {
"refine": "./src/refine.mjs",
"release": "./src/release.mjs",
"review": "./src/review.mjs"
},
"dependencies": { "dependencies": {
"@ztimson/ai-utils": "^0.2.4", "@ztimson/ai-utils": "^0.2.4",
"@ztimson/node-utils": "^1.0.7", "@ztimson/node-utils": "^1.0.7",
"@ztimson/utils": "^0.28.3", "@ztimson/utils": "^0.28.3",
"dotenv": "^17.2.3" "dotenv": "^17.2.3"
} },
"files": [
"src/"
]
} }

217
src/refine.mjs Normal file
View File

@@ -0,0 +1,217 @@
#!/usr/bin/env node
import {Ai} from '@ztimson/ai-utils';
import * as os from 'node:os';
import * as dotenv from 'dotenv';
import * as fs from 'node:fs';
import * as path from 'node:path';
dotenv.config({quiet: true, debug: false});
dotenv.config({path: '.env.local', override: true, quiet: true, debug: false});
(async () => {
let p = process.argv[process.argv.length - 1];
if(p === 'refine' || p.endsWith('refine.mjs')) p = null;
if(!/^(\/|[A-Z]:)/m.test(p)) p = path.join(process.cwd(), p);
const git = process.env['GIT_HOST'],
owner = process.env['GIT_OWNER'],
repo = process.env['GIT_REPO'],
auth = process.env['GIT_TOKEN'],
ticket = process.env['TICKET'],
host = process.env['AI_HOST'],
model = process.env['AI_MODEL'],
labelDupe = process.env['LABELS_DUPE'] || 'Review/Duplicate',
labelEnabled = process.env['LABEL_ENABLED'] || 'Review/AI',
labelsReq = process.env['LABELS_REQ'] || 'Kind/Aesthetic,Kind/Bug,Kind/DevOps,Kind/Document,Kind/Enhancement,Kind/Refactor,Kind/Security',
labelsOpt = process.env['LABELS_OPT'] || 'Breaking,Priority,QA',
token = process.env['AI_TOKEN'];
console.log(`Processing issue #${ticket}`);
// Fetch issue
const issueData = await fetch(`${git}/api/v1/repos/${owner}/${repo}/issues/${ticket}`, {
headers: {'Authorization': `token ${auth}`}
}).then(async resp => {
if(resp.ok) return resp.json();
else throw new Error(`${resp.status} ${await resp.text()}`);
});
if(issueData.labels?.length !== 1 || issueData.labels[0]?.name !== labelEnabled) {
console.log('Skipping');
return process.exit();
}
// Gather readme & template
let title = '', labels = [], readme = '', readmeP = path.join(process.cwd(), 'README.md');
if(fs.existsSync(readmeP)) readme = fs.readFileSync(readmeP, 'utf-8');
const template = p ? fs.readFileSync(p, 'utf-8') : `## Description
A clear explanation of the request
---
## Current Behavior
what's happening now or the current state/gap
## Expected Behavior
What should happen instead
## Steps to Reproduce / Desired Flow
1. First step
2. Second step
3. Third step
## Additional Context
Logs, screenshots, links, related issues
## Acceptance Criteria
- [ ] Todo requirement
- [X] Completed requirement
## Technical Notes
Implementation details, constraints, dependencies, design decisions
| Effort / Weight | Score |
|-----------------|----------|
| Size | 0-5 |
| Complexity | 0-5 |
| Unknowns | 0-5 |
| **Total** | **0-15** |
`;
// Create AI
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(),
tools: [{
name: 'title',
description: 'Set the ticket title, must be called EXACTLY ONCE',
args: {title: {type: 'string', description: 'Ticket title, must match format: Module - Verb noun', required: true}},
fn: (args) => title = args.title
}, {
name: 'add_label',
description: 'Add a label to the ticket',
args: {label: {type: 'string', description: 'Label name', required: true}},
fn: async (args) => {
labels.push(args.label);
return await fetch(`${git}/api/v1/repos/${owner}/${repo}/issues/${ticket}/labels`, {
method: 'POST',
headers: {'Authorization': `token ${auth}`, 'Content-Type': 'application/json'},
body: `{"labels":["${args.label}"]}`
}).then(async resp => { if(!resp.ok) throw new Error(`${resp.status} ${await resp.text()}`); });
}
}],
system: `Transform raw tickets into structured markdown following the template EXACTLY.
**MANDATORY STEPS:**
1. Call \`title\` tool EXACTLY ONCE in format: "[Module] - [Verb] [subject]" (example: Storage - fix file uploads)
2. Identify one label from each group which best applies to the ticket: ${labelsReq.replaceAll(',', ', ')}
3. Call the \`add_label\` tool ONCE FOR EVERY LABEL identified in the previous step
4. Filter the following labels to any that apply to this ticket: ${labelsOpt.replaceAll(',', ', ')}
5. Call the \`add_label\` tool ONCE FOR EVERY LABEL identified in the previous step
6. Output the new ticket description in formatted markdown matching the following rules:
**TEMPLATE RULES:**
- Use ## headers (match template exactly)
- Description: Clear summary of the request
- Current Behavior: What's happening now (remove for Document tickets)
- Expected Behavior: What should happen (remove for Document tickets)
- Steps to Reproduce: Numbered list for bugs, flow for enhancements, remove if not applicable
- Additional Context: Logs, screenshots, links provided by user
- Acceptance Criteria: Convert to checkboxes (- [ ] format)
- Technical Notes: Implementation approach, constraints, dependencies
- Weight table (use exact format below):
| Effort / Weight | Score |
|-----------------|----------|
| Size | 0-5 |
| Complexity | 0-5 |
| Unknowns | 0-5 |
| **Total** | **0-15** |
**SCORING:**
- Size: # of modules/layers/files affected
- Complexity: Technical difficulty
- Unknowns: Research/uncertainty needed
**PROJECT README:**
\`\`\`markdown
${readme.trim() || 'No README available'}
\`\`\`
**TEMPLATE:**
\`\`\`markdown
${template.trim()}
\`\`\`
Output ONLY markdown. No explanations, labels, or extra formatting.`});
// Format ticket with AI
const messages = await ai.language.ask(`Title: ${issueData.title}\n\nDescription:\n${issueData.body || 'No description provided'}`).catch(() => []);
const body = messages?.pop()?.content;
if(!body) throw new Error('Invalid response from AI');
// Check for duplicates
const repoInfo = await fetch(`${git}/api/v1/repos/${owner}/${repo}`, {headers: {'Authorization': `token ${auth}`},}).then(resp => resp.ok ? resp.json() : null);
const search = await fetch(`${git}/api/v1/repos/issues/search`, {
method: 'POST',
headers: {'Authorization': `token ${auth}`, 'Content-Type': 'application/json'},
body: JSON.stringify({
owner,
priority_repo_id: repoInfo.id,
type: 'issues',
limit: 3,
q: title
})
}).then(resp => resp.ok ? resp.json() : []);
let dupeId = null;
const dupeIds = search.map(t => t.id);
const dupes = search.map(t => `ID: ${t.id}\nTitle: ${t.title}\n\`\`\`markdown\n${t.body}\n\`\`\``).join('\n\n');
const hasDuplicates = (await ai.language.ask(`ID: ${issueData.id}\nTitle: ${title}\n\`\`\`markdown\n${body}\n\`\`\``, {
system: `Your job is to identify duplicates. Respond ONLY with the duplicate's ID number or "NONE" if no match exists\n\n${dupes}`
}))?.pop()?.content;
// Handle duplicates
if(hasDuplicates && !hasDuplicates.toUpperCase().includes('NONE') && (dupeId = dupeIds.find(id => id == hasDuplicates.trim())) != null && dupeId != issueData.id) {
await fetch(`${git}/api/v1/repos/${owner}/${repo}/issues/${ticket}/comments`, {
method: 'POST',
headers: {'Authorization': `token ${auth}`, 'Content-Type': 'application/json'},
body: `{"body": "Duplicate of #${dupeId}"}`
}).then(async resp => { if(!resp.ok) throw new Error(`${resp.status} ${await resp.text()}`); });
await fetch(`${git}/api/v1/repos/${owner}/${repo}/issues/${ticket}/labels`, {
method: 'POST',
headers: {'Authorization': `token ${auth}`, 'Content-Type': 'application/json'},
body: `{"labels":["${labelDupe}"]}`
}).then(async resp => { if(!resp.ok) throw new Error(`${resp.status} ${await resp.text()}`); });
await fetch(`${git}/api/v1/repos/${owner}/${repo}/issues/${ticket}`, {
method: 'PATCH',
headers: {'Authorization': `token ${auth}`, 'Content-Type': 'application/json'},
body: '{"state": "closed"}'
}).then(async resp => { if(!resp.ok) throw new Error(`${resp.status} ${await resp.text()}`); });
console.log('Duplicate');
return process.exit();
}
// Update ticket
await fetch(`${git}/api/v1/repos/${owner}/${repo}/issues/${ticket}`, {
method: 'PATCH',
headers: {'Authorization': `token ${auth}`, 'Content-Type': 'application/json'},
body: JSON.stringify({title, body})
}).then(async resp => { if(!resp.ok) throw new Error(`${resp.status} ${await resp.text()}`); });
console.log(`Title: ${title}\nLabels: ${labels.join(', ')}\nBody:\n${body}`);
})().catch(err => {
console.error(`Error: ${err.message || err.toString()}`);
process.exit(1);
});

90
src/release.mjs Normal file
View File

@@ -0,0 +1,90 @@
#!/usr/bin/env node
import {Ai} from '@ztimson/ai';
import * as dotenv from 'dotenv';
import {$} from '@ztimson/node-utils';
dotenv.config({quiet: true, debug: false});
dotenv.config({path: '.env.local', override: true, quiet: true, debug: false});
(async () => {
const git = process.env['GIT_HOST'],
owner = process.env['GIT_OWNER'],
repo = process.env['GIT_REPO'],
auth = process.env['GIT_TOKEN'],
milestone = process.env['MILESTONE'],
host = process.env['AI_HOST'] || 'ollama',
model = process.env['AI_MODEL'] || 'llama3',
token = process.env['AI_TOKEN'];
// Get milestone info
const milestoneData = await fetch(`${git}/api/v1/repos/${owner}/${repo}/milestones/${milestone}`, {
headers: {'Authorization': `token ${auth}`}
}).then(resp => resp.ok ? resp.json() : {});
// Get closed issues
const issues = await fetch(`${git}/api/v1/repos/${owner}/${repo}/issues?state=closed&milestone=${milestone}`, {
headers: {'Authorization': `token ${auth}`}
}).then(resp => resp.ok ? resp.json() : []);
// Get closed PRs
const prs = await fetch(`${git}/api/v1/repos/${owner}/${repo}/pulls?state=closed&milestone=${milestone}`, {
headers: {'Authorization': `token ${auth}`}
}).then(resp => resp.ok ? resp.json() : []);
// Get latest tag
const latestTag = await $`git describe --tags --abbrev=0`.text();
if(!latestTag) {
console.error('At least one Git tag is required');
process.exit(1);
}
// Build context
let context = `Milestone: ${milestoneData.title}
Description:
\`\`\`md
${milestoneData.description || ''}
\`\`\`
PRs:
${prs.map(pr => `- ${pr.title}\n${pr.body || ''}`).join('\n\n')}
Issues:
${issues.filter(i => !i.pull_request).map(i => `- ${i.title}\n${i.body || ''}`).join('\n\n')}`;
// Generate release notes
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],
system: `You are a release notes writer. Format the provided milestone info, PRs, and issues into clean, organized release notes. Use markdown with sections like "Features", "Bug Fixes", "Breaking Changes", etc. Be concise but informative. Include issue/PR numbers in format #123.`
});
const body = (await ai.chat(context)).pop()?.content;
if(!body) {
console.error('No release notes were generated');
process.exit(1);
}
// Create release
const name = latestTag.trim();
await fetch(`${git}/api/v1/repos/${owner}/${repo}/releases`, {
method: 'POST',
headers: {
'Authorization': `token ${auth}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
name,
tag_name: name,
body
})
}).then(resp => { if(!resp.ok) throw new Error(resp.status + ' ' + resp.statusText) });
console.log(`Title: ${name}\nDescription:\n${body}`);
})().catch(err => {
console.error(`Error: ${err.message || err.toString()}`);
process.exit(1);
});

View File

@@ -1,3 +1,5 @@
#!/usr/bin/env node
import {Ai} from '@ztimson/ai-utils'; import {Ai} from '@ztimson/ai-utils';
import {$} from '@ztimson/node-utils'; import {$} from '@ztimson/node-utils';
import * as os from 'node:os'; import * as os from 'node:os';
@@ -5,26 +7,55 @@ import * as path from 'node:path';
import * as fs from 'node:fs'; import * as fs from 'node:fs';
import * as dotenv from 'dotenv'; import * as dotenv from 'dotenv';
dotenv.config(); dotenv.config({quiet: true, debug: false});
dotenv.config({path: '.env.local', override: true}); dotenv.config({path: '.env.local', override: true, quiet: true, debug: false});
(async () => { (async () => {
const root = process.argv[2] || process.cwd(), let p = process.argv[process.argv.length - 1];
branch = process.env['GIT_BRANCH'] || await $`cd ${root} && git symbolic-ref refs/remotes/origin/HEAD`, if(p === 'review' || p.endsWith('review.mjs')) p = null;
const root = p || process.cwd(),
git = process.env['GIT_HOST'], git = process.env['GIT_HOST'],
owner = process.env['GIT_OWNER'], owner = process.env['GIT_OWNER'],
repo = process.env['GIT_REPO'], repo = process.env['GIT_REPO'],
auth = process.env['GIT_TOKEN'], auth = process.env['GIT_TOKEN'],
labelEnabled = process.env['LABEL_ENABLED'] || 'Review/AI',
pr = process.env['PULL_REQUEST'], pr = process.env['PULL_REQUEST'],
host = process.env['AI_HOST'], host = process.env['AI_HOST'],
model = process.env['AI_MODEL'], model = process.env['AI_MODEL'],
token = process.env['AI_TOKEN']; token = process.env['AI_TOKEN'];
console.log(`Reviewing: ${root}`);
const info = await fetch(`${git}/api/v1/repos/${owner}/${repo}/pulls/${pr}`)
.then(async resp => {
if(resp.ok) return resp.json();
throw new Error(`${resp.status} ${await resp.text()}`);
});
if(!info.labels.some(l => l.name === labelEnabled)) {
console.log('Skipping');
return process.exit();
}
const branch = process.env['GIT_BRANCH'] || await $`cd ${root} && git symbolic-ref refs/remotes/origin/HEAD`;
const comments = []; const comments = [];
const commit = await $`cd ${root} && git log -1 --pretty=format:%H`; const commit = await $`cd ${root} && git log -1 --pretty=format:%H`;
const gitDiff = await $`cd ${root} && git diff ${branch}`; const gitDiff = await $`cd ${root} && git diff ${branch}`;
console.log(`Reviewing: ${root}\n`); if(!gitDiff) {
console.warn('No diff found');
return process.exit();
}
let existingComments = 'Existing Comments:\n';
if(git && pr) {
const reviews = await fetch(`${git}/api/v1/repos/${owner}/${repo}/pulls/${pr}/reviews`, {
headers: {'Authorization': `token ${auth}`}
}).then(resp => resp.ok ? resp.json() : []);
const comments = await Promise.all(reviews.map(r => fetch(`${git}/api/v1/repos/${owner}/${repo}/pulls/${pr}/reviews/${r.id}/comments`, {
headers: {'Authorization': `token ${auth}`}
}).then(resp => resp.ok ? resp.json() : [])));
existingComments += comments.flat().map(c => `${c.path}:${c.position}\n${c.body}`).join('\n\n');
}
let options = {ollama: {model, host}}; let options = {ollama: {model, host}};
if(host === 'anthropic') options = {anthropic: {model, token}}; if(host === 'anthropic') options = {anthropic: {model, token}};
@@ -33,7 +64,7 @@ dotenv.config({path: '.env.local', override: true});
...options, ...options,
model: [host, model], model: [host, model],
path: process.env['path'] || os.tmpdir(), 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.`, 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 directly related to changes. Ignore formatting recommendations. After making all recommendations, provide a quick 75 words or less sitrep.${existingComments}`,
tools: [{ tools: [{
name: 'read_file', name: 'read_file',
description: 'Read contents of a file', description: 'Read contents of a file',
@@ -67,12 +98,16 @@ dotenv.config({path: '.env.local', override: true});
}] }]
}); });
if(!gitDiff) { const messages = await ai.language.ask(`Title: ${info.title || 'None'}
console.warn('No diff found'); Description:
return process.exit(); \`\`\`md
} ${info.body || 'None'}
\`\`\`
const messages = await ai.language.ask(gitDiff); Git Diff:
\`\`\`
${gitDiff}
\`\`\``);
const summary = messages.pop().content; const summary = messages.pop().content;
if(git) { if(git) {
const res = await fetch(`${git}/api/v1/repos/${owner}/${repo}/pulls/${pr}/reviews`, { const res = await fetch(`${git}/api/v1/repos/${owner}/${repo}/pulls/${pr}/reviews`, {
@@ -91,4 +126,7 @@ dotenv.config({path: '.env.local', override: true});
if(!res.ok) throw new Error(`${res.status} ${await res.text()}`); 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); console.log(comments.map(c => `${c.path}${c.new_position ? `:${c.new_position}` : ''}\n${c.body}`).join('\n\n') + '\n\n' + summary);
})(); })().catch(err => {
console.error(`Error: ${err.message || err.toString()}`);
process.exit(1);
});