11 Commits
Author SHA1 Message Date
ztimson 9ff7beee1e Fixed output
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
ztimson 8cfcb3f95c Added checks
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
ztimson e625782eec Removed --no-cache, we are already using latest tag
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
ztimson 72ffe3dcc7 Added a release bot to create release notes from closed milestones
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
ztimson 1ab97c2676 Merge pull request 'review-labels' (#14) from review-labels into master
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
ztimson 7447204351 Fixed more logic checks
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
ztimson 3b01e1bfc1 Fixed label condition check 💀
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
ztimson 1460c3a0ae Added PR info as context to AI
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
ztimson ebc3da8605 Merge branch 'master' of git.zakscode.com:ztimson/ai-agents into review-labels
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
ztimson 677f84c97a Added label to enable review bot on PRs
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
ztimson f543e08e36 Merge pull request 'Added configurable labels to ticket refiner' (#13) from ticket-duplicates into master
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
8 changed files with 149 additions and 11 deletions
+1 -3
View File
@@ -9,7 +9,5 @@
## Checklist
<!-- Complete after creating PR -->
- [ ] Linked issues
- [ ] Reviewed changes
- [ ] Reviewed changes (or use `Review/AI` label)
- [ ] Updated comments/documentation
+2 -2
View File
@@ -2,7 +2,7 @@ name: Code review
on:
pull_request:
types: [opened, synchronize, reopened]
types: [opened, synchronize, reopened, labeled]
jobs:
review:
@@ -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
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 }}
+2 -1
View File
@@ -3,6 +3,7 @@ name: Ticket refinement
on:
issues:
types: [labeled]
jobs:
format:
runs-on: ubuntu-latest
@@ -13,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
+2 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@ztimson/ai-agents",
"version": "0.1.1",
"version": "0.1.3",
"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": {
+2 -1
View File
@@ -181,8 +181,9 @@ Output ONLY markdown. No explanations, labels, or extra formatting.`});
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 !== 'NONE' && (dupeId = dupeIds.find(id => id == hasDuplicates.trim()))) {
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'},
+90
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);
});
+23 -3
View File
@@ -19,12 +19,23 @@ dotenv.config({path: '.env.local', override: true, quiet: true, debug: false});
owner = process.env['GIT_OWNER'],
repo = process.env['GIT_REPO'],
auth = process.env['GIT_TOKEN'],
labelEnabled = process.env['LABEL_ENABLED'] || 'Review/AI',
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`);
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 commit = await $`cd ${root} && git log -1 --pretty=format:%H`;
@@ -53,7 +64,7 @@ dotenv.config({path: '.env.local', override: true, quiet: true, debug: false});
...options,
model: [host, model],
path: process.env['path'] || os.tmpdir(),
system: `You are a code reviewer. Analyze the git diff and use the \`recommend\` tool for EACH issue you find. You must call \`recommend\` exactly once for every bug or improvement opportunity directly related to changes. Ignore formatting recommendations. After making all recommendations, provide some concluding remarks about the overall state of the changes.${existingComments}`,
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: [{
name: 'read_file',
description: 'Read contents of a file',
@@ -87,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`, {