generated from ztimson/template
Compare commits
52 Commits
f65fd15220
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| ecba21bc70 | |||
| 19392b70a6 | |||
| e3d38d2df8 | |||
| 9ff7beee1e | |||
| 8cfcb3f95c | |||
| e625782eec | |||
| 72ffe3dcc7 | |||
| 1ab97c2676 | |||
| 7447204351 | |||
| 3b01e1bfc1 | |||
| 1460c3a0ae | |||
| ebc3da8605 | |||
| 677f84c97a | |||
| f543e08e36 | |||
| 5b9f8e0e13 | |||
| 019b05105a | |||
| decd533e4e | |||
| 7becf99be2 | |||
| 99a1e55471 | |||
| 23cb66544e | |||
| 9e5372f37b | |||
| eb4486f196 | |||
| f3df34ec47 | |||
| f2936ae4dc | |||
| e62e11fb75 | |||
| 604e04559b | |||
| 8add830d2b | |||
| 57bbc1fdb4 | |||
| 078892297e | |||
| 3c2d6f7824 | |||
| 2d9662e86d | |||
| 91d22b8b16 | |||
| c7f8ffb32a | |||
| 27fad6a3d3 | |||
| 3daf5442d8 | |||
| 002e809ef6 | |||
| d1c0b7a872 | |||
| 1b026529fe | |||
| af09bd0f53 | |||
| 397c9aeb90 | |||
| 1f48b5a872 | |||
| 6bb78d0862 | |||
| 73ad9293ae | |||
| 4175bf363c | |||
| c0281cd57c | |||
| a24961a55c | |||
| e0f3a3cd82 | |||
| 6fccede7ba | |||
| f00906045a | |||
| bee0029469 | |||
| 1b3232b10c | |||
| 40ade3fef1 |
19
.github/issue_template/ai-refinement.md
vendored
Normal file
19
.github/issue_template/ai-refinement.md
vendored
Normal 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?
|
||||||
4
.github/pull_request_template.md
vendored
4
.github/pull_request_template.md
vendored
@@ -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
|
||||||
|
|
||||||
67
.github/workflows/build.yml
vendored
67
.github/workflows/build.yml
vendored
@@ -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
|
||||||
run: |
|
uses: ztimson/actions/clone@develop
|
||||||
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"
|
- name: Publish Library
|
||||||
if [ "$DOCKER_HUB" = "true" ]; then docker login -u "${{secrets.DOCKER_HUB_USER}}" -p "${{secrets.DOCKER_HUB_TOKEN}}" docker.io; fi
|
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"
|
||||||
|
|
||||||
docker build -t "$REGISTRY/${{github.repository}}:${{github.ref_name}}" .
|
REGISTRY="https://registry.npmjs.org/"
|
||||||
docker push "$REGISTRY/${{github.repository}}:${{github.ref_name}}"
|
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}}:${{github.ref_name}}"
|
npm publish || echo "Failed to publish"
|
||||||
docker push "docker.io/${{secrets.DOCKER_HUB_IMAGE}}:${{github.ref_name}}"
|
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
|
||||||
|
|
||||||
if [ "${{github.ref_name}}" = "master" ]; then
|
- name: Get Version Number
|
||||||
docker tag "$REGISTRY/${{github.repository}}:${{github.ref_name}}" "$REGISTRY/${{github.repository}}:$VERSION"
|
run: echo "VERSION=$(cat package.json | grep version | grep -Eo ':.+' | grep -Eo '[[:alnum:]\.\/\-]+')" >> $GITHUB_ENV
|
||||||
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"
|
- name: Tag Version
|
||||||
docker push "$REGISTRY/${{github.repository}}:latest"
|
uses: ztimson/actions/tag@develop
|
||||||
if [ "$DOCKER_HUB" = "true" ]; then
|
with:
|
||||||
docker tag "$REGISTRY/${{github.repository}}:${{github.ref_name}}" "docker.io/${{secrets.DOCKER_HUB_IMAGE}}:latest"
|
tag: ${{env.VERSION}}
|
||||||
docker push "docker.io/${{secrets.DOCKER_HUB_IMAGE}}:latest"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|||||||
29
.github/workflows/code-review.yml
vendored
Normal file
29
.github/workflows/code-review.yml
vendored
Normal 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
27
.github/workflows/release-creator.yml
vendored
Normal 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 }}
|
||||||
33
.github/workflows/review.yml
vendored
33
.github/workflows/review.yml
vendored
@@ -1,33 +0,0 @@
|
|||||||
name: Code review
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
types: [opened, synchronize, reopened]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
review:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
container: docker
|
|
||||||
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
27
.github/workflows/ticket-refinement.yml
vendored
Normal 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 }}
|
||||||
@@ -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"]
|
|
||||||
67
README.md
67
README.md
@@ -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 -->
|
||||||
[](https://git.zakscode.com/ztimson/ai-reviewer/tags)
|
[](https://git.zakscode.com/ztimson/ai-agents/tags)
|
||||||
[](https://git.zakscode.com/ztimson/ai-reviewer/pulls)
|
[](https://git.zakscode.com/ztimson/ai-agents/pulls)
|
||||||
[](https://git.zakscode.com/ztimson/ai-reviewer/issues)
|
[](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
|
||||||
[](https://docker.com/)
|
[](https://docker.com/)
|
||||||
@@ -53,43 +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
|
|
||||||
container: docker
|
|
||||||
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>
|
||||||
|
|
||||||
|
|||||||
24
package.json
24
package.json
@@ -1,17 +1,23 @@
|
|||||||
{
|
{
|
||||||
"name": "ai-reviewer",
|
"name": "@ztimson/ai-agents",
|
||||||
"version": "0.0.0",
|
"version": "0.1.5",
|
||||||
"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
217
src/refine.mjs
Normal 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
90
src/release.mjs
Normal 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);
|
||||||
|
});
|
||||||
@@ -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,56 @@ 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}`, {
|
||||||
|
headers: {'Authorization': `token ${auth}`}
|
||||||
|
}).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 +65,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 +99,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 +127,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);
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user