feat(cli,vscode): add custom commit message prompt setting (#8974)

* feat(cli,vscode): add custom commit message prompt setting

Support user-configurable system prompt for AI commit message generation.
Adds a dedicated Commit Message tab in VS Code settings with a toggle
to override the built-in conventional commits prompt.

* fix: add missing kilocode_change annotation and fix i18n formatting

* refactor: move Config reading to route handler, add textarea max-height

Move kilo-specific Config.get() from shared generate.ts to the route
handler. The generator now accepts an optional prompt param instead.
Cap textarea height at 300px with scroll for long prompts.

* fix: resolve annotation gaps and prompt clear serialization bug

Add kilocode_change markers to JSDoc comment and extend block to
cover the generateCommitMessage call. Use empty string instead of
undefined when clearing the prompt so JSON serialization persists
the removal to disk.

* refactor(cli): move commit-message module to kilocode paths

Move src/commit-message/ to src/kilocode/commit-message/, route to
src/kilocode/server/routes/, and tests to test/kilocode/commit-message/.
Extract CommitMessageSchema to KilocodeConfig and reference it from
the main Config.Info. Removes all kilocode_change markers from moved
files since they are now in exempt paths.

* test(cli): fix moved commit-message tests
This commit is contained in:
Marius
2026-04-15 16:09:05 +02:00
committed by GitHub
parent e8fd495708
commit 581bbbb319
36 changed files with 386 additions and 67 deletions
@@ -0,0 +1,60 @@
import { Component, Show, createSignal } from "solid-js"
import { Switch } from "@kilocode/kilo-ui/switch"
import { TextField } from "@kilocode/kilo-ui/text-field"
import { Card } from "@kilocode/kilo-ui/card"
import { useConfig } from "../../context/config"
import { useLanguage } from "../../context/language"
import SettingsRow from "./SettingsRow"
const CommitMessageTab: Component = () => {
const { config, updateConfig } = useConfig()
const language = useLanguage()
const [expanded, setExpanded] = createSignal(Boolean(config().commit_message?.prompt))
const toggle = (checked: boolean) => {
setExpanded(checked)
if (!checked) {
updateConfig({ commit_message: { prompt: "" } })
}
}
return (
<div>
<Card>
<SettingsRow
title={language.t("settings.commitMessage.override.title")}
description={language.t("settings.commitMessage.override.description")}
last={!expanded()}
>
<Switch checked={expanded()} onChange={toggle} hideLabel>
{language.t("settings.commitMessage.override.title")}
</Switch>
</SettingsRow>
<Show when={expanded()}>
<div style={{ "padding-top": "8px" }}>
<div data-slot="settings-row-label-title" style={{ "margin-bottom": "4px" }}>
{language.t("settings.commitMessage.prompt.title")}
</div>
<div data-slot="settings-row-label-subtitle" style={{ "margin-bottom": "8px" }}>
{language.t("settings.commitMessage.prompt.description")}
</div>
<div style={{ "max-height": "300px", overflow: "auto" }}>
<TextField
value={config().commit_message?.prompt ?? ""}
placeholder={language.t("settings.commitMessage.prompt.placeholder")}
multiline
onChange={(val) => {
updateConfig({ commit_message: { prompt: val } })
}}
/>
</div>
</div>
</Show>
</Card>
</div>
)
}
export default CommitMessageTab
@@ -18,6 +18,7 @@ import AutocompleteTab from "./AutocompleteTab"
import NotificationsTab from "./NotificationsTab"
import ContextTab from "./ContextTab"
import CommitMessageTab from "./CommitMessageTab"
import ExperimentalTab from "./ExperimentalTab"
import LanguageTab from "./LanguageTab"
import AboutKiloCodeTab from "./AboutKiloCodeTab"
@@ -138,6 +139,10 @@ const Settings: Component<SettingsProps> = (props) => {
<span class="label">{language.t("settings.context.title")}</span>
</Tabs.Trigger>
<Tabs.Trigger value="commitMessage">
<Icon name="edit" />
<span class="label">{language.t("settings.commitMessage.title")}</span>
</Tabs.Trigger>
<Tabs.Trigger value="experimental">
<Icon name="settings-gear" />
<span class="label">{language.t("settings.experimental.title")}</span>
@@ -193,6 +198,10 @@ const Settings: Component<SettingsProps> = (props) => {
<ContextTab />
</Tabs.Content>
<Tabs.Content value="commitMessage">
<h3>{language.t("settings.commitMessage.title")}</h3>
<CommitMessageTab />
</Tabs.Content>
<Tabs.Content value="experimental">
<h3>{language.t("settings.experimental.title")}</h3>
<ExperimentalTab />
@@ -29,6 +29,7 @@ export const KNOWN_KEYS: ReadonlyArray<string> = [
"formatter",
"lsp",
"compaction",
"commit_message",
"tools",
"layout",
"experimental",
+11
View File
@@ -1249,6 +1249,17 @@ export const dict = {
"settings.context.prune.description": "إزالة مخرجات الأدوات القديمة أثناء الضغط",
"settings.context.watcherPatterns": "أنماط تجاهل مراقب الملفات",
"settings.context.watcherPatterns.description": "أنماط glob للملفات التي يجب على المراقب تجاهلها",
"settings.commitMessage.title": "Commit Message",
"settings.commitMessage.override.title": "استخدام prompt مخصص",
"settings.commitMessage.override.description":
"تجاوز prompt الـ commit message الافتراضي. عند التفعيل، سيستبدل الـ prompt المخصص الخاص بك الـ prompt المدمج لـ conventional commits بالكامل.",
"settings.commitMessage.prompt.title": "prompt مخصص",
"settings.commitMessage.prompt.description":
"الـ prompt النظامي المرسل إلى الذكاء الاصطناعي عند إنشاء commit messages. هذا يستبدل الـ prompt الافتراضي بالكامل.",
"settings.commitMessage.prompt.placeholder":
"على سبيل المثال: قم بإنشاء commit messages باللغة الإسبانية باتباع تنسيق conventional commits. أرجع الـ commit message فقط.",
"settings.display.username.title": "اسم المستخدم",
"settings.display.username.description": "اسم مستخدم مخصص في المحادثات",
"settings.display.layout.title": "التخطيط",
+11
View File
@@ -1278,6 +1278,17 @@ export const dict = {
"settings.context.prune.description": "Remover saídas antigas de ferramentas durante a compactação",
"settings.context.watcherPatterns": "Padrões de ignorar do observador",
"settings.context.watcherPatterns.description": "Padrões glob para arquivos que o observador deve ignorar",
"settings.commitMessage.title": "Commit Message",
"settings.commitMessage.override.title": "Usar prompt personalizado",
"settings.commitMessage.override.description":
"Substituir o prompt padrão de commit message. Quando ativado, o seu prompt personalizado substitui totalmente o prompt integrado de conventional commits.",
"settings.commitMessage.prompt.title": "prompt personalizado",
"settings.commitMessage.prompt.description":
"O prompt de sistema enviado à IA ao gerar commit messages. Isso substitui totalmente o prompt padrão.",
"settings.commitMessage.prompt.placeholder":
"ex: Gere commit messages em espanhol seguindo o formato conventional commits. Retorne APENAS o commit message.",
"settings.display.username.title": "Nome de usuário",
"settings.display.username.description": "Nome de usuário personalizado nas conversas",
"settings.display.layout.title": "Layout",
+11
View File
@@ -1274,6 +1274,17 @@ export const dict = {
"settings.context.prune.description": "Ukloni stare izlaze alata tokom kompresije",
"settings.context.watcherPatterns": "Uzorci ignoriranja za promatrač datoteka",
"settings.context.watcherPatterns.description": "Glob uzorci za datoteke koje promatrač treba ignorirati",
"settings.commitMessage.title": "Commit Message",
"settings.commitMessage.override.title": "Koristi prilagođeni prompt",
"settings.commitMessage.override.description":
"Nadjačaj podrazumijevani prompt za commit message. Kada je omogućeno, vaš prilagođeni prompt u potpunosti zamjenjuje ugrađeni prompt za conventional commits.",
"settings.commitMessage.prompt.title": "Prilagođeni prompt",
"settings.commitMessage.prompt.description":
"Sistemski prompt koji se šalje AI-u prilikom generisanja commit messages. Ovo u potpunosti zamjenjuje podrazumijevani prompt.",
"settings.commitMessage.prompt.placeholder":
"npr. Generiši commit messages na španskom jeziku prateći conventional commits format. Vrati SAMO commit message.",
"settings.display.username.title": "Korisničko ime",
"settings.display.username.description": "Prilagođeno korisničko ime u razgovorima",
"settings.display.layout.title": "Raspored",
+11
View File
@@ -1264,6 +1264,17 @@ export const dict = {
"settings.context.prune.description": "Fjern gamle værktøjsoutput under komprimering",
"settings.context.watcherPatterns": "Filvagt-ignormønstre",
"settings.context.watcherPatterns.description": "Glob-mønstre for filer, som vagten skal ignorere",
"settings.commitMessage.title": "Commit Message",
"settings.commitMessage.override.title": "Brug brugerdefineret prompt",
"settings.commitMessage.override.description":
"Tilsidesæt standard prompt for commit message. Når dette er aktiveret, erstatter din brugerdefinerede prompt fuldstændigt den indbyggede prompt for conventional commits.",
"settings.commitMessage.prompt.title": "Brugerdefineret prompt",
"settings.commitMessage.prompt.description":
"System prompt sendt til AI'en ved generering af commit messages. Dette erstatter fuldstændigt standard prompt.",
"settings.commitMessage.prompt.placeholder":
"f.eks. Generer commit messages på spansk efter conventional commits formatet. Returner KUN commit message.",
"settings.display.username.title": "Brugernavn",
"settings.display.username.description": "Brugerdefineret brugernavn i samtaler",
"settings.display.layout.title": "Layout",
+11
View File
@@ -1290,6 +1290,17 @@ export const dict = {
"settings.context.prune.description": "Alte Werkzeugausgaben während der Komprimierung entfernen",
"settings.context.watcherPatterns": "Datei-Watcher-Ignorierungsmuster",
"settings.context.watcherPatterns.description": "Glob-Muster für Dateien, die der Watcher ignorieren soll",
"settings.commitMessage.title": "Commit Message",
"settings.commitMessage.override.title": "Benutzerdefinierten prompt verwenden",
"settings.commitMessage.override.description":
"Den Standard-prompt für die commit message überschreiben. Wenn diese Option aktiviert ist, ersetzt Ihr benutzerdefinierter prompt den integrierten prompt für conventional commits vollständig.",
"settings.commitMessage.prompt.title": "Benutzerdefinierter prompt",
"settings.commitMessage.prompt.description":
"System-prompt, der beim Generieren von commit messages an die KI gesendet wird. Dies ersetzt den Standard-prompt vollständig.",
"settings.commitMessage.prompt.placeholder":
"z. B. Generiere commit messages auf Spanisch nach dem conventional commits Format. Gib NUR die commit message zurück.",
"settings.display.username.title": "Benutzername",
"settings.display.username.description": "Benutzerdefinierter Benutzername in Gesprächen",
"settings.display.layout.title": "Layout",
@@ -1266,6 +1266,16 @@ export const dict = {
"settings.context.watcherPatterns": "File Watcher Ignore Patterns",
"settings.context.watcherPatterns.description": "Glob patterns for files the watcher should ignore",
"settings.commitMessage.title": "Commit Message",
"settings.commitMessage.override.title": "Use Custom Prompt",
"settings.commitMessage.override.description":
"Override the default commit message prompt. When enabled, your custom prompt fully replaces the built-in conventional commits prompt.",
"settings.commitMessage.prompt.title": "Custom Prompt",
"settings.commitMessage.prompt.description":
"System prompt sent to the AI when generating commit messages. This replaces the default prompt entirely.",
"settings.commitMessage.prompt.placeholder":
"e.g. Generate commit messages in Spanish following conventional commits format. Return ONLY the commit message.",
"settings.display.username.title": "Username",
"settings.display.username.description": "Custom username displayed in conversations",
"settings.display.layout.title": "Layout",
+11
View File
@@ -1282,6 +1282,17 @@ export const dict = {
"settings.context.prune.description": "Eliminar salidas de herramientas antiguas durante la compactación",
"settings.context.watcherPatterns": "Patrones de ignorar del observador",
"settings.context.watcherPatterns.description": "Patrones glob para archivos que el observador debe ignorar",
"settings.commitMessage.title": "Commit Message",
"settings.commitMessage.override.title": "Usar prompt personalizado",
"settings.commitMessage.override.description":
"Sobrescribir el prompt por defecto para el commit message. Cuando está habilitado, tu prompt personalizado reemplaza completamente el prompt integrado para conventional commits.",
"settings.commitMessage.prompt.title": "prompt personalizado",
"settings.commitMessage.prompt.description":
"prompt del sistema enviado a la IA al generar commit messages. Esto reemplaza el prompt por defecto por completo.",
"settings.commitMessage.prompt.placeholder":
"ej. Genera commit messages en español siguiendo el formato conventional commits. Devuelve SOLO el commit message.",
"settings.display.username.title": "Nombre de usuario",
"settings.display.username.description": "Nombre de usuario personalizado en conversaciones",
"settings.display.layout.title": "Diseño",
+11
View File
@@ -1295,6 +1295,17 @@ export const dict = {
"settings.context.prune.description": "Supprimer les anciennes sorties d'outils pendant la compaction",
"settings.context.watcherPatterns": "Motifs d'ignorance de l'observateur",
"settings.context.watcherPatterns.description": "Motifs glob pour les fichiers que l'observateur doit ignorer",
"settings.commitMessage.title": "Commit Message",
"settings.commitMessage.override.title": "Utiliser un prompt personnalisé",
"settings.commitMessage.override.description":
"Remplacer le prompt par défaut pour le commit message. Lorsque cette option est activée, votre prompt personnalisé remplace entièrement le prompt intégré pour les conventional commits.",
"settings.commitMessage.prompt.title": "prompt personnalisé",
"settings.commitMessage.prompt.description":
"prompt système envoyé à l'IA lors de la génération des commit messages. Cela remplace entièrement le prompt par défaut.",
"settings.commitMessage.prompt.placeholder":
"par ex. Générer des commit messages en espagnol en suivant le format conventional commits. Retourner UNIQUEMENT le commit message.",
"settings.display.username.title": "Nom d'utilisateur",
"settings.display.username.description": "Nom d'utilisateur personnalisé dans les conversations",
"settings.display.layout.title": "Disposition",
+11
View File
@@ -1265,6 +1265,17 @@ export const dict = {
"settings.context.prune.description": "圧縮時に古いツール出力を削除",
"settings.context.watcherPatterns": "ファイルウォッチャー無視パターン",
"settings.context.watcherPatterns.description": "ウォッチャーが無視すべきファイルのglobパターン",
"settings.commitMessage.title": "Commit Message",
"settings.commitMessage.override.title": "カスタム prompt を使用",
"settings.commitMessage.override.description":
"デフォルトの commit message の prompt を上書きします。有効にすると、カスタム prompt が組み込みの conventional commits の prompt を完全に置き換えます。",
"settings.commitMessage.prompt.title": "カスタム prompt",
"settings.commitMessage.prompt.description":
"commit messages を生成する際に AI に送信されるシステム prompt。これはデフォルトの prompt を完全に置き換えます。",
"settings.commitMessage.prompt.placeholder":
"例: スペイン語で conventional commits 形式に従って commit messages を生成して。commit message のみを返して。",
"settings.display.username.title": "ユーザー名",
"settings.display.username.description": "会話に表示されるカスタムユーザー名",
"settings.display.layout.title": "レイアウト",
+11
View File
@@ -1253,6 +1253,17 @@ export const dict = {
"settings.context.prune.description": "압축 중 이전 도구 출력 제거",
"settings.context.watcherPatterns": "파일 감시자 무시 패턴",
"settings.context.watcherPatterns.description": "감시자가 무시해야 할 파일의 글로브 패턴",
"settings.commitMessage.title": "Commit Message",
"settings.commitMessage.override.title": "사용자 지정 prompt 사용",
"settings.commitMessage.override.description":
"기본 commit message의 prompt를 재정의합니다. 활성화되면 사용자 지정 prompt가 기본 제공되는 conventional commits의 prompt를 완전히 대체합니다.",
"settings.commitMessage.prompt.title": "사용자 지정 prompt",
"settings.commitMessage.prompt.description":
"commit messages를 생성할 때 AI로 전송되는 시스템 prompt입니다. 이는 기본 prompt를 완전히 대체합니다.",
"settings.commitMessage.prompt.placeholder":
"예: 스페인어로 conventional commits 형식을 따라 commit messages를 생성해줘. 오직 commit message만 반환할 것.",
"settings.display.username.title": "사용자 이름",
"settings.display.username.description": "대화에 표시되는 사용자 정의 사용자 이름",
"settings.display.layout.title": "레이아웃",
+10
View File
@@ -1248,6 +1248,16 @@ export const dict = {
"settings.context.watcherPatterns": "File Watcher Negeer Patronen",
"settings.context.watcherPatterns.description": "Glob-patronen voor bestanden die de watcher moet negeren",
"settings.commitMessage.title": "Commit Message",
"settings.commitMessage.override.title": "Aangepaste prompt gebruiken",
"settings.commitMessage.override.description":
"Overschrijf de standaard prompt voor de commit message. Indien ingeschakeld, vervangt uw aangepaste prompt de ingebouwde prompt voor conventional commits volledig.",
"settings.commitMessage.prompt.title": "Aangepaste prompt",
"settings.commitMessage.prompt.description":
"Systeem prompt die naar de AI wordt gestuurd bij het genereren van commit messages. Dit vervangt de standaard prompt volledig.",
"settings.commitMessage.prompt.placeholder":
"bijv. Genereer commit messages in het Spaans volgens het conventional commits formaat. Retourneer ALLEEN de commit message.",
"settings.display.username.title": "Gebruikersnaam",
"settings.display.username.description": "Aangepaste gebruikersnaam weergegeven in gesprekken",
"settings.display.layout.title": "Lay-out",
+11
View File
@@ -1266,6 +1266,17 @@ export const dict = {
"settings.context.prune.description": "Fjern gamle verktøyutdata under komprimering",
"settings.context.watcherPatterns": "Filvakt-ignormønstre",
"settings.context.watcherPatterns.description": "Glob-mønstre for filer som vakten skal ignorere",
"settings.commitMessage.title": "Commit Message",
"settings.commitMessage.override.title": "Bruk egendefinert prompt",
"settings.commitMessage.override.description":
"Overstyr standard prompt for commit message. Når aktivert, erstatter din egendefinerte prompt fullstendig den innebygde prompten for conventional commits.",
"settings.commitMessage.prompt.title": "Egendefinert prompt",
"settings.commitMessage.prompt.description":
"System prompt sendt til AI-en ved generering av commit messages. Dette erstatter standard prompt fullstendig.",
"settings.commitMessage.prompt.placeholder":
"f.eks. Generer commit messages på spansk i henhold til conventional commits-formatet. Returner KUN commit message.",
"settings.display.username.title": "Brukernavn",
"settings.display.username.description": "Egendefinert brukernavn i samtaler",
"settings.display.layout.title": "Layout",
+11
View File
@@ -1274,6 +1274,17 @@ export const dict = {
"settings.context.prune.description": "Usuń stare wyjścia narzędzi podczas kompakcji",
"settings.context.watcherPatterns": "Wzorce ignorowania obserwatora plików",
"settings.context.watcherPatterns.description": "Wzorce glob dla plików do ignorowania",
"settings.commitMessage.title": "Commit Message",
"settings.commitMessage.override.title": "Użyj niestandardowego prompt",
"settings.commitMessage.override.description":
"Zastąp domyślny prompt dla commit message. Po włączeniu, Twój niestandardowy prompt całkowicie zastępuje wbudowany prompt dla conventional commits.",
"settings.commitMessage.prompt.title": "Niestandardowy prompt",
"settings.commitMessage.prompt.description":
"Systemowy prompt wysyłany do AI podczas generowania commit messages. Zastępuje to całkowicie domyślny prompt.",
"settings.commitMessage.prompt.placeholder":
"np. Generuj commit messages w języku hiszpańskim zgodnie z formatem conventional commits. Zwróć TYLKO commit message.",
"settings.display.username.title": "Nazwa użytkownika",
"settings.display.username.description": "Niestandardowa nazwa użytkownika w rozmowach",
"settings.display.layout.title": "Układ",
+11
View File
@@ -1273,6 +1273,17 @@ export const dict = {
"settings.context.prune.description": "Удалить старые выходные данные инструментов при сжатии",
"settings.context.watcherPatterns": "Шаблоны игнорирования наблюдателя файлов",
"settings.context.watcherPatterns.description": "Glob-шаблоны для файлов, которые наблюдатель должен игнорировать",
"settings.commitMessage.title": "Commit Message",
"settings.commitMessage.override.title": "Использовать пользовательский prompt",
"settings.commitMessage.override.description":
"Переопределить prompt по умолчанию для commit message. Если включено, ваш пользовательский prompt полностью заменяет встроенный prompt для conventional commits.",
"settings.commitMessage.prompt.title": "Пользовательский prompt",
"settings.commitMessage.prompt.description":
"Системный prompt, отправляемый ИИ при генерации commit messages. Это полностью заменяет prompt по умолчанию.",
"settings.commitMessage.prompt.placeholder":
"напр., Сгенерируй commit messages на испанском языке в формате conventional commits. Верни ТОЛЬКО commit message.",
"settings.display.username.title": "Имя пользователя",
"settings.display.username.description": "Пользовательское имя в разговорах",
"settings.display.layout.title": "Макет",
+11
View File
@@ -1249,6 +1249,17 @@ export const dict = {
"settings.context.prune.description": "ลบผลลัพธ์เครื่องมือเก่าระหว่างการบีบอัด",
"settings.context.watcherPatterns": "รูปแบบการละเว้นตัวเฝ้าดูไฟล์",
"settings.context.watcherPatterns.description": "รูปแบบ glob สำหรับไฟล์ที่ตัวเฝ้าดูควรละเว้น",
"settings.commitMessage.title": "Commit Message",
"settings.commitMessage.override.title": "ใช้ prompt แบบกำหนดเอง",
"settings.commitMessage.override.description":
"แทนที่ prompt เริ่มต้นของ commit message เมื่อเปิดใช้งาน prompt แบบกำหนดเองของคุณจะแทนที่ prompt เริ่มต้นของ conventional commits ทั้งหมด",
"settings.commitMessage.prompt.title": "prompt แบบกำหนดเอง",
"settings.commitMessage.prompt.description":
"System prompt ที่ส่งไปยัง AI เมื่อสร้าง commit messages สิ่งนี้จะแทนที่ prompt เริ่มต้นทั้งหมด",
"settings.commitMessage.prompt.placeholder":
"เช่น สร้าง commit messages เป็นภาษาสเปนตามรูปแบบ conventional commits คืนค่าเฉพาะ commit message เท่านั้น",
"settings.display.username.title": "ชื่อผู้ใช้",
"settings.display.username.description": "ชื่อผู้ใช้กำหนดเองในบทสนทนา",
"settings.display.layout.title": "เค้าโครง",
+10
View File
@@ -1241,6 +1241,16 @@ export const dict = {
"settings.context.watcherPatterns": "Dosya İzleyici Yok Sayma Kalıpları",
"settings.context.watcherPatterns.description": "İzleyicinin yok sayması gereken dosyalar için glob kalıpları",
"settings.commitMessage.title": "Commit Message",
"settings.commitMessage.override.title": "Özel prompt Kullan",
"settings.commitMessage.override.description":
"Varsayılan commit message için olan prompt değerini geçersiz kıl. Etkinleştirildiğinde, özel prompt değeriniz yerleşik conventional commits için olan prompt değerini tamamen değiştirir.",
"settings.commitMessage.prompt.title": "Özel prompt",
"settings.commitMessage.prompt.description":
"commit messages oluşturulurken yapay zekaya gönderilen sistem prompt'u. Bu, varsayılan prompt'un tamamen yerini alır.",
"settings.commitMessage.prompt.placeholder":
"örn. conventional commits formatını izleyerek İspanyolca commit messages oluştur. SADECE commit message döndür.",
"settings.display.username.title": "Kullanıcı Adı",
"settings.display.username.description": "Sohbetlerde görüntülenen özel kullanıcı adı",
"settings.display.layout.title": "Düzen",
+10
View File
@@ -1243,6 +1243,16 @@ export const dict = {
"settings.context.watcherPatterns": "Шаблони ігнорування спостерігача файлів",
"settings.context.watcherPatterns.description": "Glob-шаблони для файлів, які спостерігач має ігнорувати",
"settings.commitMessage.title": "Commit Message",
"settings.commitMessage.override.title": "Використовувати власний prompt",
"settings.commitMessage.override.description":
"Перевизначити prompt за замовчуванням для commit message. Якщо ввімкнено, ваш власний prompt повністю замінює вбудований prompt для conventional commits.",
"settings.commitMessage.prompt.title": "Власний prompt",
"settings.commitMessage.prompt.description":
"Системний prompt, що надсилається ШІ під час генерації commit messages. Це повністю замінює prompt за замовчуванням.",
"settings.commitMessage.prompt.placeholder":
"напр., Згенеруй commit messages іспанською мовою у форматі conventional commits. Поверни ЛИШЕ commit message.",
"settings.display.username.title": "Ім'я користувача",
"settings.display.username.description": "Власне ім'я користувача, що відображається в чатах",
"settings.display.layout.title": "Макет",
+11
View File
@@ -1225,6 +1225,17 @@ export const dict = {
"settings.context.prune.description": "压缩期间移除旧的工具输出",
"settings.context.watcherPatterns": "文件监视器忽略模式",
"settings.context.watcherPatterns.description": "监视器应忽略的文件的 glob 模式",
"settings.commitMessage.title": "Commit Message",
"settings.commitMessage.override.title": "使用自定义 prompt",
"settings.commitMessage.override.description":
"覆盖默认的 commit message 的 prompt。启用后,你的自定义 prompt 将完全替换内置的 conventional commits 的 prompt。",
"settings.commitMessage.prompt.title": "自定义 prompt",
"settings.commitMessage.prompt.description":
"在生成 commit messages 时发送给 AI 的系统 prompt。这将完全替换默认的 prompt。",
"settings.commitMessage.prompt.placeholder":
"例如:按照 conventional commits 格式用西班牙语生成 commit messages。只返回 commit message。",
"settings.display.username.title": "用户名",
"settings.display.username.description": "对话中显示的自定义用户名",
"settings.display.layout.title": "布局",
+11
View File
@@ -1229,6 +1229,17 @@ export const dict = {
"settings.context.prune.description": "壓縮期間移除舊的工具輸出",
"settings.context.watcherPatterns": "檔案監視器忽略模式",
"settings.context.watcherPatterns.description": "監視器應忽略的檔案的 glob 模式",
"settings.commitMessage.title": "Commit Message",
"settings.commitMessage.override.title": "使用自訂 prompt",
"settings.commitMessage.override.description":
"覆寫預設的 commit message 的 prompt。啟用後,您的自訂 prompt 將完全取代內建的 conventional commits 的 prompt。",
"settings.commitMessage.prompt.title": "自訂 prompt",
"settings.commitMessage.prompt.description":
"在產生 commit messages 時發送給 AI 的系統 prompt。這將完全取代預設的 prompt。",
"settings.commitMessage.prompt.placeholder":
"例如:按照 conventional commits 格式用西班牙語產生 commit messages。只回傳 commit message。",
"settings.display.username.title": "使用者名稱",
"settings.display.username.description": "對話中顯示的自訂使用者名稱",
"settings.display.layout.title": "佈局",
@@ -439,6 +439,10 @@ export interface ExperimentalConfig {
mcp_timeout?: number
}
export interface CommitMessageConfig {
prompt?: string
}
export interface Config {
permission?: PermissionConfig
model?: string | null
@@ -460,6 +464,7 @@ export interface Config {
formatter?: false | Record<string, unknown>
lsp?: false | Record<string, unknown>
compaction?: CompactionConfig
commit_message?: CommitMessageConfig
tools?: Record<string, boolean>
layout?: "auto" | "stretch"
experimental?: ExperimentalConfig
+1
View File
@@ -1162,6 +1162,7 @@ export namespace Config {
url: z.string().optional().describe("Enterprise URL"),
})
.optional(),
commit_message: KilocodeConfig.CommitMessageSchema, // kilocode_change
compaction: z
.object({
auto: z.boolean().optional().describe("Enable automatic compaction when context is full (default: true)"),
@@ -137,7 +137,7 @@ export async function generateCommitMessage(request: CommitMessageRequest): Prom
hidden: true,
options: {},
permission: [],
prompt: SYSTEM_PROMPT,
prompt: request.prompt || SYSTEM_PROMPT,
temperature: 0.3,
}
@@ -5,6 +5,8 @@ export interface CommitMessageRequest {
selectedFiles?: string[]
/** Previously generated message — when set, the LLM is asked to produce a different one */
previousMessage?: string
/** Optional custom system prompt — overrides the default conventional commits prompt */
prompt?: string
}
export interface CommitMessageResponse {
@@ -23,6 +23,21 @@ import { IgnoreMigrator } from "../ignore-migrator"
export namespace KilocodeConfig {
const log = Log.create({ service: "kilocode.config" })
// ── Config schema extensions ─────────────────────────────────────────
/** Schema for AI-generated commit message configuration. */
export const CommitMessageSchema = z
.object({
prompt: z
.string()
.optional()
.describe(
"Custom system prompt for AI commit message generation. When set, replaces the default conventional commits prompt entirely.",
),
})
.optional()
.describe("Configuration for AI-generated commit messages")
// ── Config file constants ────────────────────────────────────────────
/** Kilo-specific config file names (highest-to-lowest precedence within kilo). */
@@ -6,7 +6,7 @@ import { Hono } from "hono"
import { describeRoute, validator, resolver } from "hono-openapi"
import z from "zod"
import { TelemetryRoutes } from "../../server/routes/telemetry"
import { CommitMessageRoutes } from "../../server/routes/commit-message"
import { CommitMessageRoutes } from "./routes/commit-message"
import { EnhancePromptRoutes } from "../../server/routes/enhance-prompt"
import { KilocodeRoutes } from "../../server/routes/kilocode"
import { PermissionKilocodeRoutes } from "../permission/routes"
@@ -2,8 +2,9 @@ import { Hono } from "hono"
import { describeRoute, resolver, validator } from "hono-openapi"
import z from "zod"
import { generateCommitMessage } from "../../commit-message"
import { lazy } from "../../util/lazy"
import { errors } from "../error"
import { Config } from "../../../config/config"
import { lazy } from "../../../util/lazy"
import { errors } from "../../../server/error"
export const CommitMessageRoutes = lazy(() =>
new Hono().post(
@@ -37,7 +38,9 @@ export const CommitMessageRoutes = lazy(() =>
),
async (c) => {
const body = c.req.valid("json")
const result = await generateCommitMessage(body)
const config = await Config.get()
const prompt = config.commit_message?.prompt || undefined
const result = await generateCommitMessage({ ...body, prompt })
return c.json({ message: result.message })
},
),
@@ -1,5 +1,5 @@
import { describe, expect, spyOn, test } from "bun:test"
import { getGitContext } from "../../src/commit-message/git-context"
import { getGitContext } from "../../src/kilocode/commit-message/git-context"
describe("commit-message git context", () => {
test("hides Windows console windows for git subprocesses", async () => {
@@ -1,5 +1,8 @@
import { describe, expect, test, mock, beforeEach } from "bun:test"
import type { GitContext } from "../types"
import { $ } from "bun"
import * as fs from "fs/promises"
import path from "path"
import { tmpdir } from "../../fixture/fixture"
// Mock dependencies before importing the module under test.
// IMPORTANT: Bun's mock.module() is process-wide and permanent. To avoid
@@ -11,17 +14,23 @@ const realProvider = await import("@/provider/provider")
const realLLM = await import("@/session/llm")
const realAgent = await import("@/agent/agent")
let mockGitContext: GitContext = {
branch: "main",
recentCommits: ["abc1234 initial commit"],
files: [{ status: "modified" as const, path: "src/index.ts", diff: "+console.log('hello')" }],
let mockStreamText = "feat(src): add hello world logging"
async function change(dir: string, files: Record<string, string> = { "src/index.ts": "console.log('hello')\n" }) {
for (const [file, text] of Object.entries(files)) {
const target = path.join(dir, file)
await fs.mkdir(path.dirname(target), { recursive: true })
await Bun.write(target, text)
await $`git add ${file}`.cwd(dir).quiet()
}
}
mock.module("../git-context", () => ({
getGitContext: async () => mockGitContext,
}))
let mockStreamText = "feat(src): add hello world logging"
async function generated(text = mockStreamText) {
mockStreamText = text
await using tmp = await tmpdir({ git: true })
await change(tmp.path)
return generateCommitMessage({ path: tmp.path })
}
mock.module("@/provider/provider", () => ({
...realProvider,
@@ -36,7 +45,6 @@ mock.module("@/provider/provider", () => ({
},
}))
// kilocode_change start — upstream switched from stream.text to stream.textStream
mock.module("@/session/llm", () => ({
...realLLM,
LLM: {
@@ -49,7 +57,6 @@ mock.module("@/session/llm", () => ({
}),
},
}))
// kilocode_change end
mock.module("@/agent/agent", () => ({
...realAgent,
@@ -69,101 +76,75 @@ mock.module("@/util/log", () => ({
},
}))
import { generateCommitMessage } from "../generate"
import { generateCommitMessage } from "../../../src/kilocode/commit-message/generate"
describe("commit-message.generate", () => {
beforeEach(() => {
mockGitContext = {
branch: "main",
recentCommits: ["abc1234 initial commit"],
files: [{ status: "modified" as const, path: "src/index.ts", diff: "+console.log('hello')" }],
}
mockStreamText = "feat(src): add hello world logging"
})
describe("prompt construction", () => {
test("passes path to getGitContext", async () => {
const result = await generateCommitMessage({ path: "/my/repo" })
// If getGitContext is called, it returns our mock context and generates a message
await using tmp = await tmpdir({ git: true })
await change(tmp.path)
const result = await generateCommitMessage({ path: tmp.path })
expect(result.message).toBeTruthy()
})
test("generates message from git context with multiple files", async () => {
mockGitContext = {
branch: "feature/api",
recentCommits: ["abc feat: add api", "def fix: typo"],
files: [
{ status: "added" as const, path: "src/api.ts", diff: "+export function api() {}" },
{ status: "modified" as const, path: "src/index.ts", diff: "+import { api } from './api'" },
],
}
mockStreamText = "feat(api): add api module"
await using tmp = await tmpdir({ git: true })
await change(tmp.path, {
"src/api.ts": "export function api() {}\n",
"src/index.ts": "import { api } from './api'\n",
})
const result = await generateCommitMessage({ path: "/repo" })
const result = await generateCommitMessage({ path: tmp.path })
expect(result.message).toBe("feat(api): add api module")
})
})
describe("response cleaning", () => {
test("strips code block markers from response", async () => {
mockStreamText = "```\nfeat: add feature\n```"
const result = await generateCommitMessage({ path: "/repo" })
const result = await generated("```\nfeat: add feature\n```")
expect(result.message).toBe("feat: add feature")
})
test("strips code block markers with language tag", async () => {
mockStreamText = "```text\nfix(auth): resolve token refresh\n```"
const result = await generateCommitMessage({ path: "/repo" })
const result = await generated("```text\nfix(auth): resolve token refresh\n```")
expect(result.message).toBe("fix(auth): resolve token refresh")
})
test("strips surrounding double quotes", async () => {
mockStreamText = '"feat: add new feature"'
const result = await generateCommitMessage({ path: "/repo" })
const result = await generated('"feat: add new feature"')
expect(result.message).toBe("feat: add new feature")
})
test("strips surrounding single quotes", async () => {
mockStreamText = "'fix: resolve bug'"
const result = await generateCommitMessage({ path: "/repo" })
const result = await generated("'fix: resolve bug'")
expect(result.message).toBe("fix: resolve bug")
})
test("strips whitespace around the message", async () => {
mockStreamText = " \n chore: update deps \n "
const result = await generateCommitMessage({ path: "/repo" })
const result = await generated(" \n chore: update deps \n ")
expect(result.message).toBe("chore: update deps")
})
test("strips code blocks AND quotes together", async () => {
mockStreamText = '```\n"refactor: simplify logic"\n```'
const result = await generateCommitMessage({ path: "/repo" })
const result = await generated('```\n"refactor: simplify logic"\n```')
expect(result.message).toBe("refactor: simplify logic")
})
test("returns clean message when no markers present", async () => {
mockStreamText = "docs: update readme"
const result = await generateCommitMessage({ path: "/repo" })
const result = await generated("docs: update readme")
expect(result.message).toBe("docs: update readme")
})
})
describe("error on no changes", () => {
test("throws when no git changes are found", async () => {
mockGitContext = {
branch: "main",
recentCommits: [],
files: [],
}
await expect(generateCommitMessage({ path: "/repo" })).rejects.toThrow(
await using tmp = await tmpdir({ git: true })
await expect(generateCommitMessage({ path: tmp.path })).rejects.toThrow(
"No changes found to generate a commit message for",
)
})
@@ -171,12 +152,30 @@ describe("commit-message.generate", () => {
describe("selectedFiles pass-through", () => {
test("passes selectedFiles to getGitContext", async () => {
// This verifies the function doesn't crash when selectedFiles is provided
await using tmp = await tmpdir({ git: true })
await change(tmp.path, {
"src/a.ts": "export const a = 1\n",
"src/b.ts": "export const b = 1\n",
})
const result = await generateCommitMessage({
path: "/repo",
path: tmp.path,
selectedFiles: ["src/a.ts"],
})
expect(result.message).toBeTruthy()
})
})
describe("custom prompt", () => {
test("uses default prompt when no custom prompt provided", async () => {
const result = await generated()
expect(result.message).toBeTruthy()
})
test("uses custom prompt when provided", async () => {
await using tmp = await tmpdir({ git: true })
await change(tmp.path)
const result = await generateCommitMessage({ path: tmp.path, prompt: "Write a haiku commit message." })
expect(result.message).toBeTruthy()
})
})
})
@@ -16,7 +16,7 @@ function clearGitOutputs() {
// Override the git-context module with a version that uses our mock spawnSync.
// This avoids conflicts with generate.test.ts which also mocks this module.
mock.module("../git-context", () => {
mock.module("../../../src/kilocode/commit-message/git-context", () => {
function git(args: string[], cwd: string): string {
const key = args.join(" ")
return spawnSyncResults[key] ?? ""
@@ -180,7 +180,7 @@ mock.module("../git-context", () => {
return { getGitContext }
})
import { getGitContext } from "../git-context"
import { getGitContext } from "../../../src/kilocode/commit-message/git-context"
describe("commit-message.git-context", () => {
beforeEach(() => {
+9
View File
@@ -1756,6 +1756,15 @@ export type Config = {
*/
url?: string
}
/**
* Configuration for AI-generated commit messages
*/
commit_message?: {
/**
* Custom system prompt for AI commit message generation. When set, replaces the default conventional commits prompt entirely.
*/
prompt?: string
}
compaction?: {
/**
* Enable automatic compaction when context is full (default: true)
+10
View File
@@ -14887,6 +14887,16 @@
}
}
},
"commit_message": {
"description": "Configuration for AI-generated commit messages",
"type": "object",
"properties": {
"prompt": {
"description": "Custom system prompt for AI commit message generation. When set, replaces the default conventional commits prompt entirely.",
"type": "string"
}
}
},
"compaction": {
"type": "object",
"properties": {