@ztimson/ai-agents 0.0.3 → 0.0.5
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/LICENSE +11 -11
- package/README.md +86 -81
- package/package.json +2 -1
- package/src/refine.mjs +172 -0
- package/src/review.mjs +17 -6
package/LICENSE
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
Copyright (c) 2023 Zakary Timson
|
|
2
|
-
|
|
3
|
-
All Rights Reserved.
|
|
4
|
-
|
|
5
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
6
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
7
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
8
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
9
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
10
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
11
|
-
THE SOFTWARE.
|
|
1
|
+
Copyright (c) 2023 Zakary Timson
|
|
2
|
+
|
|
3
|
+
All Rights Reserved.
|
|
4
|
+
|
|
5
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
6
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
7
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
8
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
9
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
10
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
11
|
+
THE SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,81 +1,86 @@
|
|
|
1
|
-
<!-- Header -->
|
|
2
|
-
<div id="top" align="center">
|
|
3
|
-
<br />
|
|
4
|
-
|
|
5
|
-
<!-- Logo -->
|
|
6
|
-
<img src="https://git.zakscode.com/repo-avatars/
|
|
7
|
-
|
|
8
|
-
<!-- Title -->
|
|
9
|
-
### AI Agents
|
|
10
|
-
|
|
11
|
-
<!-- Description -->
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
<!-- Repo badges -->
|
|
15
|
-
[](https://git.zakscode.com/ztimson/ai-agents/tags)
|
|
16
|
-
[](https://git.zakscode.com/ztimson/ai-agents/pulls)
|
|
17
|
-
[](https://git.zakscode.com/ztimson/ai-agents/issues)
|
|
18
|
-
|
|
19
|
-
<!-- Links -->
|
|
20
|
-
---
|
|
21
|
-
<div>
|
|
22
|
-
<a href="https://git.zakscode.com/ztimson/ai-agents/releases" target="_blank">Release Notes</a>
|
|
23
|
-
• <a href="https://git.zakscode.com/ztimson/ai-agents/issues/new?template=.github%2fissue_template%2fbug.md" target="_blank">Report a Bug</a>
|
|
24
|
-
• <a href="https://git.zakscode.com/ztimson/ai-agents/issues/new?template=.github%2fissue_template%2fenhancement.md" target="_blank">Request a Feature</a>
|
|
25
|
-
</div>
|
|
26
|
-
|
|
27
|
-
---
|
|
28
|
-
</div>
|
|
29
|
-
|
|
30
|
-
## Table of Contents
|
|
31
|
-
- [AI Agents](#top)
|
|
32
|
-
- [About](#about)
|
|
33
|
-
- [Built With](#built-with)
|
|
34
|
-
- [Setup](#setup)
|
|
35
|
-
- [Production](#production)
|
|
36
|
-
- [License](#license)
|
|
37
|
-
|
|
38
|
-
## About
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
[](https://git.zakscode.com/ztimson/ai-agents/tags)
|
|
16
|
+
[](https://git.zakscode.com/ztimson/ai-agents/pulls)
|
|
17
|
+
[](https://git.zakscode.com/ztimson/ai-agents/issues)
|
|
18
|
+
|
|
19
|
+
<!-- Links -->
|
|
20
|
+
---
|
|
21
|
+
<div>
|
|
22
|
+
<a href="https://git.zakscode.com/ztimson/ai-agents/releases" target="_blank">Release Notes</a>
|
|
23
|
+
• <a href="https://git.zakscode.com/ztimson/ai-agents/issues/new?template=.github%2fissue_template%2fbug.md" target="_blank">Report a Bug</a>
|
|
24
|
+
• <a href="https://git.zakscode.com/ztimson/ai-agents/issues/new?template=.github%2fissue_template%2fenhancement.md" target="_blank">Request a Feature</a>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
## Table of Contents
|
|
31
|
+
- [AI Agents](#top)
|
|
32
|
+
- [About](#about)
|
|
33
|
+
- [Built With](#built-with)
|
|
34
|
+
- [Setup](#setup)
|
|
35
|
+
- [Production](#production)
|
|
36
|
+
- [License](#license)
|
|
37
|
+
|
|
38
|
+
## About
|
|
39
|
+
|
|
40
|
+
Only supports Gitea
|
|
41
|
+
|
|
42
|
+
Use LLM models from Anthropic, OpenAI, or Ollama to automate ticket refinement, code reviews, and releases.
|
|
43
|
+
|
|
44
|
+
### Built With
|
|
45
|
+
[](https://docker.com/)
|
|
46
|
+
[](https://javascript.com/)
|
|
47
|
+
[](https://nodejs.org/)
|
|
48
|
+
|
|
49
|
+
## Setup
|
|
50
|
+
|
|
51
|
+
<details>
|
|
52
|
+
<summary>
|
|
53
|
+
<h3 id="production" style="display: inline">
|
|
54
|
+
Production
|
|
55
|
+
</h3>
|
|
56
|
+
</summary>
|
|
57
|
+
|
|
58
|
+
#### Prerequisites
|
|
59
|
+
- [Node.js](https://nodejs.org/en/download)
|
|
60
|
+
|
|
61
|
+
#### Instructions
|
|
62
|
+
1. Run using npx: `npx -y @ztimson/ai-agents@latest review`
|
|
63
|
+
|
|
64
|
+
</details>
|
|
65
|
+
|
|
66
|
+
<details>
|
|
67
|
+
<summary>
|
|
68
|
+
<h3 id="development" style="display: inline">
|
|
69
|
+
Development
|
|
70
|
+
</h3>
|
|
71
|
+
</summary>
|
|
72
|
+
|
|
73
|
+
#### Prerequisites
|
|
74
|
+
- [Node.js](https://nodejs.org/en/download)
|
|
75
|
+
|
|
76
|
+
#### Instructions
|
|
77
|
+
1. Install the dependencies: `npm i`
|
|
78
|
+
2. Build library: `npm run review`
|
|
79
|
+
|
|
80
|
+
</details>
|
|
81
|
+
|
|
82
|
+
## License
|
|
83
|
+
|
|
84
|
+
Copyright © 2025 Zakary Timson | All Rights Reserved | Available under MIT Licensing
|
|
85
|
+
|
|
86
|
+
See the [license](./LICENSE) for more information.
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ztimson/ai-agents",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"description": "AI agents",
|
|
5
5
|
"keywords": ["ai", "review"],
|
|
6
6
|
"author": "ztimson",
|
|
7
7
|
"license": "ISC",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"bin": {
|
|
10
|
+
"refine": "./src/refine.mjs",
|
|
10
11
|
"review": "./src/review.mjs"
|
|
11
12
|
},
|
|
12
13
|
"dependencies": {
|
package/src/refine.mjs
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import {Ai} from '@ztimson/ai-utils';
|
|
4
|
+
import * as os from 'node:os';
|
|
5
|
+
import * as dotenv from 'dotenv';
|
|
6
|
+
import * as fs from 'node:fs';
|
|
7
|
+
import * as path from 'node:path';
|
|
8
|
+
|
|
9
|
+
dotenv.config({quiet: true});
|
|
10
|
+
dotenv.config({path: '.env.local', override: true, quiet: true});
|
|
11
|
+
|
|
12
|
+
(async () => {
|
|
13
|
+
let p = process.argv[process.argv.length - 1];
|
|
14
|
+
if(p === 'refine' || p.endsWith('refine.mjs')) p = null;
|
|
15
|
+
if(!/^(\/|[A-Z]:)/m.test(p)) p = path.join(process.cwd(), p);
|
|
16
|
+
|
|
17
|
+
const git = process.env['GIT_HOST'],
|
|
18
|
+
owner = process.env['GIT_OWNER'],
|
|
19
|
+
repo = process.env['GIT_REPO'],
|
|
20
|
+
auth = process.env['GIT_TOKEN'],
|
|
21
|
+
ticket = process.env['TICKET'],
|
|
22
|
+
host = process.env['AI_HOST'],
|
|
23
|
+
model = process.env['AI_MODEL'],
|
|
24
|
+
token = process.env['AI_TOKEN'];
|
|
25
|
+
|
|
26
|
+
console.log(`Processing issue #${ticket}`);
|
|
27
|
+
|
|
28
|
+
// Fetch issue
|
|
29
|
+
const issueRes = await fetch(`${git}/api/v1/repos/${owner}/${repo}/issues/${ticket}`, {
|
|
30
|
+
headers: {'Authorization': `token ${auth}`}
|
|
31
|
+
});
|
|
32
|
+
if(!issueRes.ok) throw new Error(`${issueRes.status} ${await issueRes.text()}`);
|
|
33
|
+
const issueData = await issueRes.json();
|
|
34
|
+
if(!issueData.labels?.some(l => l.name === 'Review/AI')) {
|
|
35
|
+
console.log('Skipping');
|
|
36
|
+
return process.exit();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let title = '', type = '', readme = '', readmeP = path.join(process.cwd(), 'README.md');
|
|
40
|
+
if(fs.existsSync(readmeP)) readme = fs.readFileSync(readmeP, '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
|
+
`;
|
|
82
|
+
|
|
83
|
+
let options = {ollama: {model, host}};
|
|
84
|
+
if(host === 'anthropic') options = {anthropic: {model, token}};
|
|
85
|
+
else if(host === 'openai') options = {openAi: {model, token}};
|
|
86
|
+
const ai = new Ai({
|
|
87
|
+
...options,
|
|
88
|
+
model: [host, model],
|
|
89
|
+
path: process.env['path'] || os.tmpdir(),
|
|
90
|
+
tools: [{
|
|
91
|
+
name: 'title',
|
|
92
|
+
description: 'Set the ticket title, must be called EXACTLY ONCE',
|
|
93
|
+
args: {value: {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
|
|
131
|
+
|
|
132
|
+
**README:**
|
|
133
|
+
\`\`\`markdown
|
|
134
|
+
${readme.trim() || 'No README available'}
|
|
135
|
+
\`\`\`
|
|
136
|
+
|
|
137
|
+
**TEMPLATE:**
|
|
138
|
+
\`\`\`markdown
|
|
139
|
+
${template.trim()}
|
|
140
|
+
\`\`\`
|
|
141
|
+
|
|
142
|
+
Output ONLY markdown. No explanations, labels, or extra formatting.`});
|
|
143
|
+
|
|
144
|
+
const messages = await ai.language.ask(`Title: ${issueData.title}\n\nDescription:\n${issueData.body || 'No description provided'}`).catch(() => []);
|
|
145
|
+
const body = messages?.pop()?.content;
|
|
146
|
+
if(!body) {
|
|
147
|
+
console.log('Invalid response from AI');
|
|
148
|
+
return process.exit(1);
|
|
149
|
+
}
|
|
150
|
+
const updateRes = await fetch(`${git}/api/v1/repos/${owner}/${repo}/issues/${ticket}`, {
|
|
151
|
+
method: 'PATCH',
|
|
152
|
+
headers: {
|
|
153
|
+
'Authorization': `token ${auth}`,
|
|
154
|
+
'Content-Type': 'application/json'
|
|
155
|
+
},
|
|
156
|
+
body: JSON.stringify({
|
|
157
|
+
title,
|
|
158
|
+
body,
|
|
159
|
+
})
|
|
160
|
+
});
|
|
161
|
+
if(!updateRes.ok) throw new Error(`${updateRes.status} ${await updateRes.text()}`);
|
|
162
|
+
if(type) fetch(`${git}/api/v1/repos/${owner}/${repo}/issues/${ticket}/labels`, {
|
|
163
|
+
method: 'POST',
|
|
164
|
+
headers: {
|
|
165
|
+
'Authorization': `token ${auth}`,
|
|
166
|
+
'Content-Type': 'application/json'
|
|
167
|
+
},
|
|
168
|
+
body: `["Kind/${type[0].toUpperCase() + type.slice(1).toLowerCase()}"]`
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
console.log(`Title: ${title}\nType: ${type}\nBody:\n${body}`);
|
|
172
|
+
})();
|
package/src/review.mjs
CHANGED
|
@@ -30,6 +30,22 @@ dotenv.config({path: '.env.local', override: true, quiet: true, debug: false});
|
|
|
30
30
|
const commit = await $`cd ${root} && git log -1 --pretty=format:%H`;
|
|
31
31
|
const gitDiff = await $`cd ${root} && git diff ${branch}`;
|
|
32
32
|
|
|
33
|
+
if(!gitDiff) {
|
|
34
|
+
console.warn('No diff found');
|
|
35
|
+
return process.exit();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let existingComments = 'Existing Comments:\n';
|
|
39
|
+
if(git && pr) {
|
|
40
|
+
const reviews = await fetch(`${git}/api/v1/repos/${owner}/${repo}/pulls/${pr}/reviews`, {
|
|
41
|
+
headers: {'Authorization': `token ${auth}`}
|
|
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');
|
|
47
|
+
}
|
|
48
|
+
|
|
33
49
|
let options = {ollama: {model, host}};
|
|
34
50
|
if(host === 'anthropic') options = {anthropic: {model, token}};
|
|
35
51
|
else if(host === 'openai') options = {openAi: {model, token}};
|
|
@@ -37,7 +53,7 @@ dotenv.config({path: '.env.local', override: true, quiet: true, debug: false});
|
|
|
37
53
|
...options,
|
|
38
54
|
model: [host, model],
|
|
39
55
|
path: process.env['path'] || os.tmpdir(),
|
|
40
|
-
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. After making all recommendations, provide
|
|
56
|
+
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}`,
|
|
41
57
|
tools: [{
|
|
42
58
|
name: 'read_file',
|
|
43
59
|
description: 'Read contents of a file',
|
|
@@ -71,11 +87,6 @@ dotenv.config({path: '.env.local', override: true, quiet: true, debug: false});
|
|
|
71
87
|
}]
|
|
72
88
|
});
|
|
73
89
|
|
|
74
|
-
if(!gitDiff) {
|
|
75
|
-
console.warn('No diff found');
|
|
76
|
-
return process.exit();
|
|
77
|
-
}
|
|
78
|
-
|
|
79
90
|
const messages = await ai.language.ask(gitDiff);
|
|
80
91
|
const summary = messages.pop().content;
|
|
81
92
|
if(git) {
|