@ztimson/ai-agents 0.0.4 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/package.json +1 -1
- package/src/refine.mjs +99 -34
- package/src/review.mjs +7 -11
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
### AI Agents
|
|
10
10
|
|
|
11
11
|
<!-- Description -->
|
|
12
|
-
|
|
12
|
+
AI-powered Gitea agents for automating reviews and administration
|
|
13
13
|
|
|
14
14
|
<!-- Repo badges -->
|
|
15
15
|
[](https://git.zakscode.com/ztimson/ai-agents/tags)
|
|
@@ -37,7 +37,9 @@ Automated AI-powered agents for automated reviews and code assistance
|
|
|
37
37
|
|
|
38
38
|
## About
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
Only supports Gitea
|
|
41
|
+
|
|
42
|
+
Use LLM models from Anthropic, OpenAI, or Ollama to automate ticket refinement, code reviews, and releases.
|
|
41
43
|
|
|
42
44
|
### Built With
|
|
43
45
|
[](https://docker.com/)
|
package/package.json
CHANGED
package/src/refine.mjs
CHANGED
|
@@ -14,8 +14,6 @@ dotenv.config({path: '.env.local', override: true, quiet: true});
|
|
|
14
14
|
if(p === 'refine' || p.endsWith('refine.mjs')) p = null;
|
|
15
15
|
if(!/^(\/|[A-Z]:)/m.test(p)) p = path.join(process.cwd(), p);
|
|
16
16
|
|
|
17
|
-
if(!p || !fs.existsSync(p)) throw new Error('Please provide a template');
|
|
18
|
-
|
|
19
17
|
const git = process.env['GIT_HOST'],
|
|
20
18
|
owner = process.env['GIT_OWNER'],
|
|
21
19
|
repo = process.env['GIT_REPO'],
|
|
@@ -38,9 +36,49 @@ dotenv.config({path: '.env.local', override: true, quiet: true});
|
|
|
38
36
|
return process.exit();
|
|
39
37
|
}
|
|
40
38
|
|
|
41
|
-
let readme = '', readmeP = path.join(process.cwd(), 'README.md');
|
|
39
|
+
let title = '', type = '', readme = '', readmeP = path.join(process.cwd(), 'README.md');
|
|
42
40
|
if(fs.existsSync(readmeP)) readme = fs.readFileSync(readmeP, 'utf-8');
|
|
43
|
-
const template = fs.readFileSync(p, 'utf-8')
|
|
41
|
+
const template = p ? fs.readFileSync(p, 'utf-8') : `## Description
|
|
42
|
+
|
|
43
|
+
A clear explanation of the request
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Current Behavior
|
|
48
|
+
|
|
49
|
+
what's happening now or the current state/gap
|
|
50
|
+
|
|
51
|
+
## Expected Behavior
|
|
52
|
+
|
|
53
|
+
What should happen instead
|
|
54
|
+
|
|
55
|
+
## Steps to Reproduce / Desired Flow
|
|
56
|
+
|
|
57
|
+
1. First step
|
|
58
|
+
2. Second step
|
|
59
|
+
3. Third step
|
|
60
|
+
|
|
61
|
+
## Additional Context
|
|
62
|
+
|
|
63
|
+
Logs, screenshots, links, related issues
|
|
64
|
+
|
|
65
|
+
## Acceptance Criteria
|
|
66
|
+
|
|
67
|
+
- [ ] Todo requirement
|
|
68
|
+
- [X] Completed requirement
|
|
69
|
+
|
|
70
|
+
## Technical Notes
|
|
71
|
+
|
|
72
|
+
Implementation details, constraints, dependencies, design decisions
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
| Effort / Weight | Score |
|
|
76
|
+
|-----------------|----------|
|
|
77
|
+
| Size | 0-5 |
|
|
78
|
+
| Complexity | 0-5 |
|
|
79
|
+
| Unknowns | 0-5 |
|
|
80
|
+
| **Total** | **0-15** |
|
|
81
|
+
`;
|
|
44
82
|
|
|
45
83
|
let options = {ollama: {model, host}};
|
|
46
84
|
if(host === 'anthropic') options = {anthropic: {model, token}};
|
|
@@ -49,26 +87,47 @@ dotenv.config({path: '.env.local', override: true, quiet: true});
|
|
|
49
87
|
...options,
|
|
50
88
|
model: [host, model],
|
|
51
89
|
path: process.env['path'] || os.tmpdir(),
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
90
|
+
tools: [{
|
|
91
|
+
name: 'title',
|
|
92
|
+
description: 'Set the ticket title, must be called EXACTLY ONCE',
|
|
93
|
+
args: {title: {type: 'string', description: 'Ticket title, must match format: [Module] - [Verb] [noun]', required: true}},
|
|
94
|
+
fn: (args) => title = args.title
|
|
95
|
+
}, {
|
|
96
|
+
name: 'type',
|
|
97
|
+
description: 'Set the ticket type, must be called EXACTLY ONCE',
|
|
98
|
+
args: {type: {type: 'string', description: 'Ticket type', enum: ['Bug', 'DevOps', 'Document', 'Enhancement', 'Refactor', 'Security'], required: true}},
|
|
99
|
+
fn: (args) => type = args.type
|
|
100
|
+
}],
|
|
101
|
+
system: `Transform raw tickets into structured markdown following the template EXACTLY.
|
|
102
|
+
|
|
103
|
+
**MANDATORY STEPS:**
|
|
104
|
+
1. Identify ticket type: Bug, DevOps, Document, Enhancement, Refactor, or Security
|
|
105
|
+
2. Call \`type\` tool EXACTLY ONCE with the type from step 1
|
|
106
|
+
3. Call \`title\` tool EXACTLY ONCE in format: "[Module] - [Verb] [subject]"
|
|
107
|
+
4. Output formatted markdown matching template structure below
|
|
108
|
+
|
|
109
|
+
**TEMPLATE RULES:**
|
|
110
|
+
- Use ## headers (match template exactly)
|
|
111
|
+
- Description: Clear summary of the request
|
|
112
|
+
- Current Behavior: What's happening now (remove for Document tickets)
|
|
113
|
+
- Expected Behavior: What should happen (remove for Document tickets)
|
|
114
|
+
- Steps to Reproduce: Numbered list for bugs, flow for enhancements, remove if not applicable
|
|
115
|
+
- Additional Context: Logs, screenshots, links provided by user
|
|
116
|
+
- Acceptance Criteria: Convert to checkboxes (- [ ] format)
|
|
117
|
+
- Technical Notes: Implementation approach, constraints, dependencies
|
|
118
|
+
- Weight table (use exact format below):
|
|
119
|
+
|
|
120
|
+
| Effort / Weight | Score |
|
|
121
|
+
|-----------------|----------|
|
|
122
|
+
| Size | 0-5 |
|
|
123
|
+
| Complexity | 0-5 |
|
|
124
|
+
| Unknowns | 0-5 |
|
|
125
|
+
| **Total** | **0-15** |
|
|
126
|
+
|
|
127
|
+
**SCORING:**
|
|
128
|
+
- Size: # of modules/layers/files changed
|
|
129
|
+
- Complexity: Technical difficulty
|
|
130
|
+
- Unknowns: Research/uncertainty needed
|
|
72
131
|
|
|
73
132
|
**README:**
|
|
74
133
|
\`\`\`markdown
|
|
@@ -80,19 +139,14 @@ ${readme.trim() || 'No README available'}
|
|
|
80
139
|
${template.trim()}
|
|
81
140
|
\`\`\`
|
|
82
141
|
|
|
83
|
-
Output ONLY
|
|
84
|
-
})
|
|
142
|
+
Output ONLY markdown. No explanations, labels, or extra formatting.`});
|
|
85
143
|
|
|
86
144
|
const messages = await ai.language.ask(`Title: ${issueData.title}\n\nDescription:\n${issueData.body || 'No description provided'}`).catch(() => []);
|
|
87
|
-
const
|
|
88
|
-
if(!
|
|
145
|
+
const body = messages?.pop()?.content;
|
|
146
|
+
if(!body) {
|
|
89
147
|
console.log('Invalid response from AI');
|
|
90
148
|
return process.exit(1);
|
|
91
149
|
}
|
|
92
|
-
const title = /^# (.+)$/m.exec(content)?.[1] || issueData.title;
|
|
93
|
-
const typeMatch = /^## Type:\s*(.+)$/m.exec(content);
|
|
94
|
-
const type = typeMatch?.[1]?.split('/')[0]?.trim() || 'Unassigned';
|
|
95
|
-
const body = content.replace(/^# .+$/m, '').replace(/^## Type:.+$/m, '').trim();
|
|
96
150
|
const updateRes = await fetch(`${git}/api/v1/repos/${owner}/${repo}/issues/${ticket}`, {
|
|
97
151
|
method: 'PATCH',
|
|
98
152
|
headers: {
|
|
@@ -102,9 +156,20 @@ Output ONLY the formatted ticket, no explanation.`
|
|
|
102
156
|
body: JSON.stringify({
|
|
103
157
|
title,
|
|
104
158
|
body,
|
|
105
|
-
labels: type?.length ? [`Kind/${type[0].toUpperCase() + type.slice(1).toLowerCase()}`] : []
|
|
106
159
|
})
|
|
107
160
|
});
|
|
108
161
|
if(!updateRes.ok) throw new Error(`${updateRes.status} ${await updateRes.text()}`);
|
|
109
|
-
|
|
162
|
+
if(type) {
|
|
163
|
+
const resp = await fetch(`${git}/api/v1/repos/${owner}/${repo}/issues/${ticket}/labels`, {
|
|
164
|
+
method: 'POST',
|
|
165
|
+
headers: {
|
|
166
|
+
'Authorization': `token ${auth}`,
|
|
167
|
+
'Content-Type': 'application/json'
|
|
168
|
+
},
|
|
169
|
+
body: JSON.stringify([`Kind/${type[0].toUpperCase() + type.slice(1).toLowerCase()}`])
|
|
170
|
+
});
|
|
171
|
+
if(!resp.ok) throw new Error(`${resp.status} ${await resp.text()}`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
console.log(`Title: ${title}\nType: ${type}\nBody:\n${body}`);
|
|
110
175
|
})();
|
package/src/review.mjs
CHANGED
|
@@ -35,19 +35,15 @@ dotenv.config({path: '.env.local', override: true, quiet: true, debug: false});
|
|
|
35
35
|
return process.exit();
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
let existingComments = '';
|
|
38
|
+
let existingComments = 'Existing Comments:\n';
|
|
39
39
|
if(git && pr) {
|
|
40
|
-
const
|
|
40
|
+
const reviews = await fetch(`${git}/api/v1/repos/${owner}/${repo}/pulls/${pr}/reviews`, {
|
|
41
41
|
headers: {'Authorization': `token ${auth}`}
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
existingComments = '\n\nExisting review comments (DO NOT repeat these):\n' +
|
|
48
|
-
allComments.map(c => `- ${c.path}:${c.line || c.position}: ${c.body}`).join('\n');
|
|
49
|
-
}
|
|
50
|
-
}
|
|
42
|
+
}).then(resp => resp.ok ? resp.json() : []);
|
|
43
|
+
const comments = await Promise.all(reviews.map(r => fetch(`${git}/api/v1/repos/${owner}/${repo}/pulls/${pr}/reviews/${r.id}/comments`, {
|
|
44
|
+
headers: {'Authorization': `token ${auth}`}
|
|
45
|
+
}).then(resp => resp.ok ? resp.json() : [])));
|
|
46
|
+
existingComments += comments.flatten().map(c => `${c.path}:${c.position}\n${c.body}`).join('\n\n');
|
|
51
47
|
}
|
|
52
48
|
|
|
53
49
|
let options = {ollama: {model, host}};
|