feat(scm): generate commit messages from the Source Control panel - #1218
feat(scm): generate commit messages from the Source Control panel#1218Rafael-Silva-Oliveira wants to merge 1 commit into
Conversation
Adds a Zoo Code button to the Source Control title bar that summarizes the
current changes into a commit message and writes it into the commit input box.
- Collects staged changes first, falling back to the whole working tree when
nothing is staged so the button still works before staging. `git status
--short` is used on the fallback path so untracked files stay visible.
- Registers COMMIT_MESSAGE as a support prompt, so the default Conventional
Commits template is editable from Settings > Prompts with a reset button,
reusing the existing customSupportPrompts plumbing.
- Adds a `commitMessageApiConfigId` setting in Settings > Providers to pick a
small, fast profile for this one task, mirroring `enhancementApiConfigId`.
Falls back to the active profile when unset or when the saved profile has
since been deleted.
`packages/build` had to widen `commandsSchema.icon` to accept a themed
{light, dark} pair; it previously allowed only a codicon string, which would
have failed the nightly manifest build.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
📝 WalkthroughWalkthroughAdds AI-generated commit messages from Git Source Control. The change collects staged or working-tree context, selects a provider profile, inserts the generated message into the repository commit input, exposes a configurable model setting, registers the command, and adds tests and localization. ChangesCommit-message generation flow
Estimated code review effort: 4 (Complex) | ~45 minutes Possibly related issues
Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
|
@Rafael-Silva-Oliveira If you want to join up on a PR or merge both - i don't mind. Funnily enough: PRs might need to be republished anyway as stacked PRs now to split the work correctly. As reviewing 4 separate PRs that all base on the previous one - is a lot of work. And it needs to be split, because there is a decent bit of code to catch edge cases, format better,etc. I am very happy with what i accomplished, but my adhd means that when i lost focus - the pr kinda got left in the forever box. |
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (3)
src/utils/__tests__/git.spec.ts (1)
378-378: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueExplain or replace the double assertions.
as unknown as typeof execbypasses the overload checks forexec. The nearby comment explains the fake return value, but it does not explain why each callback implementation safely matchestypeof exec.Use a typed mock adapter where possible. If the cast is unavoidable, add a nearby comment that explains the overload mismatch. As per coding guidelines, “Use double assertions only as a last resort and explain them with a comment.”
Also applies to: 416-416
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/utils/__tests__/git.spec.ts` at line 378, Update the mocked exec setup around vitest.mocked(exec) at both occurrences to avoid the double assertion by using a typed mock adapter compatible with exec overloads; if that is not feasible, retain the cast only with a nearby comment explicitly documenting the overload mismatch and why it is safe.Source: Coding guidelines
webview-ui/src/components/settings/__tests__/SettingsView.spec.tsx (1)
376-398: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winTest the unset save path.
This test covers a saved profile ID only. Add a test that selects “Use currently selected API configuration,” saves, and expects
commitMessageApiConfigId: ""inupdatedSettings.This verifies that the unset value is not omitted during message serialization.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@webview-ui/src/components/settings/__tests__/SettingsView.spec.tsx` around lines 376 - 398, Add a test alongside “includes the commit message model in the saved settings” that selects “Use currently selected API configuration,” saves the settings, and asserts the updateSettings message contains commitMessageApiConfigId set to an empty string in updatedSettings. Ensure the assertion verifies the unset value is serialized rather than omitted.Source: Coding guidelines
webview-ui/src/components/settings/__tests__/CommitMessageModelSelect.spec.tsx (1)
12-28: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winType the Select test double.
The
anyprops and event hide theonValueChangecontract. In particular,getAttribute()can returnnull, although the production callback receives a string.Define typed mock props and use
dataset.nextValuewith a null check before callingonValueChange.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@webview-ui/src/components/settings/__tests__/CommitMessageModelSelect.spec.tsx` around lines 12 - 28, Type the Select test double’s props and click event instead of using any, especially in Select and its onValueChange callback. Read the value from dataset.nextValue, check that it is defined, then invoke onValueChange with the resulting string to preserve the production contract; apply suitable typed props to the related SelectTrigger, SelectValue, SelectContent, and SelectItem mocks.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/i18n/locales/ca/common.json`:
- Line 48: Update the Catalan translation values for commit_message_failed and
the corresponding occurrence to replace “missatge de comissió” with the
established “missatge de confirmació” or “missatge de commit” terminology,
preserving the existing error placeholder.
In `@src/package.nls.ca.json`:
- Line 19: Update the Catalan value for command.generateCommitMessage.title to
use “commit” or the repository’s established Catalan Git terminology instead of
“comissió”; leave the command key and surrounding translations unchanged.
In `@src/services/commit-message/index.ts`:
- Line 87: Update getCommitContext to detect when HEAD is unborn before running
the working-tree fallback, and return usable status context or compare changes
against an empty baseline instead of invoking git diff HEAD. Preserve existing
behavior for repositories with an initial commit, and add a regression test
covering working-tree changes in an uncommitted repository.
In `@src/utils/git.ts`:
- Around line 398-400: Update the unstaged fallback’s git diff invocation in the
function containing the `status`, `diff`, and `truncateOutput` flow to remove
the `HEAD` argument and run `git diff` with only `COMMIT_DIFF_ARGS`, preserving
the existing output formatting and truncation.
In `@webview-ui/src/components/settings/CommitMessageModelSelect.tsx`:
- Around line 35-43: Update the label and SelectTrigger in
CommitMessageModelSelect to associate them accessibly: assign a unique id to the
setting label and reference that id via SelectTrigger’s aria-labelledby
attribute. Add or update the relevant component test to verify the
label-to-trigger relationship.
In `@webview-ui/src/i18n/locales/ca/prompts.json`:
- Around line 107-110: Correct the Catalan Git terminology in
webview-ui/src/i18n/locales/ca/prompts.json lines 107-110 by replacing
“comissió” in the COMMIT_MESSAGE label and description with “commit”
terminology; also update webview-ui/src/i18n/locales/ca/settings.json lines
359-363 by replacing “missatges de comissió” with the corresponding Git commit
wording.
---
Nitpick comments:
In `@src/utils/__tests__/git.spec.ts`:
- Line 378: Update the mocked exec setup around vitest.mocked(exec) at both
occurrences to avoid the double assertion by using a typed mock adapter
compatible with exec overloads; if that is not feasible, retain the cast only
with a nearby comment explicitly documenting the overload mismatch and why it is
safe.
In
`@webview-ui/src/components/settings/__tests__/CommitMessageModelSelect.spec.tsx`:
- Around line 12-28: Type the Select test double’s props and click event instead
of using any, especially in Select and its onValueChange callback. Read the
value from dataset.nextValue, check that it is defined, then invoke
onValueChange with the resulting string to preserve the production contract;
apply suitable typed props to the related SelectTrigger, SelectValue,
SelectContent, and SelectItem mocks.
In `@webview-ui/src/components/settings/__tests__/SettingsView.spec.tsx`:
- Around line 376-398: Add a test alongside “includes the commit message model
in the saved settings” that selects “Use currently selected API configuration,”
saves the settings, and asserts the updateSettings message contains
commitMessageApiConfigId set to an empty string in updatedSettings. Ensure the
assertion verifies the unset value is serialized rather than omitted.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 1ae74a4a-5156-4ee1-9dc9-51cd851467ef
📒 Files selected for processing (88)
packages/build/src/types.tspackages/types/src/global-settings.tspackages/types/src/vscode-extension-host.tspackages/types/src/vscode.tssrc/activate/registerCommands.tssrc/core/webview/ClineProvider.tssrc/i18n/locales/ca/common.jsonsrc/i18n/locales/de/common.jsonsrc/i18n/locales/en/common.jsonsrc/i18n/locales/es/common.jsonsrc/i18n/locales/fr/common.jsonsrc/i18n/locales/hi/common.jsonsrc/i18n/locales/id/common.jsonsrc/i18n/locales/it/common.jsonsrc/i18n/locales/ja/common.jsonsrc/i18n/locales/ko/common.jsonsrc/i18n/locales/nl/common.jsonsrc/i18n/locales/pl/common.jsonsrc/i18n/locales/pt-BR/common.jsonsrc/i18n/locales/ru/common.jsonsrc/i18n/locales/tr/common.jsonsrc/i18n/locales/vi/common.jsonsrc/i18n/locales/zh-CN/common.jsonsrc/i18n/locales/zh-TW/common.jsonsrc/package.jsonsrc/package.nls.ca.jsonsrc/package.nls.de.jsonsrc/package.nls.es.jsonsrc/package.nls.fr.jsonsrc/package.nls.hi.jsonsrc/package.nls.id.jsonsrc/package.nls.it.jsonsrc/package.nls.ja.jsonsrc/package.nls.jsonsrc/package.nls.ko.jsonsrc/package.nls.nl.jsonsrc/package.nls.pl.jsonsrc/package.nls.pt-BR.jsonsrc/package.nls.ru.jsonsrc/package.nls.tr.jsonsrc/package.nls.vi.jsonsrc/package.nls.zh-CN.jsonsrc/package.nls.zh-TW.jsonsrc/services/commit-message/__tests__/generateCommitMessage.spec.tssrc/services/commit-message/index.tssrc/shared/support-prompt.tssrc/utils/__tests__/git.spec.tssrc/utils/git.tswebview-ui/src/components/settings/CommitMessageModelSelect.tsxwebview-ui/src/components/settings/SettingsView.tsxwebview-ui/src/components/settings/__tests__/CommitMessageModelSelect.spec.tsxwebview-ui/src/components/settings/__tests__/SettingsView.spec.tsxwebview-ui/src/i18n/locales/ca/prompts.jsonwebview-ui/src/i18n/locales/ca/settings.jsonwebview-ui/src/i18n/locales/de/prompts.jsonwebview-ui/src/i18n/locales/de/settings.jsonwebview-ui/src/i18n/locales/en/prompts.jsonwebview-ui/src/i18n/locales/en/settings.jsonwebview-ui/src/i18n/locales/es/prompts.jsonwebview-ui/src/i18n/locales/es/settings.jsonwebview-ui/src/i18n/locales/fr/prompts.jsonwebview-ui/src/i18n/locales/fr/settings.jsonwebview-ui/src/i18n/locales/hi/prompts.jsonwebview-ui/src/i18n/locales/hi/settings.jsonwebview-ui/src/i18n/locales/id/prompts.jsonwebview-ui/src/i18n/locales/id/settings.jsonwebview-ui/src/i18n/locales/it/prompts.jsonwebview-ui/src/i18n/locales/it/settings.jsonwebview-ui/src/i18n/locales/ja/prompts.jsonwebview-ui/src/i18n/locales/ja/settings.jsonwebview-ui/src/i18n/locales/ko/prompts.jsonwebview-ui/src/i18n/locales/ko/settings.jsonwebview-ui/src/i18n/locales/nl/prompts.jsonwebview-ui/src/i18n/locales/nl/settings.jsonwebview-ui/src/i18n/locales/pl/prompts.jsonwebview-ui/src/i18n/locales/pl/settings.jsonwebview-ui/src/i18n/locales/pt-BR/prompts.jsonwebview-ui/src/i18n/locales/pt-BR/settings.jsonwebview-ui/src/i18n/locales/ru/prompts.jsonwebview-ui/src/i18n/locales/ru/settings.jsonwebview-ui/src/i18n/locales/tr/prompts.jsonwebview-ui/src/i18n/locales/tr/settings.jsonwebview-ui/src/i18n/locales/vi/prompts.jsonwebview-ui/src/i18n/locales/vi/settings.jsonwebview-ui/src/i18n/locales/zh-CN/prompts.jsonwebview-ui/src/i18n/locales/zh-CN/settings.jsonwebview-ui/src/i18n/locales/zh-TW/prompts.jsonwebview-ui/src/i18n/locales/zh-TW/settings.json
| "reset_support_prompt": "Ha fallat el restabliment del missatge de suport", | ||
| "enhance_prompt": "Ha fallat la millora del missatge", | ||
| "commit_message_no_repository": "No s'ha trobat cap repositori Git al plafó de control de codi font.", | ||
| "commit_message_failed": "No s'ha pogut generar el missatge de comissió: {{error}}", |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Correct the Catalan term for a Git commit.
missatge de comissió means “commission message.” Use missatge de confirmació or missatge de commit consistently.
Proposed fix
- "commit_message_failed": "No s'ha pogut generar el missatge de comissió: {{error}}",
+ "commit_message_failed": "No s'ha pogut generar el missatge de confirmació: {{error}}",
...
- "commit_message_generating": "Generant el missatge de comissió...",
+ "commit_message_generating": "Generant el missatge de confirmació...",Also applies to: 169-169
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/i18n/locales/ca/common.json` at line 48, Update the Catalan translation
values for commit_message_failed and the corresponding occurrence to replace
“missatge de comissió” with the established “missatge de confirmació” or
“missatge de commit” terminology, preserving the existing error placeholder.
| "command.acceptInput.title": "Acceptar Entrada/Suggeriment", | ||
| "command.showRipgrepDiagnostic.title": "Mostra el diagnòstic de Ripgrep", | ||
| "command.toggleAutoApprove.title": "Alternar Auto-Aprovació", | ||
| "command.generateCommitMessage.title": "Genera missatge de comissió", |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Use correct Git terminology in the Catalan title.
comissió means “commission”, not a Git commit. Replace it with commit or the repository’s established Catalan term for Git commits.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/package.nls.ca.json` at line 19, Update the Catalan value for
command.generateCommitMessage.title to use “commit” or the repository’s
established Catalan Git terminology instead of “comissió”; leave the command key
and surrounding translations unchanged.
| return | ||
| } | ||
|
|
||
| const gitContext = await getCommitContext(repository.rootUri.fsPath) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Support repositories without an initial commit.
Line 87 calls getCommitContext, whose working-tree fallback runs git diff HEAD. In an unborn repository, HEAD does not exist. The command rejects and commit-message generation fails instead of using the detected changes.
Detect an unborn HEAD in getCommitContext. Return usable status context or diff against an empty baseline. Add a regression test for an uncommitted repository with working-tree changes.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/services/commit-message/index.ts` at line 87, Update getCommitContext to
detect when HEAD is unborn before running the working-tree fallback, and return
usable status context or compare changes against an empty baseline instead of
invoking git diff HEAD. Preserve existing behavior for repositories with an
initial commit, and add a regression test covering working-tree changes in an
uncommitted repository.
| const { stdout: diff } = await execAsync(`git diff HEAD ${COMMIT_DIFF_ARGS}`, options) | ||
| const output = `Unstaged changes:\n\n${status.trim()}\n\n${diff.trim()}`.trim() | ||
| return truncateOutput(output, GIT_OUTPUT_LINE_LIMIT) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
repo="$(mktemp -d)"
trap 'rm -rf "$repo"' EXIT
git init -q "$repo"
printf 'example\n' > "$repo/file.txt"
if git -C "$repo" diff HEAD --unified=1 >/dev/null 2>&1; then
echo "Expected git diff HEAD to fail before the first commit" >&2
exit 1
fi
git -C "$repo" diff --unified=1 >/dev/nullRepository: Zoo-Code-Org/Zoo-Code
Length of output: 159
🏁 Script executed:
#!/bin/bash
set -euo pipefail
file="$(fd -t f '^git\.ts$' . | head -n 1)"
printf '%s\n' "FILE=$file"
sed -n '1,45p' "$file"
sed -n '340,420p' "$file"
rg -n -C 5 'getCommitContext|COMMIT_DIFF_ARGS|STAGED|git diff' "$file"Repository: Zoo-Code-Org/Zoo-Code
Length of output: 430
🏁 Script executed:
#!/bin/bash
set -euo pipefail
mapfile -t files < <(fd -t f 'git\.ts$' .)
printf '%s\n' "${files[@]}"
target="$(printf '%s\n' "${files[@]}" | grep -E '(^|/)src/utils/git\.ts$' | head -n 1)"
test -n "$target"
printf '%s\n' "TARGET=$target"
wc -l "$target"
sed -n '1,55p' "$target"
sed -n '350,415p' "$target"
rg -n -C 8 'getCommitContext|COMMIT_DIFF_ARGS|git diff|staged|status' "$target"Repository: Zoo-Code-Org/Zoo-Code
Length of output: 211
🏁 Script executed:
#!/bin/bash
set -euo pipefail
target="src/utils/git.ts"
test -f "$target"
printf '%s\n' "TARGET=$target"
wc -l "$target"
sed -n '1,55p' "$target"
sed -n '350,415p' "$target"
rg -n -C 8 'getCommitContext|COMMIT_DIFF_ARGS|git diff|staged|status' "$target"Repository: Zoo-Code-Org/Zoo-Code
Length of output: 9425
Avoid requiring HEAD in the unstaged fallback.
In an unborn repository, git diff HEAD --unified=1 fails after git status --short detects changes. Use git diff ${COMMIT_DIFF_ARGS} to preserve tracked working-tree diffs without requiring an initial commit.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/utils/git.ts` around lines 398 - 400, Update the unstaged fallback’s git
diff invocation in the function containing the `status`, `diff`, and
`truncateOutput` flow to remove the `HEAD` argument and run `git diff` with only
`COMMIT_DIFF_ARGS`, preserving the existing output formatting and truncation.
| <label className="block font-medium mb-1">{t("settings:providers.commitMessageModel.label")}</label> | ||
| <Select | ||
| value={commitMessageApiConfigId || USE_CURRENT_CONFIG} | ||
| onValueChange={(value) => | ||
| setCachedStateField("commitMessageApiConfigId", value === USE_CURRENT_CONFIG ? "" : value) | ||
| }> | ||
| <SelectTrigger data-testid="commit-message-model-select" className="w-full"> | ||
| <SelectValue placeholder={t("settings:providers.commitMessageModel.useCurrentConfig")} /> | ||
| </SelectTrigger> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -e
file="$(fd -t f 'CommitMessageModelSelect\.tsx$' . | head -n 1)"
printf '%s\n' "FILE: $file"
cat -n "$file"
printf '\n-- SelectTrigger and label patterns --\n'
rg -n -C 3 'SelectTrigger|aria-labelledby|htmlFor=|<label' webview-ui/src | head -n 240
printf '\n-- Radix Select dependency/source references --\n'
rg -n '"`@radix-ui/react-select`"|from "`@radix-ui/react-select`"|SelectTrigger' package.json webview-ui package-lock.json pnpm-lock.yaml yarn.lock 2>/dev/null | head -n 160Repository: Zoo-Code-Org/Zoo-Code
Length of output: 36050
🏁 Script executed:
#!/bin/bash
set -e
printf '%s\n' '-- shared Select implementation --'
cat -n webview-ui/src/components/ui/select.tsx | sed -n '1,80p'
printf '\n%s\n' '-- CommitMessageModelSelect test --'
cat -n webview-ui/src/components/settings/__tests__/CommitMessageModelSelect.spec.tsx
printf '\n%s\n' '-- related accessibility assertions and test tooling --'
rg -n -C 3 'toHaveAccessibleName|accessible name|aria-labelledby|axe|jest-axe|role="combobox"|getByRole\(.*combobox' webview-ui/src webview-ui/package.json | head -n 240Repository: Zoo-Code-Org/Zoo-Code
Length of output: 29400
Associate the setting label with SelectTrigger.
The sibling <label> does not label the Radix trigger. Add an ID to the label, set aria-labelledby on SelectTrigger, and test the relationship.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@webview-ui/src/components/settings/CommitMessageModelSelect.tsx` around lines
35 - 43, Update the label and SelectTrigger in CommitMessageModelSelect to
associate them accessibly: assign a unique id to the setting label and reference
that id via SelectTrigger’s aria-labelledby attribute. Add or update the
relevant component test to verify the label-to-trigger relationship.
| "COMMIT_MESSAGE": { | ||
| "label": "Missatge de comissió", | ||
| "description": "Resumeix els teus canvis en un missatge de comissió. Disponible mitjançant la icona de Zoo Code al plafó de control de codi font, que escriu el resultat directament al camp del missatge de comissió." | ||
| }, |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Correct the Catalan Git terminology.
comissió means “commission,” not a Git commit. Use commit in both strings.
webview-ui/src/i18n/locales/ca/prompts.json#L107-L110: replace “Missatge de comissió” and the description reference with Git commit terminology.webview-ui/src/i18n/locales/ca/settings.json#L359-L363: replace “missatges de comissió” with Git commit terminology.
📍 Affects 2 files
webview-ui/src/i18n/locales/ca/prompts.json#L107-L110(this comment)webview-ui/src/i18n/locales/ca/settings.json#L359-L363
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@webview-ui/src/i18n/locales/ca/prompts.json` around lines 107 - 110, Correct
the Catalan Git terminology in webview-ui/src/i18n/locales/ca/prompts.json lines
107-110 by replacing “comissió” in the COMMIT_MESSAGE label and description with
“commit” terminology; also update webview-ui/src/i18n/locales/ca/settings.json
lines 359-363 by replacing “missatges de comissió” with the corresponding Git
commit wording.
Related GitHub Issue
Part of: #145
This PR implements most of the #145 sub-issue tree in one piece:
getCommitContext()insrc/utils/git.tsCOMMIT_MESSAGEinsrc/shared/support-prompt.tssrc/services/commit-message/commitMessageApiConfigIdzoo-code.generateCommitMessage+scm/titleSourceControl.rootUriagainst the git APIrepository.inputBox.valueCommitMessageModelSelectin ProvidersHappy to split this along those lines if that's easier to review -- see "Relationship
to the existing stack" below.
Description
Adds a Zoo Code button to the Source Control title bar. Click it and the current
changes are summarized into a commit message, written straight into the commit
input box.
The implementation leans on things Zoo Code already has, so the new surface area is
small: one git helper, one ~110-line service, one React
<Select>.Where the changes come from.
getCommitContext()insrc/utils/git.tsreadsstaged changes first, since that's what a commit will actually contain. When nothing
is staged it falls back to the whole working tree, so the button still does something
useful before you've staged. The fallback path uses
git status --shortrather than adiff, because untracked files don't appear in any diff and would otherwise be
invisible to the model. Oversized diffs go through the existing
truncateOutputhelper, and
--unified=1keeps the prompt lean without hiding what changed.The prompt is editable.
COMMIT_MESSAGEis registered insrc/shared/support-prompt.tsalongsideENHANCEand the rest, which means it picksup the existing Prompts settings UI for free -- textarea, reset button, and
customSupportPromptspersistence, with no new UI code. The default asks forConventional Commits and explicitly tells the model to account for every changed file
rather than letting the largest file speak for the rest. That instruction is load
bearing: without it, models happily describe one file and silently drop the others.
Model selection.
commitMessageApiConfigIdmirrors the existingenhancementApiConfigIdpattern exactly, including thelistApiConfigMeta.find(...)guard before
getProfile()so a deleted profile falls back to the active one insteadof throwing. Summarizing a diff doesn't need a frontier model, and pointing this at a
small fast profile is the single biggest lever on how quickly the button responds.
One change outside the feature.
packages/build/src/types.tsdeclaredicon: z.string().optional(), allowing only a codicon string. The themed{light, dark}icon this feature needs would have thrown incontributesSchema.parse()during the nightly manifest build -- a failure invisible in normal dev, since only
vsix:nightlyexercises that path. I widened the field to a union.Reviewer focus:
surfacing untracked files via
git status --shortin the fallback is right.commitMessageApiConfigIdbelongs in Providers (where it is now, next to theprofile it selects) or in Prompts next to the enhancement picker.
packages/buildschema widening.Relationship to the existing stack
#145 has a split stack from @Mirrowel -- #298 (git context), #299 (generator), #300
(SCM integration), #301 (settings). Those PRs are still technically open but have had
no activity since 2026-06-30, and the feature has been unavailable to users the whole
time. I picked it up on that basis and built this independently, then found the stack
afterwards. Not a criticism of that work -- just an attempt to get the feature landed.
The honest comparison: that stack is more thorough where it overlaps. #298 alone is
~996 lines and handles rename/copy status codes,
-znull-delimited parsing, syntheticdiffs for untracked files, and binary-file summarization. This PR does none of that --
it shells out to
git diffand passes the output through. If those cases matter,#298 is the better foundation and I'd happily see it revived instead.
What this offers is a smaller diff, a feature that works end to end in one piece, and
a branch that is current with
maintoday.If @Mirrowel wants to continue, I'll close this and offer review there instead. If not,
this is ready now, and I'm glad to reshape it into the same four-part split if the
maintainers prefer that granularity.
Test Procedure
Every job in
code-qa.ymlwas run locally against this branch:pnpm lintpnpm check-typesnode scripts/find-missing-translations.jspnpm knippnpm turbo run test:coverage --filter='!@roo-code/core'pnpm turbo run test:coverage:unit --filter=@roo-code/corepnpm turbo run test:coverage:integration --filter=@roo-code/corepnpm --filter ./src vsixFull
srcsuite: 7387 passed, 37 skipped. New coverage:src/utils/__tests__/git.spec.ts-- staged path, working-tree fallback, clean-treenull, plus a test asserting diff commands contain no shell metacharacters (see below).
src/services/commit-message/__tests__/generateCommitMessage.spec.ts-- profileselection, stale-profile fallback, fence/quote stripping, multi-repo selection by
SourceControl.rootUri, no-changes path, error path.webview-ui/src/components/settings/__tests__/CommitMessageModelSelect.spec.tsx--rendering, sentinel handling,
setCachedStateFieldwiring.SettingsView.spec.tsx-- the setting survives the cachedState round trip into theupdateSettingspayload.Translations for all 17 locales were written for the four new key groups, not left as
English fallbacks --
find-missing-translations.jsgates on this and would have failed.A note on the git tests. An earlier revision of this branch used
:(exclude)pathspecs to skip lockfiles. The unit tests passed, because they mock
exec. Real gitrejected the command outright:
execruns throughcmd.exeon Windows, which does notstrip the single quotes those pathspecs require. I found it only by running against a
real repository. The exclusions are gone, and there's now a test asserting the diff
argument string stays free of shell metacharacters -- but the general caveat stands
that the mocked git tests cannot validate git syntax.
Manual verification, in a scratch repo with one staged change, one unstaged change,
and one untracked file:
untracked file.
the window -- selection persists.
effect; reset button restores the default.
Pre-Submission Checklist
Screenshots below are a review aid, not a substitute.
The model picker in Settings -> Providers:
Documentation Updates
This adds a user-facing button and two settings, so the docs repo likely wants a short
page covering the Source Control button, the Commit Message Model picker, and the
editable prompt. Happy to open that PR if this direction is accepted.
Additional Notes
Aligns with the roadmap's Enhanced User Experience goal -- it removes a small,
repeated friction point and makes Zoo Code useful from a panel where it previously had
no presence.
Two limitations worth stating plainly, both relevant to #289:
and the prompt is ~600 tokens on a small repo. A reasoning-heavy local model spends
far longer thinking than the rest of the flow takes. The Commit Message Model setting
is the fix, which is part of why [ENHANCEMENT] Add dedicated API profile setting for commit generation #285/[ENHANCEMENT] Add commit-message API profile settings UI #291 are in this PR rather than deferred.
ProgressLocation.Notificationrenders one, and a toast on every commit would beintrusive. It would also be inert today:
completePromptaccepts anabortSignal,but 24 of 25 providers ignore the options argument entirely, so the request cannot
actually be interrupted. Making cancellation real needs a provider-layer change,
which felt out of scope here.
#292 (telemetry) is untouched and remains open.
Summary by CodeRabbit