Skip to content
This repository was archived by the owner on Feb 25, 2026. It is now read-only.

feat: add commit message generation#327

Merged
markijbema merged 14 commits intodevfrom
mark/feat-commit-message-generation
Feb 19, 2026
Merged

feat: add commit message generation#327
markijbema merged 14 commits intodevfrom
mark/feat-commit-message-generation

Conversation

@markijbema
Copy link
Contributor

@markijbema markijbema commented Feb 16, 2026

What

AI-powered commit message generation using Conventional Commits format. Uses small_model from the configured provider — no user model selection needed.

CleanShot 2026-02-19 at 16 23 43

private readonly baseUrl: string
private readonly authHeader: string
private readonly authUsername = "opencode"
private readonly authUsername = "kilo"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure why this broke, but it seems like this was changed in the backend breaking the vscode extension.

@markijbema markijbema marked this pull request as ready for review February 16, 2026 17:00
connectionService: KiloConnectionService,
): vscode.Disposable[] {
const command = vscode.commands.registerCommand("kilo-code.new.generateCommitMessage", async () => {
const extension = vscode.extensions.getExtension<GitExtensionExports>("vscode.git")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: Ensure the built-in Git extension is activated before accessing exports

vscode.extensions.getExtension('vscode.git') can return an extension object with exports undefined until it’s activated. Consider await extension.activate() before calling extension.exports.getAPI(1) to avoid incorrectly reporting "No Git repository found".

return
}

const client = connectionService.getHttpClient()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRITICAL: connectionService.getHttpClient() appears to throw, so this null-check won’t work

packages/kilo-vscode/src/services/cli-backend/connection-service.ts defines getHttpClient(): HttpClient and throws when not connected. This line will throw before reaching the if (!client) branch, so the command will likely fail without showing "Kilo backend is not connected".

Consider wrapping this call in a try/catch (or adding a non-throwing accessor) so the user gets the intended error message.

type: "boolean",
default: false,
})
.option("staged-only", {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: --staged-only option is defined but not used

The command builder defines staged-only (default true), but the handler always calls generateCommitMessage({ path: process.cwd() }) without using args['staged-only']. This will surprise users and makes the option misleading.

Either wire this flag through to the underlying git-context selection logic, or remove the option until it’s supported.

function parseNameStatus(output: string): Array<{ status: string; path: string }> {
if (!output) return []
return output.split("\n").map((line) => {
const [status, ...rest] = line.split("\t")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Rename entries from git diff --name-status are not parsed correctly

For rename status lines (e.g. R100 old.ts new.ts), rest.join('\t') produces a combined string like old.ts new.ts. Downstream, this entry.path is treated as a single path (lock-file filtering, selectedFiles filtering, and git diff ... -- entry.path), which will break for renames.

Consider parsing rename lines into a structured shape (old/new), and using the new path for .path (and/or storing both).

})
}

function mapStatus(code: string): FileChange["status"] {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: FileChange['status'] includes untracked but it’s never produced

mapStatus() maps ?? / ? to added, while FileChange['status'] allows untracked. If untracked is intentionally modeled, consider returning untracked for ?? and adjusting handling; otherwise consider removing untracked from the union to avoid confusion.

@kilo-code-bot
Copy link
Contributor

kilo-code-bot bot commented Feb 16, 2026

Code Review Summary

Status: 3 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 2
WARNING 1
SUGGESTION 0

Fix these issues in Kilo Cloud

Issue Details (click to expand)

CRITICAL

File Line Issue
packages/opencode/src/commit-message/git-context.ts 137 git() trims leading whitespace via trimEnd() only, but parsePorcelain() relies on the first 2 characters being the XY status columns — trimEnd() doesn't affect leading chars, however stdout.toString() may still lose data if Bun.spawnSync truncates. More critically, git status --porcelain output has a fixed-width format where the first char is the index status and the second is the worktree status; xy.trim() on line 163 collapses " M""M", losing the distinction between staged and unstaged changes.
packages/kilo-vscode/src/services/commit-message/index.ts 63 Possible runtime error if getHttpClient() returns undefined/null instead of throwing — client.generateCommitMessage(...) would fail with an unhelpful error. The null check on line 50 mitigates this, but the catch block on line 46 is empty-ish (only shows error message, doesn't log the original error).

WARNING

File Line Issue
packages/opencode/src/commit-message/generate.ts 168 new AbortController().signal creates an orphaned controller that can never be aborted. If the HTTP client disconnects, the LLM stream continues running to completion, wasting tokens and server resources. Consider threading an AbortSignal from the request context.
Other Observations (not in diff)

Issues found in unchanged code that cannot receive inline comments:

File Line Issue
(none) (n/a) (n/a)
Files Reviewed (13 files)

})

it("shows error when no git repository is found", async () => {
vi.mocked(vscode.extensions.getExtension).mockReturnValue({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Mocked vscode.git extension is missing isActive/activate, which can cause the command callback to throw

registerCommitMessageService() now checks if (!extension.isActive) await extension.activate(). In this test, the mocked extension object only provides exports, so extension.activate will be undefined and the callback will throw before reaching the "No Git repository found" branch.

Add isActive: true (or activate: vi.fn().mockResolvedValue(undefined) + isActive: false) to the mocked extension return values so the test exercises the intended path.

}),
},
} as any)
vi.mocked(mockConnectionService.getHttpClient as any).mockReturnValue(null)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Test setup implies getHttpClient() returns null, but the production code assumes it throws (and never null-checks)

This test does mockReturnValue(null) and expects an error toast, but registerCommitMessageService() only handles the disconnected case via try/catch and then calls client.generateCommitMessage(...) unconditionally. If getHttpClient() ever returned null without throwing, this would become a runtime error instead of a user-facing message.

Either (a) update the test to mockImplementation(() => { throw new Error('...') }) to match the connection service contract, or (b) add an explicit if (!client) guard in the command implementation.

return
}

const path = folder.uri.fsPath
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: Use the git repository root path instead of the first workspace folder

When VS Code has multiple workspace folders (or the repo is not the first folder), using workspaceFolders[0] can generate a commit message for the wrong directory. Since you already have repository.rootUri, using that is more precise.

Suggested change
const path = folder.uri.fsPath
const path = repository.rootUri.fsPath

@blacksmith-sh

This comment has been minimized.

stdout: "pipe",
stderr: "pipe",
})
return result.stdout.toString().trim()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRITICAL: git() trims leading whitespace, which can corrupt git status --porcelain parsing

Using packages/opencode/src/commit-message/git-context.ts:137, .trim() can remove the leading space of the first porcelain line (e.g. " M file"), shifting the fixed-column layout that parsePorcelain() relies on (slice(0,2) / slice(3)). This can drop the first character of the filepath (and then subsequent git diff -- <path> calls target the wrong file).

Suggested change
return result.stdout.toString().trim()
return result.stdout.toString().trimEnd()

.withProgress(
{ location: vscode.ProgressLocation.SourceControl, title: "Generating commit message..." },
async () => {
const message = await client.generateCommitMessage(path, undefined, previousMessage)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRITICAL: Possible runtime error if getHttpClient() returns undefined/null instead of throwing

packages/kilo-vscode/src/services/commit-message/index.ts:65 calls client.generateCommitMessage(...), but client is declared as HttpClient | undefined and the code only handles the throwing case. If connectionService.getHttpClient() returns undefined/null when disconnected, this will throw and show a generic failure message. Consider explicitly checking client after the call and returning early with a targeted error message.

@markijbema markijbema marked this pull request as draft February 17, 2026 10:18
@markijbema markijbema force-pushed the mark/feat-commit-message-generation branch from 49a572f to b6a4668 Compare February 19, 2026 09:05
@vercel
Copy link

vercel bot commented Feb 19, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
kilo-ui-storybook Ready Ready Preview, Comment Feb 19, 2026 11:41am

Request Review

The mockGitContext variable was declared without a type annotation,
causing TypeScript to infer a narrow type from the initial assignment
(status: "modified"). This made reassignment with status: "added"
fail with TS2322. Adding the GitContext type allows all valid status
values.
- Use trimEnd() instead of trim() in git() to preserve leading whitespace
  in porcelain output (fixes status parsing for ' M file' lines)
- Remove unused 'untracked' from FileChange status union
- Use repository.rootUri.fsPath instead of workspaceFolders[0]
- Add explicit null-check for client after try/catch
- Fix test mocks: add isActive/activate, use mockImplementation to throw,
  fix expected error message to match production code
@markijbema markijbema force-pushed the mark/feat-commit-message-generation branch from 7674375 to bc27d89 Compare February 19, 2026 15:26
content: userMessage,
},
],
abort: new AbortController().signal,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: The AbortController created here is immediately orphaned — no reference is kept, so the LLM stream can never be cancelled.

If the HTTP client disconnects (e.g. user cancels in VS Code), this stream will continue running to completion, consuming LLM tokens and server resources.

Consider accepting an AbortSignal from the Hono request context (e.g. c.req.raw.signal) and threading it through to generateCommitMessage, so the stream is cancelled when the client disconnects.

@markijbema markijbema merged commit 80482b1 into dev Feb 19, 2026
7 checks passed
@markijbema markijbema deleted the mark/feat-commit-message-generation branch February 19, 2026 15:38
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants