@happyvertical/github-actions 0.74.8
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/AGENT.md +33 -0
- package/LICENSE +7 -0
- package/README.md +147 -0
- package/dist/cli/claude-context.d.ts +3 -0
- package/dist/cli/claude-context.d.ts.map +1 -0
- package/dist/cli/claude-context.js +21 -0
- package/dist/cli/claude-context.js.map +1 -0
- package/dist/cli.d.ts +18 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +325 -0
- package/dist/cli.js.map +1 -0
- package/dist/index-v2-CqFKwTm8.js +687 -0
- package/dist/index-v2-CqFKwTm8.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +637 -0
- package/dist/index.js.map +1 -0
- package/dist/labels-cli.d.ts +20 -0
- package/dist/labels-cli.d.ts.map +1 -0
- package/dist/planning/analyze.d.ts +13 -0
- package/dist/planning/analyze.d.ts.map +1 -0
- package/dist/planning/comment.d.ts +15 -0
- package/dist/planning/comment.d.ts.map +1 -0
- package/dist/planning/definition-of-ready.d.ts +15 -0
- package/dist/planning/definition-of-ready.d.ts.map +1 -0
- package/dist/planning/index.d.ts +14 -0
- package/dist/planning/index.d.ts.map +1 -0
- package/dist/planning/types.d.ts +71 -0
- package/dist/planning/types.d.ts.map +1 -0
- package/dist/shared/adapters.d.ts +11 -0
- package/dist/shared/adapters.d.ts.map +1 -0
- package/dist/shared/ai.d.ts +43 -0
- package/dist/shared/ai.d.ts.map +1 -0
- package/dist/shared/github.d.ts +52 -0
- package/dist/shared/github.d.ts.map +1 -0
- package/dist/shared/index.d.ts +9 -0
- package/dist/shared/index.d.ts.map +1 -0
- package/dist/shared/labels.d.ts +35 -0
- package/dist/shared/labels.d.ts.map +1 -0
- package/dist/shared/projects.d.ts +34 -0
- package/dist/shared/projects.d.ts.map +1 -0
- package/dist/triage/analyze.d.ts +12 -0
- package/dist/triage/analyze.d.ts.map +1 -0
- package/dist/triage/comment.d.ts +17 -0
- package/dist/triage/comment.d.ts.map +1 -0
- package/dist/triage/duplicates.d.ts +12 -0
- package/dist/triage/duplicates.d.ts.map +1 -0
- package/dist/triage/github.d.ts +5 -0
- package/dist/triage/github.d.ts.map +1 -0
- package/dist/triage/index-v2.d.ts +19 -0
- package/dist/triage/index-v2.d.ts.map +1 -0
- package/dist/triage/index.d.ts +13 -0
- package/dist/triage/index.d.ts.map +1 -0
- package/dist/triage/label-v2.d.ts +20 -0
- package/dist/triage/label-v2.d.ts.map +1 -0
- package/dist/triage/label.d.ts +16 -0
- package/dist/triage/label.d.ts.map +1 -0
- package/dist/triage/project-v2.d.ts +12 -0
- package/dist/triage/project-v2.d.ts.map +1 -0
- package/dist/triage/project.d.ts +7 -0
- package/dist/triage/project.d.ts.map +1 -0
- package/dist/triage/types.d.ts +64 -0
- package/dist/triage/types.d.ts.map +1 -0
- package/metadata.json +30 -0
- package/package.json +58 -0
|
@@ -0,0 +1,687 @@
|
|
|
1
|
+
import https from "node:https";
|
|
2
|
+
import { getProject } from "@happyvertical/projects";
|
|
3
|
+
import { getRepository } from "@happyvertical/repos";
|
|
4
|
+
async function createRepository(token, owner, repo) {
|
|
5
|
+
return getRepository({
|
|
6
|
+
type: "github",
|
|
7
|
+
owner,
|
|
8
|
+
repo,
|
|
9
|
+
token
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
async function createProject(token, projectId, statusFieldId, statusOptions) {
|
|
13
|
+
return getProject({
|
|
14
|
+
type: "github",
|
|
15
|
+
projectId,
|
|
16
|
+
token,
|
|
17
|
+
statusFieldId,
|
|
18
|
+
statusOptions
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
async function callGitHubModels(token, messages, options = {}) {
|
|
22
|
+
const model = options.model || "gpt-4o-mini";
|
|
23
|
+
const temperature = options.temperature ?? 0.3;
|
|
24
|
+
const maxTokens = options.maxTokens ?? 1e3;
|
|
25
|
+
const response = await fetch(
|
|
26
|
+
`https://models.inference.ai.azure.com/chat/completions`,
|
|
27
|
+
{
|
|
28
|
+
method: "POST",
|
|
29
|
+
headers: {
|
|
30
|
+
Authorization: `Bearer ${token}`,
|
|
31
|
+
"Content-Type": "application/json"
|
|
32
|
+
},
|
|
33
|
+
body: JSON.stringify({
|
|
34
|
+
model,
|
|
35
|
+
messages,
|
|
36
|
+
temperature,
|
|
37
|
+
max_tokens: maxTokens
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
if (!response.ok) {
|
|
42
|
+
const error = await response.text();
|
|
43
|
+
throw new Error(
|
|
44
|
+
`GitHub Models API error: ${response.status} ${response.statusText}
|
|
45
|
+
${error}`
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
const result = await response.json();
|
|
49
|
+
return result.choices[0].message.content;
|
|
50
|
+
}
|
|
51
|
+
async function callOpenAI(apiKey, messages, options = {}) {
|
|
52
|
+
const model = options.model || "gpt-4o-mini";
|
|
53
|
+
const temperature = options.temperature ?? 0.3;
|
|
54
|
+
const maxTokens = options.maxTokens ?? 1e3;
|
|
55
|
+
const response = await fetch("https://api.openai.com/v1/chat/completions", {
|
|
56
|
+
method: "POST",
|
|
57
|
+
headers: {
|
|
58
|
+
Authorization: `Bearer ${apiKey}`,
|
|
59
|
+
"Content-Type": "application/json"
|
|
60
|
+
},
|
|
61
|
+
body: JSON.stringify({
|
|
62
|
+
model,
|
|
63
|
+
messages,
|
|
64
|
+
temperature,
|
|
65
|
+
max_tokens: maxTokens
|
|
66
|
+
})
|
|
67
|
+
});
|
|
68
|
+
if (!response.ok) {
|
|
69
|
+
const error = await response.text();
|
|
70
|
+
throw new Error(
|
|
71
|
+
`OpenAI API error: ${response.status} ${response.statusText}
|
|
72
|
+
${error}`
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
const result = await response.json();
|
|
76
|
+
return result.choices[0].message.content;
|
|
77
|
+
}
|
|
78
|
+
async function callAnthropic(apiKey, messages, options = {}) {
|
|
79
|
+
const model = options.model || "claude-3-5-sonnet-20241022";
|
|
80
|
+
const temperature = options.temperature ?? 0.3;
|
|
81
|
+
const maxTokens = options.maxTokens ?? 1e3;
|
|
82
|
+
const systemMessage = messages.find((m) => m.role === "system");
|
|
83
|
+
const conversationMessages = messages.filter((m) => m.role !== "system");
|
|
84
|
+
const response = await fetch("https://api.anthropic.com/v1/messages", {
|
|
85
|
+
method: "POST",
|
|
86
|
+
headers: {
|
|
87
|
+
"x-api-key": apiKey,
|
|
88
|
+
"anthropic-version": "2023-06-01",
|
|
89
|
+
"Content-Type": "application/json"
|
|
90
|
+
},
|
|
91
|
+
body: JSON.stringify({
|
|
92
|
+
model,
|
|
93
|
+
max_tokens: maxTokens,
|
|
94
|
+
temperature,
|
|
95
|
+
system: systemMessage?.content,
|
|
96
|
+
messages: conversationMessages
|
|
97
|
+
})
|
|
98
|
+
});
|
|
99
|
+
if (!response.ok) {
|
|
100
|
+
const error = await response.text();
|
|
101
|
+
throw new Error(
|
|
102
|
+
`Anthropic API error: ${response.status} ${response.statusText}
|
|
103
|
+
${error}`
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
const result = await response.json();
|
|
107
|
+
return result.content[0].text;
|
|
108
|
+
}
|
|
109
|
+
async function getAICompletion(messages, options = {}) {
|
|
110
|
+
if (process.env.GITHUB_TOKEN) {
|
|
111
|
+
return callGitHubModels(process.env.GITHUB_TOKEN, messages, options);
|
|
112
|
+
}
|
|
113
|
+
if (process.env.OPENAI_API_KEY) {
|
|
114
|
+
return callOpenAI(process.env.OPENAI_API_KEY, messages, options);
|
|
115
|
+
}
|
|
116
|
+
if (process.env.ANTHROPIC_API_KEY) {
|
|
117
|
+
return callAnthropic(process.env.ANTHROPIC_API_KEY, messages, options);
|
|
118
|
+
}
|
|
119
|
+
throw new Error(
|
|
120
|
+
"No AI API credentials found. Set GITHUB_TOKEN, OPENAI_API_KEY, or ANTHROPIC_API_KEY environment variable."
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
function parseAIJson(response) {
|
|
124
|
+
let cleaned = response.trim();
|
|
125
|
+
if (cleaned.startsWith("```json")) {
|
|
126
|
+
cleaned = cleaned.slice(7);
|
|
127
|
+
} else if (cleaned.startsWith("```")) {
|
|
128
|
+
cleaned = cleaned.slice(3);
|
|
129
|
+
}
|
|
130
|
+
if (cleaned.endsWith("```")) {
|
|
131
|
+
cleaned = cleaned.slice(0, -3);
|
|
132
|
+
}
|
|
133
|
+
cleaned = cleaned.trim();
|
|
134
|
+
try {
|
|
135
|
+
return JSON.parse(cleaned);
|
|
136
|
+
} catch (error) {
|
|
137
|
+
throw new Error(
|
|
138
|
+
`Failed to parse AI response as JSON: ${error.message}
|
|
139
|
+
|
|
140
|
+
Response: ${response}`
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
const STANDARD_LABELS = {
|
|
145
|
+
type: [
|
|
146
|
+
{
|
|
147
|
+
name: "type: bug",
|
|
148
|
+
color: "d73a4a",
|
|
149
|
+
description: "Something isn't working"
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
name: "type: feature",
|
|
153
|
+
color: "0075ca",
|
|
154
|
+
description: "New feature or enhancement"
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
name: "type: docs",
|
|
158
|
+
color: "0075ca",
|
|
159
|
+
description: "Documentation improvements"
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
name: "type: maintenance",
|
|
163
|
+
color: "6c757d",
|
|
164
|
+
description: "Maintenance and refactoring"
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
name: "type: research",
|
|
168
|
+
color: "a371f7",
|
|
169
|
+
description: "Research and investigation"
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
name: "type: question",
|
|
173
|
+
color: "d876e3",
|
|
174
|
+
description: "Question or discussion"
|
|
175
|
+
}
|
|
176
|
+
],
|
|
177
|
+
priority: [
|
|
178
|
+
{
|
|
179
|
+
name: "priority: critical",
|
|
180
|
+
color: "b60205",
|
|
181
|
+
description: "Critical priority, needs immediate attention"
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
name: "priority: high",
|
|
185
|
+
color: "d93f0b",
|
|
186
|
+
description: "High priority"
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
name: "priority: medium",
|
|
190
|
+
color: "fbca04",
|
|
191
|
+
description: "Medium priority (default)"
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
name: "priority: low",
|
|
195
|
+
color: "fef2c0",
|
|
196
|
+
description: "Low priority"
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
name: "priority: icebox",
|
|
200
|
+
color: "e1e4e8",
|
|
201
|
+
description: "Future consideration, keep in Backlog"
|
|
202
|
+
}
|
|
203
|
+
],
|
|
204
|
+
size: [
|
|
205
|
+
{
|
|
206
|
+
name: "size: xs",
|
|
207
|
+
color: "c2e0c6",
|
|
208
|
+
description: "Extra small (< 2 hours)"
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
name: "size: s",
|
|
212
|
+
color: "7bd88f",
|
|
213
|
+
description: "Small (2-4 hours)"
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
name: "size: m",
|
|
217
|
+
color: "3fb950",
|
|
218
|
+
description: "Medium (~1 day)"
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
name: "size: l",
|
|
222
|
+
color: "2ea043",
|
|
223
|
+
description: "Large (2-3 days)"
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
name: "size: xl",
|
|
227
|
+
color: "1a7f37",
|
|
228
|
+
description: "Extra large (> 3 days)"
|
|
229
|
+
}
|
|
230
|
+
],
|
|
231
|
+
status: [
|
|
232
|
+
{
|
|
233
|
+
name: "status: blocked",
|
|
234
|
+
color: "d73a4a",
|
|
235
|
+
description: "Blocked by external dependency"
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
name: "status: help-wanted",
|
|
239
|
+
color: "008672",
|
|
240
|
+
description: "Community contributions welcome"
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
name: "status: good-first-issue",
|
|
244
|
+
color: "7057ff",
|
|
245
|
+
description: "Good for newcomers"
|
|
246
|
+
}
|
|
247
|
+
]
|
|
248
|
+
};
|
|
249
|
+
const AREA_LABEL_TEMPLATE = [
|
|
250
|
+
{
|
|
251
|
+
name: "area: core",
|
|
252
|
+
color: "fbca04",
|
|
253
|
+
description: "Core functionality"
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
name: "area: api",
|
|
257
|
+
color: "fbca04",
|
|
258
|
+
description: "API-related"
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
name: "area: ui",
|
|
262
|
+
color: "fbca04",
|
|
263
|
+
description: "User interface"
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
name: "area: cli",
|
|
267
|
+
color: "fbca04",
|
|
268
|
+
description: "Command-line interface"
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
name: "area: docs",
|
|
272
|
+
color: "fbca04",
|
|
273
|
+
description: "Documentation"
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
name: "area: infra",
|
|
277
|
+
color: "fbca04",
|
|
278
|
+
description: "Infrastructure and deployment"
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
name: "area: tests",
|
|
282
|
+
color: "fbca04",
|
|
283
|
+
description: "Testing infrastructure"
|
|
284
|
+
}
|
|
285
|
+
];
|
|
286
|
+
function getAllStandardLabels() {
|
|
287
|
+
return Object.values(STANDARD_LABELS).flat();
|
|
288
|
+
}
|
|
289
|
+
function getLabelsByCategory(category) {
|
|
290
|
+
return STANDARD_LABELS[category] || [];
|
|
291
|
+
}
|
|
292
|
+
const LABEL_MIGRATIONS = {
|
|
293
|
+
bug: "type: bug",
|
|
294
|
+
feature: "type: feature",
|
|
295
|
+
enhancement: "type: feature",
|
|
296
|
+
documentation: "type: docs",
|
|
297
|
+
question: "type: question",
|
|
298
|
+
"tech-debt": "type: maintenance",
|
|
299
|
+
epic: "type: feature"
|
|
300
|
+
};
|
|
301
|
+
function migrateLabel(oldLabel) {
|
|
302
|
+
return LABEL_MIGRATIONS[oldLabel] || oldLabel;
|
|
303
|
+
}
|
|
304
|
+
const VALID_TYPES = [
|
|
305
|
+
"bug",
|
|
306
|
+
"feature",
|
|
307
|
+
"docs",
|
|
308
|
+
"maintenance",
|
|
309
|
+
"research",
|
|
310
|
+
"question"
|
|
311
|
+
];
|
|
312
|
+
const VALID_PRIORITIES = [
|
|
313
|
+
"critical",
|
|
314
|
+
"high",
|
|
315
|
+
"medium",
|
|
316
|
+
"low",
|
|
317
|
+
"icebox"
|
|
318
|
+
];
|
|
319
|
+
const VALID_SIZES = ["xs", "s", "m", "l", "xl"];
|
|
320
|
+
function validateAIAnalysis(parsed) {
|
|
321
|
+
if (!parsed || typeof parsed !== "object") {
|
|
322
|
+
throw new Error("AI response is not a valid object");
|
|
323
|
+
}
|
|
324
|
+
const response = parsed;
|
|
325
|
+
if (!response.type || typeof response.type !== "string") {
|
|
326
|
+
throw new Error("AI response missing required field: type");
|
|
327
|
+
}
|
|
328
|
+
if (!response.priority || typeof response.priority !== "string") {
|
|
329
|
+
throw new Error("AI response missing required field: priority");
|
|
330
|
+
}
|
|
331
|
+
if (!response.size || typeof response.size !== "string") {
|
|
332
|
+
throw new Error("AI response missing required field: size");
|
|
333
|
+
}
|
|
334
|
+
if (!response.reasoning || typeof response.reasoning !== "string") {
|
|
335
|
+
throw new Error("AI response missing required field: reasoning");
|
|
336
|
+
}
|
|
337
|
+
if (!VALID_TYPES.includes(response.type)) {
|
|
338
|
+
throw new Error(`Invalid type: ${response.type}`);
|
|
339
|
+
}
|
|
340
|
+
if (!VALID_PRIORITIES.includes(response.priority)) {
|
|
341
|
+
throw new Error(`Invalid priority: ${response.priority}`);
|
|
342
|
+
}
|
|
343
|
+
if (!VALID_SIZES.includes(response.size)) {
|
|
344
|
+
throw new Error(`Invalid size: ${response.size}`);
|
|
345
|
+
}
|
|
346
|
+
if (response.affected_packages !== void 0) {
|
|
347
|
+
if (!Array.isArray(response.affected_packages)) {
|
|
348
|
+
throw new Error("affected_packages must be an array");
|
|
349
|
+
}
|
|
350
|
+
if (!response.affected_packages.every((pkg) => typeof pkg === "string")) {
|
|
351
|
+
throw new Error("affected_packages must contain only strings");
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return {
|
|
355
|
+
type: response.type,
|
|
356
|
+
priority: response.priority,
|
|
357
|
+
size: response.size,
|
|
358
|
+
reasoning: response.reasoning,
|
|
359
|
+
...response.affected_packages && {
|
|
360
|
+
affected_packages: response.affected_packages
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
async function analyzeIssue(context) {
|
|
365
|
+
const prompt = buildAnalysisPrompt(context);
|
|
366
|
+
const response = await getAICompletion([
|
|
367
|
+
{
|
|
368
|
+
role: "system",
|
|
369
|
+
content: "You are an expert GitHub issue triager. Analyze issues and provide structured triage information in JSON format."
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
role: "user",
|
|
373
|
+
content: prompt
|
|
374
|
+
}
|
|
375
|
+
]);
|
|
376
|
+
const parsed = parseAIJson(response);
|
|
377
|
+
return validateAIAnalysis(parsed);
|
|
378
|
+
}
|
|
379
|
+
function buildAnalysisPrompt(context) {
|
|
380
|
+
const { config, issueNumber, issueTitle, issueBody, issueAuthor } = context;
|
|
381
|
+
let prompt = `Analyze this GitHub issue and provide triage information for our kanban workflow.
|
|
382
|
+
|
|
383
|
+
Repository: ${context.owner}/${context.repo} (${config.repoDescription})
|
|
384
|
+
|
|
385
|
+
Issue #${issueNumber}
|
|
386
|
+
Title: ${issueTitle}
|
|
387
|
+
Body: ${issueBody || "(empty)"}
|
|
388
|
+
Author: ${issueAuthor}
|
|
389
|
+
|
|
390
|
+
Determine:
|
|
391
|
+
1. **type**: One of: bug, feature, docs, maintenance, research, question
|
|
392
|
+
2. **priority**: critical, high, medium, low, icebox
|
|
393
|
+
- Use "icebox" for low priority or future consideration items
|
|
394
|
+
3. **size**: Estimated effort: xs (<2hr), s (2-4hr), m (~1day), l (2-3days), xl (>3days)`;
|
|
395
|
+
if (config.packagePattern) {
|
|
396
|
+
prompt += `
|
|
397
|
+
4. **affected_packages**: List of ${config.packagePattern} packages affected`;
|
|
398
|
+
if (config.packageExamples && config.packageExamples.length > 0) {
|
|
399
|
+
prompt += ` (e.g., ${JSON.stringify(config.packageExamples)})`;
|
|
400
|
+
}
|
|
401
|
+
prompt += `
|
|
402
|
+
5. **reasoning**: Brief explanation of your analysis (2-3 sentences)`;
|
|
403
|
+
} else {
|
|
404
|
+
prompt += `
|
|
405
|
+
4. **reasoning**: Brief explanation of your analysis (2-3 sentences)`;
|
|
406
|
+
}
|
|
407
|
+
prompt += `
|
|
408
|
+
|
|
409
|
+
Return JSON in this exact format:
|
|
410
|
+
{
|
|
411
|
+
"type": "bug|feature|docs|maintenance|research|question",
|
|
412
|
+
"priority": "critical|high|medium|low|icebox",
|
|
413
|
+
"size": "xs|s|m|l|xl",`;
|
|
414
|
+
if (config.packagePattern) {
|
|
415
|
+
prompt += `
|
|
416
|
+
"affected_packages": ["package names"],`;
|
|
417
|
+
}
|
|
418
|
+
prompt += `
|
|
419
|
+
"reasoning": "explanation here"
|
|
420
|
+
}`;
|
|
421
|
+
return prompt;
|
|
422
|
+
}
|
|
423
|
+
async function githubAPI(token, method, path, data = null) {
|
|
424
|
+
return new Promise((resolve, reject) => {
|
|
425
|
+
const options = {
|
|
426
|
+
hostname: "api.github.com",
|
|
427
|
+
path,
|
|
428
|
+
method,
|
|
429
|
+
headers: {
|
|
430
|
+
Authorization: `token ${token}`,
|
|
431
|
+
Accept: "application/vnd.github+json",
|
|
432
|
+
"User-Agent": "happyvertical-github-actions",
|
|
433
|
+
"X-GitHub-Api-Version": "2022-11-28"
|
|
434
|
+
}
|
|
435
|
+
};
|
|
436
|
+
if (data) {
|
|
437
|
+
options.headers = {
|
|
438
|
+
...options.headers,
|
|
439
|
+
"Content-Type": "application/json"
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
const req = https.request(options, (res) => {
|
|
443
|
+
let body = "";
|
|
444
|
+
res.on("data", (chunk) => body += chunk);
|
|
445
|
+
res.on("end", () => {
|
|
446
|
+
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
|
|
447
|
+
resolve(body ? JSON.parse(body) : null);
|
|
448
|
+
} else {
|
|
449
|
+
reject(new Error(`GitHub API error: ${res.statusCode} ${body}`));
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
});
|
|
453
|
+
req.on("error", reject);
|
|
454
|
+
if (data) req.write(JSON.stringify(data));
|
|
455
|
+
req.end();
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
async function postTriageComment(context, analysis, duplicates) {
|
|
459
|
+
const comment = buildTriageComment(context, analysis, duplicates);
|
|
460
|
+
const path = `/repos/${context.owner}/${context.repo}/issues/${context.issueNumber}/comments`;
|
|
461
|
+
try {
|
|
462
|
+
await githubAPI(context.token, "POST", path, { body: comment });
|
|
463
|
+
console.log("Posted triage comment");
|
|
464
|
+
} catch (error) {
|
|
465
|
+
console.error("Error posting comment:", error.message);
|
|
466
|
+
throw error;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
async function postErrorComment(context, error) {
|
|
470
|
+
const comment = `## 🤖 AI Triage Failed
|
|
471
|
+
|
|
472
|
+
Automated triage encountered an error:
|
|
473
|
+
\`\`\`
|
|
474
|
+
${error.message}
|
|
475
|
+
\`\`\`
|
|
476
|
+
|
|
477
|
+
Please triage this issue manually.`;
|
|
478
|
+
const path = `/repos/${context.owner}/${context.repo}/issues/${context.issueNumber}/comments`;
|
|
479
|
+
try {
|
|
480
|
+
await githubAPI(context.token, "POST", path, { body: comment });
|
|
481
|
+
} catch (err) {
|
|
482
|
+
console.error("Error posting error comment:", err.message);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
function buildTriageComment(context, analysis, duplicates) {
|
|
486
|
+
let comment = `## 🤖 AI Triage
|
|
487
|
+
|
|
488
|
+
`;
|
|
489
|
+
comment += `**Type**: \`${analysis.type}\`
|
|
490
|
+
`;
|
|
491
|
+
comment += `**Priority**: \`${analysis.priority}\`
|
|
492
|
+
`;
|
|
493
|
+
comment += `**Size**: \`${analysis.size}\`
|
|
494
|
+
|
|
495
|
+
`;
|
|
496
|
+
if (analysis.affected_packages && analysis.affected_packages.length > 0) {
|
|
497
|
+
const packages = analysis.affected_packages.map((p) => `\`${p}\``).join(", ");
|
|
498
|
+
comment += `**Affected Packages**: ${packages}
|
|
499
|
+
|
|
500
|
+
`;
|
|
501
|
+
}
|
|
502
|
+
comment += `**Analysis**: ${analysis.reasoning}
|
|
503
|
+
|
|
504
|
+
`;
|
|
505
|
+
if (duplicates.length > 0) {
|
|
506
|
+
comment += `### ⚠️ Potential Duplicates
|
|
507
|
+
|
|
508
|
+
`;
|
|
509
|
+
duplicates.forEach((dup) => {
|
|
510
|
+
comment += `- #${dup.number}: ${dup.title}
|
|
511
|
+
`;
|
|
512
|
+
});
|
|
513
|
+
comment += `
|
|
514
|
+
`;
|
|
515
|
+
}
|
|
516
|
+
comment += `---
|
|
517
|
+
`;
|
|
518
|
+
comment += `*This triage was performed automatically using GitHub Models API. Please review and adjust labels as needed.*`;
|
|
519
|
+
return comment;
|
|
520
|
+
}
|
|
521
|
+
async function searchDuplicates(context) {
|
|
522
|
+
const keywords = context.issueTitle.split(" ").slice(0, 5).join(" ");
|
|
523
|
+
const query = `repo:${context.owner}/${context.repo} is:issue ${keywords}`;
|
|
524
|
+
const path = `/search/issues?q=${encodeURIComponent(query)}&per_page=5`;
|
|
525
|
+
try {
|
|
526
|
+
const result = await githubAPI(context.token, "GET", path, null);
|
|
527
|
+
return result.items.filter((issue) => issue.number !== context.issueNumber);
|
|
528
|
+
} catch (error) {
|
|
529
|
+
console.error("Error searching for duplicates:", error.message);
|
|
530
|
+
return [];
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
async function applyLabels(context, labels) {
|
|
534
|
+
if (labels.length === 0) {
|
|
535
|
+
console.log("No labels to apply");
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
try {
|
|
539
|
+
const repo = await createRepository(
|
|
540
|
+
context.token,
|
|
541
|
+
context.owner,
|
|
542
|
+
context.repo
|
|
543
|
+
);
|
|
544
|
+
await repo.addLabels(context.issueNumber, labels);
|
|
545
|
+
console.log(`Applied labels: ${labels.join(", ")}`);
|
|
546
|
+
} catch (error) {
|
|
547
|
+
console.error("Error applying labels:", error.message);
|
|
548
|
+
throw error;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
async function removeAgentLabel(context, agentType) {
|
|
552
|
+
const label = `agent: ${agentType}`;
|
|
553
|
+
try {
|
|
554
|
+
const repo = await createRepository(
|
|
555
|
+
context.token,
|
|
556
|
+
context.owner,
|
|
557
|
+
context.repo
|
|
558
|
+
);
|
|
559
|
+
await repo.removeLabel(context.issueNumber, label);
|
|
560
|
+
console.log(`Removed label: ${label}`);
|
|
561
|
+
} catch (error) {
|
|
562
|
+
if (!error.message.includes("404")) {
|
|
563
|
+
console.error("Error removing label:", error.message);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
function getTypeLabel(type) {
|
|
568
|
+
return `type: ${type}`;
|
|
569
|
+
}
|
|
570
|
+
function getPriorityLabel(priority) {
|
|
571
|
+
return `priority: ${priority}`;
|
|
572
|
+
}
|
|
573
|
+
function getSizeLabel(size) {
|
|
574
|
+
return `size: ${size}`;
|
|
575
|
+
}
|
|
576
|
+
function getAgentLabel(agentType) {
|
|
577
|
+
return `agent: ${agentType}`;
|
|
578
|
+
}
|
|
579
|
+
async function updateProjectStatus(context, statusName) {
|
|
580
|
+
if (!context.config.projectEnabled) {
|
|
581
|
+
console.log("Project board integration disabled");
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
if (!context.config.projectId || !context.config.statusFieldId) {
|
|
585
|
+
console.log("Project configuration missing");
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
if (!context.config.statusOptions || !context.config.statusOptions[statusName]) {
|
|
589
|
+
console.log(`Status "${statusName}" not configured`);
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
try {
|
|
593
|
+
const repo = await createRepository(
|
|
594
|
+
context.token,
|
|
595
|
+
context.owner,
|
|
596
|
+
context.repo
|
|
597
|
+
);
|
|
598
|
+
const project = await createProject(
|
|
599
|
+
context.token,
|
|
600
|
+
context.config.projectId,
|
|
601
|
+
context.config.statusFieldId,
|
|
602
|
+
context.config.statusOptions
|
|
603
|
+
);
|
|
604
|
+
const issue = await repo.getIssue(context.issueNumber);
|
|
605
|
+
const items = await project.listItems();
|
|
606
|
+
let itemId = items.find(
|
|
607
|
+
(item) => item.contentId === issue.id
|
|
608
|
+
)?.id;
|
|
609
|
+
if (!itemId) {
|
|
610
|
+
console.log("Issue not in project, adding...");
|
|
611
|
+
const item = await project.addItem(issue.id);
|
|
612
|
+
itemId = item.id;
|
|
613
|
+
}
|
|
614
|
+
await project.updateItemStatus(itemId, statusName);
|
|
615
|
+
console.log(`Updated project status to: ${statusName}`);
|
|
616
|
+
} catch (error) {
|
|
617
|
+
console.error("Error updating project status:", error.message);
|
|
618
|
+
throw error;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
async function triageIssue(context) {
|
|
622
|
+
console.log(`Triaging issue #${context.issueNumber}: ${context.issueTitle}`);
|
|
623
|
+
try {
|
|
624
|
+
await applyLabels(context, [getAgentLabel("triage")]);
|
|
625
|
+
console.log("Calling AI for analysis...");
|
|
626
|
+
const analysis = await analyzeIssue(context);
|
|
627
|
+
console.log("AI Analysis:", JSON.stringify(analysis, null, 2));
|
|
628
|
+
console.log("Searching for potential duplicates...");
|
|
629
|
+
const duplicates = await searchDuplicates(context);
|
|
630
|
+
console.log(`Found ${duplicates.length} potential duplicates`);
|
|
631
|
+
const labels = [
|
|
632
|
+
getTypeLabel(analysis.type),
|
|
633
|
+
getPriorityLabel(analysis.priority),
|
|
634
|
+
getSizeLabel(analysis.size)
|
|
635
|
+
];
|
|
636
|
+
await applyLabels(context, labels);
|
|
637
|
+
await postTriageComment(context, analysis, duplicates);
|
|
638
|
+
await removeAgentLabel(context, "triage");
|
|
639
|
+
if (context.config.projectEnabled) {
|
|
640
|
+
console.log("Moving issue to Backlog...");
|
|
641
|
+
await updateProjectStatus(context, "Backlog");
|
|
642
|
+
}
|
|
643
|
+
console.log("✅ Triage complete!");
|
|
644
|
+
return {
|
|
645
|
+
success: true,
|
|
646
|
+
analysis,
|
|
647
|
+
duplicates
|
|
648
|
+
};
|
|
649
|
+
} catch (error) {
|
|
650
|
+
console.error("❌ Triage failed:", error.message);
|
|
651
|
+
console.error(error.stack);
|
|
652
|
+
await postErrorComment(context, error);
|
|
653
|
+
return {
|
|
654
|
+
success: false,
|
|
655
|
+
error: error.message
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
export {
|
|
660
|
+
AREA_LABEL_TEMPLATE as A,
|
|
661
|
+
LABEL_MIGRATIONS as L,
|
|
662
|
+
STANDARD_LABELS as S,
|
|
663
|
+
createProject as a,
|
|
664
|
+
analyzeIssue as b,
|
|
665
|
+
createRepository as c,
|
|
666
|
+
postErrorComment as d,
|
|
667
|
+
applyLabels as e,
|
|
668
|
+
callAnthropic as f,
|
|
669
|
+
githubAPI as g,
|
|
670
|
+
callGitHubModels as h,
|
|
671
|
+
callOpenAI as i,
|
|
672
|
+
getAICompletion as j,
|
|
673
|
+
getAgentLabel as k,
|
|
674
|
+
getAllStandardLabels as l,
|
|
675
|
+
getLabelsByCategory as m,
|
|
676
|
+
getPriorityLabel as n,
|
|
677
|
+
getSizeLabel as o,
|
|
678
|
+
postTriageComment as p,
|
|
679
|
+
getTypeLabel as q,
|
|
680
|
+
migrateLabel as r,
|
|
681
|
+
searchDuplicates as s,
|
|
682
|
+
parseAIJson as t,
|
|
683
|
+
removeAgentLabel as u,
|
|
684
|
+
triageIssue as v,
|
|
685
|
+
updateProjectStatus as w
|
|
686
|
+
};
|
|
687
|
+
//# sourceMappingURL=index-v2-CqFKwTm8.js.map
|