@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ztimson/ai-agents",
3
- "version": "0.0.7",
3
+ "version": "0.1.0",
4
4
  "description": "AI agents",
5
5
  "keywords": ["ai", "review"],
6
6
  "author": "ztimson",
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 issueRes = await fetch(`${git}/api/v1/repos/${owner}/${repo}/issues/${ticket}`, {
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(!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
+ 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
- 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
- },
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
- title,
158
- body,
158
+ owner,
159
+ priority_repo_id: repoInfo.id,
160
+ type: 'issues',
161
+ limit: 3,
162
+ q: title
159
163
  })
160
- });
161
- if(!updateRes.ok) throw new Error(`${updateRes.status} ${await updateRes.text()}`);
162
- if(type) {
163
- const resp = await fetch(`${git}/api/v1/repos/${owner}/${repo}/issues/${ticket}/labels`, {
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
- 'Authorization': `token ${auth}`,
167
- 'Content-Type': 'application/json'
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.flatten().map(c => `${c.path}:${c.position}\n${c.body}`).join('\n\n');
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
+ });