Zum Inhalt springen

In HagiCode verwendete Prompts für AI-Commits: Designansätze und Implementierungsdetails

Seite bearbeiten
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

In HagiCode verwendete Prompts für AI-Commits: Designansätze und Implementierungsdetails

Wenn du eine chaotische Menge von Änderungen an die KI gibst und sie sie für dich committen lässt, welcher Prompt wird eigentlich an das Modell gesendet? Warum ist der Prompt so geschrieben, wie er ist? Dieser Artikel zerlegt den Prompt, der “AI-Commits” in HagiCode wirklich antreibt.

Hintergrund

Die Verwendung von KI zur Unterstützung der Entwicklung ist nach einem ganzen Tag des Programmierens ziemlich ermüdend. Eine Sammlung uncommitteter Änderungen – Konfigurationsdateien, Dokumentation, Geschäftslogik, Testfälle – alles wild vermischt, ist schon beim bloßen Ansehen stressig. Manuelles Gruppieren, Schreiben von Commit-Nachrichten, die den Konventionen entsprechen, Wechseln von Branches und Pushen – nur diese “Abschlussarbeiten” kosten eine halbe Stunde.

Natürlich entstand daraus die Forderung: Kann man alle uncommitteten Änderungen auf einmal an die KI geben, damit sie sie analysiert, gruppiert, Nachrichten schreibt und sogar direkt committet und pusht?

Die Idee ist gut, aber bei der Umsetzung gibt es viele Fallstricke. Die KI ändert oft nur --author ohne Committer zu ändern; in der Commit-Historie ist der Autor korrekt, der Committer aber falsch – das wirkt gespalten. Sie könnte sich bei Commit-Nachrichten kreativ auslassen, die überhaupt nicht zu deinem Repository-Stil passen. Sie könnte eigenständig auf den Hauptbranch wechseln und Chaos anrichten. Sie könnte Co-Authored-By vergessen oder willkürlich Signed-off-by hinzufügen und Compliance-Probleme auslösen.

Jeder dieser Fallstricke war eine Lehre. Um diese Schmerzpunkte zu beheben, haben wir “AI-Commits” zu einer parametrisierten Agenten-Aufgabe gemacht. Wie diese Aussage aussieht und warum sie so entworfen ist, ist das, worüber dieser Artikel sprechen möchte.

Über HagiCode

Die in diesem Artikel vorgestellte Lösung stammt aus unserer Arbeit am HagiCode Projekt. HagiCode ist ein KI-Code-Assistent für Entwickler-Workflows, der Git-Commits, Code-Reviews, Build- und Release-Prozesse zu KI-beteiligten Aufgaben macht. Das unten zerlegte Prompt-System ist genau das, was im HagiCode-Backend tatsächlich läuft. Am Ende wollen wir nur diese lästigen “Abschlussarbeiten” an die KI übergeben.

Die wahre Form von Prompts: Templates plus Metadaten, kein starrer String

Viele Leute denken, “Prompts” sind einfach ein Stück starre natürliche Sprache, die man an das Modell gibt. HagiCode macht das aber völlig anders.

Der Prompt, der “AI-Commits” wirklich antreibt, heißt auto-compose-commit, entspricht PromptScenario.AutoComposeCommit im Code. Er befindet sich unter repos/hagicode-core/src/PCode.Web/Resources/Prompts/ und hat diese Struktur:

Resources/Prompts/
├── auto-compose-commit.en-US.hbs # Englisches Handlebars-Template
├── auto-compose-commit.en-US.json # Englische Metadaten (Parameter-Schema, Version, Tags)
├── auto-compose-commit.zh-CN.hbs # Chinesisches Template
└── auto-compose-commit.zh-CN.json # Chinesische Metadaten

Mit anderen Worten: Ein Prompt ist eine Kombination aus einem Handlebars-Template + einem JSON-Metadaten-Objekt, flach nach Locale in mehrere Sets unterteilt.

Warum diese Aufteilung? Es gibt mehrere Überlegungen dahinter.

Erstens: Entkopplung von Metadaten und Prompt-Inhalt. Das JSON beschreibt das Parameter-Schema – Parametername, Typ, ob erforderlich, Standardwert; .hbs kümmert sich nur um “wie das ausgedrückt wird”. Das Frontend kann basierend auf dem JSON automatisch das richtige Eingabeformular rendern, ohne den Template-Inhalt zu kennen: Git-Identitätsauswahl, Co-Authored-By-Modus, Target-Branch-Strategie, ob gepusht wird… diese Steuerelemente sind alle JSON-gesteuert.

Zweitens: Sprachen werden flach aufgelistet, nicht mit i18n-Keys übersetzt. Jedes Locale hat ein vollständiges Set .hbs + .json, was ein “Driften von Übersetzungs-Keys” vermeidet. Verschiedene Sprachen ersetzen nicht nur Wörter, sondern sogar Gruppierungsbeispiele und Befehlsbeispiele können lokalisiert werden. Chinesische und englische Repositorys haben unterschiedliche Commit-Gewohnheiten – sie in ein Template zu pressen und zu übersetzen wirkt einfach unpassend.

Drittens: Migration von Scriban zu Handlebars war für die Leistung. HandlebarsTemplateRenderer verwendet Handlebars.Net, weil es “Templates direkt zu IL-Bytecode kompilieren” kann, viel schneller als interpretierte Ausführung. Während der Migration wurde auch eine interessante Kompatibilitätsbehandlung vorgenommen: True/False im Rendering-Ergebnis wurde durch true/false ersetzt, um die alte Scriban-Bool-Ausgabe zu kompatibilisieren – dieses Detail nicht zu beachten, würde alte Tests rot machen.

Der Prompt sieht so aus: fünf Schlüsselentscheidungen dahinter

Wenn man auto-compose-commit.zh-CN.hbs auseinandernimmt, sieht das Skelett grob so aus:

Nicht-interaktiver Modus Erklärung
├── <task> Aufgaben Definition: Änderungen analysieren, intelligent gruppieren, Multi-Commit
├── <context> Kontext: projectPath + Push-Steuerung + Target-Branch-Steuerung
├── <working_directory>
├── <git_profile> Identität: Author und Committer beide schreiben
├── <tools> Whitelist der Tools
├── <requirements> Harte Anforderungen (Branch, Gruppierung, Co-Authored-By, Signed-off-by, Conventional Commits)
├── <historical_format_analysis> Historische Konsistenz
├── <constraints> Einschränkungen (kein Reset, .gitignore ignorieren)
├── <workflow> Schrittweise Ausführung
├── <output_format> Strenge `---` getrennte Ausgabe
└── <final_instruction>

Hier sind fünf Punkte, die die Designabsicht am besten widerspiegeln.

Entscheidung 1: Direkte Ausführung statt nur Planung

Der Prompt betont immer wieder einen Satz: Verwende Git-Befehle direkt für jeden Commit, gib keinen Plan zurück, operiere direkt.

Das ist der grundlegende Unterschied zwischen “Auto Compose Commit” und früheren Lösungen. Der frühere ai-git-commit-message-generator (entspricht der ai-commit-message-generation-Spezifikation in OpenSpec) tat nur eine Sache: Rufe POST /api/git/generate-commit-message, gib eine Commit-Nachricht zurück, und der Rest muss vom Nutzer manuell committed werden.

Aber auto-compose-commit ist anders: es ist eine Agenten-Automatik-Aufgabe. Das Modell muss selbst das Bash(git:*)-Tool aufrufen und die gesamte Kette add → commit → push ausführen. Dieser Unterschied bestimmt den gesamten Tonfall des Prompts – er kann nicht nur beschreiben “wie die Nachricht aussehen soll”, sondern muss auch festlegen “nach welchem Prozess zu operieren ist, welche Tools zu verwenden, wie bei Fehlern zu verfahren ist”.

Entscheidung 2: Warum die Git-Identität so ausführlich ist

<git_profile> und <requirements> enthalten eine lange Erklärung zu Author und Committer, die auf den ersten Blick redundant wirkt:

- `--author="Name <email>"` ändert nur den Author
- `git -c user.name="Name" -c user.email="email" commit ...` ändert nur den Committer dieses einen Befehls
- Für jeden generierten Commit musst du sowohl Author als auch Committer auf die ausgewählte Identität setzen
- Bevorzugte Befehlsform:
git -c user.name="..." -c user.email="..." commit --author="... <...>" ...

Das ist aus echten Fehlern entstanden. In einem Git-Commit gibt es zwei Identitätsfelder; das Modell ändert oft nur --author, und der Committer bleibt die globale Konfiguration. In der Commit-Historie ist “der Autor ist richtig, der Committer falsch” – das wirkt gespalten. Deshalb gibt der Prompt die bevorzugte Befehlsform direkt vor und verlangt, dass das Modell eine Selbstprüfung mit git log --format=fuller -1 macht.

Ein Vergleich: Das ist wie beim Paketversand – “Absender” und “tatsächlicher Bearbeiter” sind zwei verschiedene Etiketten. Wenn du nur auf einem deinen Namen schreibst und auf dem anderen noch der Firmenname steht, wird das Paket zwar versendet, aber die Aufzeichnung stimmt nicht überein – das ist eben unpassend.

Entscheidung 3: Entscheidungsbaum für Gruppierung plus historische Konsistenz

Das Modell ist am besten im “freien Improvisieren”, aber freies Improvisieren bei Commit-Gruppierung ist oft eine Katastrophe. Deshalb gibt der Prompt einen klaren Entscheidungsbaum: Konfigurationsdateien in eine eigene Gruppe, Dokumentation in eine eigene Gruppe, Code-Änderungen desselben Modells zusammenführen, Cross-Modul-Änderungen je nach Situation. Es gibt auch positive Beispiele, wie src/auth/login.ts plus auth.service.ts in denselben Commit sollten.

Noch wichtiger ist der Abschnitt <historical_format_analysis>. Er verlangt vom Modell:

  1. Verwende git log -n 15 --pretty=format:"%H|%s|%b%n---%n" um die letzten Commit-Historien zu erhalten
  2. Analysiere Strukturmuster, Sprachmuster, häufige Typen, spezielle Formate
  3. Generiere Commit-Nachrichten, die den erkannten Mustern folgen

Das heißt, das Modell kann nicht schreiben, wie es will – es muss sich zuerst an den bereits existierenden Stil des Ziel-Repositorys anpassen. HagiCode Mono Hauptrepo verwendet Englisch + Conventional Commits, manche Sub-Repos verwenden chinesische Absatzform, die KI muss sich den lokalen Gewohnheiten anpassen. Diese Fähigkeit entspricht dem Archiv-Proposal 2026-02-23-auto-commit-compose-history-consistency-optimization, eine spätere Ergänzung. Schließlich will niemand, dass seine Commit-Historie wie ein Eintopf aussieht.

Entscheidung 4: Bedingtes Rendern von Co-Authored-By und Signed-off-by

Der Prompt enthält viele verschachtelte {{#if}}, die basierend auf Laufzeitparametern entscheiden, ob ein Trailer hinzugefügt wird:

  • Wenn coAuthoredByIsNone, wird Co-Authored-By überhaupt nicht hinzugefügt
  • Wenn coAuthoredByIsCustom, wird der vom Nutzer gegebene benutzerdefinierte Trailer verwendet
  • Wenn signedOffByEnabled plus gitProfileName, wird Signed-off-by hinzugefügt; bei fehlender Identität muss ein Fehler gemeldet werden, nicht einen erfinden

Die Trailer betreffen Urheberrecht und Compliance (DCO sign-off) und müssen explizit vom Nutzer gesteuert werden, nicht eigenmächtig vom Modell. HagiCode hat hier mehrere Proposals wie git-commit-coauthor-standardization, ai-commit-consent-management etc. implementiert, um die Grenzen klar zu ziehen. Bei solchen Dingen ist es besser, etwas strenger zu sein, anstatt unklar zu bleiben.

Entscheidung 5: Das Ausgabe-Vertrag mit --- Trennung

<output_format> schreibt vor, dass jede Rückgabe mit --- mehrere Commit-Blöcke trennen muss, das Format ist festgelegt:

---
Commit 1: {hash}
{message}
---
Commit 2: {hash}
{message}
---

Das ist nicht nur zur Schönheit. Ein Modell kann in einer Aufgabe N Commits erzeugen, das Backend muss diese Trennung verwenden, um den Hash und die Nachricht jedes Commits zu parsen und an das Frontend zurückzugeben. Sobald das Ausgabe-Protokoll locker ist, bricht das Backend-Parsing sofort. Deshalb wurde die ----Regel in <output_format> und <final_instruction> zweimal betont – Wichtige Dinge soll man eben dreimal sagen.

Wie der Prompt zusammengestellt und übergeben wird

Nur das Template zu sehen reicht nicht; man muss wissen, wie es läuft.

Laden und Rendern

Das Backend registriert zwei Singletons in PCodeClaudeHelperModule:

// Registriert Prompt-Loader: findet entsprechendes .json und .hbs nach scenario + locale
context.Services.AddSingleton<IPromptLoader, FilePromptLoaderV2>();
// Registriert Handlebars-Renderer: kompiliert Templates zu IL und cacht
context.Services.AddSingleton<HandlebarsTemplateRenderer>(...);

FilePromptLoaderV2 erhält den Template-Inhalt und gibt ihn an HandlebarsTemplateRenderer.Render(template, parameters) weiter. Die Kernlogik des Renderers sieht etwa so aus:

public string Render(string template, IDictionary<string, object> parameters)
{
// Cache nach SHA256 des Template-Inhalts, vermeidet Neukompilierung bei jedem Commit
var compiledTemplate = GetOrCompileTemplate(template);
var rendered = compiledTemplate(parameters ?? new Dictionary<string, object>());
// Kompatibilität mit alter Scriban-Bool-Ausgabe
rendered = rendered.Replace("True", "true").Replace("False", "false");
return rendered;
}

Kompilierungsergebnisse werden nach Inhalts-Hash gecacht, das ist für die Leistung entscheidend. Commits können hochfrequent ausgelöst werden; jedes Mal IL neu zu kompilieren, würde niemand aushalten.

Woher kommen die Parameter

Die JSON-Metadaten erklären etwa zehn Parameter: projectPath, needPush, targetBranchMode, gitProfileName, gitProfileEmail, signedOffByEnabled, coAuthoredBy* usw. Diese Parameter werden vom Frontend “AI-Commit-Schublade” gesammelt, über den AutoTask-Kanal an das Backend injiziert, und dann von FilePromptProvider nach PromptScenario.AutoComposeCommit zu diesem Template geroutet.

Dreistufige Branch-Strategie

targetBranchMode entscheidet, ob das Modell vor dem Commit den Branch ändert, ein Dreistufiger:

ModusVerhalten
currentCommit vor Ort, Branch nicht ändern
new-customVerwende targetBranchName vom Nutzer, erstelle neuen Branch vom aktuellen
ai-generated-newModell generiert selbst kebab-case Branch-Name basierend auf Änderungen, bei Konflikt stabilen Suffix anhängen

Der Prompt schreibt explizit “wechsle nicht zu einem anderen existierenden Branch”, um zu verhindern, dass das Modell eigenmächtig auf den Hauptbranch wechselt und dort committet. Diese Fähigkeit entspricht dem Proposal auto-branch-switch-on-commit. Sobald der Hauptbranch durcheinandergerät, ist das Zurücksetzen auch ein Chaos.

Ein vollständiges Rendering-Beispiel

Angenommen, der Nutzer wählt im Frontend: auf dem aktuellen Branch bleiben, Push erforderlich, Signed-off-by aktiviert, Co-Authored-By deaktiviert, Git-Identität ist newbe <newbe@newbe.pro>.

Dann wird der Abschnitt <git_profile> gerendert als:

<git_profile>
Verwende die folgende Git-Identität in allen generierten Commits:
- Ausgewählter Name: newbe
- Ausgewählte E-Mail: newbe@newbe.pro
...
- Dieser Lauf verlangt auch Git-Standard sign-off-Trailer, daher bevorzuge `git ... commit --author=... --signoff ...`
</git_profile>

In <requirements> bleibt nur der Zweig “Co-Authored-By disabled for this run”, und <workflow> gibt den Befehl:

Terminal window
# Beachte -c setzt gleichzeitig Committer, --author setzt Author, --signoff fügt DCO-Trailer
git -c user.name="newbe" -c user.email="newbe@newbe.pro" commit \
--author="newbe <newbe@newbe.pro>" --signoff -m "type(scope): subject"

Ingenieur-Praktiken zur Template-Wartung

HagiCode hat für dieses Set von .hbs-Templates eine ganze Reihe von Engineering-Garantien, nicht nur “fertig geschrieben”.

Erstens: Snapshot-Tests. Im Testverzeichnis gibt es verifizierte Snapshots wie BuildMessage_enUS.verified.txt, BuildMessage_zhCN.verified.txt; jede Rendering-Differenz im Template wird vom Test erkannt. Änderst du ein Wort, musst du den Snapshot aktualisieren, um ein Driften des Prompts zu verhindern.

Zweitens: Formatierungsscript. cleanup-prompts.py --fix bereinigt trailing whitespace, faltet überflüssige Leerzeilen; CI-Prüfung, die nicht bestanden wird, blockiert PRs direkt.

Drittens: Parameter-Validierung. Erforderliche Parameter, Standardwerte und Typen jedes Szenarios sind mit speziellen Tests abgedeckt; wenn das Template {{newParam}} verwendet, aber JSON nicht deklariert, wird der Test rot.

Viertens: Snapshot-Layering: Snapshots/Rendered/ speichert Rendering-Ergebnisse, Snapshots/Scenarios/ speichert Szenario-Metadaten; Konsistenz von Template, Metadaten und Rendering-Ergebnissen ist garantiert.

Hier ist eine ziemlich praktische Warnung aus Fehlern. Wenn du diesem Prompt neue Parameter oder neue Zweige hinzufügen willst, müssen vier Dinge synchronisiert werden:

  1. Template (.hbs) verwendet {{newParam}}
  2. Metadaten (.json) deklarieren Schema im parameters-Array
  3. Snapshot-Test aktualisiert entsprechendes .verified.txt
  4. Frontend-Formular generiert Eingabe-Steuerelemente basierend auf neuen JSON-Parametern und gibt sie über API weiter

Lässt du einen Schritt aus, ist der Parameter beim Rendern leer, oder der Snapshot-Test wird rot, oder das Frontend kann nicht konfigurieren. Diese “viermal synchronisierte” Einschränkung wirkt zwar lästig, aber zur Wartbarkeit ist es eben so.

Warum der Prompt so “ausführlich” ist

Wenn man diesen Prompt betrachtet, stellt man fest, dass er ungewöhnlich lang ist – Identität, Trailer, Ausgabeformat werden immer wieder betont. Das ist absichtlich.

Im Agenten-Modus ist das Modell besonders anfällig für “eigenmächtiges Handeln”, und harte Einschränkungen müssen über <requirements>, <workflow>, <final_instruction> mehrfach verteilt betont werden, um die Wahrscheinlichkeit von Fehlausführungen zu senken. Das ist wie beim Einweisen von Neulingen – Wichtige Dinge dreimal zu sagen ist nicht, weil der andere dumm ist, sondern weil zu viele Dinge die Aufmerksamkeit ablenken.

Im nicht-interaktiven Modus (CI/CD, Automatisierung) kann das Modell den Nutzer nicht fragen; deshalb steht am Prompt-Anfang “verbiete AskUserQuestion, fehlende Informationen mit Standardwerten und Annahmen protokollieren”, um einen unüberwachten Lauf zu ermöglichen.

Sobald das Ausgabe-Vertrag locker wird, bricht das Backend-Parsing, deshalb wurde die ----Trennungsregel zweimal betont. Wichtige Dinge soll man wirklich dreimal sagen.

Referenzen

Zusammenfassung

Zurück zum Thema “In HagiCode verwendete Prompts für AI-Commits: Designansätze und Implementierungsdetails” – was es wirklich immer wieder zu bestätigen gilt, sind nicht die verstreuten Tipps, sondern ob die Einschränkungen, Implementierungsgrenzen und Engineering-Entscheidungen bereits klar sind.

Sobald man die Urteilsgrundlagen aus diesem Artikel zu stabilen Prüfpunkten verfestigt, kann man bei ähnlichen Problemen schneller zuverlässige Entscheidungen treffen.

开始使用 HagiCode

一次安装,几分钟上手

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