@happyvertical/github-actions 0.80.0 → 0.80.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.
@@ -0,0 +1,1318 @@
1
+ import { getProject } from "@happyvertical/projects";
2
+ import { getRepository } from "@happyvertical/repos";
3
+ import https from "node:https";
4
+ //#region \0rolldown/runtime.js
5
+ var __defProp = Object.defineProperty;
6
+ var __exportAll = (all, no_symbols) => {
7
+ let target = {};
8
+ for (var name in all) __defProp(target, name, {
9
+ get: all[name],
10
+ enumerable: true
11
+ });
12
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
13
+ return target;
14
+ };
15
+ //#endregion
16
+ //#region src/shared/adapters.ts
17
+ /**
18
+ * Create a repository client from GitHub context
19
+ */
20
+ async function createRepository(token, owner, repo) {
21
+ return getRepository({
22
+ type: "github",
23
+ owner,
24
+ repo,
25
+ token
26
+ });
27
+ }
28
+ /**
29
+ * Create a project client from configuration
30
+ */
31
+ async function createProject(token, projectId, statusFieldId, statusOptions) {
32
+ return getProject({
33
+ type: "github",
34
+ projectId,
35
+ token,
36
+ statusFieldId,
37
+ statusOptions
38
+ });
39
+ }
40
+ //#endregion
41
+ //#region src/planning/analyze.ts
42
+ /**
43
+ * Generate an AI-powered implementation plan for a GitHub issue.
44
+ *
45
+ * Builds a prompt from the issue context and repository description, then
46
+ * calls the AI service to produce a structured plan with tasks, complexity,
47
+ * and technical considerations.
48
+ *
49
+ * @param context - Planning context with issue details and repository config
50
+ * @returns Structured implementation plan
51
+ */
52
+ async function analyzePlanning(context) {
53
+ const systemPrompt = `You are a software development planning assistant for the ${context.owner}/${context.repo} repository.
54
+
55
+ Repository: ${context.config.repoDescription}
56
+ ${context.config.packagePattern ? `Package pattern: ${context.config.packagePattern}` : ""}
57
+ ${context.config.packageExamples ? `Example packages: ${context.config.packageExamples.join(", ")}` : ""}
58
+
59
+ Your task is to create a detailed implementation plan for issues. Analyze the requirements and provide:
60
+ 1. A clear summary of what needs to be done
61
+ 2. A step-by-step task breakdown
62
+ 3. Complexity assessment (simple, moderate, complex)
63
+ 4. Technical considerations
64
+ 5. Files likely to be affected
65
+ 6. Dependencies or blockers`;
66
+ const userPrompt = `Issue #${context.issueNumber}: ${context.issueTitle}
67
+
68
+ ${context.issueBody || "No description provided"}
69
+
70
+ Please create a detailed implementation plan following this JSON structure:
71
+ {
72
+ "summary": "Brief summary of the implementation",
73
+ "tasks": ["Step 1", "Step 2", "Step 3"],
74
+ "complexity": "simple|moderate|complex",
75
+ "considerations": ["Technical consideration 1", "Technical consideration 2"],
76
+ "affected_files": ["file1.ts", "file2.ts"],
77
+ "dependencies": ["Optional blocker or dependency"]
78
+ }`;
79
+ console.log("AI Planning Analysis would be called here");
80
+ console.log("System:", systemPrompt);
81
+ console.log("User:", userPrompt);
82
+ return {
83
+ summary: `Implementation plan for: ${context.issueTitle}`,
84
+ tasks: [
85
+ "Analyze requirements and design approach",
86
+ "Implement core functionality",
87
+ "Add tests",
88
+ "Update documentation"
89
+ ],
90
+ complexity: "moderate",
91
+ considerations: [
92
+ "Ensure backward compatibility",
93
+ "Follow existing code patterns",
94
+ "Add comprehensive error handling"
95
+ ],
96
+ affected_files: [],
97
+ dependencies: []
98
+ };
99
+ }
100
+ //#endregion
101
+ //#region src/planning/definition-of-ready.ts
102
+ /**
103
+ * Check if an issue meets the Definition of Ready criteria
104
+ */
105
+ function validateDefinitionOfReady(issue, hasPlanComment) {
106
+ const labels = issue.labels.map((l) => typeof l === "string" ? l : l.name);
107
+ return {
108
+ hasClearDescription: !!issue.body && issue.body.trim().length > 50,
109
+ hasTypeLabel: labels.some((l) => l.startsWith("type:")),
110
+ hasPriorityLabel: labels.some((l) => l.startsWith("priority:")),
111
+ hasSizeLabel: labels.some((l) => l.startsWith("size:")),
112
+ hasPlan: hasPlanComment,
113
+ noBlockers: !labels.some((l) => l === "status: blocked")
114
+ };
115
+ }
116
+ /**
117
+ * Check if issue is ready (all criteria met)
118
+ */
119
+ function isReady(dor) {
120
+ return dor.hasClearDescription && dor.hasTypeLabel && dor.hasPriorityLabel && dor.hasSizeLabel && dor.hasPlan && dor.noBlockers;
121
+ }
122
+ /**
123
+ * Format Definition of Ready as markdown checklist
124
+ */
125
+ function formatDefinitionOfReady(dor) {
126
+ const checkbox = (checked) => checked ? "[x]" : "[ ]";
127
+ return `## Definition of Ready
128
+
129
+ ${checkbox(dor.hasClearDescription)} Clear, actionable description
130
+ ${checkbox(dor.hasTypeLabel)} Type label applied
131
+ ${checkbox(dor.hasPriorityLabel)} Priority label applied
132
+ ${checkbox(dor.hasSizeLabel)} Size label applied
133
+ ${checkbox(dor.hasPlan)} Implementation plan documented
134
+ ${checkbox(dor.noBlockers)} No blocking dependencies
135
+
136
+ ${isReady(dor) ? "✅ **This issue is ready for implementation!**" : "⚠️ **This issue needs more work before it's ready.**"}`;
137
+ }
138
+ //#endregion
139
+ //#region src/planning/comment.ts
140
+ /**
141
+ * Post implementation plan comment
142
+ */
143
+ async function postPlanComment(repo, issueNumber, plan) {
144
+ const comment = `## 🤖 Implementation Plan
145
+
146
+ ### Summary
147
+ ${plan.summary}
148
+
149
+ ### Tasks
150
+ ${plan.tasks.map((task, i) => `${i + 1}. ${task}`).join("\n")}
151
+
152
+ ### Complexity
153
+ **${plan.complexity.charAt(0).toUpperCase() + plan.complexity.slice(1)}**
154
+
155
+ ### Technical Considerations
156
+ ${plan.considerations.map((c) => `- ${c}`).join("\n")}
157
+
158
+ ${plan.affected_files && plan.affected_files.length > 0 ? `### Affected Files\n${plan.affected_files.map((f) => `- \`${f}\``).join("\n")}` : ""}
159
+
160
+ ${plan.dependencies && plan.dependencies.length > 0 ? `### Dependencies\n${plan.dependencies.map((d) => `- ${d}`).join("\n")}` : ""}
161
+
162
+ ---
163
+ *This plan was generated by AI. Please review and provide feedback in the comments.*`;
164
+ await repo.addComment(issueNumber, comment);
165
+ }
166
+ /**
167
+ * Post Definition of Ready status comment
168
+ */
169
+ async function postReadyCheckComment(repo, issueNumber, dor) {
170
+ const comment = formatDefinitionOfReady(dor);
171
+ await repo.addComment(issueNumber, comment);
172
+ }
173
+ /**
174
+ * Post planning error comment
175
+ */
176
+ async function postPlanningErrorComment(repo, issueNumber, error) {
177
+ const comment = `## ⚠️ Planning Failed
178
+
179
+ An error occurred while creating the implementation plan:
180
+
181
+ \`\`\`
182
+ ${error.message}
183
+ \`\`\`
184
+
185
+ Please try again or create a manual implementation plan in the comments.`;
186
+ await repo.addComment(issueNumber, comment);
187
+ }
188
+ //#endregion
189
+ //#region src/planning/index.ts
190
+ /**
191
+ * Planning Workflow Orchestration
192
+ */
193
+ /**
194
+ * Start planning workflow for an issue
195
+ */
196
+ async function startPlanning(context) {
197
+ console.log(`Starting planning for issue #${context.issueNumber}: ${context.issueTitle}`);
198
+ try {
199
+ const repo = await createRepository(context.token, context.owner, context.repo);
200
+ await repo.addLabels(context.issueNumber, ["agent: planning"]);
201
+ console.log("Generating implementation plan...");
202
+ const plan = await analyzePlanning(context);
203
+ await postPlanComment(repo, context.issueNumber, plan);
204
+ console.log("✅ Planning started successfully");
205
+ return {
206
+ success: true,
207
+ plan
208
+ };
209
+ } catch (error) {
210
+ console.error("❌ Planning failed:", error.message);
211
+ try {
212
+ await postPlanningErrorComment(await createRepository(context.token, context.owner, context.repo), context.issueNumber, error);
213
+ } catch {}
214
+ return {
215
+ success: false,
216
+ error: error.message
217
+ };
218
+ }
219
+ }
220
+ /**
221
+ * Complete planning workflow and move to Ready
222
+ */
223
+ async function completePlanning(context) {
224
+ console.log(`Completing planning for issue #${context.issueNumber}: ${context.issueTitle}`);
225
+ try {
226
+ const repo = await createRepository(context.token, context.owner, context.repo);
227
+ const issue = await repo.getIssue(context.issueNumber);
228
+ const dor = validateDefinitionOfReady(issue, (await repo.listComments(context.issueNumber)).some((c) => c.body?.includes("Implementation Plan")));
229
+ await postReadyCheckComment(repo, context.issueNumber, dor);
230
+ if (!isReady(dor)) {
231
+ console.log("⚠️ Issue does not meet Definition of Ready criteria");
232
+ return {
233
+ success: false,
234
+ error: "Issue does not meet Definition of Ready criteria"
235
+ };
236
+ }
237
+ await repo.removeLabel(context.issueNumber, "agent: planning");
238
+ if (context.config.projectId && context.config.statusFieldId && context.config.statusOptions) {
239
+ const project = await createProject(context.token, context.config.projectId, context.config.statusFieldId, context.config.statusOptions);
240
+ const itemId = (await project.listItems()).find((item) => item.contentId === issue.id)?.id;
241
+ if (itemId) await project.updateItemStatus(itemId, "Ready");
242
+ }
243
+ console.log("✅ Planning completed, moved to Ready");
244
+ return { success: true };
245
+ } catch (error) {
246
+ console.error("❌ Complete planning failed:", error.message);
247
+ return {
248
+ success: false,
249
+ error: error.message
250
+ };
251
+ }
252
+ }
253
+ //#endregion
254
+ //#region src/shared/ai.ts
255
+ /**
256
+ * Call GitHub Models API (available to all GitHub users)
257
+ */
258
+ async function callGitHubModels(token, messages, options = {}) {
259
+ const model = options.model || "gpt-4o-mini";
260
+ const temperature = options.temperature ?? .3;
261
+ const maxTokens = options.maxTokens ?? 1e3;
262
+ const response = await fetch(`https://models.inference.ai.azure.com/chat/completions`, {
263
+ method: "POST",
264
+ headers: {
265
+ Authorization: `Bearer ${token}`,
266
+ "Content-Type": "application/json"
267
+ },
268
+ body: JSON.stringify({
269
+ model,
270
+ messages,
271
+ temperature,
272
+ max_tokens: maxTokens
273
+ })
274
+ });
275
+ if (!response.ok) {
276
+ const error = await response.text();
277
+ throw new Error(`GitHub Models API error: ${response.status} ${response.statusText}\n${error}`);
278
+ }
279
+ return (await response.json()).choices[0].message.content;
280
+ }
281
+ /**
282
+ * Call OpenAI API directly
283
+ */
284
+ async function callOpenAI(apiKey, messages, options = {}) {
285
+ const model = options.model || "gpt-4o-mini";
286
+ const temperature = options.temperature ?? .3;
287
+ const maxTokens = options.maxTokens ?? 1e3;
288
+ const response = await fetch("https://api.openai.com/v1/chat/completions", {
289
+ method: "POST",
290
+ headers: {
291
+ Authorization: `Bearer ${apiKey}`,
292
+ "Content-Type": "application/json"
293
+ },
294
+ body: JSON.stringify({
295
+ model,
296
+ messages,
297
+ temperature,
298
+ max_tokens: maxTokens
299
+ })
300
+ });
301
+ if (!response.ok) {
302
+ const error = await response.text();
303
+ throw new Error(`OpenAI API error: ${response.status} ${response.statusText}\n${error}`);
304
+ }
305
+ return (await response.json()).choices[0].message.content;
306
+ }
307
+ /**
308
+ * Call Anthropic Claude API
309
+ */
310
+ async function callAnthropic(apiKey, messages, options = {}) {
311
+ const model = options.model || "claude-3-5-sonnet-20241022";
312
+ const temperature = options.temperature ?? .3;
313
+ const maxTokens = options.maxTokens ?? 1e3;
314
+ const systemMessage = messages.find((m) => m.role === "system");
315
+ const conversationMessages = messages.filter((m) => m.role !== "system");
316
+ const response = await fetch("https://api.anthropic.com/v1/messages", {
317
+ method: "POST",
318
+ headers: {
319
+ "x-api-key": apiKey,
320
+ "anthropic-version": "2023-06-01",
321
+ "Content-Type": "application/json"
322
+ },
323
+ body: JSON.stringify({
324
+ model,
325
+ max_tokens: maxTokens,
326
+ temperature,
327
+ system: systemMessage?.content,
328
+ messages: conversationMessages
329
+ })
330
+ });
331
+ if (!response.ok) {
332
+ const error = await response.text();
333
+ throw new Error(`Anthropic API error: ${response.status} ${response.statusText}\n${error}`);
334
+ }
335
+ return (await response.json()).content[0].text;
336
+ }
337
+ /**
338
+ * Unified AI completion function
339
+ *
340
+ * Automatically selects provider based on available environment variables:
341
+ * - GITHUB_TOKEN → GitHub Models (default)
342
+ * - OPENAI_API_KEY → OpenAI
343
+ * - ANTHROPIC_API_KEY → Anthropic
344
+ */
345
+ async function getAICompletion(messages, options = {}) {
346
+ if (process.env.GITHUB_TOKEN) return callGitHubModels(process.env.GITHUB_TOKEN, messages, options);
347
+ if (process.env.OPENAI_API_KEY) return callOpenAI(process.env.OPENAI_API_KEY, messages, options);
348
+ if (process.env.ANTHROPIC_API_KEY) return callAnthropic(process.env.ANTHROPIC_API_KEY, messages, options);
349
+ throw new Error("No AI API credentials found. Set GITHUB_TOKEN, OPENAI_API_KEY, or ANTHROPIC_API_KEY environment variable.");
350
+ }
351
+ /**
352
+ * Parse JSON from AI response
353
+ *
354
+ * Handles markdown code blocks and extracts JSON
355
+ */
356
+ function parseAIJson(response) {
357
+ let cleaned = response.trim();
358
+ if (cleaned.startsWith("```json")) cleaned = cleaned.slice(7);
359
+ else if (cleaned.startsWith("```")) cleaned = cleaned.slice(3);
360
+ if (cleaned.endsWith("```")) cleaned = cleaned.slice(0, -3);
361
+ cleaned = cleaned.trim();
362
+ try {
363
+ return JSON.parse(cleaned);
364
+ } catch (error) {
365
+ throw new Error(`Failed to parse AI response as JSON: ${error.message}\n\nResponse: ${response}`);
366
+ }
367
+ }
368
+ //#endregion
369
+ //#region src/shared/github.ts
370
+ /**
371
+ * Make a GitHub REST API request
372
+ */
373
+ async function githubRequest(context, method, path, body) {
374
+ const url = `https://api.github.com${path}`;
375
+ const headers = {
376
+ Authorization: `Bearer ${context.token}`,
377
+ Accept: "application/vnd.github+json",
378
+ "X-GitHub-Api-Version": "2022-11-28",
379
+ "Content-Type": "application/json"
380
+ };
381
+ const response = await fetch(url, {
382
+ method,
383
+ headers,
384
+ body: body ? JSON.stringify(body) : void 0
385
+ });
386
+ if (!response.ok) {
387
+ const error = await response.text();
388
+ throw new Error(`GitHub API error: ${response.status} ${response.statusText}\n${error}`);
389
+ }
390
+ return response.json();
391
+ }
392
+ /**
393
+ * Make a GitHub GraphQL API request
394
+ */
395
+ async function githubGraphQL(token, query, variables) {
396
+ const response = await fetch("https://api.github.com/graphql", {
397
+ method: "POST",
398
+ headers: {
399
+ Authorization: `Bearer ${token}`,
400
+ "Content-Type": "application/json"
401
+ },
402
+ body: JSON.stringify({
403
+ query,
404
+ variables
405
+ })
406
+ });
407
+ if (!response.ok) {
408
+ const error = await response.text();
409
+ throw new Error(`GitHub GraphQL error: ${response.status} ${response.statusText}\n${error}`);
410
+ }
411
+ const result = await response.json();
412
+ if (result.errors) throw new Error(`GitHub GraphQL errors: ${result.errors.map((e) => e.message).join(", ")}`);
413
+ return result.data;
414
+ }
415
+ /**
416
+ * Add labels to an issue
417
+ */
418
+ async function addLabels(context, issueNumber, labels) {
419
+ await githubRequest(context, "POST", `/repos/${context.owner}/${context.repo}/issues/${issueNumber}/labels`, { labels });
420
+ }
421
+ /**
422
+ * Remove a label from an issue
423
+ */
424
+ async function removeLabel(context, issueNumber, label) {
425
+ const encodedLabel = encodeURIComponent(label);
426
+ await githubRequest(context, "DELETE", `/repos/${context.owner}/${context.repo}/issues/${issueNumber}/labels/${encodedLabel}`);
427
+ }
428
+ /**
429
+ * Post a comment on an issue
430
+ */
431
+ async function postComment(context, issueNumber, body) {
432
+ await githubRequest(context, "POST", `/repos/${context.owner}/${context.repo}/issues/${issueNumber}/comments`, { body });
433
+ }
434
+ /**
435
+ * Assign users to an issue
436
+ */
437
+ async function assignIssue(context, issueNumber, assignees) {
438
+ await githubRequest(context, "POST", `/repos/${context.owner}/${context.repo}/issues/${issueNumber}/assignees`, { assignees });
439
+ }
440
+ /**
441
+ * Create or update a repository label
442
+ */
443
+ async function createOrUpdateLabel(context, name, color, description) {
444
+ try {
445
+ await githubRequest(context, "PATCH", `/repos/${context.owner}/${context.repo}/labels/${encodeURIComponent(name)}`, {
446
+ color,
447
+ description
448
+ });
449
+ } catch (error) {
450
+ if (error.message.includes("404")) await githubRequest(context, "POST", `/repos/${context.owner}/${context.repo}/labels`, {
451
+ name,
452
+ color,
453
+ description
454
+ });
455
+ else throw error;
456
+ }
457
+ }
458
+ /**
459
+ * Get issue details
460
+ */
461
+ async function getIssue(context, issueNumber) {
462
+ return await githubRequest(context, "GET", `/repos/${context.owner}/${context.repo}/issues/${issueNumber}`);
463
+ }
464
+ //#endregion
465
+ //#region src/shared/labels.ts
466
+ /**
467
+ * Standard label set organized by category
468
+ */
469
+ var STANDARD_LABELS = {
470
+ type: [
471
+ {
472
+ name: "type: bug",
473
+ color: "d73a4a",
474
+ description: "Something isn't working"
475
+ },
476
+ {
477
+ name: "type: feature",
478
+ color: "0075ca",
479
+ description: "New feature or enhancement"
480
+ },
481
+ {
482
+ name: "type: docs",
483
+ color: "0075ca",
484
+ description: "Documentation improvements"
485
+ },
486
+ {
487
+ name: "type: maintenance",
488
+ color: "6c757d",
489
+ description: "Maintenance and refactoring"
490
+ },
491
+ {
492
+ name: "type: research",
493
+ color: "a371f7",
494
+ description: "Research and investigation"
495
+ },
496
+ {
497
+ name: "type: question",
498
+ color: "d876e3",
499
+ description: "Question or discussion"
500
+ }
501
+ ],
502
+ priority: [
503
+ {
504
+ name: "priority: critical",
505
+ color: "b60205",
506
+ description: "Critical priority, needs immediate attention"
507
+ },
508
+ {
509
+ name: "priority: high",
510
+ color: "d93f0b",
511
+ description: "High priority"
512
+ },
513
+ {
514
+ name: "priority: medium",
515
+ color: "fbca04",
516
+ description: "Medium priority (default)"
517
+ },
518
+ {
519
+ name: "priority: low",
520
+ color: "fef2c0",
521
+ description: "Low priority"
522
+ },
523
+ {
524
+ name: "priority: icebox",
525
+ color: "e1e4e8",
526
+ description: "Future consideration, keep in Backlog"
527
+ }
528
+ ],
529
+ size: [
530
+ {
531
+ name: "size: xs",
532
+ color: "c2e0c6",
533
+ description: "Extra small (< 2 hours)"
534
+ },
535
+ {
536
+ name: "size: s",
537
+ color: "7bd88f",
538
+ description: "Small (2-4 hours)"
539
+ },
540
+ {
541
+ name: "size: m",
542
+ color: "3fb950",
543
+ description: "Medium (~1 day)"
544
+ },
545
+ {
546
+ name: "size: l",
547
+ color: "2ea043",
548
+ description: "Large (2-3 days)"
549
+ },
550
+ {
551
+ name: "size: xl",
552
+ color: "1a7f37",
553
+ description: "Extra large (> 3 days)"
554
+ }
555
+ ],
556
+ status: [
557
+ {
558
+ name: "status: blocked",
559
+ color: "d73a4a",
560
+ description: "Blocked by external dependency"
561
+ },
562
+ {
563
+ name: "status: help-wanted",
564
+ color: "008672",
565
+ description: "Community contributions welcome"
566
+ },
567
+ {
568
+ name: "status: good-first-issue",
569
+ color: "7057ff",
570
+ description: "Good for newcomers"
571
+ }
572
+ ]
573
+ };
574
+ /**
575
+ * Area labels are repository-specific, so we provide a template
576
+ */
577
+ var AREA_LABEL_TEMPLATE = [
578
+ {
579
+ name: "area: core",
580
+ color: "fbca04",
581
+ description: "Core functionality"
582
+ },
583
+ {
584
+ name: "area: api",
585
+ color: "fbca04",
586
+ description: "API-related"
587
+ },
588
+ {
589
+ name: "area: ui",
590
+ color: "fbca04",
591
+ description: "User interface"
592
+ },
593
+ {
594
+ name: "area: cli",
595
+ color: "fbca04",
596
+ description: "Command-line interface"
597
+ },
598
+ {
599
+ name: "area: docs",
600
+ color: "fbca04",
601
+ description: "Documentation"
602
+ },
603
+ {
604
+ name: "area: infra",
605
+ color: "fbca04",
606
+ description: "Infrastructure and deployment"
607
+ },
608
+ {
609
+ name: "area: tests",
610
+ color: "fbca04",
611
+ description: "Testing infrastructure"
612
+ }
613
+ ];
614
+ /**
615
+ * Get all standard labels as a flat array
616
+ */
617
+ function getAllStandardLabels() {
618
+ return Object.values(STANDARD_LABELS).flat();
619
+ }
620
+ /**
621
+ * Get labels by category
622
+ */
623
+ function getLabelsByCategory(category) {
624
+ return STANDARD_LABELS[category] || [];
625
+ }
626
+ /**
627
+ * Map old label names to new standard names
628
+ */
629
+ var LABEL_MIGRATIONS = {
630
+ bug: "type: bug",
631
+ feature: "type: feature",
632
+ enhancement: "type: feature",
633
+ documentation: "type: docs",
634
+ question: "type: question",
635
+ "tech-debt": "type: maintenance",
636
+ epic: "type: feature"
637
+ };
638
+ /**
639
+ * Migrate old label to new standard label
640
+ */
641
+ function migrateLabel(oldLabel) {
642
+ return LABEL_MIGRATIONS[oldLabel] || oldLabel;
643
+ }
644
+ //#endregion
645
+ //#region src/shared/projects.ts
646
+ /**
647
+ * GitHub Projects V2 API Utilities
648
+ *
649
+ * Handles interaction with GitHub Projects V2 via GraphQL API.
650
+ */
651
+ /**
652
+ * Add an issue to a project
653
+ */
654
+ async function addIssueToProject(token, projectId, issueId) {
655
+ return (await githubGraphQL(token, `
656
+ mutation($projectId: ID!, $contentId: ID!) {
657
+ addProjectV2ItemById(input: {
658
+ projectId: $projectId
659
+ contentId: $contentId
660
+ }) {
661
+ item {
662
+ id
663
+ }
664
+ }
665
+ }
666
+ `, {
667
+ projectId,
668
+ contentId: issueId
669
+ })).addProjectV2ItemById.item.id;
670
+ }
671
+ /**
672
+ * Update project item status
673
+ */
674
+ async function updateProjectItemStatus(token, projectId, itemId, statusFieldId, statusOptionId) {
675
+ await githubGraphQL(token, `
676
+ mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $value: ProjectV2FieldValue!) {
677
+ updateProjectV2ItemFieldValue(input: {
678
+ projectId: $projectId
679
+ itemId: $itemId
680
+ fieldId: $fieldId
681
+ value: $value
682
+ }) {
683
+ projectV2Item {
684
+ id
685
+ }
686
+ }
687
+ }
688
+ `, {
689
+ projectId,
690
+ itemId,
691
+ fieldId: statusFieldId,
692
+ value: { singleSelectOptionId: statusOptionId }
693
+ });
694
+ }
695
+ /**
696
+ * Get issue's node ID (required for adding to project)
697
+ */
698
+ async function getIssueNodeId(token, owner, repo, issueNumber) {
699
+ return (await githubGraphQL(token, `
700
+ query($owner: String!, $repo: String!, $issueNumber: Int!) {
701
+ repository(owner: $owner, name: $repo) {
702
+ issue(number: $issueNumber) {
703
+ id
704
+ }
705
+ }
706
+ }
707
+ `, {
708
+ owner,
709
+ repo,
710
+ issueNumber
711
+ })).repository.issue.id;
712
+ }
713
+ /**
714
+ * Get project item ID for an issue (if it exists in the project)
715
+ */
716
+ async function getProjectItemId(token, projectId, issueId) {
717
+ const item = (await githubGraphQL(token, `
718
+ query($projectId: ID!) {
719
+ node(id: $projectId) {
720
+ ... on ProjectV2 {
721
+ items(first: 100) {
722
+ nodes {
723
+ id
724
+ content {
725
+ ... on Issue {
726
+ id
727
+ }
728
+ }
729
+ }
730
+ }
731
+ }
732
+ }
733
+ }
734
+ `, { projectId })).node.items.nodes.find((node) => node.content.id === issueId);
735
+ return item ? item.id : null;
736
+ }
737
+ /**
738
+ * Move issue to a specific status in the project
739
+ */
740
+ async function moveIssueToStatus(token, owner, repo, issueNumber, config, statusName) {
741
+ const statusOptionId = config.statusOptions[statusName];
742
+ if (!statusOptionId) throw new Error(`Status "${statusName}" not found in project configuration. Available: ${Object.keys(config.statusOptions).join(", ")}`);
743
+ console.log(`Moving issue #${issueNumber} to status "${statusName}"...`);
744
+ const issueId = await getIssueNodeId(token, owner, repo, issueNumber);
745
+ let itemId = await getProjectItemId(token, config.projectId, issueId);
746
+ if (!itemId) {
747
+ console.log("Issue not in project, adding...");
748
+ itemId = await addIssueToProject(token, config.projectId, issueId);
749
+ }
750
+ console.log(`Updating status to "${statusName}"...`);
751
+ await updateProjectItemStatus(token, config.projectId, itemId, config.statusFieldId, statusOptionId);
752
+ console.log(`✅ Issue moved to "${statusName}"`);
753
+ }
754
+ //#endregion
755
+ //#region src/triage/analyze.ts
756
+ /**
757
+ * GitHub Models AI Analysis
758
+ */
759
+ var VALID_TYPES = [
760
+ "bug",
761
+ "feature",
762
+ "docs",
763
+ "maintenance",
764
+ "research",
765
+ "question"
766
+ ];
767
+ var VALID_PRIORITIES = [
768
+ "critical",
769
+ "high",
770
+ "medium",
771
+ "low",
772
+ "icebox"
773
+ ];
774
+ var VALID_SIZES = [
775
+ "xs",
776
+ "s",
777
+ "m",
778
+ "l",
779
+ "xl"
780
+ ];
781
+ function validateAIAnalysis(parsed) {
782
+ if (!parsed || typeof parsed !== "object") throw new Error("AI response is not a valid object");
783
+ const response = parsed;
784
+ if (!response.type || typeof response.type !== "string") throw new Error("AI response missing required field: type");
785
+ if (!response.priority || typeof response.priority !== "string") throw new Error("AI response missing required field: priority");
786
+ if (!response.size || typeof response.size !== "string") throw new Error("AI response missing required field: size");
787
+ if (!response.reasoning || typeof response.reasoning !== "string") throw new Error("AI response missing required field: reasoning");
788
+ if (!VALID_TYPES.includes(response.type)) throw new Error(`Invalid type: ${response.type}`);
789
+ if (!VALID_PRIORITIES.includes(response.priority)) throw new Error(`Invalid priority: ${response.priority}`);
790
+ if (!VALID_SIZES.includes(response.size)) throw new Error(`Invalid size: ${response.size}`);
791
+ if (response.affected_packages !== void 0) {
792
+ if (!Array.isArray(response.affected_packages)) throw new Error("affected_packages must be an array");
793
+ if (!response.affected_packages.every((pkg) => typeof pkg === "string")) throw new Error("affected_packages must contain only strings");
794
+ }
795
+ return {
796
+ type: response.type,
797
+ priority: response.priority,
798
+ size: response.size,
799
+ reasoning: response.reasoning,
800
+ ...response.affected_packages && { affected_packages: response.affected_packages }
801
+ };
802
+ }
803
+ /**
804
+ * Analyze a GitHub issue using AI to determine type, priority, size, and affected packages.
805
+ *
806
+ * Sends the issue details to the configured AI provider and validates the structured response.
807
+ *
808
+ * @param context - Triage context with issue details and repository config
809
+ * @returns Validated AI analysis with type, priority, size, and reasoning
810
+ * @throws If the AI response cannot be parsed or fails validation
811
+ */
812
+ async function analyzeIssue(context) {
813
+ return validateAIAnalysis(parseAIJson(await getAICompletion([{
814
+ role: "system",
815
+ content: "You are an expert GitHub issue triager. Analyze issues and provide structured triage information in JSON format."
816
+ }, {
817
+ role: "user",
818
+ content: buildAnalysisPrompt(context)
819
+ }])));
820
+ }
821
+ function buildAnalysisPrompt(context) {
822
+ const { config, issueNumber, issueTitle, issueBody, issueAuthor } = context;
823
+ let prompt = `Analyze this GitHub issue and provide triage information for our kanban workflow.
824
+
825
+ Repository: ${context.owner}/${context.repo} (${config.repoDescription})
826
+
827
+ Issue #${issueNumber}
828
+ Title: ${issueTitle}
829
+ Body: ${issueBody || "(empty)"}
830
+ Author: ${issueAuthor}
831
+
832
+ Determine:
833
+ 1. **type**: One of: bug, feature, docs, maintenance, research, question
834
+ 2. **priority**: critical, high, medium, low, icebox
835
+ - Use "icebox" for low priority or future consideration items
836
+ 3. **size**: Estimated effort: xs (<2hr), s (2-4hr), m (~1day), l (2-3days), xl (>3days)`;
837
+ if (config.packagePattern) {
838
+ prompt += `
839
+ 4. **affected_packages**: List of ${config.packagePattern} packages affected`;
840
+ if (config.packageExamples && config.packageExamples.length > 0) prompt += ` (e.g., ${JSON.stringify(config.packageExamples)})`;
841
+ prompt += `
842
+ 5. **reasoning**: Brief explanation of your analysis (2-3 sentences)`;
843
+ } else prompt += `
844
+ 4. **reasoning**: Brief explanation of your analysis (2-3 sentences)`;
845
+ prompt += `
846
+
847
+ Return JSON in this exact format:
848
+ {
849
+ "type": "bug|feature|docs|maintenance|research|question",
850
+ "priority": "critical|high|medium|low|icebox",
851
+ "size": "xs|s|m|l|xl",`;
852
+ if (config.packagePattern) prompt += `
853
+ "affected_packages": ["package names"],`;
854
+ prompt += `
855
+ "reasoning": "explanation here"
856
+ }`;
857
+ return prompt;
858
+ }
859
+ //#endregion
860
+ //#region src/triage/github.ts
861
+ /**
862
+ * GitHub API Helper Functions
863
+ */
864
+ async function githubAPI(token, method, path, data = null) {
865
+ return new Promise((resolve, reject) => {
866
+ const options = {
867
+ hostname: "api.github.com",
868
+ path,
869
+ method,
870
+ headers: {
871
+ Authorization: `token ${token}`,
872
+ Accept: "application/vnd.github+json",
873
+ "User-Agent": "happyvertical-github-actions",
874
+ "X-GitHub-Api-Version": "2022-11-28"
875
+ }
876
+ };
877
+ if (data) options.headers = {
878
+ ...options.headers,
879
+ "Content-Type": "application/json"
880
+ };
881
+ const req = https.request(options, (res) => {
882
+ let body = "";
883
+ res.on("data", (chunk) => body += chunk);
884
+ res.on("end", () => {
885
+ if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) resolve(body ? JSON.parse(body) : null);
886
+ else reject(/* @__PURE__ */ new Error(`GitHub API error: ${res.statusCode} ${body}`));
887
+ });
888
+ });
889
+ req.on("error", reject);
890
+ if (data) req.write(JSON.stringify(data));
891
+ req.end();
892
+ });
893
+ }
894
+ //#endregion
895
+ //#region src/triage/comment.ts
896
+ /**
897
+ * Issue Comment Posting
898
+ */
899
+ /**
900
+ * Post a structured triage comment on the issue with AI analysis results and duplicate links.
901
+ *
902
+ * @param context - Triage context with repo info and issue number
903
+ * @param analysis - AI analysis results (type, priority, size, reasoning)
904
+ * @param duplicates - Array of potential duplicate issues to include in the comment
905
+ */
906
+ async function postTriageComment(context, analysis, duplicates) {
907
+ const comment = buildTriageComment(context, analysis, duplicates);
908
+ const path = `/repos/${context.owner}/${context.repo}/issues/${context.issueNumber}/comments`;
909
+ try {
910
+ await githubAPI(context.token, "POST", path, { body: comment });
911
+ console.log("Posted triage comment");
912
+ } catch (error) {
913
+ console.error("Error posting comment:", error.message);
914
+ throw error;
915
+ }
916
+ }
917
+ /**
918
+ * Post a comment indicating that automated triage failed, prompting manual triage.
919
+ *
920
+ * @param context - Triage context with repo info and issue number
921
+ * @param error - The error that caused triage to fail
922
+ */
923
+ async function postErrorComment(context, error) {
924
+ const comment = `## 🤖 AI Triage Failed\n\nAutomated triage encountered an error:\n\`\`\`\n${error.message}\n\`\`\`\n\nPlease triage this issue manually.`;
925
+ const path = `/repos/${context.owner}/${context.repo}/issues/${context.issueNumber}/comments`;
926
+ try {
927
+ await githubAPI(context.token, "POST", path, { body: comment });
928
+ } catch (err) {
929
+ console.error("Error posting error comment:", err.message);
930
+ }
931
+ }
932
+ function buildTriageComment(context, analysis, duplicates) {
933
+ let comment = `## 🤖 AI Triage\n\n`;
934
+ comment += `**Type**: \`${analysis.type}\`\n`;
935
+ comment += `**Priority**: \`${analysis.priority}\`\n`;
936
+ comment += `**Size**: \`${analysis.size}\`\n\n`;
937
+ if (analysis.affected_packages && analysis.affected_packages.length > 0) {
938
+ const packages = analysis.affected_packages.map((p) => `\`${p}\``).join(", ");
939
+ comment += `**Affected Packages**: ${packages}\n\n`;
940
+ }
941
+ comment += `**Analysis**: ${analysis.reasoning}\n\n`;
942
+ if (duplicates.length > 0) {
943
+ comment += `### ⚠️ Potential Duplicates\n\n`;
944
+ duplicates.forEach((dup) => {
945
+ comment += `- #${dup.number}: ${dup.title}\n`;
946
+ });
947
+ comment += `\n`;
948
+ }
949
+ comment += `---\n`;
950
+ comment += `*This triage was performed automatically using GitHub Models API. Please review and adjust labels as needed.*`;
951
+ return comment;
952
+ }
953
+ //#endregion
954
+ //#region src/triage/duplicates.ts
955
+ /**
956
+ * Duplicate Issue Detection
957
+ */
958
+ /**
959
+ * Search for potential duplicate issues using GitHub Search API.
960
+ *
961
+ * Extracts keywords from the issue title and searches the repository for similar issues.
962
+ * The current issue is excluded from results.
963
+ *
964
+ * @param context - Triage context with issue details and repo info
965
+ * @returns Array of potentially duplicate issues (up to 4)
966
+ */
967
+ async function searchDuplicates(context) {
968
+ const keywords = context.issueTitle.split(" ").slice(0, 5).join(" ");
969
+ const query = `repo:${context.owner}/${context.repo} is:issue ${keywords}`;
970
+ const path = `/search/issues?q=${encodeURIComponent(query)}&per_page=5`;
971
+ try {
972
+ return (await githubAPI(context.token, "GET", path, null)).items.filter((issue) => issue.number !== context.issueNumber);
973
+ } catch (error) {
974
+ console.error("Error searching for duplicates:", error.message);
975
+ return [];
976
+ }
977
+ }
978
+ //#endregion
979
+ //#region src/triage/label.ts
980
+ /**
981
+ * Issue Labeling
982
+ */
983
+ /**
984
+ * Apply labels to an issue using the raw GitHub API.
985
+ * @deprecated Use the v2 export which uses `@happyvertical/repos`.
986
+ */
987
+ async function applyLabels$1(context, labels) {
988
+ if (labels.length === 0) {
989
+ console.log("No labels to apply");
990
+ return;
991
+ }
992
+ try {
993
+ await addLabels(context, context.issueNumber, labels);
994
+ console.log(`Applied labels: ${labels.join(", ")}`);
995
+ } catch (error) {
996
+ console.error("Error applying labels:", error.message);
997
+ throw error;
998
+ }
999
+ }
1000
+ /**
1001
+ * Remove an `agent: <type>` label from an issue. Silently ignores 404 errors.
1002
+ * @deprecated Use the v2 export which uses `@happyvertical/repos`.
1003
+ */
1004
+ async function removeAgentLabel$1(context, agentType) {
1005
+ const label = `agent: ${agentType}`;
1006
+ try {
1007
+ await removeLabel(context, context.issueNumber, label);
1008
+ console.log(`Removed label: ${label}`);
1009
+ } catch (error) {
1010
+ if (!error.message.includes("404")) console.error("Error removing label:", error.message);
1011
+ }
1012
+ }
1013
+ function getTypeLabel$1(type) {
1014
+ return `type: ${type}`;
1015
+ }
1016
+ function getPriorityLabel$1(priority) {
1017
+ return `priority: ${priority}`;
1018
+ }
1019
+ function getSizeLabel$1(size) {
1020
+ return `size: ${size}`;
1021
+ }
1022
+ function getAgentLabel$1(agentType) {
1023
+ return `agent: ${agentType}`;
1024
+ }
1025
+ //#endregion
1026
+ //#region src/triage/project.ts
1027
+ /**
1028
+ * GitHub Project Board Management
1029
+ */
1030
+ /**
1031
+ * Update an issue's status on the GitHub Projects V2 board via raw GraphQL.
1032
+ * @deprecated Use the v2 export which uses `@happyvertical/projects`.
1033
+ */
1034
+ async function updateProjectStatus$1(context, statusName) {
1035
+ if (!context.config.projectEnabled) {
1036
+ console.log("Project board integration disabled");
1037
+ return;
1038
+ }
1039
+ if (!context.config.projectId || !context.config.statusFieldId) {
1040
+ console.log("Project configuration missing");
1041
+ return;
1042
+ }
1043
+ if (!context.config.statusOptions || !context.config.statusOptions[statusName]) {
1044
+ console.log(`Status "${statusName}" not configured`);
1045
+ return;
1046
+ }
1047
+ const query = `
1048
+ query($owner: String!, $repo: String!, $number: Int!) {
1049
+ repository(owner: $owner, name: $repo) {
1050
+ issue(number: $number) {
1051
+ projectItems(first: 10) {
1052
+ nodes {
1053
+ id
1054
+ }
1055
+ }
1056
+ }
1057
+ }
1058
+ }
1059
+ `;
1060
+ try {
1061
+ const variables = {
1062
+ owner: context.owner,
1063
+ repo: context.repo,
1064
+ number: context.issueNumber
1065
+ };
1066
+ const itemId = (await githubAPI(context.token, "POST", "/graphql", {
1067
+ query,
1068
+ variables
1069
+ })).data.repository.issue.projectItems.nodes[0]?.id;
1070
+ if (!itemId) {
1071
+ console.log("Issue not in project board, skipping status update");
1072
+ return;
1073
+ }
1074
+ const updateQuery = `
1075
+ mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $optionId: String!) {
1076
+ updateProjectV2ItemFieldValue(
1077
+ input: {
1078
+ projectId: $projectId
1079
+ itemId: $itemId
1080
+ fieldId: $fieldId
1081
+ value: { singleSelectOptionId: $optionId }
1082
+ }
1083
+ ) {
1084
+ projectV2Item {
1085
+ id
1086
+ }
1087
+ }
1088
+ }
1089
+ `;
1090
+ const updateVars = {
1091
+ projectId: context.config.projectId,
1092
+ itemId,
1093
+ fieldId: context.config.statusFieldId,
1094
+ optionId: context.config.statusOptions[statusName]
1095
+ };
1096
+ await githubAPI(context.token, "POST", "/graphql", {
1097
+ query: updateQuery,
1098
+ variables: updateVars
1099
+ });
1100
+ console.log(`Updated project status to: ${statusName}`);
1101
+ } catch (error) {
1102
+ console.error("Error updating project status:", error.message);
1103
+ }
1104
+ }
1105
+ //#endregion
1106
+ //#region src/triage/index.ts
1107
+ /**
1108
+ * Issue Triage Orchestration
1109
+ */
1110
+ var triage_exports = /* @__PURE__ */ __exportAll({
1111
+ analyzeIssue: () => analyzeIssue,
1112
+ applyLabels: () => applyLabels$1,
1113
+ getAgentLabel: () => getAgentLabel$1,
1114
+ getPriorityLabel: () => getPriorityLabel$1,
1115
+ getSizeLabel: () => getSizeLabel$1,
1116
+ getTypeLabel: () => getTypeLabel$1,
1117
+ postErrorComment: () => postErrorComment,
1118
+ postTriageComment: () => postTriageComment,
1119
+ removeAgentLabel: () => removeAgentLabel$1,
1120
+ searchDuplicates: () => searchDuplicates,
1121
+ triageIssue: () => triageIssue$1,
1122
+ updateProjectStatus: () => updateProjectStatus$1
1123
+ });
1124
+ /**
1125
+ * @deprecated Use the default export from `@happyvertical/github-actions` instead.
1126
+ * Legacy triage orchestrator using raw GitHub API calls.
1127
+ */
1128
+ async function triageIssue$1(context) {
1129
+ console.log(`Triaging issue #${context.issueNumber}: ${context.issueTitle}`);
1130
+ try {
1131
+ await applyLabels$1(context, [getAgentLabel$1("triage")]);
1132
+ console.log("Calling AI for analysis...");
1133
+ const analysis = await analyzeIssue(context);
1134
+ console.log("AI Analysis:", JSON.stringify(analysis, null, 2));
1135
+ console.log("Searching for potential duplicates...");
1136
+ const duplicates = await searchDuplicates(context);
1137
+ console.log(`Found ${duplicates.length} potential duplicates`);
1138
+ await applyLabels$1(context, [
1139
+ getTypeLabel$1(analysis.type),
1140
+ getPriorityLabel$1(analysis.priority),
1141
+ getSizeLabel$1(analysis.size)
1142
+ ]);
1143
+ await postTriageComment(context, analysis, duplicates);
1144
+ await removeAgentLabel$1(context, "triage");
1145
+ if (context.config.projectEnabled) {
1146
+ console.log("Moving issue to Backlog...");
1147
+ await updateProjectStatus$1(context, "Backlog");
1148
+ }
1149
+ console.log("✅ Triage complete!");
1150
+ return {
1151
+ success: true,
1152
+ analysis,
1153
+ duplicates
1154
+ };
1155
+ } catch (error) {
1156
+ console.error("❌ Triage failed:", error.message);
1157
+ console.error(error.stack);
1158
+ await postErrorComment(context, error);
1159
+ return {
1160
+ success: false,
1161
+ error: error.message
1162
+ };
1163
+ }
1164
+ }
1165
+ //#endregion
1166
+ //#region src/triage/label-v2.ts
1167
+ /**
1168
+ * Issue Labeling (Refactored to use @happyvertical/repos)
1169
+ */
1170
+ /**
1171
+ * Apply labels to an issue using `@happyvertical/repos`.
1172
+ *
1173
+ * @param context - Triage context with token and repo info
1174
+ * @param labels - Label names to apply
1175
+ */
1176
+ async function applyLabels(context, labels) {
1177
+ if (labels.length === 0) {
1178
+ console.log("No labels to apply");
1179
+ return;
1180
+ }
1181
+ try {
1182
+ await (await createRepository(context.token, context.owner, context.repo)).addLabels(context.issueNumber, labels);
1183
+ console.log(`Applied labels: ${labels.join(", ")}`);
1184
+ } catch (error) {
1185
+ console.error("Error applying labels:", error.message);
1186
+ throw error;
1187
+ }
1188
+ }
1189
+ /**
1190
+ * Remove an `agent: <type>` label from an issue. Silently ignores 404 errors.
1191
+ *
1192
+ * @param context - Triage context with token and repo info
1193
+ * @param agentType - Agent type suffix (e.g., "triage", "planning")
1194
+ */
1195
+ async function removeAgentLabel(context, agentType) {
1196
+ const label = `agent: ${agentType}`;
1197
+ try {
1198
+ await (await createRepository(context.token, context.owner, context.repo)).removeLabel(context.issueNumber, label);
1199
+ console.log(`Removed label: ${label}`);
1200
+ } catch (error) {
1201
+ if (!error.message.includes("404")) console.error("Error removing label:", error.message);
1202
+ }
1203
+ }
1204
+ function getTypeLabel(type) {
1205
+ return `type: ${type}`;
1206
+ }
1207
+ function getPriorityLabel(priority) {
1208
+ return `priority: ${priority}`;
1209
+ }
1210
+ function getSizeLabel(size) {
1211
+ return `size: ${size}`;
1212
+ }
1213
+ function getAgentLabel(agentType) {
1214
+ return `agent: ${agentType}`;
1215
+ }
1216
+ //#endregion
1217
+ //#region src/triage/project-v2.ts
1218
+ /**
1219
+ * GitHub Project Board Management (Refactored to use @happyvertical/projects)
1220
+ */
1221
+ /**
1222
+ * Update an issue's status on the GitHub Projects V2 board using `@happyvertical/projects`.
1223
+ *
1224
+ * Adds the issue to the project if not already present, then sets the status field.
1225
+ * No-ops if project integration is disabled or the status name is not configured.
1226
+ *
1227
+ * @param context - Triage context with project config
1228
+ * @param statusName - Target status name (must exist in `config.statusOptions`)
1229
+ */
1230
+ async function updateProjectStatus(context, statusName) {
1231
+ if (!context.config.projectEnabled) {
1232
+ console.log("Project board integration disabled");
1233
+ return;
1234
+ }
1235
+ if (!context.config.projectId || !context.config.statusFieldId) {
1236
+ console.log("Project configuration missing");
1237
+ return;
1238
+ }
1239
+ if (!context.config.statusOptions || !context.config.statusOptions[statusName]) {
1240
+ console.log(`Status "${statusName}" not configured`);
1241
+ return;
1242
+ }
1243
+ try {
1244
+ const repo = await createRepository(context.token, context.owner, context.repo);
1245
+ const project = await createProject(context.token, context.config.projectId, context.config.statusFieldId, context.config.statusOptions);
1246
+ const issue = await repo.getIssue(context.issueNumber);
1247
+ let itemId = (await project.listItems()).find((item) => item.contentId === issue.id)?.id;
1248
+ if (!itemId) {
1249
+ console.log("Issue not in project, adding...");
1250
+ itemId = (await project.addItem(issue.id)).id;
1251
+ }
1252
+ await project.updateItemStatus(itemId, statusName);
1253
+ console.log(`Updated project status to: ${statusName}`);
1254
+ } catch (error) {
1255
+ console.error("Error updating project status:", error.message);
1256
+ throw error;
1257
+ }
1258
+ }
1259
+ //#endregion
1260
+ //#region src/triage/index-v2.ts
1261
+ /**
1262
+ * Issue Triage Orchestration (Refactored to use @happyvertical/repos and @happyvertical/projects)
1263
+ */
1264
+ /**
1265
+ * Run the full issue triage workflow using `@happyvertical/repos`.
1266
+ *
1267
+ * Applies an agent label, runs AI analysis, searches for duplicates,
1268
+ * applies type/priority/size labels, posts a triage comment, and
1269
+ * optionally updates the project board status.
1270
+ *
1271
+ * @param context - Triage context including token, repo info, issue details, and config
1272
+ * @returns Result indicating success/failure with analysis and duplicate data
1273
+ */
1274
+ async function triageIssue(context) {
1275
+ console.log(`Triaging issue #${context.issueNumber}: ${context.issueTitle}`);
1276
+ try {
1277
+ await applyLabels(context, [getAgentLabel("triage")]);
1278
+ console.log("Calling AI for analysis...");
1279
+ const analysis = await analyzeIssue(context);
1280
+ console.log("AI Analysis:", JSON.stringify(analysis, null, 2));
1281
+ console.log("Searching for potential duplicates...");
1282
+ const duplicates = await searchDuplicates(context);
1283
+ console.log(`Found ${duplicates.length} potential duplicates`);
1284
+ await applyLabels(context, [
1285
+ getTypeLabel(analysis.type),
1286
+ getPriorityLabel(analysis.priority),
1287
+ getSizeLabel(analysis.size)
1288
+ ]);
1289
+ await postTriageComment(context, analysis, duplicates);
1290
+ await removeAgentLabel(context, "triage");
1291
+ if (context.config.projectEnabled) {
1292
+ console.log("Moving issue to Backlog...");
1293
+ await updateProjectStatus(context, "Backlog");
1294
+ }
1295
+ console.log("✅ Triage complete!");
1296
+ return {
1297
+ success: true,
1298
+ analysis,
1299
+ duplicates
1300
+ };
1301
+ } catch (error) {
1302
+ console.error("❌ Triage failed:", error.message);
1303
+ console.error(error.stack);
1304
+ await postErrorComment(context, error);
1305
+ return {
1306
+ success: false,
1307
+ error: error.message
1308
+ };
1309
+ }
1310
+ }
1311
+ //#endregion
1312
+ //#region src/index.ts
1313
+ /** @internal */
1314
+ var PACKAGE_VERSION_INITIALIZED = true;
1315
+ //#endregion
1316
+ export { githubGraphQL as A, startPlanning as B, getAllStandardLabels as C, assignIssue as D, addLabels as E, callGitHubModels as F, isReady as G, postPlanningErrorComment as H, callOpenAI as I, createProject as J, validateDefinitionOfReady as K, getAICompletion as L, postComment as M, removeLabel as N, createOrUpdateLabel as O, callAnthropic as P, parseAIJson as R, STANDARD_LABELS as S, migrateLabel as T, postReadyCheckComment as U, postPlanComment as V, formatDefinitionOfReady as W, createRepository as Y, getProjectItemId as _, getAgentLabel as a, AREA_LABEL_TEMPLATE as b, getTypeLabel as c, searchDuplicates as d, postErrorComment as f, getIssueNodeId as g, addIssueToProject as h, applyLabels as i, githubRequest as j, getIssue as k, removeAgentLabel as l, analyzeIssue as m, triageIssue as n, getPriorityLabel as o, postTriageComment as p, analyzePlanning as q, updateProjectStatus as r, getSizeLabel as s, PACKAGE_VERSION_INITIALIZED as t, triage_exports as u, moveIssueToStatus as v, getLabelsByCategory as w, LABEL_MIGRATIONS as x, updateProjectItemStatus as y, completePlanning as z };
1317
+
1318
+ //# sourceMappingURL=src-CezXcNYi.js.map