Промпты для AI-коммитов в HagiCode: подход к проектированию и анализ реализации
Промпты для AI-коммитов в HagiCode: подход к проектированию и анализ реализации
Когда вы бросаете кучу изменений в AI, чтобы он помог вам сделать коммит, какой промпт отправляется модели за кулисами? Почему промпт написан именно так? В этой статье мы разбираем промпты, которые реально работают в “AI-коммите” HagiCode.
Контекст
Использование AI для разработки — это, в конце концов, усталость от целого дня написания кода. Накопилось куча незакоммиченных изменений: конфигурационные файлы, документация, бизнес-логика, тесты — всё смешано, и от этого просто болит голова. Вручную группировать, писать commit message по стандартам, переключать ветки и push — на эти “завершающие работы” уходит полчаса.
И тут возникает естественное требование — можно ли сразу передать все незакоммиченные изменения AI, чтобы он сам проанализировал, сгруппировал, написал message, и даже сделал commit + push?
Идея хорошая, но на практике много подводных камней. AI легко может изменить только --author, но не Committer — в истории коммитов автор правильный, а коммитер неправильный, выглядит разорванным; он может написать кучу вычурных message, совсем не соответствующих стилю вашего репозитория; он может самовольно переключиться на главную ветку и всё испортить; он может пропустить Co-Authored-By или добавить Signed-off-by в неправильном месте, что вызовет проблемы с соблюдением стандартов.
Все эти ямы — это уроки. Чтобы заполнить эти боли, мы сделали “AI-коммит” параметризованным контрактом задачи Agent. Как выглядит этот контракт, почему он так спроектирован — об этом и пойдёт речь в этой статье.
О HagiCode
Решение, которое мы делим в этой статье, происходит из практики в проекте HagiCode. HagiCode — это AI-кодовый ассистент для workflow разработчиков, который превращает Git-коммиты, code review, сборку и релиз в задачи, в которых AI может участвовать. Система промптов, которую мы разберём ниже, — это именно та система, которая реально работает в backend 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 только занимается “как эту фразу сказать”. Таким образом, frontend может автоматически генерировать правильную форму ввода, основываясь только на JSON, не зная текст шаблона: селектор Git-идентичности, режим Co-Authored-By, стратегия целевой ветки, нужен ли push — все эти контролы генерируются из JSON.
Во-вторых, плоское multiязычие, а не перевод с i18n key. Для каждого locale есть полный комплект .hbs + .json, что избегает “дрейфа ключей перевода”. Разные языки не просто заменяют слова, даже примеры группировки, примеры команд можно локализовать. Привычки коммитов в китайских и английских репозиториях本来就 разные, заталкивать их в один шаблон и переводить — это странно.
В-третьих, миграция с 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 (соответствует спецификации ai-commit-message-generation в OpenSpec) делал только одно: вызывал POST /api/git/generate-commit-message, возвращал строку commit message, остальное пользователь делал вручную.
Но auto-compose-commit отличается — это автозадача Agent. Модель должна сама вызвать инструмент Bash(git:*), пройти полный путь add → commit → push. Это отличие определяет тон всего промпта — он не может только описывать “какой message писать”, но и должен регламентировать “по какому процессу работать, какие инструменты использовать, что делать при ошибке”.
Решение второе: почему Git-идентичность описана так подробно
В <git_profile> и <requirements> есть большой фрагмент про Author и Committer, на первый взгляд кажется избыточным:
- `--author="Name <email>"` только изменяет Author- `git -c user.name="Name" -c user.email="email" commit ...` только изменяет Committer для этой команды- Для каждого сгенерированного коммита вы должны одновременно установить Author и Committer в выбранную идентичность- Предпочтительная форма команды: git -c user.name="..." -c user.email="..." commit --author="... <...>" ...На самом деле это пришло из реальной практики. В Git-коммите есть два поля идентичности, модель легко может изменить только --author, в результате Committer всё ещё является глобальной конфигурацией. В истории коммитов “автор правильный, коммитер неправильный”, выглядит разорванным. Поэтому промпт напрямую даёт предпочтительный шаблон команды, и требует, чтобы модель сделала самопроверку с git log --format=fuller -1.
Аналогия: как вы отправляете посылку, “отправитель” и “фактический исполнитель” — это два разных бланка. Вы написали имя только на одном бланке, на другом ещё напечатано название компании — посылка отправилась, но запись не совпадает, в конце концов это странно.
Решение третье: дерево решений группировки плюс историческая согласованность
Модель лучше всего умеет “свободно творить”, но свободное творчество в группировке коммитов — это часто катастрофа. Поэтому промпт даёт чёткое дерево решений: конфигурационные файлы в отдельную группу, документация в отдельную группу, изменения кода одного модуля объединить, изменения между модулями посмотреть по ситуации. И даёт положительные примеры, например src/auth/login.ts и auth.service.ts должны войти в один коммит.
Ещё важнее фрагмент <historical_format_analysis>. Он требует модели:
- Использовать
git log -n 15 --pretty=format:"%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}}, в зависимости от runtime параметров решается, добавлять ли trailer:
- Когда
coAuthoredByIsNone, совсем не добавлятьCo-Authored-By - Когда
coAuthoredByIsCustom, использовать заданный пользователем custom trailer - Когда
signedOffByEnabledплюс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 коммитов за одну задачу, backend должен использовать этот разделитель, чтобы разобрать hash и message каждого коммита, и передать на frontend для отображения. Если протокол вывода ослабнет, парсинг backend прямо рухнет. Поэтому правило --- подчёркивается дважды в <output_format> и <final_instruction> — важные вещи, действительно нужно сказать три раза.
Как промпт собирается и передаётся
Смотреть только на шаблон недостаточно, нужно знать, как он запускается.
Загрузка и рендеринг
Backend в PCodeClaudeHelperModule регистрирует два singleton:
// Регистрация загрузчика промптов: по scenario + locale найти соответствующие .json и .hbscontext.Services.AddSingleton<IPromptLoader, FilePromptLoaderV2>();// Регистрация рендерера Handlebars: компилировать шаблоны в IL и кэшироватьcontext.Services.AddSingleton<HandlebarsTemplateRenderer>(...);FilePromptLoaderV2 после получения текста шаблона передаёт его HandlebarsTemplateRenderer.Render(template, parameters) для рендеринга. Основная логика рендерера примерно такая:
public string Render(string template, IDictionary<string, object> parameters){ // Кэширование по SHA256 содержимого шаблона, чтобы избежать повторной компиляции при каждом коммите var compiledTemplate = GetOrCompileTemplate(template); var rendered = compiledTemplate(parameters ?? new Dictionary<string, object>()); // Совместимость с привычкой вывода булевых значений старого Scriban rendered = rendered.Replace("True", "true").Replace("False", "false"); return rendered;}Результат компиляции кэшируется по хешу содержимого — это ключ к производительности. Коммит может часто запускаться, компилировать IL каждый раз никто не выдержит.
Откуда берутся параметры
В JSON-метаданных объявлено десяток параметров: projectPath, needPush, targetBranchMode, gitProfileName, gitProfileEmail, signedOffByEnabled, coAuthoredBy* и т. д. Эти параметры собираются из “ящика AI-коммита” на frontend, передаются через канал AutoTask в backend, и FilePromptProvider маршрутизирует к этому шаблону по PromptScenario.AutoComposeCommit.
Трёхсостоятная обработка стратегии веток
targetBranchMode решает, нужно ли модели трогать ветку перед коммитом, это три состояния:
| Режим | Поведение |
|---|---|
current | Коммитить на месте, не трогать ветку |
new-custom | Использовать заданный пользователем targetBranchName для создания новой ветки из текущей |
ai-generated-new | Модель сама генерирует имя ветки в kebab-case по изменениям, при конфликте добавляет стабильный суффикс |
В промпте чётко написано “не переключаться на любую другую существующую ветку”, чтобы модель не самовольно переключилась на главную ветку для коммита. Эта способность соответствует предложению auto-branch-switch-on-commit. В конце концов, если главная ветка будет испорчена, откат — это каша.
Пример полного рендеринга
Предположим, пользователь выбрал на frontend: остаться на текущей ветке, нужен push, включить Signed-off-by, выключить Co-Authored-By, Git-идентичность — newbe <newbe@newbe.pro>.
Тогда фрагмент <git_profile> будет отрендерен как:
<git_profile>Использовать следующую Git-идентичность во всех сгенерированных коммитах:- Выбранное имя: newbe- Выбранный email: newbe@newbe.pro...- Этот запуск также требует стандартного Git sign-off trailer, поэтому предпочтительно использовать `git ... commit --author=... --signoff ...`</git_profile>В <requirements> остаётся только ветка Co-Authored-By disabled for this run, команды из <workflow> становятся:
# Обратите внимание: -c устанавливает Committer, --author устанавливает Author, --signoff добавляет DCO trailergit -c user.name="newbe" -c user.email="newbe@newbe.pro" commit \ --author="newbe <newbe@newbe.pro>" --signoff -m "type(scope): subject"Инженерная практика поддержки шаблонов
HagiCode даёт этому набору .hbs шаблонов полный инженерный гарантий, не просто написать и забыть.
Во-первых, snapshot тесты. В тестовой директории есть BuildMessage_enUS.verified.txt, BuildMessage_zhCN.verified.txt и другие проверенные snapshot, любые различия в рендеринге будут захвачены тестами. Изменишь один символ — нужно обновить snapshot, чтобы предотвратить дрейф промпта.
Во-вторых, скрипт форматирования. cleanup-prompts.py --fix очистит trailing whitespace, свернёт лишние пустые строки, если проверка CI не пройдёт, PR будет заблокирован.
В-третьих, проверка параметров. Для каждого scenario обязательные параметры, значения по умолчанию, типы покрыты специальными тестами, если в шаблоне использован {{newParam}}, но в JSON не объявлен, тест будет красным.
В-четвёртых, слоистые snapshot: Snapshots/Rendered/ хранит результаты рендеринга, Snapshots/Scenarios/ хранит метаданные сценариев, гарантирует, что шаблон, метаданные и результат рендеринга согласованы.
Есть практичный совет. Если вы хотите добавить новый параметр или новую ветку к этому промпту, нужно синхронно сделать четыре вещи:
- Использовать
{{newParam}}в шаблоне (.hbs) - Объявить schema в массиве
parametersметаданных (.json) - Обновить соответствующий
.verified.txtв snapshot тесте - Frontend форма генерирует контролы ввода по новым JSON параметрам, и передаёт через API
Если пропустить любой этап, либо при рендеринге параметр пустой, либо snapshot тест красный, либо frontend не может настроить. Это ограничение “синхронизации в четырёх местах” выглядит раздражающим, но для обеспечения поддерживаемости, это неизбежно.
Почему промпт такой “многословный”
Если вернуться к этому промпту,会发现 он необычно длинный, идентичность, trailer, формат вывода подчёркиваются снова и снова. На самом деле это намеренно.
В режиме Agent модель особенно легко “самовольно решает”, нужно распределить жёсткие ограничения по <requirements>, <workflow>, <final_instruction> и повторно объявить несколько раз, чтобы снизить вероятность пропуска выполнения. Это как обучение новичка — важные вещи нужно говорить три раза, не потому что он глупый, а потому что отвлекающих факторов слишком много.
В неинтерактивном режиме (CI/CD, автоматизация) модель не может задавать вопросы пользователю, поэтому в начале промпта чётко написано “запрещено использовать AskUserQuestion, отсутствующую информацию использовать по умолчанию и записать предположения”, чтобы гарантировать, что в автономном режиме тоже будет работать.
Если контракт вывода ослабнет, парсинг backend рухнет, поэтому правило разделителя --- подчёркивается дважды. Важные вещи, действительно нужно сказать три раза.
Ссылки
- Официальный сайт HagiCode
- GitHub репозиторий HagiCode
- Спецификация Conventional Commits: conventionalcommits.org
- Handlebars.Net: github.com/Handlebars-Net/Handlebars.Net
- Git DCO (Developer Certificate of Origin): developercertificate.org
Заключение
Возвращаясь к теме “Промпты для AI-коммитов в HagiCode: подход к проектированию и анализ реализации”, что действительно нужно подтвердить снова и снова — это не разрозненные техники, а условия ограничений, границы реализации и инженерные компромиссы.
Только если превратить основания суждения в статье в стабильные проверки, в будущем при похожих проблемах можно будет быстрее принять надёжные решения.
开始使用 HagiCode
一次安装,几分钟上手
HagiCode for Windows 在 Microsoft Store 免费提供。打开商店即可安装并保持更新;也可以先对比各版本与定价,再决定从哪个渠道开始。