AI Compose Commit:用 AI 智能重构 Git 提交工作流
本文分享的方案来自我们在 HagiCode 项目中的实践经验。Background传统 Git 提交的痛点Git 作为版本控制系统,为开发者提供了强大的代码管理能力。但在实际使用中,提交操作往往成为开发流程中的瓶颈:手动分组耗时: 当有大量文件变更时,开发者需要逐个检查文件内容,判断哪些属于同一个功能,这需要耗费大量脑力提交信息质量参差: 撰写符合 Conventional Commits 规范的提交信息需要经验和技巧,新手常常写出不规范的提交多仓库管理复杂: 在 monorepo 环境中,需要在不同仓库间切换,增加了操作复杂度工作流被打断: 提交代码会打断开发思路,影响编码效率这些问题在大型项目和团队协作环境中尤为明显。一个优秀的开发工具应该让开发者专注于核心的编码工作,而不是被繁琐的提交流程所困扰。AI 辅助开发的趋势近年来,AI 技术在软件开发领域的应用日益广泛。从代码补全、错误检测到自动生成文档,AI 正在逐步渗透到开发的各个环节。在 Git 工作流方面,虽然已有一些工具提供提交信息生成的功能,但大多局限于单次提交的场景,缺乏对整个工作区变更的智能分析和分组能力。其实 HagiCode 在开发过程中也遇到了这些痛点,我们曾尝试过多种工具,但都或多或少存在一些局限性。要么是功能不够完善,要么是用户体验不够好。这也是为什么我们最终决定自己实现 AI Compose Commit 功能的原因。HagiCode 的 AI Compose Commit 功能正是为了填补这一空白而生,它不仅是生成提交信息,而是完整接管从文件分析到执行提交的整个流程。Problem技术挑战在实现 AI Compose Commit 功能的过程中,我们面临了多个技术挑战:文件语义理解: AI 需要理解文件变更的语义关系,判断哪些文件属于同一个功能模块。这需要深入分析文件内容、目录结构以及变更的上下文。提交分组策略: 如何定义合理的分组标准?是按功能、按模块,还是按文件类型?不同的项目可能适用不同的策略。实时反馈与异步处理: Git 操作可能需要较长时间,特别是处理大量文件时。如何在保证用户体验的同时完成复杂操作?多仓库支持: 在 monorepo 架构下,需要在主仓库和子仓库之间正确路由操作。错误处理与回滚: 如果某个提交失败,如何处理已执行的提交?是否需要回滚已暂存的文件?提交信息一致性: 生成的提交信息需要符合项目现有的风格,保持历史提交的格式一致。性能考量AI 处理大量文件变更会消耗显著的时间和计算资源。我们需要在以下方面进行优化:减少不必要的 AI 调用优化文件上下文的构建方式实现高效的 Git 操作批处理这些问题在 HagiCode 的实际使用中都真实出现过,我们通过不断的迭代和优化才找到了相对完美的解决方案。如果你也在开发类似的工具,希望我们的经验能给你一些启发。Solution整体架构设计我们采用了分层架构来实现 AI Compose Commit 功能,确保系统具有良好的可扩展性和可维护性:1. API 层(Web 层)GitController提供了POST /api/git/auto-compose-commit端点,作为功能入口。为了优化用户体验,我们采用了 Fire-and-Forget 异步模式:客户端发起请求后,服务器立即返回 HTTP 202 Accepted实际的 AI 处理在后台异步执行处理完成后通过 SignalR 通知客户端这种设计确保了即使 AI 处理需要几分钟,用户也能立即得到响应,不会感觉系统卡顿。2. 应用服务层(Application 层)GitAppService负责核心业务逻辑:仓库检测:支持 monorepo 中的多仓库管理锁管理:防止并发操作导致的冲突文件暂存协调:与 AI 处理流程的交互错误回滚:处理失败场景下的状态恢复3. 分布式计算层(Orleans Grains)AIGrain作为 AI 操作的核心执行单元,实现了IAIGrain接口中的AutoComposeCommitAsync方法:// 定义 AI 自动组合提交的接口方法// 参数说明:// - projectId: 项目唯一标识符// - unstagedFiles: 未暂存文件列表,包含文件路径和状态信息// - projectPath: 项目根目录路径(可选),用于访问项目上下文// 返回值: 包含执行结果的响应对象,包括成功/失败状态和详细信息[Alias(AutoComposeCommitAsync)][ResponseTimeout(00:20:00)] // 20 分钟超时,适用于处理大型变更集TaskAutoComposeCommitResponseDto AutoComposeCommitAsync(string projectId,GitFileStatusDto[] unstagedFiles,string? projectPath null);这个方法设置了 20 分钟的超时时间,以处理大型变更集。HagiCode 在实际使用中发现,有些项目的单次变更可能涉及上百个文件,需要更长的处理时间。4. AI 服务层通过抽象的IAIService接口,我们实现了 AI 服务的可插拔架构。目前使用 Claude Helper 服务,但可以轻松切换到其他 AI 提供商。核心实现逻辑文件上下文构建AI 需要了解每个文件的状态才能做出智能决策。我们通过BuildFileChangesXml方法构建文件上下文:/// summary/// 构建文件变更的 XML 表示形式,用于为 AI 提供完整的文件上下文信息/// /summary/// param namestagedFiles已暂存的文件列表,包含文件路径、状态和旧路径(针对重命名操作)/param/// returns格式化的 XML 字符串,包含所有文件的元数据信息/returnsprivate static string BuildFileChangesXml(GitFileStatusDto[] stagedFiles){var sb new StringBuilder();sb.AppendLine(files);foreach (var file in stagedFiles){sb.AppendLine( file);// 使用 XML 转义确保特殊字符不会破坏 XML 结构sb.AppendLine($ path{System.Security.SecurityElement.Escape(file.Path)}/path);sb.AppendLine($ status{System.Security.SecurityElement.Escape(file.Status)}/status);// 处理文件重命名场景,记录旧路径以便 AI 理解变更关系if (!string.IsNullOrEmpty(file.OldPath)){sb.AppendLine($ oldPath{System.Security.SecurityElement.Escape(file.OldPath)}/oldPath);}sb.AppendLine( /file);}sb.AppendLine(/files);return sb.ToString();}这个 XML 格式的上下文包含文件路径、状态和旧路径(针对重命名操作),为 AI 提供了完整的元数据。通过结构化的 XML 格式,我们确保了 AI 能够准确理解每个文件的状态和变更类型。AI 权限管理为了让 AI 能够直接执行 Git 操作,我们配置了全面的工具权限:// 定义 AI 可以使用的工具集合,包括文件操作和 Git 命令执行权限// Read/Write/Edit: 文件读写和编辑能力// Bash(git:*): 执行所有 Git 命令的权限// 其他 Bash 命令: 用于查看文件内容和目录结构,辅助 AI 理解上下文var allowedTools new[]{Read, Write, Edit,Bash(git:*), Bash(cat:*), Bash(ls:*), Bash(find:*),Bash(grep:*), Bash(head:*), Bash(tail:*), Bash(wc:*)};// 构建完整的 AI 请求对象var request new AIRequest{Prompt prompt, // 完整的 Prompt 模板,包含任务指令和约束条件WorkingDirectory projectPath ?? GetTempDirectory(), // 工作目录,确保 AI 在正确的项目上下文中执行AllowedTools allowedTools, // 允许使用的工具集合PermissionMode PermissionMode.bypassPermissions, // 绕过权限检查,允许直接执行 Git 操作LanguagePreference languagePreference // 语言偏好设置,确保生成符合用户期望的提交信息};这里使用了PermissionMode.bypassPermissions模式,允许 AI 直接执行 Git 命令而无需用户确认。这是功能设计的核心,但同时也需要严格的输入验证来防止滥用。HagiCode 在实际部署中,通过后端的参数验证和日志监控,确保了这个机制的安全性。提交结果解析AI 执行完成后,会返回结构化的结果。我们实现了双重解析策略以确保兼容性:/// summary/// 解析 AI 返回的提交执行结果,支持分隔符格式和正则表达式格式/// /summary/// param nameaiResponseAI 返回的原始响应内容/param/// returns解析后的提交结果列表,每个结果包含提交哈希和执行状态/returnsprivate ListCommitResultDto ParseCommitExecutionResults(string aiResponse){var results new ListCommitResultDto();// 优先使用分隔符解析(新格式),这种格式更加明确和可靠if (aiResponse.Contains(---)){logger.LogDebug(Using delimiter-based parsing for AI response);results ParseDelimitedFormat(aiResponse);if (results.Count 0){return results; // 成功解析,直接返回结果}logger.LogWarning(Delimiter-based parsing produced no results, falling back to regex);}else{logger.LogDebug(No delimiter found, using legacy regex-based parsing);}// 回退到正则表达式解析(旧格式),确保向后兼容性return ParseLegacyFormat(aiResponse);}分隔符格式使用---作为提交之间的分隔,格式清晰且易于解析:---Commit 1: abc123def456feat(auth): add user login functionalityImplement JWT-based authentication with login form and API endpoints.Co-Authored-By: Hagicode noreplyhagicode.com---Commit 2: 789ghi012jkldocs(readme): update installation instructionsAdd new setup steps for Docker environment.Co-Authored-By: Hagicode noreplyhagicode.com---这种格式设计让解析变得简单可靠,同时人类阅读也很清晰。锁管理机制为了防止并发操作导致的状态冲突,我们实现了仓库锁机制:// 获取仓库锁,防止并发操作// 参数说明:// - fullPath: 仓库的完整路径,用于标识不同的仓库实例// - requestedBy: 请求者标识,用于追踪和日志记录await _autoComposeLockService.AcquireLockAsync(fullPath, requestedBy);try{// 执行 AI Compose Commit 操作// 这部分代码会调用 Orleans Grain 的方法,执行实际的 AI 处理和 Git 操作await aiGrain.AutoComposeCommitAsync(projectId, unstagedFiles, projectPath);}finally{// 确保锁被释放,无论操作成功或失败// 使用 finally 块可以保证异常情况下也能释放锁,避免死锁await _autoComposeLockService.ReleaseLockAsync(fullPath);}锁具有 20 分钟的超时时间,与 AI 操作的超时设置保持一致。如果操作失败或超时,系统会自动释放锁,避免永久阻塞。HagiCode 在实际使用中发现,这个锁机制非常重要,特别是在团队协作环境中,多个开发者可能同时触发 AI Compose Commit 操作。SignalR 实时通知处理完成后,系统通过 SignalR 向前端发送通知:/// summary/// 发送自动组合提交完成的通知/// /summary/// param nameprojectId项目标识符,用于路由通知到正确的客户端/param/// param nametotalCount总提交数量,包括成功和失败/param/// param namesuccessCount成功提交的数量/param/// param namefailureCount失败提交的数量/param/// param namesuccess整体操作是否成功标志/param/// param nameerror错误信息(如果操作失败)/paramprivate async Task SendAutoComposeCommitNotificationAsync(string projectId,int totalCount,int successCount,int failureCount,bool success,string? error){try{// 构建通知数据传输对象,包含详细的执行结果var notification new AutoComposeCommitCompletedDto{ProjectId projectId,TotalCount totalCount,SuccessCount successCount,FailureCount failureCount,Success success,Error error};// 通过 SignalR Hub 广播通知到所有连接的客户端await messageService.SendAutoComposeCommitCompletedAsync(notification);logger.LogInformation(Auto compose commit notification sent for project {ProjectId}: {SuccessCount}/{TotalCount} succeeded,projectId, successCount, totalCount);}catch (Exception ex){// 记录通知错误但不影响主操作流程// 通知失败不应该导致整个操作失败logger.LogError(ex, Failed to send auto compose commit notification for project {ProjectId}, projectId);}}前端收到通知后可以更新 UI,显示提交成功或失败的状态,提升用户体验。这种实时反馈机制在 HagiCode 的使用中获得了很好的用户反馈,用户可以清楚地知道操作何时完成以及结果如何。ImplementationPrompt 工程设计AI 的行为完全由 Prompt 决定,我们精心设计了 Auto Compose Commit 的 Prompt 模板。以中文版本为例(auto-compose-commit.zh-CN.hbs):非交互式模式支持Prompt 开头明确声明支持非交互式运行模式,这是 CI/CD 和自动化脚本的关键需求:**重要提示**:此提示词可能在非交互式环境中运行(如 CI/CD、自动化脚本)。**非交互式模式**:- 禁止使用 AskUserQuestion 或任何交互式工具- 当需要用户输入时:- 使用合理的默认值(如提交类型使用 feat)- 跳过可选的确认步骤- 记录所做的假设这个设计确保了 AI Compose Commit 功能不仅能在交互式 IDE 环境中使用,也能集成到 CI/CD 流程中,实现完全自动化的提交流程。分支保护机制为了防止 AI 执行危险操作,我们在 Prompt 中添加了严格的分支保护规则:**分支保护**:- 禁止执行任何分支切换操作(git checkout、git switch)- 所有 git commit 命令必须在当前分支上执行- 不得创建、删除或重命名分支- 不得修改未跟踪文件或未暂存变更- 如果需要分支切换才能完成操作,应返回错误而非执行这些规则通过约束 AI 的工具使用范围,确保操作的安全性。HagiCode 在实际测试中验证了这些约束的有效性,AI 在遇到需要分支切换的场景时会安全地返回错误,而不是执行危险操作。智能分组决策树Prompt 中详细定义了文件分组的决策逻辑:**文件分组决策树**:├── 是否为配置文件(package.json、tsconfig.json、.env 等)?│ ├── 是 → 独立提交(类型:chore 或 build)│ └── 否 → 继续├── 是否为文档文件(README.md、*.md、docs/**)?│ ├── 是 → 独立提交(类型:docs)│ └── 否 → 继续├── 是否与同一功能相关?│ ├── 是 → 合并到同一提交│ └── 否 → 分别提交└── 是否为跨模块变更?├── 是 → 按模块分组└── 否 → 按功能分组这个决策树为 AI 提供了清晰的分组逻辑,确保生成的提交符合语义合理性。HagiCode 在实际使用中发现,这个决策树能够处理绝大多数常见场景,生成的分组结果符合开发者预期。历史格式一致性分析为了让提交信息与项目历史保持一致,Prompt 要求 AI 在生成前分析最近的提交历史:**历史格式一致性**:在生成提交信息之前,你**必须**分析当前仓库的提交历史以匹配现有风格1. 使用 git log -n 15 --prettyformat:%H|%s|%b%n---%n 获取最近的提交历史2. 分析提交以识别:- 结构模式:项目是否使用多段落?是否有 Changes: 或 Capabilities: 部分?- 语言模式:提交信息是英文、中文还是混合?- 常用类型:最常使用哪些提交类型(feat、fix、docs 等)?- 特殊格式:是否有 Co-Authored-By 行?其他项目特定的约定?3. 生成遵循检测到的模式的提交信息这个分析确保了 AI 生成的提交信息不会显得突兀,而是与项目的提交历史保持风格一致。在 HagiCode 的多语言项目中,这个功能特别重要,它能够根据项目的提交历史自动选择合适的语言和格式。Co-Authored-By 要求每个提交必须包含 Co-Authored-By 信息:**重要**:每个提交必须添加 Co-Authored-By 信息- 使用以下格式:git commit -m type(scope): subject -m -m Co-Authored-By: Hagicode noreplyhagicode.com- 或者直接在提交信息中包含 Co-Authored-By 行这不仅是为了贡献规范,也是为了追踪 AI 辅助的提交历史。HagiCode 将这个要求作为强制规则,确保所有 AI 生成的提交都带有明确的来源标识。工作流程详解完整的 AI Compose Commit 工作流程如下:用户触发: 用户在 Git Status 面板或 Quick Actions Zone 点击AI Auto Compose Commit按钮API 请求: 前端发送 POST 请求到/api/git/auto-compose-commit端点立即响应: 服务器返回 HTTP 202 Accepted,不等待处理完成后台处理:GitAppService 获取仓库锁调用 AIGrain 的 AutoComposeCommitAsync 方法构建文件上下文 XML执行 AI Prompt,让 AI 分析并执行提交AI 执行:使用 Git 命令获取所有未暂存变更读取文件内容理解变更性质按语义关系对文件分组对每组执行git add和git commit操作结果解析: 解析 AI 返回的执行结果通知发送: 通过 SignalR 通知前端锁释放: 无论成功或失败,都释放仓库锁这个流程的设计确保了用户可以在发起操作后立即继续其他工作,而不需要等待 AI 处理完成。HagiCode 的用户反馈表明,这种异步处理方式大大提升了工作流体验。错误处理机制我们实现了多层级的错误处理:1. 输入验证// 验证请求参数的有效性,防止无效请求到达后端处理逻辑if (request.UnstagedFiles null || request.UnstagedFiles.Count 0){return BadRequest(new{message No unstaged files provided. Please make changes in the working directory first.,status validation_failed});}2. 错误回滚如果 AI 处理过程中出现错误,系统会执行回滚操作,将已暂存的文件取消暂存,避免留下不一致的状态。这个机制在 HagiCode 的实际使用中挽救了多次意外中断,确保了仓库状态的完整性。3. 超时处理20 分钟的超时设置确保了长时间运行的操作不会无限期阻塞资源。超时后,系统会释放锁并通知用户操作失败。HagiCode 在实际使用中发现,大部分操作能够在 2-5 分钟内完成,只有处理超大型变更集时才会接近超时限制。Best Practices使用 AI Compose Commit 的最佳实践1. 合理使用时机AI Compose Commit 最适合以下场景:一天工作结束后,批量处理多个文件的变更重构操作后,多个相关文件需要分别提交功能开发完成,需要将相关变更分组提交不适合以下场景:单个文件的快速提交(直接使用普通提交更快)需要精确控制提交内容的场景包含敏感信息的提交(需要人工审核)2. 审查 AI 生成的提交虽然 AI 智能分组很强大,但开发者仍应审查生成的提交:检查提交的分组是否符合预期验证提交信息的准确性确认没有遗漏或错误包含文件如果发现不合理的分组,可以使用git reset --soft HEAD~N撤销后重新分组。HagiCode 的经验表明,即使 AI 分组很智能,人工审查仍然是有价值的,特别是在重要的功能提交时。3. 配合项目规范确保项目的 Git 配置支持 Conventional Commits:# 安装 commitlintnpm install -g commitlint/cli commitlint/config-conventional# 配置 commitlintecho module.exports {extends: [commitlint/config-conventional]} commitlint.config.js这样可以在 CI/CD 流程中验证提交信息格式,与 AI Compose Commit 生成的格式保持一致。实现类似功能的建议如果你想在项目中实现类似的 AI 辅助提交功能,以下是我们的建议:1. 从小规模开始先实现单次提交信息生成,再逐步扩展到多提交分组功能。这样更容易验证和迭代。HagiCode 也是按照这个路径逐步完善功能的,早期版本只支持单次提交,后来才扩展到多提交智能分组。2. 使用成熟的 AI SDK不要自己实现 AI 调用逻辑,使用现有的 SDK 可以减少开发时间和潜在 bug。我们使用了 Claude Helper 服务,它提供了稳定的接口和完善的错误处理。3. 重视 Prompt 设计Prompt 的质量直接决定了 AI 输出的质量。投入时间设计详细的 Prompt,包括:明确的任务描述具体的输出格式要求边界情况的处理规则示例说明HagiCode 在 Prompt 设计上投入了大量时间,这是功能成功的关键因素之一。4. 实现全面的错误处理AI 操作可能因为各种原因失败(网络问题、API 限流、内容审查等)。确保你的系统能够优雅地处理这些错误,并提供有意义的错误信息。5. 提供手动干预机制不要完全自动化,给用户保留控制权。提供查看分组结果、调整分组、手动编辑提交信息等选项,平衡自动化与灵活性。HagiCode 虽然实现了自动执行,但仍然保留了预览和调整的能力。性能优化技巧1. 文件过滤在构建文件上下文时,过滤掉不需要 AI 分析的文件:// 过滤掉自动生成的文件和过大的文件,减少 AI 处理负担var relevantFiles stagedFiles.Where(f !IsGeneratedFile(f.Path)).Where(f !IsLargeFile(f.Path)).ToArray();2. 并行处理如果支持多个独立仓库,可以并行处理不同仓库的提交,提高整体效率。3. 缓存优化缓存项目提交历史分析结果,避免每次都重新分析。可以在配置文件中存储历史格式偏好,减少 AI 调用次数。ConclusionAI Compose Commit 功能代表了 AI 技术在软件开发工具中的深度应用。通过智能分析文件变更、自动分组提交、生成规范的提交信息,它显著提升了 Git 工作流的效率,让开发者能够更专注于核心的编码工作。在实现过程中,我们学到了几个重要的经验:用户反馈是关键: 早期版本采用同步等待方式,用户反馈体验不佳,改为 Fire-and-Forget 模式后满意度大幅提升Prompt 设计决定质量: 一个精心设计的 Prompt 比复杂的算法更能保证 AI 输出的质量安全永远是第一位的: 虽然赋予 AI 直接执行 Git 命令的权限带来了效率提升,但必须配合严格的约束和