generated from ztimson/template
Added ticket refinement bot
This commit is contained in:
53
.github/issue_template/ai-refinement.md
vendored
Normal file
53
.github/issue_template/ai-refinement.md
vendored
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
---
|
||||||
|
|
||||||
|
name: "AI refinement"
|
||||||
|
about: "Use AI to refine ticket"
|
||||||
|
ref: "develop"
|
||||||
|
labels:
|
||||||
|
- Review/AI
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# [Module] - [Add/Change/Fix/Refactor/Remove] [Feature/Component]
|
||||||
|
|
||||||
|
## Type: [Bug/DevOps/Enhancement/Refactor/Security]
|
||||||
|
|
||||||
|
| | Score |
|
||||||
|
|------------|----------|
|
||||||
|
| Size | 0-5 |
|
||||||
|
| Complexity | 0-5 |
|
||||||
|
| Unknowns | 0-5 |
|
||||||
|
| **Total** | **0-15** |
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
A clear explanation of the issue, feature, or change needed
|
||||||
|
|
||||||
|
## Current Behavior
|
||||||
|
|
||||||
|
For bugs: what's happening now
|
||||||
|
For refactors: what exists today
|
||||||
|
For enhancements: current state/gap
|
||||||
|
|
||||||
|
## Expected Behavior
|
||||||
|
|
||||||
|
What should happen instead
|
||||||
|
|
||||||
|
## Steps to Reproduce
|
||||||
|
|
||||||
|
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
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
name: Code review
|
name: Code review
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [opened, synchronize, reopened]
|
types: [opened, synchronize, reopened]
|
||||||
26
.github/workflows/ticket-refinement.yml
vendored
Normal file
26
.github/workflows/ticket-refinement.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
name: Ticket refinement
|
||||||
|
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types: [opened, 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 @ztimson/ai-agents@latest format .github/issue_templates/ai-refinement.md
|
||||||
|
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,12 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "@ztimson/ai-agents",
|
"name": "@ztimson/ai-agents",
|
||||||
"version": "0.0.3",
|
"version": "0.0.4",
|
||||||
"description": "AI agents",
|
"description": "AI agents",
|
||||||
"keywords": ["ai", "review"],
|
"keywords": ["ai", "review"],
|
||||||
"author": "ztimson",
|
"author": "ztimson",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
"refine": "./src/refine.mjs",
|
||||||
"review": "./src/review.mjs"
|
"review": "./src/review.mjs"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
106
src/refine.mjs
Normal file
106
src/refine.mjs
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
#!/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 === 'review' || p.endsWith('review.mjs')) p = null;
|
||||||
|
if(!/(\/|[A-Z]:)/.test(p)) p = path.join(process.cwd(), p);
|
||||||
|
|
||||||
|
if(!p || !fs.existsSync(p)) throw new Error('Please provide a template');
|
||||||
|
|
||||||
|
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 issueRes = await fetch(`${git}/api/v1/repos/${owner}/${repo}/issues/${ticket}`, {
|
||||||
|
headers: {'Authorization': `token ${auth}`}
|
||||||
|
});
|
||||||
|
if(!issueRes.ok) throw new Error(`${issueRes.status} ${await issueRes.text()}`);
|
||||||
|
const issueData = await issueRes.json();
|
||||||
|
if(!issueData.labels?.some(l => l.name === 'Review/AI')) {
|
||||||
|
console.log('Skipping');
|
||||||
|
return process.exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
let readme = '', readmeP = path.join(process.cwd(), 'README.md');
|
||||||
|
if(fs.existsSync(readmeP)) readme = fs.readFileSync(readmeP, 'utf-8');
|
||||||
|
const template = fs.readFileSync(p, 'utf-8');
|
||||||
|
|
||||||
|
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(),
|
||||||
|
system: `You are a ticket formatter. Transform raw issue descriptions into structured tickets.
|
||||||
|
|
||||||
|
**CRITICAL RULES:**
|
||||||
|
1. Identify the ticket type (Bug, DevOps, Enhancement, Refactor, Security)
|
||||||
|
2. Output MUST only contain the new ticket information in markdown, no extra fluff
|
||||||
|
3. Follow the template structure EXACTLY:
|
||||||
|
- Title format: [Module] - [Verb] [noun]
|
||||||
|
Example: Storage - Fix file uploads
|
||||||
|
- Fill in the identified ticket type
|
||||||
|
- Write a clear description
|
||||||
|
- For bugs: fill Steps to Reproduce with numbered list
|
||||||
|
- For enhancements/refactors: REMOVE the Steps to Reproduce section entirely
|
||||||
|
- Acceptance Criteria: convert requirements into checkboxes (- [ ])
|
||||||
|
- Weight scoring (0-5 each):
|
||||||
|
* Size: Number of modules, layers & files affected by change
|
||||||
|
* Complexity: Technical difficulty to implement
|
||||||
|
* Unknowns: Research/uncertainty in work estimation
|
||||||
|
* Calculate Total as sum of the three
|
||||||
|
- Remove sections that are not applicable based on ticket type
|
||||||
|
- Use proper markdown headers (##)
|
||||||
|
|
||||||
|
**README:**
|
||||||
|
\`\`\`markdown
|
||||||
|
${readme.trim() || 'No README available'}
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
**TEMPLATE:**
|
||||||
|
\`\`\`markdown
|
||||||
|
${template.trim()}
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
Output ONLY the formatted ticket, no explanation.`
|
||||||
|
});
|
||||||
|
|
||||||
|
const messages = await ai.language.ask(`Title: ${issueData.title}\n\nDescription:\n${issueData.body || 'No description provided'}`);
|
||||||
|
const content = messages.pop().content;
|
||||||
|
const title = /^# (.+)$/m.exec(content)?.[1] || issueData.title;
|
||||||
|
const typeMatch = /^## Type:\s*(.+)$/m.exec(content);
|
||||||
|
const type = typeMatch?.[1]?.split('/')[0]?.trim() || 'Unassigned';
|
||||||
|
const body = content.replace(/^# .+$/m, '').replace(/^## Type:.+$/m, '').trim();
|
||||||
|
const updateRes = 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,
|
||||||
|
labels: [`Type/${type[0].toUpperCase() + type.slice(1).toLowerCase()}`]
|
||||||
|
})
|
||||||
|
});
|
||||||
|
if(!updateRes.ok) throw new Error(`${updateRes.status} ${await updateRes.text()}`);
|
||||||
|
console.log(body);
|
||||||
|
})();
|
||||||
Reference in New Issue
Block a user