From 9c010f8d8e69f096721d32a21f2225c90c56af61 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Thu, 26 Mar 2026 01:14:07 +0000 Subject: [PATCH 1/3] feat: add automatic commit message generation via SCM button --- packages/types/src/vscode.ts | 1 + src/activate/registerCommands.ts | 82 ++++++++++++++ src/i18n/locales/en/common.json | 8 ++ src/package.json | 13 +++ src/package.nls.json | 1 + .../commit-message-generator.spec.ts | 93 +++++++++++++++ src/utils/commit-message-generator.ts | 106 ++++++++++++++++++ 7 files changed, 304 insertions(+) create mode 100644 src/utils/__tests__/commit-message-generator.spec.ts create mode 100644 src/utils/commit-message-generator.ts diff --git a/packages/types/src/vscode.ts b/packages/types/src/vscode.ts index fd28f2e9945..d78d4a3e9e0 100644 --- a/packages/types/src/vscode.ts +++ b/packages/types/src/vscode.ts @@ -47,6 +47,7 @@ export const commandIds = [ "acceptInput", "focusPanel", "toggleAutoApprove", + "generateCommitMessage", ] as const export type CommandId = (typeof commandIds)[number] diff --git a/src/activate/registerCommands.ts b/src/activate/registerCommands.ts index f02ee8309a3..a350c2a5f8d 100644 --- a/src/activate/registerCommands.ts +++ b/src/activate/registerCommands.ts @@ -14,6 +14,12 @@ import { CodeIndexManager } from "../services/code-index/manager" import { importSettingsWithFeedback } from "../core/config/importExport" import { MdmService } from "../services/mdm/MdmService" import { t } from "../i18n" +import { + getGitDiff, + generateCommitMessageFromDiff, + getWorkspaceRoot, + setScmInputBoxMessage, +} from "../utils/commit-message-generator" /** * Helper to get the visible ClineProvider instance or log if not found. @@ -195,6 +201,82 @@ const getCommandsMap = ({ context, outputChannel, provider }: RegisterCommandOpt action: "toggleAutoApprove", }) }, + generateCommitMessage: async () => { + const workspaceRoot = getWorkspaceRoot() + + if (!workspaceRoot) { + vscode.window.showErrorMessage(t("common:commit.no_workspace")) + return + } + + const visibleProvider = getVisibleProviderOrLog(outputChannel) + + if (!visibleProvider) { + return + } + + // Let the user optionally pick a different API profile + const listApiConfigMeta = await visibleProvider.providerSettingsManager.listConfig() + let apiConfiguration = visibleProvider.contextProxy.getProviderSettings() + + if (listApiConfigMeta.length > 1) { + const items = [ + { label: t("common:commit.use_current_profile"), id: undefined }, + ...listApiConfigMeta.map((config) => ({ + label: config.name ?? config.id, + id: config.id, + })), + ] + + const selected = await vscode.window.showQuickPick(items, { + placeHolder: t("common:commit.select_profile"), + }) + + if (!selected) { + return // User cancelled + } + + if (selected.id) { + const { name: _, ...providerSettings } = await visibleProvider.providerSettingsManager.getProfile({ + id: selected.id, + }) + + if (providerSettings.apiProvider) { + apiConfiguration = providerSettings + } + } + } + + await vscode.window.withProgress( + { + location: vscode.ProgressLocation.Notification, + title: t("common:commit.generating"), + cancellable: false, + }, + async () => { + try { + const diff = await getGitDiff(workspaceRoot) + + if (!diff.trim()) { + vscode.window.showInformationMessage(t("common:commit.no_changes")) + return + } + + const commitMessage = await generateCommitMessageFromDiff(apiConfiguration, diff) + const success = await setScmInputBoxMessage(commitMessage) + + if (success) { + // Focus the SCM view to show the generated message + await vscode.commands.executeCommand("workbench.view.scm") + } + } catch (error) { + vscode.window.showErrorMessage( + `${t("common:commit.generation_failed")}: ${error instanceof Error ? error.message : String(error)}`, + ) + } + }, + ) + }, }) export const openClineInNewTab = async ({ context, outputChannel }: Omit) => { diff --git a/src/i18n/locales/en/common.json b/src/i18n/locales/en/common.json index d65fe183679..40f54074e80 100644 --- a/src/i18n/locales/en/common.json +++ b/src/i18n/locales/en/common.json @@ -243,5 +243,13 @@ "docsLink": { "label": "Docs", "url": "https://docs.roocode.com" + }, + "commit": { + "no_workspace": "No workspace folder found. Open a folder to generate a commit message.", + "generating": "Generating commit message...", + "no_changes": "No changes detected. Stage or modify files before generating a commit message.", + "generation_failed": "Failed to generate commit message", + "use_current_profile": "Use current profile", + "select_profile": "Select an API profile for commit message generation" } } diff --git a/src/package.json b/src/package.json index 7c4889abd89..2f5a733237c 100644 --- a/src/package.json +++ b/src/package.json @@ -169,6 +169,12 @@ "command": "roo-cline.toggleAutoApprove", "title": "%command.toggleAutoApprove.title%", "category": "%configuration.title%" + }, + { + "command": "roo-cline.generateCommitMessage", + "title": "%command.generateCommitMessage.title%", + "icon": "$(sparkle)", + "category": "%configuration.title%" } ], "menus": { @@ -275,6 +281,13 @@ "group": "overflow@2", "when": "activeWebviewPanelId == roo-cline.TabPanelProvider" } + ], + "scm/title": [ + { + "command": "roo-cline.generateCommitMessage", + "group": "navigation", + "when": "scmProvider == git" + } ] }, "keybindings": [ diff --git a/src/package.nls.json b/src/package.nls.json index 177b392f775..e241f525fb0 100644 --- a/src/package.nls.json +++ b/src/package.nls.json @@ -25,6 +25,7 @@ "command.terminal.explainCommand.title": "Explain This Command", "command.acceptInput.title": "Accept Input/Suggestion", "command.toggleAutoApprove.title": "Toggle Auto-Approve", + "command.generateCommitMessage.title": "Generate Commit Message", "configuration.title": "Roo Code", "commands.allowedCommands.description": "Commands that can be auto-executed when 'Always approve execute operations' is enabled", "commands.deniedCommands.description": "Command prefixes that will be automatically denied without asking for approval. In case of conflicts with allowed commands, the longest prefix match takes precedence. Add * to deny all commands.", diff --git a/src/utils/__tests__/commit-message-generator.spec.ts b/src/utils/__tests__/commit-message-generator.spec.ts new file mode 100644 index 00000000000..87108913c13 --- /dev/null +++ b/src/utils/__tests__/commit-message-generator.spec.ts @@ -0,0 +1,93 @@ +import { generateCommitMessageFromDiff, getGitDiff } from "../commit-message-generator" +import * as singleCompletionHandlerModule from "../single-completion-handler" +import type { ProviderSettings } from "@roo-code/types" + +vi.mock("../single-completion-handler") +vi.mock("child_process") +vi.mock("util", async (importOriginal) => { + const actual = await importOriginal() + return { + ...actual, + promisify: vi.fn((fn: any) => fn), + } +}) + +describe("commit-message-generator", () => { + let mockSingleCompletionHandler: ReturnType + + const mockApiConfig: ProviderSettings = { + apiProvider: "anthropic", + apiKey: "test-key", + apiModelId: "claude-sonnet-4-20250514", + } as ProviderSettings + + beforeEach(() => { + vi.clearAllMocks() + mockSingleCompletionHandler = vi.fn().mockResolvedValue("feat: add user authentication") + vi.mocked(singleCompletionHandlerModule).singleCompletionHandler = mockSingleCompletionHandler + }) + + describe("generateCommitMessageFromDiff", () => { + it("generates a commit message from a diff", async () => { + const diff = `diff --git a/src/auth.ts b/src/auth.ts ++export function login(user: string) { ++ return true ++}` + + const result = await generateCommitMessageFromDiff(mockApiConfig, diff) + + expect(result).toBe("feat: add user authentication") + expect(mockSingleCompletionHandler).toHaveBeenCalledWith(mockApiConfig, expect.stringContaining(diff)) + }) + + it("truncates very large diffs", async () => { + const largeDiff = "a".repeat(15000) + + await generateCommitMessageFromDiff(mockApiConfig, largeDiff) + + const calledPrompt = mockSingleCompletionHandler.mock.calls[0][1] + expect(calledPrompt).toContain("...(truncated)") + }) + + it("strips markdown code blocks from the result", async () => { + mockSingleCompletionHandler.mockResolvedValue("```\nfeat: add feature\n```") + + const result = await generateCommitMessageFromDiff(mockApiConfig, "some diff") + + expect(result).toBe("feat: add feature") + }) + + it("propagates errors from the completion handler", async () => { + mockSingleCompletionHandler.mockRejectedValue(new Error("API Error")) + + await expect(generateCommitMessageFromDiff(mockApiConfig, "some diff")).rejects.toThrow("API Error") + }) + }) + + describe("getGitDiff", () => { + it("returns staged diff when available", async () => { + const { exec } = await import("child_process") + const mockExec = vi.mocked(exec) as any + mockExec.mockImplementation( + ( + cmd: string, + _opts: any, + callback?: (err: Error | null, result: { stdout: string; stderr: string }) => void, + ) => { + if (callback) { + if (cmd === "git diff --cached") { + callback(null, { stdout: "staged changes", stderr: "" }) + } else { + callback(null, { stdout: "", stderr: "" }) + } + } + return { stdout: cmd === "git diff --cached" ? "staged changes" : "", stderr: "" } + }, + ) + + // Since we mock promisify, exec is already "promisified" via our mock + // The actual function uses execAsync which is promisify(exec) + // We need to test the logic differently since promisify is mocked + }) + }) +}) diff --git a/src/utils/commit-message-generator.ts b/src/utils/commit-message-generator.ts new file mode 100644 index 00000000000..3429abaabd9 --- /dev/null +++ b/src/utils/commit-message-generator.ts @@ -0,0 +1,106 @@ +import * as vscode from "vscode" +import { exec } from "child_process" +import { promisify } from "util" + +import type { ProviderSettings } from "@roo-code/types" + +import { singleCompletionHandler } from "./single-completion-handler" + +const execAsync = promisify(exec) + +const MAX_DIFF_LENGTH = 10000 + +const COMMIT_MESSAGE_PROMPT = `You are a commit message generator. Given the following git diff, generate a concise and descriptive commit message following the Conventional Commits format. + +Rules: +- Use one of these types: feat, fix, refactor, docs, style, test, chore, perf, ci, build +- The first line should be the type, optional scope in parentheses, and a short description (max 72 chars) +- If the changes are complex, add a blank line followed by a more detailed description +- Focus on WHAT changed and WHY, not HOW +- Do NOT include any markdown formatting, code blocks, or extra explanation +- Output ONLY the commit message text + +Git diff: +` + +/** + * Gets the staged diff from the git repository. Falls back to unstaged diff + * if nothing is staged. + */ +export async function getGitDiff(workspaceRoot: string): Promise { + try { + // Try staged changes first + const { stdout: stagedDiff } = await execAsync("git diff --cached", { + cwd: workspaceRoot, + maxBuffer: 1024 * 1024, + }) + + if (stagedDiff.trim()) { + return stagedDiff + } + + // Fall back to unstaged changes + const { stdout: unstagedDiff } = await execAsync("git diff", { + cwd: workspaceRoot, + maxBuffer: 1024 * 1024, + }) + + return unstagedDiff + } catch (error) { + throw new Error(`Failed to get git diff: ${error instanceof Error ? error.message : String(error)}`) + } +} + +/** + * Generates a commit message from the given diff using the AI provider. + */ +export async function generateCommitMessageFromDiff(apiConfiguration: ProviderSettings, diff: string): Promise { + // Truncate very large diffs to avoid token limits + const truncatedDiff = diff.length > MAX_DIFF_LENGTH ? diff.substring(0, MAX_DIFF_LENGTH) + "\n...(truncated)" : diff + + const prompt = COMMIT_MESSAGE_PROMPT + truncatedDiff + + const result = await singleCompletionHandler(apiConfiguration, prompt) + + // Clean up the result - remove any markdown formatting the model might add + return result + .replace(/^```[\s\S]*?\n/, "") + .replace(/\n```$/, "") + .trim() +} + +/** + * Gets the workspace root for git operations. + */ +export function getWorkspaceRoot(): string | undefined { + const workspaceFolders = vscode.workspace.workspaceFolders + if (!workspaceFolders || workspaceFolders.length === 0) { + return undefined + } + return workspaceFolders[0].uri.fsPath +} + +/** + * Sets the SCM input box value with the generated commit message. + */ +export async function setScmInputBoxMessage(message: string): Promise { + const gitExtension = vscode.extensions.getExtension("vscode.git") + + if (!gitExtension) { + vscode.window.showErrorMessage("Git extension is not available.") + return false + } + + const git = gitExtension.isActive ? gitExtension.exports : await gitExtension.activate() + const api = git.getAPI(1) + + if (!api || api.repositories.length === 0) { + vscode.window.showErrorMessage("No git repository found.") + return false + } + + // Use the first repository (or the one matching the workspace) + const repo = api.repositories[0] + repo.inputBox.value = message + return true +} From a30365d5dbd58742b541ed12639c75f51232b452 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Fri, 27 Mar 2026 00:25:29 +0000 Subject: [PATCH 2/3] fix: add missing translations for commit message generation feature --- src/i18n/locales/ca/common.json | 8 ++++++++ src/i18n/locales/de/common.json | 8 ++++++++ src/i18n/locales/es/common.json | 8 ++++++++ src/i18n/locales/fr/common.json | 8 ++++++++ src/i18n/locales/hi/common.json | 8 ++++++++ src/i18n/locales/id/common.json | 8 ++++++++ src/i18n/locales/it/common.json | 8 ++++++++ src/i18n/locales/ja/common.json | 8 ++++++++ src/i18n/locales/ko/common.json | 8 ++++++++ src/i18n/locales/nl/common.json | 8 ++++++++ src/i18n/locales/pl/common.json | 8 ++++++++ src/i18n/locales/pt-BR/common.json | 8 ++++++++ src/i18n/locales/ru/common.json | 8 ++++++++ src/i18n/locales/tr/common.json | 8 ++++++++ src/i18n/locales/vi/common.json | 8 ++++++++ src/i18n/locales/zh-CN/common.json | 8 ++++++++ src/i18n/locales/zh-TW/common.json | 8 ++++++++ src/package.nls.ca.json | 1 + src/package.nls.de.json | 1 + src/package.nls.es.json | 1 + src/package.nls.fr.json | 1 + src/package.nls.hi.json | 1 + src/package.nls.id.json | 1 + src/package.nls.it.json | 1 + src/package.nls.ja.json | 1 + src/package.nls.ko.json | 1 + src/package.nls.nl.json | 1 + src/package.nls.pl.json | 1 + src/package.nls.pt-BR.json | 1 + src/package.nls.ru.json | 1 + src/package.nls.tr.json | 1 + src/package.nls.vi.json | 1 + src/package.nls.zh-CN.json | 1 + src/package.nls.zh-TW.json | 1 + 34 files changed, 153 insertions(+) diff --git a/src/i18n/locales/ca/common.json b/src/i18n/locales/ca/common.json index 33188fce193..8f2002f61a4 100644 --- a/src/i18n/locales/ca/common.json +++ b/src/i18n/locales/ca/common.json @@ -251,5 +251,13 @@ "docsLink": { "label": "Docs", "url": "https://docs.roocode.com" + }, + "commit": { + "no_workspace": "No s'ha trobat cap carpeta de treball. Obre una carpeta per generar un missatge de commit.", + "generating": "Generant missatge de commit...", + "no_changes": "No s'han detectat canvis. Prepara o modifica fitxers abans de generar un missatge de commit.", + "generation_failed": "No s'ha pogut generar el missatge de commit", + "use_current_profile": "Utilitza el perfil actual", + "select_profile": "Selecciona un perfil d'API per a la generació de missatges de commit" } } diff --git a/src/i18n/locales/de/common.json b/src/i18n/locales/de/common.json index 861d9da5768..98de7c6d538 100644 --- a/src/i18n/locales/de/common.json +++ b/src/i18n/locales/de/common.json @@ -246,5 +246,13 @@ "docsLink": { "label": "Docs", "url": "https://docs.roocode.com" + }, + "commit": { + "no_workspace": "Kein Arbeitsbereichsordner gefunden. Öffne einen Ordner, um eine Commit-Nachricht zu generieren.", + "generating": "Commit-Nachricht wird generiert...", + "no_changes": "Keine Änderungen erkannt. Bereite Dateien vor oder ändere sie, bevor du eine Commit-Nachricht generierst.", + "generation_failed": "Commit-Nachricht konnte nicht generiert werden", + "use_current_profile": "Aktuelles Profil verwenden", + "select_profile": "Wähle ein API-Profil für die Commit-Nachricht-Generierung" } } diff --git a/src/i18n/locales/es/common.json b/src/i18n/locales/es/common.json index 82be83956b0..dde75ee81f0 100644 --- a/src/i18n/locales/es/common.json +++ b/src/i18n/locales/es/common.json @@ -246,5 +246,13 @@ "docsLink": { "label": "Docs", "url": "https://docs.roocode.com" + }, + "commit": { + "no_workspace": "No se encontró carpeta de trabajo. Abre una carpeta para generar un mensaje de commit.", + "generating": "Generando mensaje de commit...", + "no_changes": "No se detectaron cambios. Prepara o modifica archivos antes de generar un mensaje de commit.", + "generation_failed": "Error al generar el mensaje de commit", + "use_current_profile": "Usar perfil actual", + "select_profile": "Selecciona un perfil de API para la generación de mensajes de commit" } } diff --git a/src/i18n/locales/fr/common.json b/src/i18n/locales/fr/common.json index 6fc05ff94a3..ef3932bcd5b 100644 --- a/src/i18n/locales/fr/common.json +++ b/src/i18n/locales/fr/common.json @@ -251,5 +251,13 @@ "docsLink": { "label": "Docs", "url": "https://docs.roocode.com" + }, + "commit": { + "no_workspace": "Aucun dossier de travail trouvé. Ouvre un dossier pour générer un message de commit.", + "generating": "Génération du message de commit...", + "no_changes": "Aucun changement détecté. Prépare ou modifie des fichiers avant de générer un message de commit.", + "generation_failed": "Échec de la génération du message de commit", + "use_current_profile": "Utiliser le profil actuel", + "select_profile": "Sélectionne un profil d'API pour la génération de messages de commit" } } diff --git a/src/i18n/locales/hi/common.json b/src/i18n/locales/hi/common.json index 528ed6d45f5..c6468439f37 100644 --- a/src/i18n/locales/hi/common.json +++ b/src/i18n/locales/hi/common.json @@ -251,5 +251,13 @@ "docsLink": { "label": "Docs", "url": "https://docs.roocode.com" + }, + "commit": { + "no_workspace": "कोई कार्यस्थान फ़ोल्डर नहीं मिला। कमिट संदेश बनाने के लिए एक फ़ोल्डर खोलें।", + "generating": "कमिट संदेश बनाया जा रहा है...", + "no_changes": "कोई बदलाव नहीं मिला। कमिट संदेश बनाने से पहले फ़ाइलें तैयार करें या बदलें।", + "generation_failed": "कमिट संदेश बनाने में विफल", + "use_current_profile": "वर्तमान प्रोफ़ाइल का उपयोग करें", + "select_profile": "कमिट संदेश बनाने के लिए एक API प्रोफ़ाइल चुनें" } } diff --git a/src/i18n/locales/id/common.json b/src/i18n/locales/id/common.json index cb1c3231fb8..06b13a0febe 100644 --- a/src/i18n/locales/id/common.json +++ b/src/i18n/locales/id/common.json @@ -251,5 +251,13 @@ "docsLink": { "label": "Docs", "url": "https://docs.roocode.com" + }, + "commit": { + "no_workspace": "Folder workspace tidak ditemukan. Buka folder untuk membuat pesan commit.", + "generating": "Membuat pesan commit...", + "no_changes": "Tidak ada perubahan terdeteksi. Siapkan atau ubah file sebelum membuat pesan commit.", + "generation_failed": "Gagal membuat pesan commit", + "use_current_profile": "Gunakan profil saat ini", + "select_profile": "Pilih profil API untuk pembuatan pesan commit" } } diff --git a/src/i18n/locales/it/common.json b/src/i18n/locales/it/common.json index b4e522cb732..3d9840e2f8d 100644 --- a/src/i18n/locales/it/common.json +++ b/src/i18n/locales/it/common.json @@ -251,5 +251,13 @@ "docsLink": { "label": "Docs", "url": "https://docs.roocode.com" + }, + "commit": { + "no_workspace": "Nessuna cartella di lavoro trovata. Apri una cartella per generare un messaggio di commit.", + "generating": "Generazione del messaggio di commit...", + "no_changes": "Nessuna modifica rilevata. Prepara o modifica i file prima di generare un messaggio di commit.", + "generation_failed": "Impossibile generare il messaggio di commit", + "use_current_profile": "Usa il profilo corrente", + "select_profile": "Seleziona un profilo API per la generazione del messaggio di commit" } } diff --git a/src/i18n/locales/ja/common.json b/src/i18n/locales/ja/common.json index 7b63b6f7298..44ffa3cb5d8 100644 --- a/src/i18n/locales/ja/common.json +++ b/src/i18n/locales/ja/common.json @@ -251,5 +251,13 @@ "docsLink": { "label": "Docs", "url": "https://docs.roocode.com" + }, + "commit": { + "no_workspace": "ワークスペースフォルダーが見つかりません。コミットメッセージを生成するにはフォルダーを開いてください。", + "generating": "コミットメッセージを生成中...", + "no_changes": "変更が検出されませんでした。コミットメッセージを生成する前にファイルをステージするか変更してください。", + "generation_failed": "コミットメッセージの生成に失敗しました", + "use_current_profile": "現在のプロファイルを使用", + "select_profile": "コミットメッセージ生成用のAPIプロファイルを選択" } } diff --git a/src/i18n/locales/ko/common.json b/src/i18n/locales/ko/common.json index fbde3225bb1..0aa495f2671 100644 --- a/src/i18n/locales/ko/common.json +++ b/src/i18n/locales/ko/common.json @@ -251,5 +251,13 @@ "docsLink": { "label": "Docs", "url": "https://docs.roocode.com" + }, + "commit": { + "no_workspace": "워크스페이스 폴더를 찾을 수 없습니다. 커밋 메시지를 생성하려면 폴더를 열어주세요.", + "generating": "커밋 메시지 생성 중...", + "no_changes": "변경 사항이 없습니다. 커밋 메시지를 생성하기 전에 파일을 스테이지하거나 수정하세요.", + "generation_failed": "커밋 메시지 생성에 실패했습니다", + "use_current_profile": "현재 프로필 사용", + "select_profile": "커밋 메시지 생성에 사용할 API 프로필을 선택하세요" } } diff --git a/src/i18n/locales/nl/common.json b/src/i18n/locales/nl/common.json index eba274c96ec..8c4a381916d 100644 --- a/src/i18n/locales/nl/common.json +++ b/src/i18n/locales/nl/common.json @@ -251,5 +251,13 @@ "docsLink": { "label": "Docs", "url": "https://docs.roocode.com" + }, + "commit": { + "no_workspace": "Geen werkruimtemap gevonden. Open een map om een commitbericht te genereren.", + "generating": "Commitbericht genereren...", + "no_changes": "Geen wijzigingen gedetecteerd. Stage of wijzig bestanden voordat je een commitbericht genereert.", + "generation_failed": "Kan commitbericht niet genereren", + "use_current_profile": "Huidig profiel gebruiken", + "select_profile": "Selecteer een API-profiel voor het genereren van commitberichten" } } diff --git a/src/i18n/locales/pl/common.json b/src/i18n/locales/pl/common.json index 20b568281bb..db6cbb73763 100644 --- a/src/i18n/locales/pl/common.json +++ b/src/i18n/locales/pl/common.json @@ -251,5 +251,13 @@ "docsLink": { "label": "Docs", "url": "https://docs.roocode.com" + }, + "commit": { + "no_workspace": "Nie znaleziono folderu roboczego. Otwórz folder, aby wygenerować wiadomość commita.", + "generating": "Generowanie wiadomości commita...", + "no_changes": "Nie wykryto zmian. Przygotuj lub zmodyfikuj pliki przed wygenerowaniem wiadomości commita.", + "generation_failed": "Nie udało się wygenerować wiadomości commita", + "use_current_profile": "Użyj bieżącego profilu", + "select_profile": "Wybierz profil API do generowania wiadomości commita" } } diff --git a/src/i18n/locales/pt-BR/common.json b/src/i18n/locales/pt-BR/common.json index 38abc8c8047..baae0807682 100644 --- a/src/i18n/locales/pt-BR/common.json +++ b/src/i18n/locales/pt-BR/common.json @@ -251,5 +251,13 @@ "docsLink": { "label": "Docs", "url": "https://docs.roocode.com" + }, + "commit": { + "no_workspace": "Nenhuma pasta de trabalho encontrada. Abra uma pasta para gerar uma mensagem de commit.", + "generating": "Gerando mensagem de commit...", + "no_changes": "Nenhuma alteração detectada. Prepare ou modifique arquivos antes de gerar uma mensagem de commit.", + "generation_failed": "Falha ao gerar mensagem de commit", + "use_current_profile": "Usar perfil atual", + "select_profile": "Selecione um perfil de API para a geração de mensagens de commit" } } diff --git a/src/i18n/locales/ru/common.json b/src/i18n/locales/ru/common.json index d124f597318..bf7f1d93fe2 100644 --- a/src/i18n/locales/ru/common.json +++ b/src/i18n/locales/ru/common.json @@ -251,5 +251,13 @@ "docsLink": { "label": "Docs", "url": "https://docs.roocode.com" + }, + "commit": { + "no_workspace": "Папка рабочего пространства не найдена. Откройте папку для генерации сообщения коммита.", + "generating": "Генерация сообщения коммита...", + "no_changes": "Изменения не обнаружены. Подготовьте или измените файлы перед генерацией сообщения коммита.", + "generation_failed": "Не удалось сгенерировать сообщение коммита", + "use_current_profile": "Использовать текущий профиль", + "select_profile": "Выберите профиль API для генерации сообщения коммита" } } diff --git a/src/i18n/locales/tr/common.json b/src/i18n/locales/tr/common.json index 00dcf6fc33d..46e8adb9a2e 100644 --- a/src/i18n/locales/tr/common.json +++ b/src/i18n/locales/tr/common.json @@ -251,5 +251,13 @@ "docsLink": { "label": "Docs", "url": "https://docs.roocode.com" + }, + "commit": { + "no_workspace": "Çalışma alanı klasörü bulunamadı. Commit mesajı oluşturmak için bir klasör aç.", + "generating": "Commit mesajı oluşturuluyor...", + "no_changes": "Değişiklik algılanmadı. Commit mesajı oluşturmadan önce dosyaları hazırla veya değiştir.", + "generation_failed": "Commit mesajı oluşturulamadı", + "use_current_profile": "Mevcut profili kullan", + "select_profile": "Commit mesajı oluşturma için bir API profili seç" } } diff --git a/src/i18n/locales/vi/common.json b/src/i18n/locales/vi/common.json index decd4ff53ef..856d5e748a7 100644 --- a/src/i18n/locales/vi/common.json +++ b/src/i18n/locales/vi/common.json @@ -258,5 +258,13 @@ "docsLink": { "label": "Docs", "url": "https://docs.roocode.com" + }, + "commit": { + "no_workspace": "Không tìm thấy thư mục làm việc. Mở một thư mục để tạo thông điệp commit.", + "generating": "Đang tạo thông điệp commit...", + "no_changes": "Không phát hiện thay đổi nào. Hãy chuẩn bị hoặc chỉnh sửa tệp trước khi tạo thông điệp commit.", + "generation_failed": "Không thể tạo thông điệp commit", + "use_current_profile": "Sử dụng hồ sơ hiện tại", + "select_profile": "Chọn một hồ sơ API để tạo thông điệp commit" } } diff --git a/src/i18n/locales/zh-CN/common.json b/src/i18n/locales/zh-CN/common.json index 6df1f78b167..b205ecebbed 100644 --- a/src/i18n/locales/zh-CN/common.json +++ b/src/i18n/locales/zh-CN/common.json @@ -256,5 +256,13 @@ "docsLink": { "label": "Docs", "url": "https://docs.roocode.com" + }, + "commit": { + "no_workspace": "未找到工作区文件夹。请打开一个文件夹以生成提交信息。", + "generating": "正在生成提交信息...", + "no_changes": "未检测到更改。请在生成提交信息之前暂存或修改文件。", + "generation_failed": "生成提交信息失败", + "use_current_profile": "使用当前配置", + "select_profile": "选择用于生成提交信息的 API 配置" } } diff --git a/src/i18n/locales/zh-TW/common.json b/src/i18n/locales/zh-TW/common.json index be4a76fc5b9..0400390c1cc 100644 --- a/src/i18n/locales/zh-TW/common.json +++ b/src/i18n/locales/zh-TW/common.json @@ -251,5 +251,13 @@ "docsLink": { "label": "Docs", "url": "https://docs.roocode.com" + }, + "commit": { + "no_workspace": "未找到工作區資料夾。請開啟一個資料夾以產生提交訊息。", + "generating": "正在產生提交訊息...", + "no_changes": "未偵測到變更。請在產生提交訊息之前暫存或修改檔案。", + "generation_failed": "產生提交訊息失敗", + "use_current_profile": "使用目前的設定檔", + "select_profile": "選擇用於產生提交訊息的 API 設定檔" } } diff --git a/src/package.nls.ca.json b/src/package.nls.ca.json index 2781ed169cf..31cdcddd9b4 100644 --- a/src/package.nls.ca.json +++ b/src/package.nls.ca.json @@ -15,6 +15,7 @@ "command.terminal.explainCommand.title": "Explicar Aquesta Ordre", "command.acceptInput.title": "Acceptar Entrada/Suggeriment", "command.toggleAutoApprove.title": "Alternar Auto-Aprovació", + "command.generateCommitMessage.title": "Generar Missatge de Commit", "views.activitybar.title": "Roo Code", "views.contextMenu.label": "Roo Code", "views.terminalMenu.label": "Roo Code", diff --git a/src/package.nls.de.json b/src/package.nls.de.json index a77a253ef06..fbc49f2b482 100644 --- a/src/package.nls.de.json +++ b/src/package.nls.de.json @@ -15,6 +15,7 @@ "command.terminal.explainCommand.title": "Diesen Befehl Erklären", "command.acceptInput.title": "Eingabe/Vorschlag Akzeptieren", "command.toggleAutoApprove.title": "Auto-Genehmigung Umschalten", + "command.generateCommitMessage.title": "Commit-Nachricht Generieren", "views.activitybar.title": "Roo Code", "views.contextMenu.label": "Roo Code", "views.terminalMenu.label": "Roo Code", diff --git a/src/package.nls.es.json b/src/package.nls.es.json index a1c729080e2..a8bdf0a4c44 100644 --- a/src/package.nls.es.json +++ b/src/package.nls.es.json @@ -15,6 +15,7 @@ "command.terminal.explainCommand.title": "Explicar Este Comando", "command.acceptInput.title": "Aceptar Entrada/Sugerencia", "command.toggleAutoApprove.title": "Alternar Auto-Aprobación", + "command.generateCommitMessage.title": "Generar Mensaje de Commit", "views.activitybar.title": "Roo Code", "views.contextMenu.label": "Roo Code", "views.terminalMenu.label": "Roo Code", diff --git a/src/package.nls.fr.json b/src/package.nls.fr.json index 2d009c0038d..c2a72cd679c 100644 --- a/src/package.nls.fr.json +++ b/src/package.nls.fr.json @@ -15,6 +15,7 @@ "command.terminal.explainCommand.title": "Expliquer cette Commande", "command.acceptInput.title": "Accepter l'Entrée/Suggestion", "command.toggleAutoApprove.title": "Basculer Auto-Approbation", + "command.generateCommitMessage.title": "Générer un Message de Commit", "views.activitybar.title": "Roo Code", "views.contextMenu.label": "Roo Code", "views.terminalMenu.label": "Roo Code", diff --git a/src/package.nls.hi.json b/src/package.nls.hi.json index c51f3ee95ee..42e510f7f73 100644 --- a/src/package.nls.hi.json +++ b/src/package.nls.hi.json @@ -15,6 +15,7 @@ "command.terminal.explainCommand.title": "यह कमांड समझाएं", "command.acceptInput.title": "इनपुट/सुझाव स्वीकारें", "command.toggleAutoApprove.title": "ऑटो-अनुमोदन टॉगल करें", + "command.generateCommitMessage.title": "कमिट संदेश बनाएं", "views.activitybar.title": "Roo Code", "views.contextMenu.label": "Roo Code", "views.terminalMenu.label": "Roo Code", diff --git a/src/package.nls.id.json b/src/package.nls.id.json index 2a7607f3e7c..d2ccf119242 100644 --- a/src/package.nls.id.json +++ b/src/package.nls.id.json @@ -25,6 +25,7 @@ "command.terminal.explainCommand.title": "Jelaskan Perintah Ini", "command.acceptInput.title": "Terima Input/Saran", "command.toggleAutoApprove.title": "Alihkan Persetujuan Otomatis", + "command.generateCommitMessage.title": "Buat Pesan Commit", "configuration.title": "Roo Code", "commands.allowedCommands.description": "Perintah yang dapat dijalankan secara otomatis ketika 'Selalu setujui operasi eksekusi' diaktifkan", "commands.deniedCommands.description": "Awalan perintah yang akan otomatis ditolak tanpa meminta persetujuan. Jika terjadi konflik dengan perintah yang diizinkan, pencocokan awalan terpanjang akan diprioritaskan. Tambahkan * untuk menolak semua perintah.", diff --git a/src/package.nls.it.json b/src/package.nls.it.json index c94471355d4..8cd01157d82 100644 --- a/src/package.nls.it.json +++ b/src/package.nls.it.json @@ -15,6 +15,7 @@ "command.terminal.explainCommand.title": "Spiega Questo Comando", "command.acceptInput.title": "Accetta Input/Suggerimento", "command.toggleAutoApprove.title": "Attiva/Disattiva Auto-Approvazione", + "command.generateCommitMessage.title": "Genera Messaggio di Commit", "views.activitybar.title": "Roo Code", "views.contextMenu.label": "Roo Code", "views.terminalMenu.label": "Roo Code", diff --git a/src/package.nls.ja.json b/src/package.nls.ja.json index ff6040d7734..e88119359cc 100644 --- a/src/package.nls.ja.json +++ b/src/package.nls.ja.json @@ -25,6 +25,7 @@ "command.terminal.explainCommand.title": "このコマンドを説明", "command.acceptInput.title": "入力/提案を承認", "command.toggleAutoApprove.title": "自動承認を切替", + "command.generateCommitMessage.title": "コミットメッセージを生成", "configuration.title": "Roo Code", "commands.allowedCommands.description": "'常に実行操作を承認する'が有効な場合に自動実行できるコマンド", "commands.deniedCommands.description": "承認を求めずに自動的に拒否されるコマンドプレフィックス。許可されたコマンドとの競合がある場合、最長プレフィックスマッチが優先されます。すべてのコマンドを拒否するには * を追加してください。", diff --git a/src/package.nls.ko.json b/src/package.nls.ko.json index f0912835b8b..57a13276977 100644 --- a/src/package.nls.ko.json +++ b/src/package.nls.ko.json @@ -15,6 +15,7 @@ "command.terminal.explainCommand.title": "이 명령어 설명", "command.acceptInput.title": "입력/제안 수락", "command.toggleAutoApprove.title": "자동 승인 전환", + "command.generateCommitMessage.title": "커밋 메시지 생성", "views.activitybar.title": "Roo Code", "views.contextMenu.label": "Roo Code", "views.terminalMenu.label": "Roo Code", diff --git a/src/package.nls.nl.json b/src/package.nls.nl.json index fef3ca7219c..e9275872643 100644 --- a/src/package.nls.nl.json +++ b/src/package.nls.nl.json @@ -25,6 +25,7 @@ "command.terminal.explainCommand.title": "Leg Dit Commando Uit", "command.acceptInput.title": "Invoer/Suggestie Accepteren", "command.toggleAutoApprove.title": "Auto-Goedkeuring Schakelen", + "command.generateCommitMessage.title": "Commitbericht Genereren", "configuration.title": "Roo Code", "commands.allowedCommands.description": "Commando's die automatisch kunnen worden uitgevoerd wanneer 'Altijd goedkeuren uitvoerbewerkingen' is ingeschakeld", "commands.deniedCommands.description": "Commando-prefixen die automatisch worden geweigerd zonder om goedkeuring te vragen. Bij conflicten met toegestane commando's heeft de langste prefix-match voorrang. Voeg * toe om alle commando's te weigeren.", diff --git a/src/package.nls.pl.json b/src/package.nls.pl.json index 8c1f66450d1..9135c1a5969 100644 --- a/src/package.nls.pl.json +++ b/src/package.nls.pl.json @@ -15,6 +15,7 @@ "command.terminal.explainCommand.title": "Wyjaśnij tę Komendę", "command.acceptInput.title": "Akceptuj Wprowadzanie/Sugestię", "command.toggleAutoApprove.title": "Przełącz Auto-Zatwierdzanie", + "command.generateCommitMessage.title": "Generuj Wiadomość Commita", "views.activitybar.title": "Roo Code", "views.contextMenu.label": "Roo Code", "views.terminalMenu.label": "Roo Code", diff --git a/src/package.nls.pt-BR.json b/src/package.nls.pt-BR.json index 84cbf42c097..717f0d0dd9e 100644 --- a/src/package.nls.pt-BR.json +++ b/src/package.nls.pt-BR.json @@ -15,6 +15,7 @@ "command.terminal.explainCommand.title": "Explicar Este Comando", "command.acceptInput.title": "Aceitar Entrada/Sugestão", "command.toggleAutoApprove.title": "Alternar Auto-Aprovação", + "command.generateCommitMessage.title": "Gerar Mensagem de Commit", "views.activitybar.title": "Roo Code", "views.contextMenu.label": "Roo Code", "views.terminalMenu.label": "Roo Code", diff --git a/src/package.nls.ru.json b/src/package.nls.ru.json index be8df040323..da657759fe1 100644 --- a/src/package.nls.ru.json +++ b/src/package.nls.ru.json @@ -25,6 +25,7 @@ "command.terminal.explainCommand.title": "Объяснить эту команду", "command.acceptInput.title": "Принять ввод/предложение", "command.toggleAutoApprove.title": "Переключить Авто-Подтверждение", + "command.generateCommitMessage.title": "Сгенерировать Сообщение Коммита", "configuration.title": "Roo Code", "commands.allowedCommands.description": "Команды, которые могут быть автоматически выполнены, когда включена опция 'Всегда подтверждать операции выполнения'", "commands.deniedCommands.description": "Префиксы команд, которые будут автоматически отклонены без запроса подтверждения. В случае конфликтов с разрешенными командами приоритет имеет самое длинное совпадение префикса. Добавьте * чтобы отклонить все команды.", diff --git a/src/package.nls.tr.json b/src/package.nls.tr.json index a815188e8aa..d136be925a2 100644 --- a/src/package.nls.tr.json +++ b/src/package.nls.tr.json @@ -15,6 +15,7 @@ "command.terminal.explainCommand.title": "Bu Komutu Açıkla", "command.acceptInput.title": "Girişi/Öneriyi Kabul Et", "command.toggleAutoApprove.title": "Otomatik Onayı Değiştir", + "command.generateCommitMessage.title": "Commit Mesajı Oluştur", "views.activitybar.title": "Roo Code", "views.contextMenu.label": "Roo Code", "views.terminalMenu.label": "Roo Code", diff --git a/src/package.nls.vi.json b/src/package.nls.vi.json index 6052080dfa3..4650e504f59 100644 --- a/src/package.nls.vi.json +++ b/src/package.nls.vi.json @@ -15,6 +15,7 @@ "command.terminal.explainCommand.title": "Giải Thích Lệnh Này", "command.acceptInput.title": "Chấp Nhận Đầu Vào/Gợi Ý", "command.toggleAutoApprove.title": "Bật/Tắt Tự Động Phê Duyệt", + "command.generateCommitMessage.title": "Tạo Thông Điệp Commit", "views.activitybar.title": "Roo Code", "views.contextMenu.label": "Roo Code", "views.terminalMenu.label": "Roo Code", diff --git a/src/package.nls.zh-CN.json b/src/package.nls.zh-CN.json index 9254d494d9b..43bc8d70dac 100644 --- a/src/package.nls.zh-CN.json +++ b/src/package.nls.zh-CN.json @@ -15,6 +15,7 @@ "command.terminal.explainCommand.title": "解释此命令", "command.acceptInput.title": "接受输入/建议", "command.toggleAutoApprove.title": "切换自动批准", + "command.generateCommitMessage.title": "生成提交信息", "views.activitybar.title": "Roo Code", "views.contextMenu.label": "Roo Code", "views.terminalMenu.label": "Roo Code", diff --git a/src/package.nls.zh-TW.json b/src/package.nls.zh-TW.json index a8030d69141..c4c6f1835a3 100644 --- a/src/package.nls.zh-TW.json +++ b/src/package.nls.zh-TW.json @@ -15,6 +15,7 @@ "command.terminal.explainCommand.title": "解釋此命令", "command.acceptInput.title": "接受輸入/建議", "command.toggleAutoApprove.title": "切換自動批准", + "command.generateCommitMessage.title": "產生提交訊息", "views.activitybar.title": "Roo Code", "views.contextMenu.label": "Roo Code", "views.terminalMenu.label": "Roo Code", From 364941f17ca6b2b42b00e64c18d2e7237f8067f2 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Fri, 27 Mar 2026 00:34:48 +0000 Subject: [PATCH 3/3] fix: improve commit message generator quality and test coverage - Rewrite getGitDiff tests with proper assertions (3 new tests) - Fix markdown stripping regex to handle language-tagged code blocks - Add --no-color flag to git diff commands for clean output - Internationalize hardcoded error strings in setScmInputBoxMessage - Add no_git_extension and no_git_repo i18n keys to all locales --- src/i18n/locales/ca/common.json | 2 + src/i18n/locales/de/common.json | 2 + src/i18n/locales/en/common.json | 2 + src/i18n/locales/es/common.json | 2 + src/i18n/locales/fr/common.json | 2 + src/i18n/locales/hi/common.json | 2 + src/i18n/locales/id/common.json | 2 + src/i18n/locales/it/common.json | 2 + src/i18n/locales/ja/common.json | 2 + src/i18n/locales/ko/common.json | 2 + src/i18n/locales/nl/common.json | 2 + src/i18n/locales/pl/common.json | 2 + src/i18n/locales/pt-BR/common.json | 2 + src/i18n/locales/ru/common.json | 2 + src/i18n/locales/tr/common.json | 2 + src/i18n/locales/vi/common.json | 2 + src/i18n/locales/zh-CN/common.json | 2 + src/i18n/locales/zh-TW/common.json | 2 + .../commit-message-generator.spec.ts | 98 +++++++++++++++---- src/utils/commit-message-generator.ts | 11 ++- 20 files changed, 123 insertions(+), 22 deletions(-) diff --git a/src/i18n/locales/ca/common.json b/src/i18n/locales/ca/common.json index 8f2002f61a4..48ec1494992 100644 --- a/src/i18n/locales/ca/common.json +++ b/src/i18n/locales/ca/common.json @@ -257,6 +257,8 @@ "generating": "Generant missatge de commit...", "no_changes": "No s'han detectat canvis. Prepara o modifica fitxers abans de generar un missatge de commit.", "generation_failed": "No s'ha pogut generar el missatge de commit", + "no_git_extension": "Git extension is not available.", + "no_git_repo": "No git repository found.", "use_current_profile": "Utilitza el perfil actual", "select_profile": "Selecciona un perfil d'API per a la generació de missatges de commit" } diff --git a/src/i18n/locales/de/common.json b/src/i18n/locales/de/common.json index 98de7c6d538..8591ff40028 100644 --- a/src/i18n/locales/de/common.json +++ b/src/i18n/locales/de/common.json @@ -252,6 +252,8 @@ "generating": "Commit-Nachricht wird generiert...", "no_changes": "Keine Änderungen erkannt. Bereite Dateien vor oder ändere sie, bevor du eine Commit-Nachricht generierst.", "generation_failed": "Commit-Nachricht konnte nicht generiert werden", + "no_git_extension": "Git extension is not available.", + "no_git_repo": "No git repository found.", "use_current_profile": "Aktuelles Profil verwenden", "select_profile": "Wähle ein API-Profil für die Commit-Nachricht-Generierung" } diff --git a/src/i18n/locales/en/common.json b/src/i18n/locales/en/common.json index 40f54074e80..51aaadc43ef 100644 --- a/src/i18n/locales/en/common.json +++ b/src/i18n/locales/en/common.json @@ -249,6 +249,8 @@ "generating": "Generating commit message...", "no_changes": "No changes detected. Stage or modify files before generating a commit message.", "generation_failed": "Failed to generate commit message", + "no_git_extension": "Git extension is not available.", + "no_git_repo": "No git repository found.", "use_current_profile": "Use current profile", "select_profile": "Select an API profile for commit message generation" } diff --git a/src/i18n/locales/es/common.json b/src/i18n/locales/es/common.json index dde75ee81f0..a533653a6d6 100644 --- a/src/i18n/locales/es/common.json +++ b/src/i18n/locales/es/common.json @@ -252,6 +252,8 @@ "generating": "Generando mensaje de commit...", "no_changes": "No se detectaron cambios. Prepara o modifica archivos antes de generar un mensaje de commit.", "generation_failed": "Error al generar el mensaje de commit", + "no_git_extension": "Git extension is not available.", + "no_git_repo": "No git repository found.", "use_current_profile": "Usar perfil actual", "select_profile": "Selecciona un perfil de API para la generación de mensajes de commit" } diff --git a/src/i18n/locales/fr/common.json b/src/i18n/locales/fr/common.json index ef3932bcd5b..f768fb7d0b0 100644 --- a/src/i18n/locales/fr/common.json +++ b/src/i18n/locales/fr/common.json @@ -257,6 +257,8 @@ "generating": "Génération du message de commit...", "no_changes": "Aucun changement détecté. Prépare ou modifie des fichiers avant de générer un message de commit.", "generation_failed": "Échec de la génération du message de commit", + "no_git_extension": "Git extension is not available.", + "no_git_repo": "No git repository found.", "use_current_profile": "Utiliser le profil actuel", "select_profile": "Sélectionne un profil d'API pour la génération de messages de commit" } diff --git a/src/i18n/locales/hi/common.json b/src/i18n/locales/hi/common.json index c6468439f37..ceb579ea7b7 100644 --- a/src/i18n/locales/hi/common.json +++ b/src/i18n/locales/hi/common.json @@ -257,6 +257,8 @@ "generating": "कमिट संदेश बनाया जा रहा है...", "no_changes": "कोई बदलाव नहीं मिला। कमिट संदेश बनाने से पहले फ़ाइलें तैयार करें या बदलें।", "generation_failed": "कमिट संदेश बनाने में विफल", + "no_git_extension": "Git extension is not available.", + "no_git_repo": "No git repository found.", "use_current_profile": "वर्तमान प्रोफ़ाइल का उपयोग करें", "select_profile": "कमिट संदेश बनाने के लिए एक API प्रोफ़ाइल चुनें" } diff --git a/src/i18n/locales/id/common.json b/src/i18n/locales/id/common.json index 06b13a0febe..243ef822f9a 100644 --- a/src/i18n/locales/id/common.json +++ b/src/i18n/locales/id/common.json @@ -257,6 +257,8 @@ "generating": "Membuat pesan commit...", "no_changes": "Tidak ada perubahan terdeteksi. Siapkan atau ubah file sebelum membuat pesan commit.", "generation_failed": "Gagal membuat pesan commit", + "no_git_extension": "Git extension is not available.", + "no_git_repo": "No git repository found.", "use_current_profile": "Gunakan profil saat ini", "select_profile": "Pilih profil API untuk pembuatan pesan commit" } diff --git a/src/i18n/locales/it/common.json b/src/i18n/locales/it/common.json index 3d9840e2f8d..8e7e7223927 100644 --- a/src/i18n/locales/it/common.json +++ b/src/i18n/locales/it/common.json @@ -257,6 +257,8 @@ "generating": "Generazione del messaggio di commit...", "no_changes": "Nessuna modifica rilevata. Prepara o modifica i file prima di generare un messaggio di commit.", "generation_failed": "Impossibile generare il messaggio di commit", + "no_git_extension": "Git extension is not available.", + "no_git_repo": "No git repository found.", "use_current_profile": "Usa il profilo corrente", "select_profile": "Seleziona un profilo API per la generazione del messaggio di commit" } diff --git a/src/i18n/locales/ja/common.json b/src/i18n/locales/ja/common.json index 44ffa3cb5d8..ae92ab3bb9a 100644 --- a/src/i18n/locales/ja/common.json +++ b/src/i18n/locales/ja/common.json @@ -257,6 +257,8 @@ "generating": "コミットメッセージを生成中...", "no_changes": "変更が検出されませんでした。コミットメッセージを生成する前にファイルをステージするか変更してください。", "generation_failed": "コミットメッセージの生成に失敗しました", + "no_git_extension": "Git extension is not available.", + "no_git_repo": "No git repository found.", "use_current_profile": "現在のプロファイルを使用", "select_profile": "コミットメッセージ生成用のAPIプロファイルを選択" } diff --git a/src/i18n/locales/ko/common.json b/src/i18n/locales/ko/common.json index 0aa495f2671..aec996a38cf 100644 --- a/src/i18n/locales/ko/common.json +++ b/src/i18n/locales/ko/common.json @@ -257,6 +257,8 @@ "generating": "커밋 메시지 생성 중...", "no_changes": "변경 사항이 없습니다. 커밋 메시지를 생성하기 전에 파일을 스테이지하거나 수정하세요.", "generation_failed": "커밋 메시지 생성에 실패했습니다", + "no_git_extension": "Git extension is not available.", + "no_git_repo": "No git repository found.", "use_current_profile": "현재 프로필 사용", "select_profile": "커밋 메시지 생성에 사용할 API 프로필을 선택하세요" } diff --git a/src/i18n/locales/nl/common.json b/src/i18n/locales/nl/common.json index 8c4a381916d..bd1294f147d 100644 --- a/src/i18n/locales/nl/common.json +++ b/src/i18n/locales/nl/common.json @@ -257,6 +257,8 @@ "generating": "Commitbericht genereren...", "no_changes": "Geen wijzigingen gedetecteerd. Stage of wijzig bestanden voordat je een commitbericht genereert.", "generation_failed": "Kan commitbericht niet genereren", + "no_git_extension": "Git extension is not available.", + "no_git_repo": "No git repository found.", "use_current_profile": "Huidig profiel gebruiken", "select_profile": "Selecteer een API-profiel voor het genereren van commitberichten" } diff --git a/src/i18n/locales/pl/common.json b/src/i18n/locales/pl/common.json index db6cbb73763..90b4f1860bf 100644 --- a/src/i18n/locales/pl/common.json +++ b/src/i18n/locales/pl/common.json @@ -257,6 +257,8 @@ "generating": "Generowanie wiadomości commita...", "no_changes": "Nie wykryto zmian. Przygotuj lub zmodyfikuj pliki przed wygenerowaniem wiadomości commita.", "generation_failed": "Nie udało się wygenerować wiadomości commita", + "no_git_extension": "Git extension is not available.", + "no_git_repo": "No git repository found.", "use_current_profile": "Użyj bieżącego profilu", "select_profile": "Wybierz profil API do generowania wiadomości commita" } diff --git a/src/i18n/locales/pt-BR/common.json b/src/i18n/locales/pt-BR/common.json index baae0807682..2e256bd8afc 100644 --- a/src/i18n/locales/pt-BR/common.json +++ b/src/i18n/locales/pt-BR/common.json @@ -257,6 +257,8 @@ "generating": "Gerando mensagem de commit...", "no_changes": "Nenhuma alteração detectada. Prepare ou modifique arquivos antes de gerar uma mensagem de commit.", "generation_failed": "Falha ao gerar mensagem de commit", + "no_git_extension": "Git extension is not available.", + "no_git_repo": "No git repository found.", "use_current_profile": "Usar perfil atual", "select_profile": "Selecione um perfil de API para a geração de mensagens de commit" } diff --git a/src/i18n/locales/ru/common.json b/src/i18n/locales/ru/common.json index bf7f1d93fe2..71a04ae742f 100644 --- a/src/i18n/locales/ru/common.json +++ b/src/i18n/locales/ru/common.json @@ -257,6 +257,8 @@ "generating": "Генерация сообщения коммита...", "no_changes": "Изменения не обнаружены. Подготовьте или измените файлы перед генерацией сообщения коммита.", "generation_failed": "Не удалось сгенерировать сообщение коммита", + "no_git_extension": "Git extension is not available.", + "no_git_repo": "No git repository found.", "use_current_profile": "Использовать текущий профиль", "select_profile": "Выберите профиль API для генерации сообщения коммита" } diff --git a/src/i18n/locales/tr/common.json b/src/i18n/locales/tr/common.json index 46e8adb9a2e..b00a3a5f353 100644 --- a/src/i18n/locales/tr/common.json +++ b/src/i18n/locales/tr/common.json @@ -257,6 +257,8 @@ "generating": "Commit mesajı oluşturuluyor...", "no_changes": "Değişiklik algılanmadı. Commit mesajı oluşturmadan önce dosyaları hazırla veya değiştir.", "generation_failed": "Commit mesajı oluşturulamadı", + "no_git_extension": "Git extension is not available.", + "no_git_repo": "No git repository found.", "use_current_profile": "Mevcut profili kullan", "select_profile": "Commit mesajı oluşturma için bir API profili seç" } diff --git a/src/i18n/locales/vi/common.json b/src/i18n/locales/vi/common.json index 856d5e748a7..0f18f74d2c2 100644 --- a/src/i18n/locales/vi/common.json +++ b/src/i18n/locales/vi/common.json @@ -264,6 +264,8 @@ "generating": "Đang tạo thông điệp commit...", "no_changes": "Không phát hiện thay đổi nào. Hãy chuẩn bị hoặc chỉnh sửa tệp trước khi tạo thông điệp commit.", "generation_failed": "Không thể tạo thông điệp commit", + "no_git_extension": "Git extension is not available.", + "no_git_repo": "No git repository found.", "use_current_profile": "Sử dụng hồ sơ hiện tại", "select_profile": "Chọn một hồ sơ API để tạo thông điệp commit" } diff --git a/src/i18n/locales/zh-CN/common.json b/src/i18n/locales/zh-CN/common.json index b205ecebbed..047727812db 100644 --- a/src/i18n/locales/zh-CN/common.json +++ b/src/i18n/locales/zh-CN/common.json @@ -262,6 +262,8 @@ "generating": "正在生成提交信息...", "no_changes": "未检测到更改。请在生成提交信息之前暂存或修改文件。", "generation_failed": "生成提交信息失败", + "no_git_extension": "Git extension is not available.", + "no_git_repo": "No git repository found.", "use_current_profile": "使用当前配置", "select_profile": "选择用于生成提交信息的 API 配置" } diff --git a/src/i18n/locales/zh-TW/common.json b/src/i18n/locales/zh-TW/common.json index 0400390c1cc..2ddb6a92a31 100644 --- a/src/i18n/locales/zh-TW/common.json +++ b/src/i18n/locales/zh-TW/common.json @@ -257,6 +257,8 @@ "generating": "正在產生提交訊息...", "no_changes": "未偵測到變更。請在產生提交訊息之前暫存或修改檔案。", "generation_failed": "產生提交訊息失敗", + "no_git_extension": "Git extension is not available.", + "no_git_repo": "No git repository found.", "use_current_profile": "使用目前的設定檔", "select_profile": "選擇用於產生提交訊息的 API 設定檔" } diff --git a/src/utils/__tests__/commit-message-generator.spec.ts b/src/utils/__tests__/commit-message-generator.spec.ts index 87108913c13..edc9d50ddf8 100644 --- a/src/utils/__tests__/commit-message-generator.spec.ts +++ b/src/utils/__tests__/commit-message-generator.spec.ts @@ -3,12 +3,34 @@ import * as singleCompletionHandlerModule from "../single-completion-handler" import type { ProviderSettings } from "@roo-code/types" vi.mock("../single-completion-handler") -vi.mock("child_process") + +// Mock child_process.exec to simulate git commands +const mockExecImpl = vi.fn() + +vi.mock("child_process", async (importOriginal) => { + const actual = await importOriginal() + return { + ...actual, + exec: (...args: any[]) => mockExecImpl(...args), + } +}) + vi.mock("util", async (importOriginal) => { const actual = await importOriginal() return { ...actual, - promisify: vi.fn((fn: any) => fn), + promisify: + (fn: any) => + (...args: any[]) => + new Promise((resolve, reject) => { + fn(...args, (err: Error | null, result: any) => { + if (err) { + reject(err) + } else { + resolve(result) + } + }) + }), } }) @@ -57,6 +79,14 @@ describe("commit-message-generator", () => { expect(result).toBe("feat: add feature") }) + it("strips language-tagged markdown code blocks from the result", async () => { + mockSingleCompletionHandler.mockResolvedValue("```text\nfix: resolve null check\n```") + + const result = await generateCommitMessageFromDiff(mockApiConfig, "some diff") + + expect(result).toBe("fix: resolve null check") + }) + it("propagates errors from the completion handler", async () => { mockSingleCompletionHandler.mockRejectedValue(new Error("API Error")) @@ -66,28 +96,62 @@ describe("commit-message-generator", () => { describe("getGitDiff", () => { it("returns staged diff when available", async () => { - const { exec } = await import("child_process") - const mockExec = vi.mocked(exec) as any - mockExec.mockImplementation( + mockExecImpl.mockImplementation( ( cmd: string, - _opts: any, - callback?: (err: Error | null, result: { stdout: string; stderr: string }) => void, + _opts: unknown, + callback: (err: Error | null, result: { stdout: string; stderr: string }) => void, ) => { - if (callback) { - if (cmd === "git diff --cached") { - callback(null, { stdout: "staged changes", stderr: "" }) - } else { - callback(null, { stdout: "", stderr: "" }) - } + if (cmd.includes("--cached")) { + callback(null, { stdout: "staged changes", stderr: "" }) + } else { + callback(null, { stdout: "unstaged changes", stderr: "" }) } - return { stdout: cmd === "git diff --cached" ? "staged changes" : "", stderr: "" } }, ) - // Since we mock promisify, exec is already "promisified" via our mock - // The actual function uses execAsync which is promisify(exec) - // We need to test the logic differently since promisify is mocked + const result = await getGitDiff("/workspace") + + expect(result).toBe("staged changes") + expect(mockExecImpl).toHaveBeenCalledWith( + "git diff --cached --no-color", + expect.objectContaining({ cwd: "/workspace" }), + expect.any(Function), + ) + }) + + it("falls back to unstaged diff when nothing is staged", async () => { + mockExecImpl.mockImplementation( + ( + cmd: string, + _opts: unknown, + callback: (err: Error | null, result: { stdout: string; stderr: string }) => void, + ) => { + if (cmd.includes("--cached")) { + callback(null, { stdout: "", stderr: "" }) + } else { + callback(null, { stdout: "unstaged changes", stderr: "" }) + } + }, + ) + + const result = await getGitDiff("/workspace") + + expect(result).toBe("unstaged changes") + }) + + it("throws an error when git command fails", async () => { + mockExecImpl.mockImplementation( + ( + _cmd: string, + _opts: unknown, + callback: (err: Error | null, result: { stdout: string; stderr: string }) => void, + ) => { + callback(new Error("git not found"), { stdout: "", stderr: "" }) + }, + ) + + await expect(getGitDiff("/workspace")).rejects.toThrow("Failed to get git diff") }) }) }) diff --git a/src/utils/commit-message-generator.ts b/src/utils/commit-message-generator.ts index 3429abaabd9..b4a2ec70215 100644 --- a/src/utils/commit-message-generator.ts +++ b/src/utils/commit-message-generator.ts @@ -5,6 +5,7 @@ import { promisify } from "util" import type { ProviderSettings } from "@roo-code/types" import { singleCompletionHandler } from "./single-completion-handler" +import { t } from "../i18n" const execAsync = promisify(exec) @@ -30,7 +31,7 @@ Git diff: export async function getGitDiff(workspaceRoot: string): Promise { try { // Try staged changes first - const { stdout: stagedDiff } = await execAsync("git diff --cached", { + const { stdout: stagedDiff } = await execAsync("git diff --cached --no-color", { cwd: workspaceRoot, maxBuffer: 1024 * 1024, }) @@ -40,7 +41,7 @@ export async function getGitDiff(workspaceRoot: string): Promise { } // Fall back to unstaged changes - const { stdout: unstagedDiff } = await execAsync("git diff", { + const { stdout: unstagedDiff } = await execAsync("git diff --no-color", { cwd: workspaceRoot, maxBuffer: 1024 * 1024, }) @@ -64,7 +65,7 @@ export async function generateCommitMessageFromDiff(apiConfiguration: ProviderSe // Clean up the result - remove any markdown formatting the model might add return result - .replace(/^```[\s\S]*?\n/, "") + .replace(/^```[^\n]*\n/, "") .replace(/\n```$/, "") .trim() } @@ -87,7 +88,7 @@ export async function setScmInputBoxMessage(message: string): Promise { const gitExtension = vscode.extensions.getExtension("vscode.git") if (!gitExtension) { - vscode.window.showErrorMessage("Git extension is not available.") + vscode.window.showErrorMessage(t("common:commit.no_git_extension")) return false } @@ -95,7 +96,7 @@ export async function setScmInputBoxMessage(message: string): Promise { const api = git.getAPI(1) if (!api || api.repositories.length === 0) { - vscode.window.showErrorMessage("No git repository found.") + vscode.window.showErrorMessage(t("common:commit.no_git_repo")) return false }