
背景用 AI 辅助开发这事其实也算是经历了一整天敲代码的疲惫了吧。攒了一堆没提交的改动配置文件、文档、业务逻辑、测试用例全混在一起看着就让人头疼。手动分组、手写符合规范的 commit message、再切分支 push 一遍——光是这些收尾活半小时就这么没了。其实这事儿自然就有了诉求——能不能一次性把未提交的改动扔给 AI让它自己分析、分组、写 message、甚至直接 commit push想法是好的真做起来坑可不少。AI 很容易只改--author不改 Committer提交历史里作者对了、提交者错了看着就撕裂它可能自由发挥写一堆花里胡哨的 message完全不对齐你仓库的风格它可能擅自切到主干分支把事情搞砸它可能漏掉Co-Authored-By或者乱加Signed-off-by触发合规问题。这一个个坑踩下来也都是教训罢了。为了填平这些痛点我们把AI 提交做成了一个参数化的 Agent 任务契约。这份契约长什么样、为什么这么设计就是这篇文章想聊清楚的事。关于 HagiCode本文分享的方案来自我们在 HagiCode 项目里的实践。HagiCode 是一个面向开发者工作流的 AI 代码助手把 Git 提交、代码审查、构建发布这些日常环节都做成了 AI 可参与的任务。下文拆解的提示词系统正是 HagiCode 后端里真实在跑的那一套。说到底也只是想把那点琐碎的收尾活交给 AI 罢了。提示词的真实形态模板加元数据而不是一段写死的字符串很多人以为提示词就是一段写死的自然语言丢给模型就完事了。其实 HagiCode 的做法完全不是这样。真正驱动AI 提交的提示词叫auto-compose-commit对应代码里的PromptScenario.AutoComposeCommit。它位于repos/hagicode-core/src/PCode.Web/Resources/Prompts/下结构是这样的Resources/Prompts/├── auto-compose-commit.en-US.hbs # 英文 Handlebars 模板├── auto-compose-commit.en-US.json # 英文元数据参数 schema、版本、标签├── auto-compose-commit.zh-CN.hbs # 中文模板└── auto-compose-commit.zh-CN.json # 中文元数据也就是说一个提示词是一份 Handlebars 模板 一份 JSON 元数据的组合按 locale 平铺成多套。为什么要这么拆呢其实背后有几个考量。第一元数据和提示词正文解耦。JSON 描述参数 schema——参数叫什么、什么类型、是否必填、默认值是什么.hbs只管这段话怎么说。这样一来前端可以在完全不知道模板正文的前提下依据 JSON 自动渲染出正确的输入表单Git 身份选择器、Co-Authored-By 模式、目标分支策略、要不要 push……这些控件都是 JSON 驱动出来的。第二多语言平铺而不是用 i18n key 做翻译。每个 locale 一整套完整的.hbs.json避免了翻译 key 漂移。不同语言不只是把词替换掉连分组示例、命令示例都可以本地化。中英文仓库的提交习惯本就不同硬塞进一套模板再翻译反而别扭罢了。第三从 Scriban 迁到 Handlebars是为了性能。HandlebarsTemplateRenderer选用了Handlebars.Net因为它能compile templates directly to IL bytecode比解释执行快得多。迁移过程中还做了个有意思的兼容处理把渲染结果里的True/False替换成true/false兼容旧 Scriban 的布尔输出习惯——这种细节不留意旧测试会全红。提示词长成这样背后有五个关键决策把auto-compose-commit.zh-CN.hbs拆开看骨架大致是非交互模式说明├── task 任务定义分析变更、智能分组、多提交├── context 上下文projectPath push 控制 目标分支控制├── working_directory├── git_profile 身份Author 加 Committer 双写├── tools 工具白名单├── requirements 硬性要求分支、分组、Co-Authored-By、Signed-off-by、Conventional Commits├── historical_format_analysis 历史一致性├── constraints 约束禁止 reset、忽略 .gitignore├── workflow 分步执行流程├── output_format 严格的 --- 分隔输出└── final_instruction下面挑五个最能体现设计意图的点展开聊聊。决策一直接执行而不是只生成计划提示词里反复强调一句话直接使用 Git 命令执行每个提交不返回计划直接操作。这是Auto Compose Commit区别于早期方案的根本不同。早期的ai-git-commit-message-generator对应 OpenSpec 里的ai-commit-message-generation规范只做一件事调一个POST /api/git/generate-commit-message返回一段 commit message 字符串剩下的用户自己手动去提交。可是auto-compose-commit不一样它是一个Agent 自动任务。模型必须自己调用Bash(git:*)工具把 add → commit → push 的全链路跑完。这一区别就决定了整段提示词的基调——它不能只描述要写什么样的 message还得规定按什么流程操作、用什么工具、出错怎么办。决策二为什么 Git 身份要写得这么啰嗦git_profile和requirements里有一大段关于 Author 与 Committer 的说明乍看挺冗余- --authorName email 只会修改 Author- git -c user.nameName -c user.emailemail commit ... 只会修改这一次命令的 Committer- 对于每一个生成的提交你都必须同时把 Author 和 Committer 设置为选定身份- 首选命令形式git -c user.name... -c user.email... commit --author... ... ...这其实是真实踩坑换来的。Git 提交里有两个身份字段模型很容易只改--author结果 Committer 还是全局配置的那个身份。提交历史里作者是对的、提交者是错的看着就撕裂。所以提示词直接把首选命令模板贴出来还要求模型用git log --formatfuller -1做自检。类比一下这就像你寄快递寄件人和实际经手人是两张不同的单子。你只在一张单子上写了名字另一张还印着公司的名字——快递是寄出去了可记录对不上终归是别扭罢了。决策三分组决策树加历史一致性模型最擅长的就是自由发挥可自由发挥在提交分组这事上往往是灾难。所以提示词给了一棵明确的决策树配置文件单独一组、文档单独一组、同模块的代码改动合并、跨模块的改动看情况。还配了正例比如src/auth/login.ts加上auth.service.ts应该进同一个提交。更关键的是historical_format_analysis这一段。它要求模型使用git log -n 15 --prettyformat:%H|%s|%b%n---%n获取最近的提交历史分析结构模式、语言模式、常用类型、特殊格式生成遵循检测到的模式的提交信息也就是说模型不能想怎么写就怎么写得先去对齐目标仓库已有的风格。HagiCode Mono 主仓用英文 Conventional Commits某些子仓库用中文段落式AI 必须入乡随俗。这个能力对应归档提案2026-02-23-auto-commit-compose-history-consistency-optimization是后来补上的优化。毕竟谁也不希望自家提交历史像一锅乱炖罢了。决策四Co-Authored-By 和 Signed-off-by 的条件渲染提示词里有大量嵌套的{{#if}}根据运行参数决定要不要加 trailercoAuthoredByIsNone时完全不加Co-Authored-BycoAuthoredByIsCustom时用用户给的自定义 trailersignedOffByEnabled加上gitProfileName时加Signed-off-by缺失身份时必须报错而不是臆造一个trailer 这块涉及署名归属和合规DCO sign-off必须由用户显式控制绝不能让模型自作主张。HagiCode 在这块陆续落地了git-commit-coauthor-standardization、ai-commit-consent-management等一系列提案才把边界划清楚。这种事宁可严一点也不能含糊。决策五---分隔的输出契约output_format规定每次返回必须用---分隔多个 commit 块格式写死---Commit 1: {hash}{message}---Commit 2: {hash}{message}---这可不是为了好看。模型一次任务可能产出 N 个提交后端要靠这个分隔符把每条提交的 hash 和 message 解析出来回传给前端展示。一旦输出协议松动后端解析直接崩。所以---这条规则在output_format和final_instruction里被强调了两次——重要的事本就该说三遍罢了。提示词是怎么被组装和投递的光看模板还不够得知道它怎么跑起来。加载与渲染后端在PCodeClaudeHelperModule里注册了两个单例