@ztimson/ai-agents 0.0.7 → 0.1.0
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/package.json +1 -1
- package/src/refine.mjs +63 -28
- package/src/review.mjs +5 -2
package/package.json
CHANGED
package/src/refine.mjs
CHANGED
|
@@ -26,16 +26,18 @@ dotenv.config({path: '.env.local', override: true, quiet: true});
|
|
|
26
26
|
console.log(`Processing issue #${ticket}`);
|
|
27
27
|
|
|
28
28
|
// Fetch issue
|
|
29
|
-
const
|
|
29
|
+
const issueData = await fetch(`${git}/api/v1/repos/${owner}/${repo}/issues/${ticket}`, {
|
|
30
30
|
headers: {'Authorization': `token ${auth}`}
|
|
31
|
+
}).then(async resp => {
|
|
32
|
+
if(resp.ok) return resp.json();
|
|
33
|
+
else throw new Error(`${resp.status} ${await resp.text()}`);
|
|
31
34
|
});
|
|
32
|
-
if(
|
|
33
|
-
const issueData = await issueRes.json();
|
|
34
|
-
if(!issueData.labels?.some(l => l.name === 'Review/AI')) {
|
|
35
|
+
if(issueData.labels?.[0] !== 1 || issueData.labels?.[0]?.name !== 'Review/AI') {
|
|
35
36
|
console.log('Skipping');
|
|
36
37
|
return process.exit();
|
|
37
38
|
}
|
|
38
39
|
|
|
40
|
+
// Gather readme & template
|
|
39
41
|
let title = '', type = '', readme = '', readmeP = path.join(process.cwd(), 'README.md');
|
|
40
42
|
if(fs.existsSync(readmeP)) readme = fs.readFileSync(readmeP, 'utf-8');
|
|
41
43
|
const template = p ? fs.readFileSync(p, 'utf-8') : `## Description
|
|
@@ -80,6 +82,7 @@ Implementation details, constraints, dependencies, design decisions
|
|
|
80
82
|
| **Total** | **0-15** |
|
|
81
83
|
`;
|
|
82
84
|
|
|
85
|
+
// Create AI
|
|
83
86
|
let options = {ollama: {model, host}};
|
|
84
87
|
if(host === 'anthropic') options = {anthropic: {model, token}};
|
|
85
88
|
else if(host === 'openai') options = {openAi: {model, token}};
|
|
@@ -141,35 +144,67 @@ ${template.trim()}
|
|
|
141
144
|
|
|
142
145
|
Output ONLY markdown. No explanations, labels, or extra formatting.`});
|
|
143
146
|
|
|
147
|
+
// Format ticket with AI
|
|
144
148
|
const messages = await ai.language.ask(`Title: ${issueData.title}\n\nDescription:\n${issueData.body || 'No description provided'}`).catch(() => []);
|
|
145
149
|
const body = messages?.pop()?.content;
|
|
146
|
-
if(!body)
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
}
|
|
150
|
-
const
|
|
151
|
-
method: '
|
|
152
|
-
headers: {
|
|
153
|
-
'Authorization': `token ${auth}`,
|
|
154
|
-
'Content-Type': 'application/json'
|
|
155
|
-
},
|
|
150
|
+
if(!body) throw new Error('Invalid response from AI');
|
|
151
|
+
|
|
152
|
+
// Check for duplicates
|
|
153
|
+
const repoInfo = await fetch(`${git}/api/v1/repos/${owner}/${repo}`, {headers: {'Authorization': `token ${auth}`},}).then(resp => resp.ok ? resp.json() : null);
|
|
154
|
+
const search = await fetch(`${git}/api/v1/repos/issues/search`, {
|
|
155
|
+
method: 'POST',
|
|
156
|
+
headers: {'Authorization': `token ${auth}`, 'Content-Type': 'application/json'},
|
|
156
157
|
body: JSON.stringify({
|
|
157
|
-
|
|
158
|
-
|
|
158
|
+
owner,
|
|
159
|
+
priority_repo_id: repoInfo.id,
|
|
160
|
+
type: 'issues',
|
|
161
|
+
limit: 3,
|
|
162
|
+
q: title
|
|
159
163
|
})
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
+
}).then(resp => resp.ok ? resp.json() : []);
|
|
165
|
+
let dupeId = null;
|
|
166
|
+
const dupeIds = search.map(t => t.id);
|
|
167
|
+
const dupes = search.map(t => `ID: ${t.id}\nTitle: ${t.title}\n\`\`\`markdown\n${t.body}\n\`\`\``).join('\n\n');
|
|
168
|
+
const hasDuplicates = (await ai.language.ask(`${title}\n\`\`\`markdown\n${body}\n\`\`\``, {
|
|
169
|
+
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}`
|
|
170
|
+
}))?.pop()?.content;
|
|
171
|
+
// Handle duplicates
|
|
172
|
+
if(!!hasDuplicates && (dupeId = dupeIds.find(id => new RegExp(`\\b${id}\\b`, 'm').test(hasDuplicates)))) {
|
|
173
|
+
await fetch(`${git}/api/v1/repos/${owner}/${repo}/issues/${ticket}/comments`, {
|
|
174
|
+
method: 'POST',
|
|
175
|
+
headers: {'Authorization': `token ${auth}`, 'Content-Type': 'application/json'},
|
|
176
|
+
body: `{"body": "Duplicate of #${dupeId}"}`
|
|
177
|
+
}).then(async resp => { if(!resp.ok) throw new Error(`${resp.status} ${await resp.text()}`); });
|
|
178
|
+
await fetch(`${git}/api/v1/repos/${owner}/${repo}/issues/${ticket}/labels`, {
|
|
179
|
+
method: 'POST',
|
|
180
|
+
headers: {'Authorization': `token ${auth}`, 'Content-Type': 'application/json'},
|
|
181
|
+
body: '{"labels":["Reviewed/Duplicate"]}'
|
|
182
|
+
}).then(async resp => { if(!resp.ok) throw new Error(`${resp.status} ${await resp.text()}`); });
|
|
183
|
+
await fetch(`${git}/api/v1/repos/${owner}/${repo}/issues/${ticket}`, {
|
|
184
|
+
method: 'PATCH',
|
|
185
|
+
headers: {'Authorization': `token ${auth}`, 'Content-Type': 'application/json'},
|
|
186
|
+
body: '{"state": "closed"}'
|
|
187
|
+
}).then(async resp => { if(!resp.ok) throw new Error(`${resp.status} ${await resp.text()}`); });
|
|
188
|
+
console.log('Duplicate');
|
|
189
|
+
return process.exit();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Update ticket
|
|
193
|
+
await fetch(`${git}/api/v1/repos/${owner}/${repo}/issues/${ticket}`, {
|
|
194
|
+
method: 'PATCH',
|
|
195
|
+
headers: {'Authorization': `token ${auth}`, 'Content-Type': 'application/json'},
|
|
196
|
+
body: JSON.stringify({title, body})
|
|
197
|
+
}).then(async resp => { if(!resp.ok) throw new Error(`${resp.status} ${await resp.text()}`); });
|
|
198
|
+
if(type) { // Label
|
|
199
|
+
await fetch(`${git}/api/v1/repos/${owner}/${repo}/issues/${ticket}/labels`, {
|
|
164
200
|
method: 'POST',
|
|
165
|
-
headers: {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
},
|
|
169
|
-
body: JSON.stringify({labels: [`Kind/${type[0].toUpperCase() + type.slice(1).toLowerCase()}`]})
|
|
170
|
-
});
|
|
171
|
-
if(!resp.ok) throw new Error(`${resp.status} ${await resp.text()}`);
|
|
201
|
+
headers: {'Authorization': `token ${auth}`, 'Content-Type': 'application/json'},
|
|
202
|
+
body: `{"labels":["Reviewed/${type[0].toUpperCase() + type.slice(1).toLowerCase()}"]}`
|
|
203
|
+
}).then(async resp => { if(!resp.ok) throw new Error(`${resp.status} ${await resp.text()}`); });
|
|
172
204
|
}
|
|
173
205
|
|
|
174
206
|
console.log(`Title: ${title}\nType: ${type}\nBody:\n${body}`);
|
|
175
|
-
})()
|
|
207
|
+
})().catch(err => {
|
|
208
|
+
console.error(`Error: ${err.message || err.toString()}`);
|
|
209
|
+
process.exit(1);
|
|
210
|
+
});
|
package/src/review.mjs
CHANGED
|
@@ -43,7 +43,7 @@ dotenv.config({path: '.env.local', override: true, quiet: true, debug: false});
|
|
|
43
43
|
const comments = await Promise.all(reviews.map(r => fetch(`${git}/api/v1/repos/${owner}/${repo}/pulls/${pr}/reviews/${r.id}/comments`, {
|
|
44
44
|
headers: {'Authorization': `token ${auth}`}
|
|
45
45
|
}).then(resp => resp.ok ? resp.json() : [])));
|
|
46
|
-
existingComments += comments.
|
|
46
|
+
existingComments += comments.flat().map(c => `${c.path}:${c.position}\n${c.body}`).join('\n\n');
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
let options = {ollama: {model, host}};
|
|
@@ -106,4 +106,7 @@ dotenv.config({path: '.env.local', override: true, quiet: true, debug: false});
|
|
|
106
106
|
if(!res.ok) throw new Error(`${res.status} ${await res.text()}`);
|
|
107
107
|
}
|
|
108
108
|
console.log(comments.map(c => `${c.path}${c.new_position ? `:${c.new_position}` : ''}\n${c.body}`).join('\n\n') + '\n\n' + summary);
|
|
109
|
-
})()
|
|
109
|
+
})().catch(err => {
|
|
110
|
+
console.error(`Error: ${err.message || err.toString()}`);
|
|
111
|
+
process.exit(1);
|
|
112
|
+
});
|