Перейти к содержимому

Промпты для AI-коммитов в HagiCode: подход к проектированию и анализ реализации

HagiCode for Windows Microsoft Store artwork
HagiCode for Windows is now on Microsoft Store
HagiCode for Windows is officially live on Microsoft Store. Windows users can install it directly from the storefront and stay on the store-managed update path. Open the listing and take a look.
Open Microsoft Store

Промпты для 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>. Он требует модели:

  1. Использовать git log -n 15 --pretty=format:"%H|%s|%b%n---%n" чтобы получить недавнюю историю коммитов
  2. Проанализировать структуру шаблона, языковые шаблоны, общие типы, специальные форматы
  3. Сгенерировать сообщение коммита, соответствующее обнаруженным шаблонам

То есть модель не может писать как хочет, она должна сначала выровняться с уже существующим стилем целевого репозитория. 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 и .hbs
context.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> становятся:

Terminal window
# Обратите внимание: -c устанавливает Committer, --author устанавливает Author, --signoff добавляет DCO trailer
git -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/ хранит метаданные сценариев, гарантирует, что шаблон, метаданные и результат рендеринга согласованы.

Есть практичный совет. Если вы хотите добавить новый параметр или новую ветку к этому промпту, нужно синхронно сделать четыре вещи:

  1. Использовать {{newParam}} в шаблоне (.hbs)
  2. Объявить schema в массиве parameters метаданных (.json)
  3. Обновить соответствующий .verified.txt в snapshot тесте
  4. Frontend форма генерирует контролы ввода по новым JSON параметрам, и передаёт через API

Если пропустить любой этап, либо при рендеринге параметр пустой, либо snapshot тест красный, либо frontend не может настроить. Это ограничение “синхронизации в четырёх местах” выглядит раздражающим, но для обеспечения поддерживаемости, это неизбежно.

Почему промпт такой “многословный”

Если вернуться к этому промпту,会发现 он необычно длинный, идентичность, trailer, формат вывода подчёркиваются снова и снова. На самом деле это намеренно.

В режиме Agent модель особенно легко “самовольно решает”, нужно распределить жёсткие ограничения по <requirements>, <workflow>, <final_instruction> и повторно объявить несколько раз, чтобы снизить вероятность пропуска выполнения. Это как обучение новичка — важные вещи нужно говорить три раза, не потому что он глупый, а потому что отвлекающих факторов слишком много.

В неинтерактивном режиме (CI/CD, автоматизация) модель не может задавать вопросы пользователю, поэтому в начале промпта чётко написано “запрещено использовать AskUserQuestion, отсутствующую информацию использовать по умолчанию и записать предположения”, чтобы гарантировать, что в автономном режиме тоже будет работать.

Если контракт вывода ослабнет, парсинг backend рухнет, поэтому правило разделителя --- подчёркивается дважды. Важные вещи, действительно нужно сказать три раза.

Ссылки

Заключение

Возвращаясь к теме “Промпты для AI-коммитов в HagiCode: подход к проектированию и анализ реализации”, что действительно нужно подтвердить снова и снова — это не разрозненные техники, а условия ограничений, границы реализации и инженерные компромиссы.

Только если превратить основания суждения в статье в стабильные проверки, в будущем при похожих проблемах можно будет быстрее принять надёжные решения.

开始使用 HagiCode

一次安装,几分钟上手

HagiCode for Windows 在 Microsoft Store 免费提供。打开商店即可安装并保持更新;也可以先对比各版本与定价,再决定从哪个渠道开始。