# Commit Message Generation — Reimplementation Guide ## Overview The commit message generation feature allows users to automatically generate [Conventional Commits](https://www.conventionalcommits.org/) messages from their staged (or unstaged) git changes using an LLM. It is accessible from: - **VS Code**: The Source Control panel title bar and the command palette (`Kilo Code: Generate Commit Message`) - **JetBrains**: A button in the commit dialog The feature collects git context (diffs, branch name, recent commits), builds a prompt, sends it to an LLM, and writes the resulting commit message into the IDE's commit input box. > **Note:** No screenshots of this feature were found in the repository. A screenshot showing the SCM panel button and generated message would be helpful here. --- ## Architecture The current implementation has a clean layered architecture that should be preserved. The key change is that the LLM integration layer will use a different calling mechanism — everything else can be largely reused or adapted. ### Layered Design ```mermaid graph TD A[IDE Integration Layer] --> B[Orchestrator] B --> C[Git Context Service] B --> D[Commit Message Generator] D --> E[Prompt Builder] D --> F[LLM Integration Point] D --> G[Response Cleaner] A --> H[VS Code Adapter] A --> I[JetBrains Adapter] style F fill:#ff9,stroke:#f90,stroke-width:3px ``` ### Component Responsibilities | Layer | Component | Responsibility | |-------|-----------|---------------| | Entry Point | `registerCommitMessageProvider()` | Wires everything up during extension activation | | IDE Integration | `CommitMessageProvider` | Registers IDE commands, dispatches to the correct adapter | | Adapter | `VSCodeCommitMessageAdapter` | VS Code SCM panel progress + writes to input box | | Adapter | `JetBrainsCommitMessageAdapter` | Returns result string to Kotlin host | | Orchestrator | `CommitMessageOrchestrator` | Sequences: git discovery → diff collection → AI generation → result delivery | | Business Logic | `CommitMessageGenerator` | Builds prompt, calls LLM, cleans response | | Git Operations | `GitExtensionService` | Runs git CLI commands, collects diffs and metadata | | Utilities | `exclusionUtils` | Filters lock files from diffs | --- ## Components to Implement ### 1. Entry Point — `registerCommitMessageProvider()` **Responsibility:** Called during extension activation to wire up all components and register commands. **Reference:** `/Users/mark/dev/kilo/kilocode-5/src/services/commit-message/index.ts` **Key details:** - Creates instances of `GitExtensionService`, `CommitMessageGenerator`, `CommitMessageOrchestrator` - Creates the appropriate adapter(s) based on the IDE environment - Registers VS Code commands and disposables - Returns disposables for cleanup ### 2. CommitMessageProvider — Command Router **Responsibility:** Registers VS Code commands and dispatches generation requests to the correct adapter. **Reference:** `/Users/mark/dev/kilo/kilocode-5/src/services/commit-message/CommitMessageProvider.ts` **Interface:** ```typescript interface CommitMessageProvider { // Register VS Code commands and return disposables register(): vscode.Disposable[] // Handle generation request from either IDE handleGenerateRequest(context?: { workspacePath?: string; selectedFiles?: string[] }): Promise } ``` **Key details:** - Registers command `kilo-code.vsc.generateCommitMessage` in the `scm/title` menu - Registers command `kilo-code.jetbrains.generateCommitMessage` for JetBrains RPC - Determines which adapter to use based on the calling context ### 3. CommitMessageOrchestrator — Workflow Coordinator **Responsibility:** Sequences the full workflow from git discovery through to delivering the result. **Reference:** `/Users/mark/dev/kilo/kilocode-5/src/services/commit-message/CommitMessageOrchestrator.ts` **Interface:** ```typescript interface CommitMessageOrchestrator { generate(options?: { workspacePath?: string selectedFiles?: string[] }): Promise } interface CommitMessageResult { message: string regenerated: boolean } ``` **Workflow sequence:** 1. Discover the git repository root 2. Collect git context via `GitExtensionService` 3. Check for re-generation (same diff as last time) 4. Call `CommitMessageGenerator.generateMessage()` with the context 5. Return the result to the adapter for delivery ### 4. CommitMessageGenerator — Business Logic **Responsibility:** Builds the prompt, calls the LLM, and cleans the response. **Reference:** `/Users/mark/dev/kilo/kilocode-5/src/services/commit-message/CommitMessageGenerator.ts` **Interface:** ```typescript interface CommitMessageGenerator { generateMessage(context: GitContext, options?: { isRegeneration?: boolean previousMessage?: string }): Promise } ``` **Key details:** - Constructs the prompt using `supportPrompt.create("COMMIT_MESSAGE", ...)` or equivalent - Loads custom instructions for the "commit" context - Handles re-generation by prepending "generate a completely different message" - Calls the LLM (see **Integration Point** below) - Cleans the response: strips code block markers and surrounding quotes **Response cleaning logic:** ```typescript function cleanResponse(raw: string): string { let cleaned = raw.trim() // Strip code block markers cleaned = cleaned.replace(/^```[\w]*\n?/, "").replace(/\n?```$/, "") // Strip surrounding quotes cleaned = cleaned.replace(/^["']|["']$/g, "") return cleaned.trim() } ``` ### 5. GitExtensionService — Git Operations **Responsibility:** Runs git CLI commands to gather all context needed for prompt construction. **Reference:** `/Users/mark/dev/kilo/kilocode-5/src/services/commit-message/GitExtensionService.ts` **Interface:** ```typescript interface GitContext { stagedFiles: FileChange[] diffs: Map // filepath → diff content branch: string recentCommits: string[] // last 5 commit summaries } interface FileChange { status: "added" | "modified" | "deleted" | "renamed" | "untracked" path: string } interface GitExtensionService { getGitContext(repoPath: string, selectedFiles?: string[]): Promise } ``` **See section: [Git Context Gathering](#git-context-gathering) for full details.** ### 6. Exclusion Utilities **Responsibility:** Filters lock files and ignored files from the diff set. **Reference:** `/Users/mark/dev/kilo/kilocode-5/src/services/commit-message/exclusionUtils.ts` **Key details:** - Uses the `ignore` library to match 60+ lock file patterns - Patterns include `package-lock.json`, `yarn.lock`, `Cargo.lock`, `poetry.lock`, `pnpm-lock.yaml`, etc. - Also respects `.kilocode-ignore` / `.roo-ignore` via `RooIgnoreController` ### 7. IDE Adapters **VS Code Adapter:** **Reference:** `/Users/mark/dev/kilo/kilocode-5/src/services/commit-message/adapters/VSCodeCommitMessageAdapter.ts` ```typescript interface VSCodeCommitMessageAdapter { generate(orchestrator: CommitMessageOrchestrator): Promise } ``` - Shows progress via `vscode.window.withProgress(ProgressLocation.SourceControl)` - Writes result to `repository.inputBox.value` **JetBrains Adapter:** **Reference:** `/Users/mark/dev/kilo/kilocode-5/src/services/commit-message/adapters/JetBrainsCommitMessageAdapter.ts` ```typescript interface JetBrainsCommitMessageAdapter { generate(orchestrator: CommitMessageOrchestrator, workspacePath: string, selectedFiles: string[]): Promise<{ message: string }> } ``` - Returns the message string for the Kotlin host to use --- ## LLM Integration Point > **⚠️ INTEGRATION POINT — This is the part that will differ from the current implementation.** ### Current Implementation (for reference only) The current code calls `singleCompletionHandler(config, prompt)` which internally uses `buildApiHandler(apiConfig)` to create a provider-specific handler. If the handler has a `completePrompt()` method, it uses single-shot completion; otherwise it streams and collects the full response. This mechanism **will not be used** in the new implementation. **Reference:** `/Users/mark/dev/kilo/kilocode-5/src/services/commit-message/CommitMessageGenerator.ts` — see `callAIForCommitMessage()` ### Required Contract The LLM integration must satisfy this contract: ```typescript interface CommitMessageLLMProvider { /** * Send a prompt to the LLM and receive a complete text response. * * This is a non-streaming, single-shot completion call. * The full response must be collected before returning. * * @param prompt - The complete prompt string including system instructions * and git context * @param config - Which model/provider to use. May be a dedicated * commit message profile or the default profile. * @returns The raw LLM response text (will be cleaned by the caller) * @throws If the LLM call fails (network error, auth error, etc.) */ complete(prompt: string, config: LLMConfig): Promise } interface LLMConfig { /** The API config ID — either `commitMessageApiConfigId` or the default */ configId: string /** Any additional model parameters if needed */ [key: string]: unknown } ``` ### What the caller provides - **Input:** A single prompt string (typically 100–2000 tokens depending on diff size). The prompt includes system instructions, git context, and any custom instructions. - **Config:** An identifier for which API configuration/model to use. This supports the dedicated `commitMessageApiConfigId` setting which lets users pick a different (often cheaper/faster) model for commit messages. ### What the caller expects - **Output:** A single string containing the commit message. May include code block markers or quotes which will be stripped by the response cleaner. - **Behavior:** Non-streaming. The call should block until the full response is available. - **Errors:** Should throw on failure so the orchestrator can catch and display an error to the user. ### Configuration Resolution The config resolution order is: 1. If `commitMessageApiConfigId` is set in global settings → use that API profile 2. Otherwise → use the default/active API profile --- ## Git Context Gathering This part is **largely reusable** from the current implementation. It uses `spawnSync` to run git CLI commands. **Reference:** `/Users/mark/dev/kilo/kilocode-5/src/services/commit-message/GitExtensionService.ts` ### Git Commands Used | Command | Purpose | |---------|---------| | `git diff --name-status --cached` | List staged file changes with status | | `git status --porcelain` | List all changes (fallback when nothing is staged) | | `git diff [--cached] -- ` | Per-file diff content | | `git branch --show-current` | Current branch name | | `git log --oneline -5` | Last 5 commit messages for context | ### File Processing Rules 1. **Lock file exclusion:** Files matching any of the 60+ lock file patterns are excluded (see `exclusionUtils`) 2. **Ignore file exclusion:** Files matching `.kilocode-ignore` / `.roo-ignore` patterns are excluded via `shouldIncludeFile()` 3. **Binary files:** Replaced with placeholder text `"Binary file has been modified"` 4. **Untracked files:** Replaced with placeholder text `"New untracked file: "` 5. **Staged vs unstaged:** Prefers staged changes (`--cached`); falls back to all changes if nothing is staged 6. **Selected files (JetBrains):** When the JetBrains adapter provides `selectedFiles`, only those files are included ### Fallback Behavior If no staged changes exist, the service falls back to `git status --porcelain` to capture all modified/untracked files. This ensures the feature works even when users haven't explicitly staged changes. --- ## Prompt Engineering ### Prompt Template The prompt is built using `supportPrompt.create("COMMIT_MESSAGE", { gitContext, customInstructions })`. The template is a ~70-line Conventional Commits guide that includes: 1. **System instruction:** You are a commit message generator following Conventional Commits format 2. **Format specification:** `type(scope): description` with allowed types (`feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `build`, `ci`, `chore`, `revert`) 3. **Rules:** Keep subject under 72 chars, use imperative mood, no period at end, etc. 4. **Git context injection:** Branch name, recent commits, file changes, diffs 5. **Custom instructions:** User-defined instructions from `.kilocode/rules/` for the "commit" context ### Custom Instructions Custom instructions are loaded via `addCustomInstructions()` for the `"commit"` mode context. Users can place files in `.kilocode/rules/` that apply to commit message generation. ### Re-generation Logic When the user requests a new message for the same diff: 1. The orchestrator detects that the diff hash matches the previous generation 2. It prepends to the prompt: `"GENERATE A COMPLETELY DIFFERENT COMMIT MESSAGE. The previous message was: "` 3. This ensures variety when the user isn't satisfied with the first suggestion ### Prompt Template Override Users can override the entire prompt template via the `customSupportPrompts.COMMIT_MESSAGE` setting. This allows complete customization of the commit message format and style. --- ## IDE Integration ### VS Code **Command registration:** ```typescript // In package.json contributes.commands { "command": "kilo-code.vsc.generateCommitMessage", "title": "Generate Commit Message" } // In package.json contributes.menus { "scm/title": [{ "command": "kilo-code.vsc.generateCommitMessage" }] } ``` **Progress reporting:** ```typescript await vscode.window.withProgress( { location: vscode.ProgressLocation.SourceControl, title: "Generating commit message..." }, async () => { /* ... generation logic ... */ } ) ``` **Result delivery:** ```typescript repository.inputBox.value = generatedMessage ``` **Reference:** `/Users/mark/dev/kilo/kilocode-5/src/services/commit-message/adapters/VSCodeCommitMessageAdapter.ts` ### JetBrains **Kotlin side:** A `CommitMessageHandler` adds a button to the commit dialog. When clicked, it sends an RPC command. **RPC command:** `kilo-code.jetbrains.generateCommitMessage` with arguments `[workspacePath, selectedFiles]` **Result delivery:** The result is returned via RPC to Kotlin which calls `panel.setCommitMessage(result.message)` **Reference:** `/Users/mark/dev/kilo/kilocode-5/src/services/commit-message/adapters/JetBrainsCommitMessageAdapter.ts` --- ## Configuration | Setting | Type | Description | |---------|------|-------------| | `commitMessageApiConfigId` | `string` | ID of a dedicated API profile for commit messages. Allows using a cheaper/faster model. | | `customSupportPrompts.COMMIT_MESSAGE` | `string` | Override the entire commit message prompt template | | Custom instructions in `.kilocode/rules/` | files | Per-project or global instructions applied to the "commit" context | | `.kilocode-ignore` / `.roo-ignore` | files | File exclusion patterns — excluded files won't appear in diffs | ### Settings UI **Reference:** `/Users/mark/dev/kilo/kilocode-5/webview-ui/src/components/settings/CommitMessagePromptSettings.tsx` A dropdown in the Settings panel allows users to select which API configuration to use for commit messages. This is separate from the main chat model selection. --- ## Error Handling ### Edge Cases to Handle | Scenario | Handling | |----------|----------| | No git repository found | Show error message: "No git repository found in the current workspace" | | No changes detected | Show info message: "No changes to generate a commit message for" | | Empty diff after filtering | Show info message: "All changed files are excluded by lock file or ignore rules" | | LLM call fails (network/auth) | Show error with details; do not write to input box | | LLM returns empty response | Retry once; if still empty, show error | | Very large diff (token limit) | Truncate diffs, prioritize staged files, include file names even if diffs are cut | | Git command fails | Log the error, attempt to continue with partial context | | User cancels during progress | Abort the LLM call if possible, clean up gracefully | | Binary files in diff | Replace with placeholder text instead of including binary content | | Concurrent generation requests | Debounce or queue — don't send multiple simultaneous LLM requests | ### Error Display - **VS Code:** Use `vscode.window.showErrorMessage()` or `showInformationMessage()` as appropriate - **JetBrains:** Return error in the RPC response for Kotlin-side display --- ## Testing Strategy ### Unit Tests **Reference for existing tests:** - `/Users/mark/dev/kilo/kilocode-5/src/services/commit-message/__tests__/CommitMessageGenerator.spec.ts` - `/Users/mark/dev/kilo/kilocode-5/src/services/commit-message/__tests__/GitExtensionService.spec.ts` - `/Users/mark/dev/kilo/kilocode-5/src/services/commit-message/__tests__/progress-reporting.spec.ts` | Component | What to Test | |-----------|-------------| | `CommitMessageGenerator` | Prompt construction, response cleaning, re-generation logic, custom instructions injection | | `GitExtensionService` | Parsing of `git diff --name-status` output, `git status --porcelain` output, branch name extraction, handling of binary files and untracked files | | `exclusionUtils` | Lock file pattern matching — ensure all 60+ patterns work, edge cases with nested paths | | `CommitMessageOrchestrator` | Full workflow sequencing, re-generation detection, error propagation | | `VSCodeCommitMessageAdapter` | Progress reporting, writing to input box, error display | | `JetBrainsCommitMessageAdapter` | Correct return format, error handling | | Response cleaner | Stripping code blocks, quotes, whitespace normalization | ### Integration Tests | Test | Description | |------|-------------| | Full generation flow | Mock the LLM call, verify end-to-end from git context to result delivery | | Re-generation | Verify that requesting a new message for the same diff includes the "different message" instruction | | Config resolution | Verify `commitMessageApiConfigId` is used when set, falls back to default otherwise | | Large diff handling | Verify truncation behavior with oversized diffs | ### Mocking Strategy - **Git commands:** Mock `spawnSync` to return predefined git output - **LLM calls:** Mock the LLM integration point to return controlled responses - **VS Code API:** Mock `vscode.window.withProgress`, `repository.inputBox`, and command registration - **File system:** Mock ignore file reading for exclusion tests ### Test File Convention Per project convention, test files should use `.spec.ts` extension and live in `__tests__/` directories adjacent to the source code.