generated from ztimson/template
Compare commits
10 Commits
ebc3da8605
...
0.1.4
| Author | SHA1 | Date | |
|---|---|---|---|
| 19392b70a6 | |||
| e3d38d2df8 | |||
| 9ff7beee1e | |||
| 8cfcb3f95c | |||
| e625782eec | |||
| 72ffe3dcc7 | |||
| 1ab97c2676 | |||
| 7447204351 | |||
| 3b01e1bfc1 | |||
| 1460c3a0ae |
2
.github/workflows/code-review.yml
vendored
2
.github/workflows/code-review.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
git checkout ${{ github.event.pull_request.head.sha }}
|
||||
git fetch origin ${{ github.event.pull_request.base.ref }}
|
||||
|
||||
- name: Run AI Review
|
||||
- name: Create review
|
||||
run: npx -y -p @ztimson/ai-agents@latest review $GITHUB_WORKSPACE
|
||||
env:
|
||||
AI_HOST: anthropic
|
||||
|
||||
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 }}
|
||||
2
.github/workflows/ticket-refinement.yml
vendored
2
.github/workflows/ticket-refinement.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
git clone "$(echo ${{github.server_url}}/${{github.repository}}.git | sed s%://%://${{github.token}}@% )" .
|
||||
git checkout ${{ github.event.repository.default_branch }}
|
||||
|
||||
- name: Run AI Formatter
|
||||
- name: Refine ticket
|
||||
run: npx -y -p @ztimson/ai-agents@latest refine
|
||||
env:
|
||||
AI_HOST: anthropic
|
||||
|
||||
@@ -37,9 +37,12 @@ AI-powered Gitea agents for automating reviews and administration
|
||||
|
||||
## About
|
||||
|
||||
Only supports Gitea
|
||||
AI-powered Gitea agents for automating administration of code repos:
|
||||
- Code Review
|
||||
- Release Notes
|
||||
- Ticket Refinement
|
||||
|
||||
Use LLM models from Anthropic, OpenAI, or Ollama to automate ticket refinement, code reviews, and releases.
|
||||
Only supports Gitea, copy the relevant `.github/workflows/______.yml` action to start using it
|
||||
|
||||
### Built With
|
||||
[](https://docker.com/)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ztimson/ai-agents",
|
||||
"version": "0.1.2",
|
||||
"version": "0.1.4",
|
||||
"description": "AI agents",
|
||||
"keywords": ["ai", "review"],
|
||||
"author": "ztimson",
|
||||
@@ -8,6 +8,7 @@
|
||||
"type": "module",
|
||||
"bin": {
|
||||
"refine": "./src/refine.mjs",
|
||||
"release": "./src/release.mjs",
|
||||
"review": "./src/review.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
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);
|
||||
});
|
||||
@@ -25,10 +25,13 @@ dotenv.config({path: '.env.local', override: true, quiet: true, debug: false});
|
||||
model = process.env['AI_MODEL'],
|
||||
token = process.env['AI_TOKEN'];
|
||||
|
||||
console.log(`Reviewing: ${root}\n`);
|
||||
console.log(`Reviewing: ${root}`);
|
||||
const info = await fetch(`${git}/api/v1/repos/${owner}/${repo}/pulls/${pr}`)
|
||||
.then(async resp => { return resp.ok ? resp.json() : throw new Error(`${resp.status} ${await resp.text()}`); });
|
||||
if(info.labels?.length > 0 || !info.labels.some(l => l.name === labelEnabled)) {
|
||||
.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();
|
||||
}
|
||||
@@ -95,7 +98,16 @@ dotenv.config({path: '.env.local', override: true, quiet: true, debug: false});
|
||||
}]
|
||||
});
|
||||
|
||||
const messages = await ai.language.ask(gitDiff);
|
||||
const messages = await ai.language.ask(`Title: ${info.title || 'None'}
|
||||
Description:
|
||||
\`\`\`md
|
||||
${info.body || 'None'}
|
||||
\`\`\`
|
||||
|
||||
Git Diff:
|
||||
\`\`\`
|
||||
${gitDiff}
|
||||
\`\`\``);
|
||||
const summary = messages.pop().content;
|
||||
if(git) {
|
||||
const res = await fetch(`${git}/api/v1/repos/${owner}/${repo}/pulls/${pr}/reviews`, {
|
||||
|
||||
Reference in New Issue
Block a user