feat(worktree): add AI commit message generation feature

- Implemented a new endpoint to generate commit messages based on git diffs.
- Updated worktree routes to include the AI commit message generation functionality.
- Enhanced the UI to support automatic generation of commit messages when the commit dialog opens, based on user settings.
- Added settings for enabling/disabling AI-generated commit messages and configuring the model used for generation.
This commit is contained in:
Shirone
2026-01-12 20:38:00 +01:00
parent 8b19266c9a
commit 5e4f5f86cd
10 changed files with 162 additions and 42 deletions

View File

@@ -13,6 +13,7 @@ import { Label } from '@/components/ui/label';
import { GitCommit, Loader2, Sparkles } from 'lucide-react';
import { getElectronAPI } from '@/lib/electron';
import { toast } from 'sonner';
import { useAppStore } from '@/store/app-store';
interface WorktreeInfo {
path: string;
@@ -39,6 +40,7 @@ export function CommitWorktreeDialog({
const [isLoading, setIsLoading] = useState(false);
const [isGenerating, setIsGenerating] = useState(false);
const [error, setError] = useState<string | null>(null);
const enableAiCommitMessages = useAppStore((state) => state.enableAiCommitMessages);
const handleCommit = async () => {
if (!worktree || !message.trim()) return;
@@ -83,19 +85,24 @@ export function CommitWorktreeDialog({
}
};
// Generate AI commit message when dialog opens
// Generate AI commit message when dialog opens (if enabled)
useEffect(() => {
if (open && worktree) {
// Reset state
setMessage('');
setError(null);
// Only generate AI commit message if enabled
if (!enableAiCommitMessages) {
return;
}
setIsGenerating(true);
const generateMessage = async () => {
try {
const api = getElectronAPI();
if (!api?.worktree?.generateCommitMessage) {
setError('AI commit message generation not available');
setIsGenerating(false);
return;
}
@@ -120,7 +127,7 @@ export function CommitWorktreeDialog({
generateMessage();
}
}, [open, worktree]);
}, [open, worktree, enableAiCommitMessages]);
if (!worktree) return null;

View File

@@ -44,6 +44,8 @@ export function SettingsView() {
setEnableDependencyBlocking,
skipVerificationInAutoMode,
setSkipVerificationInAutoMode,
enableAiCommitMessages,
setEnableAiCommitMessages,
useWorktrees,
setUseWorktrees,
muteDoneSound,
@@ -182,11 +184,13 @@ export function SettingsView() {
skipVerificationInAutoMode={skipVerificationInAutoMode}
defaultPlanningMode={defaultPlanningMode}
defaultRequirePlanApproval={defaultRequirePlanApproval}
enableAiCommitMessages={enableAiCommitMessages}
onDefaultSkipTestsChange={setDefaultSkipTests}
onEnableDependencyBlockingChange={setEnableDependencyBlocking}
onSkipVerificationInAutoModeChange={setSkipVerificationInAutoMode}
onDefaultPlanningModeChange={setDefaultPlanningMode}
onDefaultRequirePlanApprovalChange={setDefaultRequirePlanApproval}
onEnableAiCommitMessagesChange={setEnableAiCommitMessages}
/>
);
case 'worktrees':

View File

@@ -10,6 +10,7 @@ import {
ScrollText,
ShieldCheck,
FastForward,
Sparkles,
} from 'lucide-react';
import { cn } from '@/lib/utils';
import {
@@ -28,11 +29,13 @@ interface FeatureDefaultsSectionProps {
skipVerificationInAutoMode: boolean;
defaultPlanningMode: PlanningMode;
defaultRequirePlanApproval: boolean;
enableAiCommitMessages: boolean;
onDefaultSkipTestsChange: (value: boolean) => void;
onEnableDependencyBlockingChange: (value: boolean) => void;
onSkipVerificationInAutoModeChange: (value: boolean) => void;
onDefaultPlanningModeChange: (value: PlanningMode) => void;
onDefaultRequirePlanApprovalChange: (value: boolean) => void;
onEnableAiCommitMessagesChange: (value: boolean) => void;
}
export function FeatureDefaultsSection({
@@ -41,11 +44,13 @@ export function FeatureDefaultsSection({
skipVerificationInAutoMode,
defaultPlanningMode,
defaultRequirePlanApproval,
enableAiCommitMessages,
onDefaultSkipTestsChange,
onEnableDependencyBlockingChange,
onSkipVerificationInAutoModeChange,
onDefaultPlanningModeChange,
onDefaultRequirePlanApprovalChange,
onEnableAiCommitMessagesChange,
}: FeatureDefaultsSectionProps) {
return (
<div
@@ -251,6 +256,34 @@ export function FeatureDefaultsSection({
</p>
</div>
</div>
{/* Separator */}
<div className="border-t border-border/30" />
{/* AI Commit Messages Setting */}
<div className="group flex items-start space-x-3 p-3 rounded-xl hover:bg-accent/30 transition-colors duration-200 -mx-3">
<Checkbox
id="enable-ai-commit-messages"
checked={enableAiCommitMessages}
onCheckedChange={(checked) => onEnableAiCommitMessagesChange(checked === true)}
className="mt-1"
data-testid="enable-ai-commit-messages-checkbox"
/>
<div className="space-y-1.5">
<Label
htmlFor="enable-ai-commit-messages"
className="text-foreground cursor-pointer font-medium flex items-center gap-2"
>
<Sparkles className="w-4 h-4 text-brand-500" />
Generate AI commit messages
</Label>
<p className="text-xs text-muted-foreground/80 leading-relaxed">
When enabled, opening the commit dialog will automatically generate a commit message
using AI based on your staged or unstaged changes. You can configure the model used in
Model Defaults.
</p>
</div>
</div>
</div>
</div>
);

View File

@@ -28,6 +28,11 @@ const QUICK_TASKS: PhaseConfig[] = [
label: 'Image Descriptions',
description: 'Analyzes and describes context images',
},
{
key: 'commitMessageModel',
label: 'Commit Messages',
description: 'Generates git commit messages from diffs',
},
];
const VALIDATION_TASKS: PhaseConfig[] = [

View File

@@ -1538,11 +1538,10 @@ function createMockWorktreeAPI(): WorktreeAPI {
},
generateCommitMessage: async (worktreePath: string) => {
console.log('[Mock] Generating commit message:', { worktreePath });
console.log('[Mock] Generating commit message for:', worktreePath);
return {
success: true,
message:
'feat: Add new feature implementation\n\nThis is a mock AI-generated commit message.',
message: 'feat: Add mock commit message generation',
};
},

View File

@@ -536,6 +536,7 @@ export interface AppState {
defaultSkipTests: boolean; // Default value for skip tests when creating new features
enableDependencyBlocking: boolean; // When true, show blocked badges and warnings for features with incomplete dependencies (default: true)
skipVerificationInAutoMode: boolean; // When true, auto-mode grabs features even if dependencies are not verified (only checks they're not running)
enableAiCommitMessages: boolean; // When true, auto-generate commit messages using AI when opening commit dialog
planUseSelectedWorktreeBranch: boolean; // When true, Plan dialog creates features on the currently selected worktree branch
addFeatureUseSelectedWorktreeBranch: boolean; // When true, Add Feature dialog defaults to custom mode with selected worktree branch
@@ -932,6 +933,7 @@ export interface AppActions {
setDefaultSkipTests: (skip: boolean) => void;
setEnableDependencyBlocking: (enabled: boolean) => void;
setSkipVerificationInAutoMode: (enabled: boolean) => Promise<void>;
setEnableAiCommitMessages: (enabled: boolean) => Promise<void>;
setPlanUseSelectedWorktreeBranch: (enabled: boolean) => Promise<void>;
setAddFeatureUseSelectedWorktreeBranch: (enabled: boolean) => Promise<void>;
@@ -1218,6 +1220,7 @@ const initialState: AppState = {
defaultSkipTests: true, // Default to manual verification (tests disabled)
enableDependencyBlocking: true, // Default to enabled (show dependency blocking UI)
skipVerificationInAutoMode: false, // Default to disabled (require dependencies to be verified)
enableAiCommitMessages: true, // Default to enabled (auto-generate commit messages)
planUseSelectedWorktreeBranch: true, // Default to enabled (Plan creates features on selected worktree branch)
addFeatureUseSelectedWorktreeBranch: false, // Default to disabled (Add Feature uses normal defaults)
useWorktrees: true, // Default to enabled (git worktree isolation)
@@ -1848,6 +1851,17 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
const { syncSettingsToServer } = await import('@/hooks/use-settings-migration');
await syncSettingsToServer();
},
setEnableAiCommitMessages: async (enabled) => {
const previous = get().enableAiCommitMessages;
set({ enableAiCommitMessages: enabled });
// Sync to server settings file
const { syncSettingsToServer } = await import('@/hooks/use-settings-migration');
const ok = await syncSettingsToServer();
if (!ok) {
logger.error('Failed to sync enableAiCommitMessages setting to server - reverting');
set({ enableAiCommitMessages: previous });
}
},
setPlanUseSelectedWorktreeBranch: async (enabled) => {
const previous = get().planUseSelectedWorktreeBranch;
set({ planUseSelectedWorktreeBranch: enabled });