Skip to content

Commit 481998f

Browse files
feat(scm): add Source Control button for commit message generation
Wires the generator into VS Code. Part 3 of 4 for AI commit-message generation: a button in the Source Control panel that writes a message into the commit box. The command is contributed to both `scm/title` and `scm/inputBox`, so it is reachable from the panel header and from the commit box itself. Nothing the user has typed is ever overwritten. A non-empty box short-circuits before any request is made, rather than spending tokens on a message that would be discarded, and the box is compared against its captured value afterwards so text typed while the request was in flight survives too. Only a box that was empty at the start and is still empty at the end gets written to. The target repository is now resolved rather than assumed. The SCM menus pass the `SourceControl` that was clicked, which identifies it exactly; without one, the only unambiguous case is a workspace with a single repository. Previously this fell back to `repositories[0]`, which in a multi-root workspace would eventually describe one repository's changes in another's commit box. Each `getCommitContext` outcome now gets its own response: no changes is informational, a collection failure reports why, and a missing repository is reported as such rather than as "no changes". `packages/build` gains a test for the command icon schema. That field was widened to accept a `{light, dark}` pair for this button, and the existing fixtures only use codicon strings, so nothing would have caught it being narrowed back. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 5134ec8 commit 481998f

44 files changed

Lines changed: 769 additions & 1 deletion

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// npx vitest run src/__tests__/types.test.ts
2+
3+
import { contributesSchema } from "../types.js"
4+
5+
describe("contributes commands schema", () => {
6+
// Reached through `.shape` so this stays focused on the icon field, without needing a whole
7+
// valid `contributes` object around it.
8+
const commandsSchema = contributesSchema.shape.commands
9+
10+
const command = (icon: unknown) => [
11+
{ command: "zoo-code.generateCommitMessage", title: "%command.generateCommitMessage.title%", icon },
12+
]
13+
14+
it("accepts a codicon reference", () => {
15+
expect(commandsSchema.safeParse(command("$(edit)")).success).toBe(true)
16+
})
17+
18+
// The Source Control button ships a PNG per theme rather than a codicon. This field used to
19+
// allow only a string, which rejected the manifest outright when generating the nightly build.
20+
it("accepts a pair of theme-specific icon paths", () => {
21+
const icon = { light: "assets/icons/panel_light.png", dark: "assets/icons/panel_dark.png" }
22+
23+
expect(commandsSchema.safeParse(command(icon)).success).toBe(true)
24+
})
25+
26+
it("rejects an icon pair that is missing a theme", () => {
27+
expect(commandsSchema.safeParse(command({ light: "assets/icons/panel_light.png" })).success).toBe(false)
28+
})
29+
})

packages/build/src/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ const commandsSchema = z.array(
3131
command: z.string(),
3232
title: z.string(),
3333
category: z.string().optional(),
34-
icon: z.string().optional(),
34+
// Either a codicon reference (e.g. `$(edit)`) or a pair of theme-specific image paths.
35+
icon: z.union([z.string(), z.object({ light: z.string(), dark: z.string() })]).optional(),
3536
}),
3637
)
3738

packages/types/src/vscode.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ export const commandIds = [
4747
"focusPanel",
4848
"toggleAutoApprove",
4949

50+
"generateCommitMessage",
51+
5052
"showRipgrepDiagnostic",
5153
] as const
5254

src/activate/__tests__/registerCommands.spec.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,10 @@ vi.mock("../../i18n", () => ({
8989
t: (key: string) => key,
9090
}))
9191

92+
vi.mock("../../services/commit-message", () => ({
93+
generateCommitMessage: vi.fn().mockResolvedValue(undefined),
94+
}))
95+
9296
vi.mock("../../services/ripgrep/diagnostic", () => ({
9397
registerRipgrepDiagnosticCommand: vi.fn().mockReturnValue({ dispose: vi.fn() }),
9498
}))
@@ -192,6 +196,17 @@ describe("registerCommands handlers", () => {
192196
expect(mockContext.subscriptions).toContain(disposable)
193197
})
194198

199+
it("generateCommitMessage forwards the clicked source control to the generator", async () => {
200+
const { generateCommitMessage } = await import("../../services/commit-message")
201+
const sourceControl = { rootUri: { fsPath: "/repo" } }
202+
203+
await handlers["zoo-code.generateCommitMessage"](sourceControl)
204+
205+
// Uses the registered provider rather than the visible one, so the Source Control button
206+
// still works while the Zoo Code sidebar is closed.
207+
expect(vi.mocked(generateCommitMessage)).toHaveBeenCalledWith(mockProvider, sourceControl)
208+
})
209+
195210
it("settingsButtonClicked posts both settingsButtonClicked and didBecomeVisible actions", () => {
196211
handlers["zoo-code.settingsButtonClicked"]()
197212

src/activate/registerCommands.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { CodeIndexManager } from "../services/code-index/manager"
1414
import { importSettingsWithFeedback } from "../core/config/importExport"
1515
import { MdmService } from "../services/mdm/MdmService"
1616
import { registerRipgrepDiagnosticCommand } from "../services/ripgrep/diagnostic"
17+
import { generateCommitMessage } from "../services/commit-message"
1718
import { t } from "../i18n"
1819

1920
/**
@@ -219,6 +220,9 @@ const getCommandsMap = ({
219220
outputChannel.appendLine(`[toggleAutoApprove] postMessageToWebview failed: ${error}`)
220221
}
221222
},
223+
// Uses `provider` rather than the visible instance so the Source Control button still works
224+
// while the Zoo Code sidebar is closed.
225+
generateCommitMessage: (sourceControl?: vscode.SourceControl) => generateCommitMessage(provider, sourceControl),
222226
})
223227

224228
export const openClineInNewTab = async ({ context, outputChannel }: Omit<RegisterCommandOptions, "provider">) => {

src/i18n/locales/ca/common.json

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/i18n/locales/de/common.json

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/i18n/locales/en/common.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@
4141
"reset_support_prompt": "Failed to reset support prompt",
4242
"enhance_prompt": "Failed to enhance prompt",
4343
"commit_message_empty_response": "The model returned an empty commit message.",
44+
"commit_message_no_repository": "No Git repository found in the Source Control panel.",
45+
"commit_message_failed": "Failed to generate commit message: {{error}}",
46+
"commit_message_ambiguous_repository": "Several Git repositories are open. Use the Zoo Code button in the Source Control panel of the repository you want.",
4447
"get_system_prompt": "Failed to get system prompt",
4548
"search_commits": "Failed to search commits",
4649
"save_api_config": "Failed to save api configuration",
@@ -161,6 +164,10 @@
161164
},
162165
"info": {
163166
"no_changes": "No changes found.",
167+
"commit_message_generating": "Generating commit message...",
168+
"commit_message_no_changes": "No changes to commit.",
169+
"commit_message_box_not_empty": "Kept your commit message. Clear the box to generate a new one.",
170+
"commit_message_already_generating": "Already generating a commit message.",
164171
"clipboard_copy": "System prompt successfully copied to clipboard",
165172
"history_cleanup": "Cleaned up {{count}} task(s) with missing files from history.",
166173
"custom_storage_path_set": "Custom storage path set: {{path}}",

src/i18n/locales/es/common.json

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/i18n/locales/fr/common.json

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)