-
Notifications
You must be signed in to change notification settings - Fork 8
Open
Labels
enhancementNew feature or requestNew feature or requestgood first issueGood for newcomersGood for newcomershacktoberfestParticipation in the Hacktoberfest eventParticipation in the Hacktoberfest eventhelp wantedExtra attention is neededExtra attention is needed👷🏼 infrastructureInfrastructure-related tasks or issuesInfrastructure-related tasks or issues📝 documentationTasks related to writing or updating documentationTasks related to writing or updating documentation🕔 high effortA task that can be completed in a few daysA task that can be completed in a few days🧪 testsTasks related to testingTasks related to testing
Description
We need a reusable and testable caching layer for commit message generation results from the OpenAI API.
The goal is to store and reuse results for identical inputs, using a hash of the model name, branch, author message, and diff content to ensure consistent results across sessions. Cached responses should:
- Be stored in a platform-agnostic location under the user's AppData/config directory
- Use a model-aware SHA256 hash for lookup
- Include a checksum for validation
- Have a 30-day TTL
- Be pluggable via an
ICacheProviderinterface - Allow override of cache location via environment variable (
COMMIT_CACHE_PATH)
Proposed Implementation:
✅ ICacheProvider.cs
public interface ICacheProvider
{
string GenerateHash(string model, string branch, string authorMessage, string diff);
Task<string?> LoadAsync(string model, string hash, int maxAgeDays = 30);
Task SaveAsync(string model, string hash, string response);
}🗂️ FileCacheProvider.cs (cross-platform version)
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
public class FileCacheProvider : ICacheProvider
{
private record CachedResponse(string Model, string Response, string Checksum, DateTime Timestamp);
private readonly string _cacheDir;
public FileCacheProvider(string appName = "CommitMessageTool")
{
string? envPath = Environment.GetEnvironmentVariable("COMMIT_CACHE_PATH");
if (!string.IsNullOrWhiteSpace(envPath))
{
_cacheDir = Path.Combine(envPath, appName, "commit-cache");
}
else
{
string baseDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
// Fallback for non-Windows
if (string.IsNullOrWhiteSpace(baseDir))
{
string? home = Environment.GetEnvironmentVariable("HOME")
?? Environment.GetEnvironmentVariable("USERPROFILE");
if (string.IsNullOrEmpty(home))
throw new InvalidOperationException("Unable to determine a valid user data directory");
baseDir = Path.Combine(home, ".config");
}
_cacheDir = Path.Combine(baseDir, appName, "commit-cache");
}
Directory.CreateDirectory(_cacheDir);
}
public string GenerateHash(string model, string branch, string authorMessage, string diff)
{
string combined = $"{model}|{branch}|{authorMessage}|{diff}";
using var sha = SHA256.Create();
byte[] hashBytes = sha.ComputeHash(Encoding.UTF8.GetBytes(combined));
return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
}
public async Task<string?> LoadAsync(string model, string hash, int maxAgeDays = 30)
{
string path = Path.Combine(_cacheDir, $"{hash}.json");
if (!File.Exists(path)) return null;
var json = await File.ReadAllTextAsync(path);
var cached = JsonSerializer.Deserialize<CachedResponse>(json);
if (cached is null || cached.Model != model)
return null;
bool expired = DateTime.UtcNow - cached.Timestamp > TimeSpan.FromDays(maxAgeDays);
bool validChecksum = cached.Checksum == ComputeChecksum(model, cached.Response);
if (expired || !validChecksum)
{
File.Delete(path);
return null;
}
return cached.Response;
}
public async Task SaveAsync(string model, string hash, string response)
{
string path = Path.Combine(_cacheDir, $"{hash}.json");
var checksum = ComputeChecksum(model, response);
var cached = new CachedResponse(model, response, checksum, DateTime.UtcNow);
var json = JsonSerializer.Serialize(cached);
await File.WriteAllTextAsync(path, json);
}
private string ComputeChecksum(string model, string content)
{
using var sha = SHA256.Create();
var combined = $"{model}|{content}";
byte[] hashBytes = sha.ComputeHash(Encoding.UTF8.GetBytes(combined));
return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
}
}🧠 CommitMessageCacheService.cs
public class CommitMessageCacheService
{
private readonly ICacheProvider _cacheProvider;
public CommitMessageCacheService(ICacheProvider cacheProvider)
{
_cacheProvider = cacheProvider;
}
public async Task<string> GetOrGenerateAsync(
string model,
string branch,
string authorMessage,
string diff,
Func<Task<string>> generateFunc)
{
string hash = _cacheProvider.GenerateHash(model, branch, authorMessage, diff);
string? cached = await _cacheProvider.LoadAsync(model, hash);
if (cached != null)
{
Console.WriteLine("✅ Loaded from cache.");
return cached;
}
string result = await generateFunc();
await _cacheProvider.SaveAsync(model, hash, result);
Console.WriteLine("💬 Cached new result.");
return result;
}
}🧪 Example usage
var cacheProvider = new FileCacheProvider("YourAppName");
var cacheService = new CommitMessageCacheService(cacheProvider);
string result = await cacheService.GetOrGenerateAsync(
model: "gpt-4-turbo",
branch: "feature/user-auth",
authorMessage: "add login endpoint",
diff: yourDiffContent,
generateFunc: () => CallOpenAiApiAsync(...)
);
Console.WriteLine(result);Acceptance Criteria:
-
ICacheProviderinterface defined withGenerateHash,LoadAsync,SaveAsync -
FileCacheProvideris cross-platform (Windows/macOS/Linux) - Fallback to
$HOME/.configifApplicationDatais empty - Optional override via
COMMIT_CACHE_PATHenv var - 30-day TTL is applied and expired files are discarded
- Checksum includes model name for integrity
-
CommitMessageCacheServiceorchestrates cache lookup, call, and save - Example usage added to project docs/tests
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
enhancementNew feature or requestNew feature or requestgood first issueGood for newcomersGood for newcomershacktoberfestParticipation in the Hacktoberfest eventParticipation in the Hacktoberfest eventhelp wantedExtra attention is neededExtra attention is needed👷🏼 infrastructureInfrastructure-related tasks or issuesInfrastructure-related tasks or issues📝 documentationTasks related to writing or updating documentationTasks related to writing or updating documentation🕔 high effortA task that can be completed in a few daysA task that can be completed in a few days🧪 testsTasks related to testingTasks related to testing