diff --git a/apps/backend/internal/utility/store/builtins.go b/apps/backend/internal/utility/store/builtins.go index 0bb744b114..a18bedff01 100644 --- a/apps/backend/internal/utility/store/builtins.go +++ b/apps/backend/internal/utility/store/builtins.go @@ -42,7 +42,9 @@ func (r *sqliteRepository) getBuiltinAgents() []*models.UtilityAgent { now := time.Now().UTC() return []*models.UtilityAgent{ r.builtinCommitMessage(now), + r.builtinCommitDescription(now), r.builtinBranchName(now), + r.builtinPRTitle(now), r.builtinPRDescription(now), r.builtinEnhancePrompt(now), } @@ -77,6 +79,33 @@ Return ONLY the commit message, no explanations or markdown.`, } } +func (r *sqliteRepository) builtinCommitDescription(now time.Time) *models.UtilityAgent { + return &models.UtilityAgent{ + ID: "builtin-commit-description", + Name: "commit-description", + Description: "Generate a detailed commit description explaining the changes", + AgentID: "", // User must configure + Model: "", // User must configure + Builtin: true, + Enabled: false, // Disabled until user configures + CreatedAt: now, + UpdatedAt: now, + Prompt: `You are a commit description generator. You output ONLY bullet points — no preamble, no commentary, no explanation, no conversational text. + +## Staged Changes (git diff --staged): +{{GitDiff}} + +## Rules: +- Output bullet points ONLY (lines starting with "• " or "- ") +- Do NOT include any text before or after the bullet points +- Do NOT include a commit title/subject line +- Do NOT say things like "Here's the description" or "Based on the changes" +- Focus on what changed and why, not which files were modified +- Group related changes together +- 3-8 bullet points, concise but informative`, + } +} + func (r *sqliteRepository) builtinBranchName(now time.Time) *models.UtilityAgent { return &models.UtilityAgent{ ID: "builtin-branch-name", @@ -109,6 +138,41 @@ Example: feature/add-user-authentication`, } } +func (r *sqliteRepository) builtinPRTitle(now time.Time) *models.UtilityAgent { + return &models.UtilityAgent{ + ID: "builtin-pr-title", + Name: "pr-title", + Description: "Generate a PR title based on commits and changes", + AgentID: "", // User must configure + Model: "", // User must configure + Builtin: true, + Enabled: false, // Disabled until user configures + CreatedAt: now, + UpdatedAt: now, + Prompt: `Generate a concise Pull Request title for the following changes. + +## Commits: +{{CommitLog}} + +## Changed Files: +{{ChangedFiles}} + +## Diff Summary: +{{DiffSummary}} + +## Instructions: +1. Follow the Conventional Commits format: (): +2. Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore +3. Scope is optional but recommended (e.g., api, ui, db) +4. Description should be imperative mood ("add" not "added") +5. Keep it under 72 characters +6. Focus on the overall purpose of the PR, not individual commits + +## Output Format: +Return ONLY the PR title, no explanations or markdown.`, + } +} + func (r *sqliteRepository) builtinPRDescription(now time.Time) *models.UtilityAgent { return &models.UtilityAgent{ ID: "builtin-pr-description", diff --git a/apps/web/components/task/mobile/session-mobile-top-bar-git-controls.tsx b/apps/web/components/task/mobile/session-mobile-top-bar-git-controls.tsx index f9f63c556c..c195738547 100644 --- a/apps/web/components/task/mobile/session-mobile-top-bar-git-controls.tsx +++ b/apps/web/components/task/mobile/session-mobile-top-bar-git-controls.tsx @@ -91,6 +91,42 @@ function useGitToast() { ); } +function useCommitDialogForm( + onOpenChange: (open: boolean) => void, + onCommit: (message: string, stageAll: boolean) => void, +) { + const [commitMessage, setCommitMessage] = useState(""); + const [commitBody, setCommitBody] = useState(""); + const [stageAll, setStageAll] = useState(true); + + const handleOpen = (isOpen: boolean) => { + if (isOpen) { + setCommitMessage(""); + setCommitBody(""); + setStageAll(true); + } + onOpenChange(isOpen); + }; + + const handleCommit = () => { + const title = commitMessage.trim(); + const body = commitBody.trim(); + const fullMessage = body ? `${title}\n\n${body}` : title; + onCommit(fullMessage, stageAll); + }; + + return { + commitMessage, + setCommitMessage, + commitBody, + setCommitBody, + stageAll, + setStageAll, + handleOpen, + handleCommit, + }; +} + export function CommitDialog({ open, onOpenChange, @@ -108,19 +144,10 @@ export function CommitDialog({ isGitLoading: boolean; onCommit: (message: string, stageAll: boolean) => void; }) { - const [commitMessage, setCommitMessage] = useState(""); - const [stageAll, setStageAll] = useState(true); - - const handleOpen = (isOpen: boolean) => { - if (isOpen) { - setCommitMessage(""); - setStageAll(true); - } - onOpenChange(isOpen); - }; + const form = useCommitDialogForm(onOpenChange, onCommit); return ( - + @@ -137,16 +164,31 @@ export function CommitDialog({ /> setCommitMessage(e.target.value)} + value={form.commitMessage} + onChange={(e) => form.setCommitMessage(e.target.value)} autoFocus /> +
+ +