49 Commits

Author SHA1 Message Date
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
97895096f3 Fixed review build
Some checks failed
Code review / review (pull_request) Failing after 1s
Build and publish / Build Container (push) Successful in 59s
2025-12-27 20:34:22 -05:00
001d016a8a Fixed review build
Some checks failed
Code review / review (pull_request) Failing after 1s
Build and publish / Build Container (push) Successful in 1m0s
2025-12-27 20:11:52 -05:00
882b845b11 Fixed review build
Some checks failed
Code review / review (pull_request) Failing after 1s
Build and publish / Build Container (push) Successful in 1m1s
2025-12-27 20:06:29 -05:00
f50324a072 Fixed review build
Some checks failed
Code review / review (pull_request) Failing after 1s
Build and publish / Build Container (push) Successful in 1m11s
2025-12-27 20:01:11 -05:00
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
12 changed files with 2247 additions and 135 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=

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?

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

@@ -0,0 +1,45 @@
name: Publish Library
run-name: Publish Library
on:
push:
jobs:
build:
name: Build NPM Project
runs-on: ubuntu-latest
container: node:alpine
steps:
- name: Clone Repository
uses: ztimson/actions/clone@develop
- name: Publish Library
run: |
if [ "${{github.ref_name}}" = "master" ]; then
REGISTRY="${{github.server_url}}/api/packages/${{github.repository_owner}}/npm/"
npm set registry "$REGISTRY"
npm set $(echo $REGISTRY | sed s%http:%% | sed s%https:%% ):_authToken "${{secrets.DEPLOY_TOKEN}}"
npm publish || echo "Failed to publish"
REGISTRY="https://registry.npmjs.org/"
npm set registry "$REGISTRY"
npm set $(echo $REGISTRY | sed s%http:%% | sed s%https:%% ):_authToken "${{secrets.NPM_TOKEN}}"
npm publish || echo "Failed to publish"
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]
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: Run AI 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 }}

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

@@ -0,0 +1,26 @@
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: Run AI Formatter
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 }}

1
.gitignore vendored Normal file
View File

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

View File

@@ -3,94 +3,48 @@
<br /> <br />
<!-- Logo --> <!-- Logo -->
<img src="https://git.zakscode.com/repo-avatars/2b4ee6ba1f2e2618bf7694e4a52fb56d1d0ea6abafa2dcbe496ab786b86d5a76" alt="Logo" width="200" height="200"> <img src="https://git.zakscode.com/repo-avatars/309c233243bcd1c1e9b3f359ec3f59769bb01b655e8ed7b32587781be4c8b21c" alt="Logo" width="200" height="200">
<!-- Title --> <!-- Title -->
### Template ### AI Agents
<!-- Description --> <!-- Description -->
Simple repository template 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/template/tags&query=$[0].name)](https://git.zakscode.com/ztimson/template/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/template&query=open_pr_counter)](https://git.zakscode.com/ztimson/template/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/template&query=open_issues_count)](https://git.zakscode.com/ztimson/template/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/template/wiki" target="_blank">Documentation</a> <a href="https://git.zakscode.com/ztimson/ai-agents/releases" target="_blank">Release Notes</a>
• <a href="https://git.zakscode.com/ztimson/template/releases" target="_blank">Release Notes</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/template/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%2fenhancement.md" target="_blank">Request a Feature</a>
• <a href="https://git.zakscode.com/ztimson/template/issues/new?template=.github%2fissue_template%2fenhancement.md" target="_blank">Request a Feature</a>
</div> </div>
--- ---
</div> </div>
## Table of Contents ## Table of Contents
- [Template](#top) - [AI Agents](#top)
- [About](#about) - [About](#about)
- [Demo](#demo)
- [Built With](#built-with) - [Built With](#built-with)
- [Setup](#setup) - [Setup](#setup)
- [Production](#production) - [Production](#production)
- [Development](#development)
- [License](#license) - [License](#license)
## About ## 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. Only supports Gitea
### Demo Use LLM models from Anthropic, OpenAI, or Ollama to automate ticket refinement, code reviews, and releases.
Website: https://git.zakscode.com
### Built With ### 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/) [![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/) [![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/) [![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 ## Setup
@@ -102,11 +56,11 @@ Website: https://git.zakscode.com
</summary> </summary>
#### Prerequisites #### Prerequisites
- [Docker](https://docs.docker.com/install/) - [Node.js](https://nodejs.org/en/download)
#### Instructions #### Instructions
1. Run the docker image: `docker run -p 80:80 git.zakscode.com/ztimson/template:latest` 1. Run using npx: `npx -y @ztimson/ai-agents@latest review`
2. Open [http://localhost](http://localhost)
</details> </details>
<details> <details>
@@ -120,13 +74,13 @@ Website: https://git.zakscode.com
- [Node.js](https://nodejs.org/en/download) - [Node.js](https://nodejs.org/en/download)
#### Instructions #### Instructions
1. Install the dependencies: `npm install` 1. Install the dependencies: `npm i`
2. Start the Angular server: `npm run start` 2. Build library: `npm run review`
3. Open [http://localhost:4200](http://localhost:4200)
</details> </details>
## License ## 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. 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

@@ -1,16 +1,22 @@
{ {
"name": "ai-reviewer", "name": "@ztimson/ai-agents",
"version": "0.0.0", "version": "0.1.0",
"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",
"review": "./src/review.mjs"
},
"dependencies": { "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/utils": "^0.28.3",
"@ztimson/node-utils": "^1.0.4" "dotenv": "^17.2.3"
} },
"files": [
"src/"
]
} }

210
src/refine.mjs Normal file
View File

@@ -0,0 +1,210 @@
#!/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});
dotenv.config({path: '.env.local', override: true, quiet: true});
(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'],
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?.[0] !== 1 || issueData.labels?.[0]?.name !== 'Review/AI') {
console.log('Skipping');
return process.exit();
}
// Gather readme & template
let title = '', type = '', 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: 'type',
description: 'Set the ticket type, must be called EXACTLY ONCE',
args: {type: {type: 'string', description: 'Ticket type', enum: ['Bug', 'DevOps', 'Document', 'Enhancement', 'Refactor', 'Security'], required: true}},
fn: (args) => type = args.type
}],
system: `Transform raw tickets into structured markdown following the template EXACTLY.
**MANDATORY STEPS:**
1. Identify ticket type: Bug, DevOps, Document, Enhancement, Refactor, or Security
2. Call \`type\` tool EXACTLY ONCE with the type from step 1
3. Call \`title\` tool EXACTLY ONCE in format: "[Module] - [Verb] [subject]" (example: Storage - fix file uploads)
4. Output formatted markdown matching template structure below
**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 changed
- Complexity: Technical difficulty
- Unknowns: Research/uncertainty needed
**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(`${title}\n\`\`\`markdown\n${body}\n\`\`\``, {
system: `Your job is to identify duplicates. Respond with the ID number of the duplicate or nothing if there are no matches \n\n${dupes}`
}))?.pop()?.content;
// Handle duplicates
if(!!hasDuplicates && (dupeId = dupeIds.find(id => new RegExp(`\\b${id}\\b`, 'm').test(hasDuplicates)))) {
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":["Reviewed/Duplicate"]}'
}).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()}`); });
if(type) { // Label
await fetch(`${git}/api/v1/repos/${owner}/${repo}/issues/${ticket}/labels`, {
method: 'POST',
headers: {'Authorization': `token ${auth}`, 'Content-Type': 'application/json'},
body: `{"labels":["Reviewed/${type[0].toUpperCase() + type.slice(1).toLowerCase()}"]}`
}).then(async resp => { if(!resp.ok) throw new Error(`${resp.status} ${await resp.text()}`); });
}
console.log(`Title: ${title}\nType: ${type}\nBody:\n${body}`);
})().catch(err => {
console.error(`Error: ${err.message || err.toString()}`);
process.exit(1);
});

View File

@@ -1,31 +1,59 @@
/** #!/usr/bin/env node
* 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 {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';
import * as path from 'node:path'; import * as path from 'node:path';
import * as fs from 'node:fs'; import * as fs from 'node:fs';
import * as dotenv from 'dotenv';
const gitDiff = await $`git diff HEAD^..HEAD`; dotenv.config({quiet: true, debug: false});
const root = process.env['ROOT'] || process.cwd(), dotenv.config({path: '.env.local', override: true, quiet: true, debug: false});
host = process.env['HOST'],
model = process.env['MODEL'], (async () => {
token = process.env['TOKEN']; let p = process.argv[process.argv.length - 1];
if(p === 'review' || p.endsWith('review.mjs')) p = null;
const root = p || process.cwd(),
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'];
console.log(`Reviewing: ${root}\n`);
const branch = process.env['GIT_BRANCH'] || await $`cd ${root} && git symbolic-ref refs/remotes/origin/HEAD`;
const comments = [];
const commit = await $`cd ${root} && git log -1 --pretty=format:%H`;
const gitDiff = await $`cd ${root} && git diff ${branch}`;
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}};
else if(host === 'openai') options = {openAi: {model, token}}; else if(host === 'openai') options = {openAi: {model, token}};
const ai = new Ai({ const ai = new Ai({
...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, use the following git diff to find any bugs and recommend changes using the `recommend` tool.', 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 some concluding remarks about the overall state of the changes.${existingComments}`,
tools: [{ tools: [{
name: 'read_file', name: 'read_file',
description: 'Read contents of a file', description: 'Read contents of a file',
@@ -42,17 +70,43 @@ const ai = new Ai({
fn: async (args) => await $`git diff HEAD^..HEAD -- ${path.join(root, args.path)}` fn: async (args) => await $`git diff HEAD^..HEAD -- ${path.join(root, args.path)}`
}, { }, {
name: 'recommend', name: 'recommend',
description: 'Make review recommendation', description: 'REQUIRED: Call this once for every bug, improvement, or concern identified in the review.',
args: { args: {
file: {type: 'string', description: 'File path'}, file: {type: 'string', description: 'File path'},
line: {type: 'number', description: 'Line number', optional: true}, line: {type: 'number', description: 'Line number in new file'},
comment: {type: 'string', description: 'Review comment'} comment: {type: 'string', description: 'Review comment explaining the issue'}
}, },
fn: (args) => { fn: (args) => {
console.log(`💬 ${args.file}${args.line ? `:${args.line}` : ''} - ${args.comment}`); comments.push({
// TODO: Add fetch to gitea to add comment to PR path: args.file,
new_position: args.line,
body: args.comment,
});
return 'Comment recorded, continue reviewing';
} }
}] }]
}); });
await ai.language.ask(gitDiff); 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);
})().catch(err => {
console.error(`Error: ${err.message || err.toString()}`);
process.exit(1);
});