Files

291 lines
10 KiB
Markdown

# MarketAlly.GitCommitEditor
A production-ready .NET 9 library for analyzing, improving, and rewriting git commit messages using configurable quality rules and AI-powered suggestions.
## Features
- **Commit Message Analysis** - Score commit messages against configurable quality rules (subject length, conventional commits, banned phrases, etc.)
- **AI-Powered Suggestions** - Generate improved commit messages using Claude, OpenAI, Gemini, or Qwen
- **Repository Management** - Discover, register, and manage multiple git repositories
- **Commit Rewriting** - Amend latest commits or reword older commits in history with automatic backup
- **Batch Operations** - Process multiple commits across multiple repositories efficiently
- **History Health Analysis** - Comprehensive repository health scoring with cleanup recommendations
- **Push Operations** - Check push status, get tracking info, and perform force pushes
- **State Persistence** - Save and restore analysis state between sessions
- **Dependency Injection** - First-class support for Microsoft.Extensions.DependencyInjection
## Installation
```bash
dotnet add package MarketAlly.GitCommitEditor
```
## Quick Start
### With Dependency Injection (Recommended)
```csharp
using MarketAlly.GitCommitEditor.Extensions;
services.AddGitMessageImprover(options =>
{
options.WorkspaceRoot = @"C:\Projects";
options.StateFilePath = "git-state.json";
options.MaxCommitsPerRepo = 100;
options.AnalyzeSince = DateTimeOffset.Now.AddMonths(-3);
options.ExcludedAuthors = ["dependabot[bot]@users.noreply.github.com"];
// Configure commit message rules
options.Rules.MinSubjectLength = 10;
options.Rules.MaxSubjectLength = 72;
options.Rules.RequireConventionalCommit = true;
options.Rules.RequireIssueReference = false;
// Configure AI provider
options.Ai.Provider = "Claude";
options.Ai.Model = "claude-sonnet-4-20250514";
options.Ai.ApiKey = Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY")!;
options.Ai.IncludeDiffContext = true;
});
```
### Without DI (Simple Usage)
```csharp
using MarketAlly.GitCommitEditor.Options;
using MarketAlly.GitCommitEditor.Services;
var options = new GitImproverOptions
{
WorkspaceRoot = @"C:\Projects",
Ai = { ApiKey = "your-api-key", Provider = "Claude" }
};
await using var service = await GitMessageImproverService.CreateAsync(options);
```
## Core Workflows
### 1. Scan and Analyze Repositories
```csharp
IGitMessageImproverService improver = ...;
// Load persisted state
await improver.LoadStateAsync();
// Discover git repos in workspace
var newRepos = await improver.ScanAndRegisterReposAsync();
Console.WriteLine($"Found {newRepos.Count} new repositories");
// Analyze all repos for commit message issues
var analyses = await improver.AnalyzeAllReposAsync(
onlyNeedsImprovement: true,
progress: new Progress<(string Repo, int Count)>(p =>
Console.WriteLine($"Analyzed {p.Count} commits in {p.Repo}")));
Console.WriteLine($"Found {analyses.Count} commits needing improvement");
```
### 2. Generate AI Suggestions
```csharp
// Generate suggestions for commits that need improvement
var commitsToImprove = analyses.Where(a => a.Quality.NeedsImprovement).ToList();
var result = await improver.GenerateSuggestionsAsync(
commitsToImprove,
progress: new Progress<int>(count =>
Console.WriteLine($"Generated {count}/{commitsToImprove.Count} suggestions")));
Console.WriteLine($"Success: {result.SuccessCount}, Failed: {result.FailedCount}");
// Or generate for a single commit
var suggestion = await improver.GenerateSuggestionAsync(analyses.First());
if (suggestion.Success)
Console.WriteLine($"Suggested: {suggestion.Suggestion}");
```
### 3. Safe Batch Rewrite (Recommended)
```csharp
// Check safety before rewriting
var safetyInfo = improver.GetRewriteSafetyInfo(repoPath, commitsToRewrite);
if (!safetyInfo.CanProceedSafely)
{
foreach (var warning in safetyInfo.GetWarnings())
Console.WriteLine($"Warning: {warning}");
}
// Execute batch rewrite with automatic backup
var result = await improver.ExecuteBatchRewriteAsync(
repoPath,
commitsToRewrite,
createBackup: true,
progress: new Progress<(int Current, int Total, string Hash)>(p =>
Console.WriteLine($"Rewriting {p.Current}/{p.Total}: {p.Hash}")));
if (result.Success)
{
Console.WriteLine($"Rewrote {result.SuccessCount} commits");
if (result.BackupBranchName != null)
Console.WriteLine($"Backup branch: {result.BackupBranchName}");
if (result.RequiresForcePush)
Console.WriteLine("Force push required to update remote");
}
```
### 4. Preview and Apply Changes (Granular Control)
```csharp
// Preview changes before applying
var operations = improver.PreviewChanges(commitsToImprove);
foreach (var op in operations)
{
Console.WriteLine($"{op.CommitHash[..7]}: {op.OriginalMessage}");
Console.WriteLine($" -> {op.NewMessage}");
}
// Apply changes (dryRun: false to actually modify commits)
var result = await improver.ApplyChangesAsync(
operations,
dryRun: false,
progress: new Progress<(int Processed, int Total)>(p =>
Console.WriteLine($"Applied {p.Processed}/{p.Total}")));
Console.WriteLine($"Success: {result.Successful}, Failed: {result.Failed}");
```
### 5. Repository History Health Analysis
```csharp
// Analyze repository health
var healthReport = await improver.AnalyzeHistoryHealthAsync(
repoPath,
new HistoryAnalysisOptions { Depth = AnalysisDepth.Standard },
progress: new Progress<AnalysisProgress>(p =>
Console.WriteLine($"Analyzing: {p.PercentComplete}%")));
Console.WriteLine($"Health Score: {healthReport.Score.OverallScore}/100 ({healthReport.Score.Grade})");
Console.WriteLine($"Issues: {healthReport.CriticalIssueCount} critical, {healthReport.ErrorCount} errors");
// Export report
var markdown = await improver.ExportHealthReportAsync(healthReport, ReportFormat.Markdown);
// Execute cleanup suggestions
if (healthReport.CleanupSuggestions?.AutomatedOperations.Any() == true)
{
var cleanupResult = await improver.ExecuteAllCleanupsAsync(
repo,
healthReport.CleanupSuggestions,
new CleanupExecutionOptions { CreateBackup = true });
Console.WriteLine($"Cleaned up {cleanupResult.Successful} issues");
}
```
### 6. Handle Pushed Commits
```csharp
// Check if commit is already pushed
bool isPushed = improver.IsCommitPushed(repoPath, commitHash);
if (isPushed)
Console.WriteLine("Warning: Commit has been pushed. Force push required after amending.");
// Get tracking info
var tracking = improver.GetTrackingInfo(repoPath);
Console.WriteLine($"Tracking: {tracking.UpstreamBranch}, Ahead: {tracking.AheadBy}, Behind: {tracking.BehindBy}");
// After amending a pushed commit, force push
var pushResult = improver.ForcePush(repoPath);
if (pushResult.Success)
Console.WriteLine("Force push successful");
else
Console.WriteLine($"Push failed: {pushResult.Message}");
```
## Configuration
### GitImproverOptions
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `WorkspaceRoot` | string | required | Root directory to scan for git repositories |
| `StateFilePath` | string | `"git-improver-state.json"` | Path to persist state between sessions |
| `MaxCommitsPerRepo` | int | `100` | Maximum commits to analyze per repository |
| `AnalyzeSince` | DateTimeOffset? | `null` | Only analyze commits after this date |
| `ExcludedAuthors` | string[] | `[]` | Author emails to exclude (e.g., bots) |
| `Rules` | CommitMessageRules | default | Commit message quality rules |
| `Ai` | AiOptions | default | AI provider configuration |
### CommitMessageRules
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `MinSubjectLength` | int | `10` | Minimum subject line length |
| `MaxSubjectLength` | int | `72` | Maximum subject line length |
| `MinBodyLength` | int | `0` | Minimum body length (0 = optional) |
| `RequireConventionalCommit` | bool | `false` | Require conventional commit format |
| `RequireIssueReference` | bool | `false` | Require issue/ticket reference |
| `BannedPhrases` | string[] | see below | Phrases that trigger warnings |
| `ConventionalTypes` | string[] | see below | Valid conventional commit types |
**Default Banned Phrases:** `fix`, `wip`, `temp`, `asdf`, `test`, `stuff`, `things`, `changes`, `update`, `misc`, `minor`, `oops`, `commit`, `save`, `checkpoint`, `progress`, `done`, `finished`
**Default Conventional Types:** `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `build`, `ci`, `chore`, `revert`
### AiOptions
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `ApiKey` | string | `""` | API key for the selected provider |
| `Provider` | string | `"Claude"` | AI provider (Claude, OpenAI, Gemini, Qwen) |
| `Model` | string | `"claude-sonnet-4-20250514"` | Model name for the provider |
| `IncludeDiffContext` | bool | `true` | Include diff in AI prompt |
| `MaxDiffLines` | int | `200` | Maximum diff lines to include |
| `MaxTokens` | int | `500` | Maximum tokens in AI response |
| `RateLimitDelayMs` | int | `500` | Delay between AI requests |
## Interfaces
The library follows Interface Segregation Principle with focused interfaces:
| Interface | Purpose |
|-----------|---------|
| `IRepositoryManager` | Repository discovery and registration |
| `ICommitAnalysisService` | Commit message analysis |
| `ISuggestionService` | AI suggestion generation |
| `ICommitRewriteService` | Commit message rewriting |
| `IGitPushService` | Push operations and tracking |
| `IHistoryHealthService` | Repository health analysis and cleanup |
| `IGitMessageImproverService` | Unified facade (composes all above) |
## Supported AI Providers
| Provider | Models |
|----------|--------|
| **Claude** | claude-sonnet-4-20250514, claude-opus-4-20250514, claude-3-5-haiku-20241022 |
| **OpenAI** | gpt-4o, gpt-4o-mini, gpt-4-turbo, gpt-3.5-turbo |
| **Gemini** | gemini-1.5-pro, gemini-1.5-flash, gemini-pro |
| **Qwen** | qwen-turbo, qwen-plus, qwen-max |
## Thread Safety
- `GitOperationsService` uses an LRU cache with TTL for repository handles
- State operations are async and support cancellation tokens
- The service implements `IDisposable` - ensure proper disposal
## Dependencies
- .NET 9.0
- MarketAlly.LibGit2Sharp (git operations)
- MarketAlly.AIPlugin (AI provider abstraction)
- Microsoft.Extensions.DependencyInjection.Abstractions
## License
MIT License - Copyright 2025 MarketAlly