HagiCode에서 AI 커밋에 사용되는 프롬프트: 설계 아이디어와 구현 분해
HagiCode에서 AI 커밋에 사용되는 프롬프트: 설계 아이디어와 구현 분해
일련의 엉망인 변경 사항을 AI에 던져 커밋하도록 요청할 때, 실제로 어떤 프롬프트가 모델에 전송됩니까? 왜 프롬프트를 그런 식으로 작성해야 할까요? 이 글에서는 HagiCode에서 실제로 “AI 커밋”을 구동하는 프롬프트를 분석해 보겠습니다.
배경
AI를 활용한 개발은 하루 종일 코드를 작성하는 피로감을 겪은 후나 도움이 되는 일입니다. 커밋되지 않은 변경 사항이 쌓여 있고, 구성 파일, 문서, 비즈니스 로직, 테스트 케이스가 뒤섞여 있으면 보기만 해도 골치가 아픕니다. 수동으로 그룹화하고, 규격에 맞는 커밋 메시지를 작성하고, 브랜치를 전환하여 한 번 푸시하는 것—이러한 “마무리 작업”만으로도 반나절이 금방 지나갑니다.
사실 이런 상황에서 자연스럽게 요구 사항이 생깁니다. 한 번에 커밋되지 않은 변경 사항을 AI에 던져서, 스스로 분석, 그룹화, 메시지 작성, 심지어 직접 commit + push까지 할 수 있을까요?
아이디어는 좋지만, 실제로 구현하면 함정이 꽤 많습니다. AI는 --author만 수정하고 Committer는 수정하지 않기 쉬워서, 커밋 기록에서 작성자는 맞지만 커미터가 틀린 상태가 되어 보기에 어색할 수 있습니다. 또한, 귀하의 저장소 스타일과 전혀 맞지 않는 화려한 메시지를 자유롭게 작성할 수 있고, 주요 브랜치로 전환하여 엉망으로 만들 수도 있으며, 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 # 영어 메타데이터(매개변수 스키마, 버전, 태그)├── auto-compose-commit.zh-CN.hbs # 중국어 템플릿└── auto-compose-commit.zh-CN.json # 중국어 메타데이터즉, 하나의 프롬프트는 Handlebars 템플릿 하나 + JSON 메타데이터 하나의 조합이며, locale별로 여러 세트로 평면화되어 있습니다.
왜 이렇게 분리했을까요? 실제로 몇 가지 고려 사항이 있습니다.
첫째, 메타데이터와 프롬프트 본문의 분리입니다. JSON은 매개변수 스키마를 설명합니다—매개변수 이름, 유형, 필수 여부, 기본값이 무엇인지; .hbs는 “이 말을 어떻게 할 것인가”만 담당합니다. 이렇게 하면 프론트엔드는 템플릿 본문을 전혀 모르더라도 JSON을 기반으로 올바른 입력 양식을 자동으로 렌더링할 수 있습니다: Git ID 선택기, Co-Authored-By 모드, 대상 브랜치 전략, push 여부 등—이러한 컨트롤은 모두 JSON에서 구동됩니다.
둘째, 다국어 평면화, i18n 키를 통한 번역이 아닙니다. 각 locale에 완전한 .hbs + .json 세트가 있어 “번역 키 드리프트”를 방지합니다. 다른 언어는 단순히 단어를 대체하는 것이 아니라, 그룹화 예제, 명령 예제까지 현지화할 수 있습니다. 중국어와 영어 저장소의 커밋 습관은 본래 다르기 때문에, 하나의 템플릿에 강제로 넣고 번역하면 오히려 어색합니다.
셋째, Scriban에서 Handlebars로 마이그레이션은 성능을 위해서입니다. HandlebarsTemplateRenderer는 “templates directly to IL bytecode”를 컴파일할 수 있기 때문에 Handlebars.Net을 선택했으며, 해석 실행보다 훨씬 빠릅니다. 마이그레이션 과정에서 흥미로운 호환성 처리를 했습니다: 렌더링 결과의 True/False를 true/false로 대체하여 이전 Scriban의 불리언 출력 습관을 호환합니다—이러한 세부 사항을 주의하지 않으면 이전 테스트가 모두 실패합니다.
프롬프트는 다음과 같은 형태를 가지며, 그 뒤에는 5가지 핵심 결정이 있습니다
auto-compose-commit.zh-CN.hbs를 분해하면 대략적인 뼈대는 다음과 같습니다:
비대화형 모드 설명├── <task> 작업 정의: 변경 분석, 지능형 그룹화, 여러 커밋├── <context> 컨텍스트: projectPath + push 제어 + 대상 브랜치 제어├── <working_directory>├── <git_profile> ID: Author와 Committer 이중 작성├── <tools> 도구 화이트리스트├── <requirements> 필수 요구사항(브랜치, 그룹화, Co-Authored-By, Signed-off-by, Conventional Commits)├── <historical_format_analysis> 기록 일관성├── <constraints> 제약사항(reset 금지, .gitignore 무시)├── <workflow> 단계별 실행 프로세스├── <output_format> 엄격한 `---` 구분 출력└── <final_instruction>아래에서 설계 의도를 가장 잘 반영하는 5가지 포인트를 선택하여 확장해 보겠습니다.
결정 1: 직접 실행, 계획 생성만 하지 않음
프롬프트에서 한 문장을 반복해서 강조합니다: Git 명령을 직접 사용하여 각 커밋을 실행하고, 계획을 반환하지 않고 직접 조작합니다.
이것은 “Auto Compose Commit”이 초기 솔루션과 근본적으로 다른 점입니다. 초기의 ai-git-commit-message-generator(OpenSpec의 ai-commit-message-generation 사양에 해당)는 한 가지만 수행했습니다: POST /api/git/generate-commit-message를 호출하여 커밋 메시지 문자열을 반환하고, 나머지는 사용자가 직접 커밋합니다.
하지만 auto-compose-commit은 다릅니다. 이것은 Agent 자동 작업입니다. 모델은 직접 Bash(git:*) 도구를 호출하여 add → commit → push의 전체 체인을 완료해야 합니다. 이러한 차이점 때문에 전체 프롬프트의 기조가 결정됩니다—어떤 메시지를 작성해야 하는지뿐만 아니라, 어떤 프로세스로 조작하고, 어떤 도구를 사용하고, 오류가 발생했을 때 어떻게 해야 하는지도 규정해야 합니다.
결정 2: 왜 Git ID가 이렇게 길게 작성되어야 하는가
<git_profile>과 <requirements>에는 Author와 Committer에 대한 긴 설명이 있어 처음에는 중복처럼 보입니다:
- `--author="Name <email>"`은 Author만 수정합니다- `git -c user.name="Name" -c user.email="email" commit ...`은 이 명령의 Committer만 수정합니다- 생성된 각 커밋에 대해 Author와 Committer를 모두 선택한 ID로 설정해야 합니다- 선호하는 명령 형식: git -c user.name="..." -c user.email="..." commit --author="... <...>" ...이것은 실제로 경험에서 얻은 것입니다. Git 커밋에는 두 개의 ID 필드가 있으며, 모델은 --author만 수정하기 쉬워서 결과적으로 Committer는 전역 구성의 그 ID가 됩니다. 커밋 기록에서 “작성자는 맞지만 커미터가 틀린” 상태가 되면 보기에 어색합니다. 따라서 프롬프트는 직접 선호하는 명령 템플릿을 제공하고, 모델이 git log --format=fuller -1을 사용하여 자체 검사를 수행하도록 요구합니다.
비유하자면, 택배를 보낼 때 “보내는 사람”과 “실제 처리자”는 두 개의 다른 양식입니다. 한 양식에만 이름을 쓰고, 다른 양식에는 회사 이름이 인쇄되어 있다면—택배는 보내졌지만 기록이 일치하지 않아 결국 어색할 뿐입니다.
결정 3: 그룹화 결정 트리와 기록 일관성
모델이 가장 잘하는 것은 “자유 발휘”이지만, 커밋 그룹화에서 자유 발휘하는 것은 종종 재앙입니다. 따라서 프롬프트에는 명확한 결정 트리가 제공됩니다: 구성 파일은 별도 그룹, 문서는 별도 그룹, 동일 모듈의 코드 변경은 병합, 모듈 간 변경은 상황에 따라 결정. 또한 긍정적인 예제가 제공됩니다. 예를 들어 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 제안에 해당하며, 나중에 추가된 최적화입니다. 아무도 자신의 커밋 기록이 뒤섞인 상태가 되기를 원하지 않을 것입니다.
결정 4: Co-Authored-By 및 Signed-off-by의 조건부 렌더링
프롬프트에는 중첩된 {{#if}}이 많으며, 실행 매개변수에 따라 트레일러를 추가할지 여부를 결정합니다:
coAuthoredByIsNone일 때,Co-Authored-By를 전혀 추가하지 않습니다coAuthoredByIsCustom일 때, 사용자가 제공한 사용자 정의 트레일러를 사용합니다signedOffByEnabled와gitProfileName이 있을 때,Signed-off-by를 추가하고, ID가 누락된 경우 오류를 보고해야 하며 상상으로 만들어서는 안 됩니다
트레일러 영역은 서명 소속 및 규정 준수(DCO sign-off)와 관련되므로 사용자가 명시적으로 제어해야 하며, 모델이 임의로 결정하도록 해서는 안 됩니다. HagiCode는 이 영역에서 git-commit-coauthor-standardization, ai-commit-consent-management 등 일련의 제안을 점진적으로 구현하여 경계를 명확히 했습니다. 이러한 일은 엄격하게 해야하며, 모호해서는 안 됩니다.
결정 5: ---로 구분된 출력 계약
<output_format>은 각 반환에서 ---로 여러 커밋 블록을 구분해야 하며, 형식은 고정되어 있습니다:
---Commit 1: {hash}{message}---Commit 2: {hash}{message}---이것은 단순히 보기 좋게 하기 위한 것이 아닙니다. 모델은 한 작업에서 N개의 커밋을 생성할 수 있으며, 백엔드는 이 구분 기호를 사용하여 각 커밋의 해시와 메시지를 구문 분석하고 프론트엔드에 전달하여 표시해야 합니다. 출력 프로토콜이 느슨해지면 백엔드 구문 분석이 즉시 충돌합니다. 따라서 --- 규칙은 <output_format>과 <final_instruction>에서 두 번 강조됩니다—중요한 일은 세 번 말해야 하는 것입니다.
프롬프트가 어떻게 조립되고 전달되는가
템플릿만으로는 충분하지 않으며, 어떻게 실행되는지 알아야 합니다.
로드 및 렌더링
백엔드는 PCodeClaudeHelperModule에서 두 개의 싱글톤을 등록합니다:
// 프롬프트 로더 등록: 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 메타데이터에는 10여 개의 매개변수가 선언되어 있습니다: projectPath, needPush, targetBranchMode, gitProfileName, gitProfileEmail, signedOffByEnabled, coAuthoredBy* 등입니다. 이러한 매개변수는 프론트엔드의 “AI 커밋 서랍”에서 수집되고, AutoTask 채널을 통해 백엔드에 주입된 다음 FilePromptProvider에 의해 PromptScenario.AutoComposeCommit별로 이 템플릿 세트로 라우팅됩니다.
브랜치 전략의 3상태 처리
targetBranchMode는 모델이 커밋 전에 브랜치를 변경할지 여부를 결정하며, 3가지 상태가 있습니다:
| 모드 | 동작 |
|---|---|
current | 현재 위치에서 커밋, 브랜치 변경 없음 |
new-custom | 사용자가 제공한 targetBranchName을 사용하여 현재 브랜치에서 새 브랜치 생성 |
ai-generated-new | 모델이 변경 사항에 따라 kebab-case 브랜치 이름을 생성하고, 충돌 시 안정적인 접미사 추가 |
프롬프트에는 “다른 기존 브랜치로 전환하지 마십시오”라고 명시되어 있어 모델이 임의로 주요 브랜치로 전환하여 커밋하는 것을 방지합니다. 이 기능은 auto-branch-switch-on-commit 제안에 해당합니다. 주요 브랜치가 엉망이 되면 롤백하는 것도 골치 아픈 일입니다.
완전한 렌더링 예제
사용자가 프론트엔드에서 다음을 선택했다고 가정합니다: 현재 브랜치에 유지, push 필요, Signed-off-by 활성화, Co-Authored-By 비활성화, Git ID는 newbe <newbe@newbe.pro>입니다.
그러면 <git_profile> 섹션은 다음과 같이 렌더링됩니다:
<git_profile>생성된 모든 커밋에서 다음 Git ID를 사용합니다:- 선택된 이름: newbe- 선택된 이메일: newbe@newbe.pro...- 이번 실행에서는 Git 표준 sign-off 트레일러가 요청되므로 `git ... commit --author=... --signoff ...`를 우선적으로 사용합니다</git_profile><requirements>에는 Co-Authored-By disabled for this run 분기만 유지되고, <workflow>에서 제공하는 명령은 다음과 같이 됩니다:
# -c는 Committer를 동시에 설정하고, --author는 Author를 설정하며, --signoff는 DCO 트레일러를 추가합니다git -c user.name="newbe" -c user.email="newbe@newbe.pro" commit \ --author="newbe <newbe@newbe.pro>" --signoff -m "type(scope): subject"템플릿 유지 관리의 엔지니어링 실천
HagiCode는 이러한 .hbs 템플릿에 완전한 엔지니어링 보장을 제공하며, 작성하면 끝이 아닙니다.
첫째, 스냅샷 테스트입니다. 테스트 디렉터리에는 BuildMessage_enUS.verified.txt, BuildMessage_zhCN.verified.txt와 같은 검증된 스냅샷이 있으며, 템플릿의 모든 렌더링 차이는 테스트에서 포착됩니다. 한 글자만 변경해도 스냅샷을 업데이트해야 하며, 프롬프트가 은밀하게 드리프트하는 것을 방지합니다.
둘째, 포맷팅 스크립트입니다. cleanup-prompts.py --fix는 후행 공백을 정리하고 여러 빈 줄을 접으며, CI 검사가 실패하면 PR을 직접 차단합니다.
셋째, 매개변수 검증입니다. 각 시나리오의 필수 매개변수, 기본값, 유형은 전용 테스트로覆盖되며, 템플릿에서 {{newParam}}을 사용했지만 JSON에 선언하지 않으면 테스트가 실패합니다.
넷째, 스냅샷 계층화: Snapshots/Rendered/는 렌더링 결과를 저장하고 Snapshots/Scenarios/는 시나리오 메타데이터를 저장하여 템플릿, 메타데이터, 렌더링 결과의 세 가지가 일치하도록 합니다.
실용적인 함정 경고가 있습니다. 이 프롬프트에 새 매개변수나 새 분기를 추가하려면 네 가지 작업을 동기화해야 합니다:
- 템플릿(
.hbs)에서{{newParam}}을 사용합니다 - 메타데이터(
.json)의parameters배열에 스키마를 선언합니다 - 스냅샷 테스트에서 해당
.verified.txt를 업데이트합니다 - 프론트엔드 양식이 새 JSON 매개변수를 기반으로 입력 컨트롤을 생성하고 API를 통해 전달합니다
어떤 단계라도 누락되면 렌더링 시 매개변수가 비어 있거나, 스냅샷 테스트가 실패하거나, 프론트엔드에서 구성할 수 없게 됩니다. 이러한 “4곳 동기화” 제약은 번거롭지만 유지 관리성을 보장하기 위해서는 어쩔 수 없습니다.
왜 프롬프트가 이렇게 “길게” 작성되었는가
이 프롬프트를 다시 보면 ID, 트레일러, 출력 형식이 반복해서 강조되어 있어 비정상적으로 길다는 것을 알 수 있습니다. 이것은 의도적인 것입니다.
모델은 Agent 모드에서 특히 “임의로 결정”하기 쉽기 때문에, 강력한 제약 조건을 <requirements>, <workflow>, <final_instruction>의 여러 곳에 분산하여 반복해서 선언해야만 누락 실행 확률을 줄일 수 있습니다. 이것은 신입 직원을 지도하는 것과 같습니다—중요한 일은 세 번 말해야 합니다. 그것은 상대가 어리석어서가 아니라, 주의를 분산시키는 일이 너무 많기 때문입니다.
비대화형 모드(CI/CD, 자동화)에서는 모델이 사용자에게 질문할 수 없으므로, 프롬프트 시작 부분에 “AskUserQuestion 사용 금지, 누락된 정보는 기본값을 사용하고 가정을 기록하십시오”라고 명시하여 무인 실행도 가능하도록 합니다.
출력 계약이 느슨해지면 백엔드 구문 분석이 충돌하므로, --- 구분 규칙이 두 번 강조되었습니다. 중요한 일은 정말로 세 번 말해야 하는 것입니다.
참고 자료
- HagiCode 공식 웹사이트
- HagiCode GitHub 저장소
- Conventional Commits 사양: conventionalcommits.org
- Handlebars.Net: github.com/Handlebars-Net/Handlebars.Net
- Git DCO (Developer Certificate of Origin): developercertificate.org
요약
“HagiCode에서 AI 커밋에 사용되는 프롬프트: 설계 아이디어와 구현 분해”라는 주제로 돌아가서, 반복해서 확인해야 할 것은 분산된 기술이 아니라 제약 조건, 구현 경계 및 엔지니어링의 균형이 명확하게 이해되었는지입니다.
글의 판단 근거를 안정적인 검사 항목으로 정리하면, 나중에 유사한 문제에 직면했을 때 더 빨리 신뢰할 수 있는 결정을 내릴 수 있습니다.
开始使用 HagiCode
一次安装,几分钟上手
HagiCode for Windows 在 Microsoft Store 免费提供。打开商店即可安装并保持更新;也可以先对比各版本与定价,再决定从哪个渠道开始。