13 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
5 changed files with 76 additions and 35 deletions

View File

@@ -16,4 +16,4 @@ How can it be fixed or improved?
Steps to reproduce? Steps to reproduce?
Anything other useful information, logs or screenshots? Any other useful information? Logs, screenshots, steps to reproduce?

View File

@@ -16,7 +16,7 @@ jobs:
git fetch origin ${{ github.event.pull_request.base.ref }} git fetch origin ${{ github.event.pull_request.base.ref }}
- name: Run AI Review - name: Run AI Review
run: npx -y -p @ztimson/ai-agents@latest review run: npx -y -p @ztimson/ai-agents@latest review $GITHUB_WORKSPACE
env: env:
AI_HOST: anthropic AI_HOST: anthropic
AI_MODEL: claude-sonnet-4-5 AI_MODEL: claude-sonnet-4-5

View File

@@ -1,6 +1,6 @@
{ {
"name": "@ztimson/ai-agents", "name": "@ztimson/ai-agents",
"version": "0.0.4", "version": "0.1.0",
"description": "AI agents", "description": "AI agents",
"keywords": ["ai", "review"], "keywords": ["ai", "review"],
"author": "ztimson", "author": "ztimson",

View File

@@ -26,16 +26,18 @@ dotenv.config({path: '.env.local', override: true, quiet: true});
console.log(`Processing issue #${ticket}`); console.log(`Processing issue #${ticket}`);
// Fetch issue // Fetch issue
const issueRes = await fetch(`${git}/api/v1/repos/${owner}/${repo}/issues/${ticket}`, { const issueData = await fetch(`${git}/api/v1/repos/${owner}/${repo}/issues/${ticket}`, {
headers: {'Authorization': `token ${auth}`} headers: {'Authorization': `token ${auth}`}
}).then(async resp => {
if(resp.ok) return resp.json();
else throw new Error(`${resp.status} ${await resp.text()}`);
}); });
if(!issueRes.ok) throw new Error(`${issueRes.status} ${await issueRes.text()}`); if(issueData.labels?.[0] !== 1 || issueData.labels?.[0]?.name !== 'Review/AI') {
const issueData = await issueRes.json();
if(!issueData.labels?.some(l => l.name === 'Review/AI')) {
console.log('Skipping'); console.log('Skipping');
return process.exit(); return process.exit();
} }
// Gather readme & template
let title = '', type = '', readme = '', readmeP = path.join(process.cwd(), 'README.md'); let title = '', type = '', readme = '', readmeP = path.join(process.cwd(), 'README.md');
if(fs.existsSync(readmeP)) readme = fs.readFileSync(readmeP, 'utf-8'); if(fs.existsSync(readmeP)) readme = fs.readFileSync(readmeP, 'utf-8');
const template = p ? fs.readFileSync(p, 'utf-8') : `## Description const template = p ? fs.readFileSync(p, 'utf-8') : `## Description
@@ -80,6 +82,7 @@ Implementation details, constraints, dependencies, design decisions
| **Total** | **0-15** | | **Total** | **0-15** |
`; `;
// Create AI
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}};
@@ -90,7 +93,7 @@ Implementation details, constraints, dependencies, design decisions
tools: [{ tools: [{
name: 'title', name: 'title',
description: 'Set the ticket title, must be called EXACTLY ONCE', description: 'Set the ticket title, must be called EXACTLY ONCE',
args: {value: {type: 'string', description: 'Ticket title, must match format: [Module] - [Verb] [noun]', required: true}}, args: {title: {type: 'string', description: 'Ticket title, must match format: Module - Verb noun', required: true}},
fn: (args) => title = args.title fn: (args) => title = args.title
}, { }, {
name: 'type', name: 'type',
@@ -103,7 +106,7 @@ Implementation details, constraints, dependencies, design decisions
**MANDATORY STEPS:** **MANDATORY STEPS:**
1. Identify ticket type: Bug, DevOps, Document, Enhancement, Refactor, or Security 1. Identify ticket type: Bug, DevOps, Document, Enhancement, Refactor, or Security
2. Call \`type\` tool EXACTLY ONCE with the type from step 1 2. Call \`type\` tool EXACTLY ONCE with the type from step 1
3. Call \`title\` tool EXACTLY ONCE in format: "[Module] - [Verb] [subject]" 3. Call \`title\` tool EXACTLY ONCE in format: "[Module] - [Verb] [subject]" (example: Storage - fix file uploads)
4. Output formatted markdown matching template structure below 4. Output formatted markdown matching template structure below
**TEMPLATE RULES:** **TEMPLATE RULES:**
@@ -141,32 +144,67 @@ ${template.trim()}
Output ONLY markdown. No explanations, labels, or extra formatting.`}); 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 messages = await ai.language.ask(`Title: ${issueData.title}\n\nDescription:\n${issueData.body || 'No description provided'}`).catch(() => []);
const body = messages?.pop()?.content; const body = messages?.pop()?.content;
if(!body) { if(!body) throw new Error('Invalid response from AI');
console.log('Invalid response from AI');
return process.exit(1); // 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 updateRes = await fetch(`${git}/api/v1/repos/${owner}/${repo}/issues/${ticket}`, { const search = await fetch(`${git}/api/v1/repos/issues/search`, {
method: 'PATCH',
headers: {
'Authorization': `token ${auth}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
title,
body,
})
});
if(!updateRes.ok) throw new Error(`${updateRes.status} ${await updateRes.text()}`);
if(type) fetch(`${git}/api/v1/repos/${owner}/${repo}/issues/${ticket}/labels`, {
method: 'POST', method: 'POST',
headers: { headers: {'Authorization': `token ${auth}`, 'Content-Type': 'application/json'},
'Authorization': `token ${auth}`, body: JSON.stringify({
'Content-Type': 'application/json' owner,
}, priority_repo_id: repoInfo.id,
body: `["Kind/${type[0].toUpperCase() + type.slice(1).toLowerCase()}"]` 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}`); console.log(`Title: ${title}\nType: ${type}\nBody:\n${body}`);
})(); })().catch(err => {
console.error(`Error: ${err.message || err.toString()}`);
process.exit(1);
});

View File

@@ -43,7 +43,7 @@ dotenv.config({path: '.env.local', override: true, quiet: true, debug: false});
const comments = await Promise.all(reviews.map(r => fetch(`${git}/api/v1/repos/${owner}/${repo}/pulls/${pr}/reviews/${r.id}/comments`, { 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}`} headers: {'Authorization': `token ${auth}`}
}).then(resp => resp.ok ? resp.json() : []))); }).then(resp => resp.ok ? resp.json() : [])));
existingComments += comments.flatten().map(c => `${c.path}:${c.position}\n${c.body}`).join('\n\n'); 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}};
@@ -106,4 +106,7 @@ dotenv.config({path: '.env.local', override: true, quiet: true, debug: false});
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);
});