Skip to content

Commit e5c7b40

Browse files
konardclaude
andcommitted
Implement GitHub Bot code optimizer using ChatGPT/GPT-4
- Add OpenAI 2.1.0 package dependency for ChatGPT/GPT-4 integration - Create CodeOptimizerTrigger that detects optimization requests in issues - Implement code analysis and optimization using GPT-4 - Support multiple programming languages (.cs, .js, .ts, .py, .java, .cpp, .c, .h) - Create optimized code PRs with detailed descriptions - Add optional OpenAI API key parameter to bot configuration - Integrate trigger into main issue tracking loop The bot now responds to issues containing keywords like 'optimize', 'optimization', 'improve performance' by analyzing repository code files, sending them to GPT-4 for optimization suggestions, and creating pull requests with the optimized code. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent f8d84f8 commit e5c7b40

File tree

3 files changed

+311
-4
lines changed

3 files changed

+311
-4
lines changed

csharp/Platform.Bot/Platform.Bot.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
<ItemGroup>
1010
<PackageReference Include="CommandLineParser" Version="2.9.1" />
1111
<PackageReference Include="Octokit" Version="7.0.1" />
12+
<PackageReference Include="OpenAI" Version="2.1.0" />
1213
<PackageReference Include="Platform.Communication.Protocol.Lino" Version="0.4.0" />
1314
<PackageReference Include="Platform.Data.Doublets.Sequences" Version="0.1.1" />
1415
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />

csharp/Platform.Bot/Program.cs

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,29 +73,53 @@ private static async Task<int> Main(string[] args)
7373
description: "Minimum interaction interval in seconds.",
7474
getDefaultValue: () => 60);
7575

76+
var openAiApiKeyOption = new Option<string?>(
77+
name: "--openai-api-key",
78+
description: "OpenAI API key for code optimization features.");
79+
7680
var rootCommand = new RootCommand("Sample app for System.CommandLine")
7781
{
7882
githubUserNameOption,
7983
githubApiTokenOption,
8084
githubApplicationNameOption,
8185
databaseFilePathOption,
8286
fileSetNameOption,
83-
minimumInteractionIntervalOption
87+
minimumInteractionIntervalOption,
88+
openAiApiKeyOption
8489
};
8590

86-
rootCommand.SetHandler(async (githubUserName, githubApiToken, githubApplicationName, databaseFilePath, fileSetName, minimumInteractionInterval) =>
91+
rootCommand.SetHandler(async (githubUserName, githubApiToken, githubApplicationName, databaseFilePath, fileSetName, minimumInteractionInterval, openAiApiKey) =>
8792
{
8893
Debug.WriteLine($"Nickname: {githubUserName}");
8994
Debug.WriteLine($"GitHub API Token: {githubApiToken}");
9095
Debug.WriteLine($"Application Name: {githubApplicationName}");
9196
Debug.WriteLine($"Database File Path: {databaseFilePath?.FullName}");
9297
Debug.WriteLine($"File Set Name: {fileSetName}");
9398
Debug.WriteLine($"Minimum Interaction Interval: {minimumInteractionInterval} seconds");
99+
Debug.WriteLine($"OpenAI API Key: {(string.IsNullOrEmpty(openAiApiKey) ? "Not provided" : "Provided")}");
94100

95101
var dbContext = new FileStorage(databaseFilePath?.FullName ?? new TemporaryFile().Filename);
96102
Console.WriteLine($"Bot has been started. {Environment.NewLine}Press CTRL+C to close");
97103
var githubStorage = new GitHubStorage(githubUserName, githubApiToken, githubApplicationName);
98-
var issueTracker = new IssueTracker(githubStorage, new HelloWorldTrigger(githubStorage, dbContext, fileSetName), new OrganizationLastMonthActivityTrigger(githubStorage), new LastCommitActivityTrigger(githubStorage), new AdminAuthorIssueTriggerDecorator(new ProtectDefaultBranchTrigger(githubStorage), githubStorage), new AdminAuthorIssueTriggerDecorator(new ChangeOrganizationRepositoriesDefaultBranchTrigger(githubStorage, dbContext), githubStorage), new AdminAuthorIssueTriggerDecorator(new ChangeOrganizationPullRequestsBaseBranchTrigger(githubStorage, dbContext), githubStorage));
104+
105+
// Create list of triggers
106+
var triggers = new List<ITrigger<Issue>>
107+
{
108+
new HelloWorldTrigger(githubStorage, dbContext, fileSetName),
109+
new OrganizationLastMonthActivityTrigger(githubStorage),
110+
new LastCommitActivityTrigger(githubStorage),
111+
new AdminAuthorIssueTriggerDecorator(new ProtectDefaultBranchTrigger(githubStorage), githubStorage),
112+
new AdminAuthorIssueTriggerDecorator(new ChangeOrganizationRepositoriesDefaultBranchTrigger(githubStorage, dbContext), githubStorage),
113+
new AdminAuthorIssueTriggerDecorator(new ChangeOrganizationPullRequestsBaseBranchTrigger(githubStorage, dbContext), githubStorage)
114+
};
115+
116+
// Add CodeOptimizerTrigger if OpenAI API key is provided
117+
if (!string.IsNullOrEmpty(openAiApiKey))
118+
{
119+
triggers.Add(new CodeOptimizerTrigger(githubStorage, dbContext, openAiApiKey));
120+
}
121+
122+
var issueTracker = new IssueTracker(githubStorage, triggers.ToArray());
99123
var pullRequenstTracker = new PullRequestTracker(githubStorage, new MergeDependabotBumpsTrigger(githubStorage));
100124
var timestampTracker = new DateTimeTracker(githubStorage, new CreateAndSaveOrganizationRepositoriesMigrationTrigger(githubStorage, dbContext, Path.Combine(Directory.GetCurrentDirectory(), "/github-migrations")));
101125
var cancellation = new CancellationTokenSource();
@@ -114,7 +138,7 @@ private static async Task<int> Main(string[] args)
114138
}
115139
}
116140
},
117-
githubUserNameOption, githubApiTokenOption, githubApplicationNameOption, databaseFilePathOption, fileSetNameOption, minimumInteractionIntervalOption);
141+
githubUserNameOption, githubApiTokenOption, githubApplicationNameOption, databaseFilePathOption, fileSetNameOption, minimumInteractionIntervalOption, openAiApiKeyOption);
118142

119143
return await rootCommand.InvokeAsync(args);
120144
}
Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Text;
6+
using System.Threading.Tasks;
7+
using Interfaces;
8+
using Octokit;
9+
using OpenAI;
10+
using OpenAI.Chat;
11+
using Storage.Local;
12+
using Storage.Remote.GitHub;
13+
14+
namespace Platform.Bot.Triggers
15+
{
16+
using TContext = Issue;
17+
18+
/// <summary>
19+
/// <para>
20+
/// Represents the code optimizer trigger that uses ChatGPT/GPT-4 to optimize code.
21+
/// </para>
22+
/// <para></para>
23+
/// </summary>
24+
/// <seealso cref="ITrigger{TContext}"/>
25+
internal class CodeOptimizerTrigger : ITrigger<TContext>
26+
{
27+
private readonly GitHubStorage _storage;
28+
private readonly FileStorage _fileStorage;
29+
private readonly OpenAIClient _openAiClient;
30+
private readonly string _model;
31+
32+
/// <summary>
33+
/// <para>
34+
/// Initializes a new <see cref="CodeOptimizerTrigger"/> instance.
35+
/// </para>
36+
/// <para></para>
37+
/// </summary>
38+
/// <param name="storage">
39+
/// <para>A GitHub storage.</para>
40+
/// <para></para>
41+
/// </param>
42+
/// <param name="fileStorage">
43+
/// <para>A file storage.</para>
44+
/// <para></para>
45+
/// </param>
46+
/// <param name="openAiApiKey">
47+
/// <para>OpenAI API key.</para>
48+
/// <para></para>
49+
/// </param>
50+
/// <param name="model">
51+
/// <para>The model to use (default: gpt-4).</para>
52+
/// <para></para>
53+
/// </param>
54+
public CodeOptimizerTrigger(GitHubStorage storage, FileStorage fileStorage, string openAiApiKey, string model = "gpt-4")
55+
{
56+
_storage = storage ?? throw new ArgumentNullException(nameof(storage));
57+
_fileStorage = fileStorage ?? throw new ArgumentNullException(nameof(fileStorage));
58+
_openAiClient = new OpenAIClient(openAiApiKey);
59+
_model = model;
60+
}
61+
62+
/// <summary>
63+
/// <para>
64+
/// Determines whether this instance condition.
65+
/// </para>
66+
/// <para></para>
67+
/// </summary>
68+
/// <param name="context">
69+
/// <para>The context.</para>
70+
/// <para></para>
71+
/// </param>
72+
/// <returns>
73+
/// <para>The bool</para>
74+
/// <para></para>
75+
/// </returns>
76+
public async Task<bool> Condition(TContext context)
77+
{
78+
var title = context.Title.ToLower();
79+
var body = context.Body?.ToLower() ?? "";
80+
81+
return title.Contains("optimize") || title.Contains("optimization") ||
82+
body.Contains("optimize code") || body.Contains("code optimization") ||
83+
title.Contains("improve performance") || body.Contains("improve performance");
84+
}
85+
86+
/// <summary>
87+
/// <para>
88+
/// Actions the context.
89+
/// </para>
90+
/// <para></para>
91+
/// </summary>
92+
/// <param name="context">
93+
/// <para>The context.</para>
94+
/// <para></para>
95+
/// </param>
96+
public async Task Action(TContext context)
97+
{
98+
try
99+
{
100+
var repository = context.Repository;
101+
var optimizedFiles = new List<(string path, string content)>();
102+
103+
// Get all code files from the repository
104+
var codeFiles = await GetCodeFilesFromRepository(repository);
105+
106+
foreach (var file in codeFiles)
107+
{
108+
var optimizedCode = await OptimizeCodeWithGPT(file.content, file.path);
109+
if (!string.IsNullOrWhiteSpace(optimizedCode) && optimizedCode != file.content)
110+
{
111+
optimizedFiles.Add((file.path, optimizedCode));
112+
}
113+
}
114+
115+
// Create a new branch for optimizations
116+
var branchName = $"optimize-code-{context.Number}";
117+
var defaultBranch = repository.DefaultBranch;
118+
119+
// Create branch and commit optimized files
120+
if (optimizedFiles.Any())
121+
{
122+
// Create branch reference
123+
var defaultBranchRef = await _storage.GetBranch(repository.Id, defaultBranch);
124+
var newReference = new NewReference($"refs/heads/{branchName}", defaultBranchRef.Commit.Sha);
125+
await _storage.CreateReference(repository.Id, newReference);
126+
127+
foreach (var file in optimizedFiles)
128+
{
129+
await _storage.CreateOrUpdateFile(file.content, repository, branchName, file.path,
130+
$"Optimize code in {file.path} using GPT-4");
131+
}
132+
133+
// Create pull request using Octokit client directly
134+
var newPr = new NewPullRequest(
135+
$"Code optimization for issue #{context.Number}",
136+
branchName,
137+
defaultBranch)
138+
{
139+
Body = $"This PR contains code optimizations generated by GPT-4 for issue #{context.Number}.\n\n" +
140+
$"Files optimized:\n{string.Join("\n", optimizedFiles.Select(f => $"- {f.path}"))}"
141+
};
142+
143+
await _storage.Client.PullRequest.Create(repository.Id, newPr);
144+
145+
// Add comment to the issue
146+
var comment = $"I've analyzed the code and created optimizations using GPT-4. " +
147+
$"Please check the pull request with the optimized code: {branchName}";
148+
await _storage.CreateIssueComment(repository.Id, context.Number, comment);
149+
}
150+
else
151+
{
152+
await _storage.CreateIssueComment(repository.Id, context.Number,
153+
"I've analyzed the code but couldn't find any significant optimizations to suggest at this time.");
154+
}
155+
}
156+
catch (Exception ex)
157+
{
158+
await _storage.CreateIssueComment(context.Repository.Id, context.Number,
159+
$"An error occurred while optimizing the code: {ex.Message}");
160+
}
161+
}
162+
163+
private async Task<List<(string path, string content)>> GetCodeFilesFromRepository(Repository repository)
164+
{
165+
var files = new List<(string path, string content)>();
166+
var supportedExtensions = new[] { ".cs", ".js", ".ts", ".py", ".java", ".cpp", ".c", ".h" };
167+
168+
try
169+
{
170+
// Get all contents from repository using Octokit client directly
171+
var contents = await _storage.Client.Repository.Content.GetAllContents(repository.Id);
172+
173+
foreach (var content in contents.Where(c => c.Type == ContentType.File))
174+
{
175+
var extension = Path.GetExtension(content.Name);
176+
if (supportedExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
177+
{
178+
// Get file content
179+
var fileContents = await _storage.Client.Repository.Content.GetAllContentsByRef(repository.Id, content.Path, repository.DefaultBranch);
180+
var fileContent = fileContents.FirstOrDefault()?.Content;
181+
182+
if (!string.IsNullOrWhiteSpace(fileContent))
183+
{
184+
// Decode base64 content
185+
var decodedContent = Encoding.UTF8.GetString(Convert.FromBase64String(fileContent));
186+
files.Add((content.Path, decodedContent));
187+
}
188+
}
189+
}
190+
}
191+
catch (Exception ex)
192+
{
193+
// Log error but continue
194+
Console.WriteLine($"Error getting repository contents: {ex.Message}");
195+
}
196+
197+
return files;
198+
}
199+
200+
private async Task<string> OptimizeCodeWithGPT(string code, string filePath)
201+
{
202+
try
203+
{
204+
var fileExtension = Path.GetExtension(filePath);
205+
var language = GetLanguageFromExtension(fileExtension);
206+
207+
var systemPrompt = $"You are a code optimization expert. Analyze the provided {language} code and suggest optimizations for:" +
208+
"\n1. Performance improvements" +
209+
"\n2. Memory usage optimization" +
210+
"\n3. Code readability and maintainability" +
211+
"\n4. Best practices compliance" +
212+
"\n5. Algorithmic improvements" +
213+
"\n\nProvide only the optimized code without explanations. If no significant optimizations are possible, return the original code.";
214+
215+
var userPrompt = $"Optimize this {language} code:\n\n```{language}\n{code}\n```";
216+
217+
var chatClient = _openAiClient.GetChatClient(_model);
218+
var messages = new List<ChatMessage>
219+
{
220+
ChatMessage.CreateSystemMessage(systemPrompt),
221+
ChatMessage.CreateUserMessage(userPrompt)
222+
};
223+
224+
var response = await chatClient.CompleteChatAsync(messages);
225+
var optimizedCode = response.Value.Content[0].Text;
226+
227+
// Clean up the response - remove markdown code blocks if present
228+
optimizedCode = CleanCodeResponse(optimizedCode);
229+
230+
return optimizedCode;
231+
}
232+
catch (Exception ex)
233+
{
234+
Console.WriteLine($"Error optimizing code with GPT: {ex.Message}");
235+
return code; // Return original code if optimization fails
236+
}
237+
}
238+
239+
private string GetLanguageFromExtension(string extension)
240+
{
241+
return extension.ToLower() switch
242+
{
243+
".cs" => "C#",
244+
".js" => "JavaScript",
245+
".ts" => "TypeScript",
246+
".py" => "Python",
247+
".java" => "Java",
248+
".cpp" => "C++",
249+
".c" => "C",
250+
".h" => "C/C++ Header",
251+
_ => "code"
252+
};
253+
}
254+
255+
private string CleanCodeResponse(string response)
256+
{
257+
if (string.IsNullOrWhiteSpace(response))
258+
return response;
259+
260+
// Remove markdown code blocks
261+
var lines = response.Split('\n');
262+
var codeLines = new List<string>();
263+
bool inCodeBlock = false;
264+
265+
foreach (var line in lines)
266+
{
267+
if (line.StartsWith("```"))
268+
{
269+
inCodeBlock = !inCodeBlock;
270+
continue;
271+
}
272+
273+
if (inCodeBlock || !response.Contains("```"))
274+
{
275+
codeLines.Add(line);
276+
}
277+
}
278+
279+
return string.Join('\n', codeLines).Trim();
280+
}
281+
}
282+
}

0 commit comments

Comments
 (0)