# 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(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(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