@ztimson/ai-agents 0.1.0 → 0.1.2
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 +33 -26
- package/src/review.mjs +22 -2
package/package.json
CHANGED
package/src/refine.mjs
CHANGED
|
@@ -6,8 +6,8 @@ import * as dotenv from 'dotenv';
|
|
|
6
6
|
import * as fs from 'node:fs';
|
|
7
7
|
import * as path from 'node:path';
|
|
8
8
|
|
|
9
|
-
dotenv.config({quiet: true});
|
|
10
|
-
dotenv.config({path: '.env.local', override: true, quiet: true});
|
|
9
|
+
dotenv.config({quiet: true, debug: false});
|
|
10
|
+
dotenv.config({path: '.env.local', override: true, quiet: true, debug: false});
|
|
11
11
|
|
|
12
12
|
(async () => {
|
|
13
13
|
let p = process.argv[process.argv.length - 1];
|
|
@@ -21,6 +21,10 @@ dotenv.config({path: '.env.local', override: true, quiet: true});
|
|
|
21
21
|
ticket = process.env['TICKET'],
|
|
22
22
|
host = process.env['AI_HOST'],
|
|
23
23
|
model = process.env['AI_MODEL'],
|
|
24
|
+
labelDupe = process.env['LABELS_DUPE'] || 'Review/Duplicate',
|
|
25
|
+
labelEnabled = process.env['LABEL_ENABLED'] || 'Review/AI',
|
|
26
|
+
labelsReq = process.env['LABELS_REQ'] || 'Kind/Aesthetic,Kind/Bug,Kind/DevOps,Kind/Document,Kind/Enhancement,Kind/Refactor,Kind/Security',
|
|
27
|
+
labelsOpt = process.env['LABELS_OPT'] || 'Breaking,Priority,QA',
|
|
24
28
|
token = process.env['AI_TOKEN'];
|
|
25
29
|
|
|
26
30
|
console.log(`Processing issue #${ticket}`);
|
|
@@ -32,13 +36,13 @@ dotenv.config({path: '.env.local', override: true, quiet: true});
|
|
|
32
36
|
if(resp.ok) return resp.json();
|
|
33
37
|
else throw new Error(`${resp.status} ${await resp.text()}`);
|
|
34
38
|
});
|
|
35
|
-
if(issueData.labels?.
|
|
39
|
+
if(issueData.labels?.length !== 1 || issueData.labels[0]?.name !== labelEnabled) {
|
|
36
40
|
console.log('Skipping');
|
|
37
41
|
return process.exit();
|
|
38
42
|
}
|
|
39
43
|
|
|
40
44
|
// Gather readme & template
|
|
41
|
-
let title = '',
|
|
45
|
+
let title = '', labels = [], readme = '', readmeP = path.join(process.cwd(), 'README.md');
|
|
42
46
|
if(fs.existsSync(readmeP)) readme = fs.readFileSync(readmeP, 'utf-8');
|
|
43
47
|
const template = p ? fs.readFileSync(p, 'utf-8') : `## Description
|
|
44
48
|
|
|
@@ -96,18 +100,27 @@ Implementation details, constraints, dependencies, design decisions
|
|
|
96
100
|
args: {title: {type: 'string', description: 'Ticket title, must match format: Module - Verb noun', required: true}},
|
|
97
101
|
fn: (args) => title = args.title
|
|
98
102
|
}, {
|
|
99
|
-
name: '
|
|
100
|
-
description: '
|
|
101
|
-
args: {
|
|
102
|
-
fn: (args) =>
|
|
103
|
+
name: 'add_label',
|
|
104
|
+
description: 'Add a label to the ticket',
|
|
105
|
+
args: {label: {type: 'string', description: 'Label name', required: true}},
|
|
106
|
+
fn: async (args) => {
|
|
107
|
+
labels.push(args.label);
|
|
108
|
+
return await fetch(`${git}/api/v1/repos/${owner}/${repo}/issues/${ticket}/labels`, {
|
|
109
|
+
method: 'POST',
|
|
110
|
+
headers: {'Authorization': `token ${auth}`, 'Content-Type': 'application/json'},
|
|
111
|
+
body: `{"labels":["${args.label}"]}`
|
|
112
|
+
}).then(async resp => { if(!resp.ok) throw new Error(`${resp.status} ${await resp.text()}`); });
|
|
113
|
+
}
|
|
103
114
|
}],
|
|
104
115
|
system: `Transform raw tickets into structured markdown following the template EXACTLY.
|
|
105
116
|
|
|
106
117
|
**MANDATORY STEPS:**
|
|
107
|
-
1.
|
|
108
|
-
2.
|
|
109
|
-
3. Call \`
|
|
110
|
-
4.
|
|
118
|
+
1. Call \`title\` tool EXACTLY ONCE in format: "[Module] - [Verb] [subject]" (example: Storage - fix file uploads)
|
|
119
|
+
2. Identify one label from each group which best applies to the ticket: ${labelsReq.replaceAll(',', ', ')}
|
|
120
|
+
3. Call the \`add_label\` tool ONCE FOR EVERY LABEL identified in the previous step
|
|
121
|
+
4. Filter the following labels to any that apply to this ticket: ${labelsOpt.replaceAll(',', ', ')}
|
|
122
|
+
5. Call the \`add_label\` tool ONCE FOR EVERY LABEL identified in the previous step
|
|
123
|
+
6. Output the new ticket description in formatted markdown matching the following rules:
|
|
111
124
|
|
|
112
125
|
**TEMPLATE RULES:**
|
|
113
126
|
- Use ## headers (match template exactly)
|
|
@@ -128,11 +141,11 @@ Implementation details, constraints, dependencies, design decisions
|
|
|
128
141
|
| **Total** | **0-15** |
|
|
129
142
|
|
|
130
143
|
**SCORING:**
|
|
131
|
-
- Size: # of modules/layers/files
|
|
144
|
+
- Size: # of modules/layers/files affected
|
|
132
145
|
- Complexity: Technical difficulty
|
|
133
146
|
- Unknowns: Research/uncertainty needed
|
|
134
147
|
|
|
135
|
-
**README:**
|
|
148
|
+
**PROJECT README:**
|
|
136
149
|
\`\`\`markdown
|
|
137
150
|
${readme.trim() || 'No README available'}
|
|
138
151
|
\`\`\`
|
|
@@ -165,11 +178,12 @@ Output ONLY markdown. No explanations, labels, or extra formatting.`});
|
|
|
165
178
|
let dupeId = null;
|
|
166
179
|
const dupeIds = search.map(t => t.id);
|
|
167
180
|
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(
|
|
169
|
-
system: `Your job is to identify duplicates. Respond with the ID number
|
|
181
|
+
const hasDuplicates = (await ai.language.ask(`ID: ${issueData.id}\nTitle: ${title}\n\`\`\`markdown\n${body}\n\`\`\``, {
|
|
182
|
+
system: `Your job is to identify duplicates. Respond ONLY with the duplicate's ID number or "NONE" if no match exists\n\n${dupes}`
|
|
170
183
|
}))?.pop()?.content;
|
|
184
|
+
|
|
171
185
|
// Handle duplicates
|
|
172
|
-
if(
|
|
186
|
+
if(hasDuplicates && !hasDuplicates.toUpperCase().includes('NONE') && (dupeId = dupeIds.find(id => id == hasDuplicates.trim())) != null && dupeId != issueData.id) {
|
|
173
187
|
await fetch(`${git}/api/v1/repos/${owner}/${repo}/issues/${ticket}/comments`, {
|
|
174
188
|
method: 'POST',
|
|
175
189
|
headers: {'Authorization': `token ${auth}`, 'Content-Type': 'application/json'},
|
|
@@ -178,7 +192,7 @@ Output ONLY markdown. No explanations, labels, or extra formatting.`});
|
|
|
178
192
|
await fetch(`${git}/api/v1/repos/${owner}/${repo}/issues/${ticket}/labels`, {
|
|
179
193
|
method: 'POST',
|
|
180
194
|
headers: {'Authorization': `token ${auth}`, 'Content-Type': 'application/json'},
|
|
181
|
-
body:
|
|
195
|
+
body: `{"labels":["${labelDupe}"]}`
|
|
182
196
|
}).then(async resp => { if(!resp.ok) throw new Error(`${resp.status} ${await resp.text()}`); });
|
|
183
197
|
await fetch(`${git}/api/v1/repos/${owner}/${repo}/issues/${ticket}`, {
|
|
184
198
|
method: 'PATCH',
|
|
@@ -195,15 +209,8 @@ Output ONLY markdown. No explanations, labels, or extra formatting.`});
|
|
|
195
209
|
headers: {'Authorization': `token ${auth}`, 'Content-Type': 'application/json'},
|
|
196
210
|
body: JSON.stringify({title, body})
|
|
197
211
|
}).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`, {
|
|
200
|
-
method: 'POST',
|
|
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()}`); });
|
|
204
|
-
}
|
|
205
212
|
|
|
206
|
-
console.log(`Title: ${title}\
|
|
213
|
+
console.log(`Title: ${title}\nLabels: ${labels.join(', ')}\nBody:\n${body}`);
|
|
207
214
|
})().catch(err => {
|
|
208
215
|
console.error(`Error: ${err.message || err.toString()}`);
|
|
209
216
|
process.exit(1);
|
package/src/review.mjs
CHANGED
|
@@ -19,12 +19,23 @@ dotenv.config({path: '.env.local', override: true, quiet: true, debug: false});
|
|
|
19
19
|
owner = process.env['GIT_OWNER'],
|
|
20
20
|
repo = process.env['GIT_REPO'],
|
|
21
21
|
auth = process.env['GIT_TOKEN'],
|
|
22
|
+
labelEnabled = process.env['LABEL_ENABLED'] || 'Review/AI',
|
|
22
23
|
pr = process.env['PULL_REQUEST'],
|
|
23
24
|
host = process.env['AI_HOST'],
|
|
24
25
|
model = process.env['AI_MODEL'],
|
|
25
26
|
token = process.env['AI_TOKEN'];
|
|
26
27
|
|
|
27
28
|
console.log(`Reviewing: ${root}\n`);
|
|
29
|
+
const info = await fetch(`${git}/api/v1/repos/${owner}/${repo}/pulls/${pr}`)
|
|
30
|
+
.then(async resp => {
|
|
31
|
+
if(resp.ok) return resp.json();
|
|
32
|
+
throw new Error(`${resp.status} ${await resp.text()}`);
|
|
33
|
+
});
|
|
34
|
+
if(!info.labels.some(l => l.name === labelEnabled)) {
|
|
35
|
+
console.log('Skipping');
|
|
36
|
+
return process.exit();
|
|
37
|
+
}
|
|
38
|
+
|
|
28
39
|
const branch = process.env['GIT_BRANCH'] || await $`cd ${root} && git symbolic-ref refs/remotes/origin/HEAD`;
|
|
29
40
|
const comments = [];
|
|
30
41
|
const commit = await $`cd ${root} && git log -1 --pretty=format:%H`;
|
|
@@ -53,7 +64,7 @@ dotenv.config({path: '.env.local', override: true, quiet: true, debug: false});
|
|
|
53
64
|
...options,
|
|
54
65
|
model: [host, model],
|
|
55
66
|
path: process.env['path'] || os.tmpdir(),
|
|
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
|
|
67
|
+
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 a quick 75 words or less sitrep.${existingComments}`,
|
|
57
68
|
tools: [{
|
|
58
69
|
name: 'read_file',
|
|
59
70
|
description: 'Read contents of a file',
|
|
@@ -87,7 +98,16 @@ dotenv.config({path: '.env.local', override: true, quiet: true, debug: false});
|
|
|
87
98
|
}]
|
|
88
99
|
});
|
|
89
100
|
|
|
90
|
-
const messages = await ai.language.ask(
|
|
101
|
+
const messages = await ai.language.ask(`Title: ${info.title || 'None'}
|
|
102
|
+
Description:
|
|
103
|
+
\`\`\`md
|
|
104
|
+
${info.body || 'None'}
|
|
105
|
+
\`\`\`
|
|
106
|
+
|
|
107
|
+
Git Diff:
|
|
108
|
+
\`\`\`
|
|
109
|
+
${gitDiff}
|
|
110
|
+
\`\`\``);
|
|
91
111
|
const summary = messages.pop().content;
|
|
92
112
|
if(git) {
|
|
93
113
|
const res = await fetch(`${git}/api/v1/repos/${owner}/${repo}/pulls/${pr}/reviews`, {
|