diff --git a/docs/designs/2025-12-20-slash-commit-command.md b/docs/designs/2025-12-20-slash-commit-command.md new file mode 100644 index 00000000..9f409f17 --- /dev/null +++ b/docs/designs/2025-12-20-slash-commit-command.md @@ -0,0 +1,59 @@ +# Slash Commit Command + +**Date:** 2025-12-20 + +## Context + +用户期望在 `src/slash-commands/builtin/` 下新增一个 `/commit` 内置命令,复用 `src/nodeBridge.ts` 中的 commit message 生成提示词。核心需求是将该提示词抽取为独立函数,支持传入 `language` 参数,使其可复用。 + +## Discussion + +### 命令类型选择 +- **prompt 类型**: 类似 `/review`,生成提示词让 AI 执行 commit 流程 ✅ 用户选择 +- **local-jsx 类型**: 复用现有 CommitUI 组件,在聊天中渲染交互式界面 +- **local 类型**: 直接调用 nodeBridge 生成 commit message 并返回文本结果 + +用户选择 prompt 类型,因为它更轻量且符合 slash command 的交互模式。 + +### 提示词函数位置 +- **新建 prompts 目录**: 抽取函数到独立目录如 `src/prompts/generateCommit.ts` +- **utils 目录**: 放在 `src/utils/` 下作为工具函数 ✅ 用户选择 + +### 用户交互 +用户期望 AI 使用 `AskUserQuestion` 工具提供多个操作选项,类似 `neo commit` CLI 命令的交互体验: +- Commit - 提交更改 +- Commit & Push - 提交并推送 +- Create Branch & Commit - 创建新分支并提交 + +## Approach + +采用简洁函数式抽取方案: +1. 将 `createGenerateCommitSystemPrompt` 抽取到 `src/utils/commitPrompt.ts` +2. 函数接受 `language: string` 参数 +3. `nodeBridge.ts` 改用新函数,保持向后兼容 +4. 新建 `/commit` 命令作为 prompt 类型,引导 AI 使用 `AskUserQuestion` 提供操作选项 + +## Architecture + +### 文件结构 +``` +src/utils/commitPrompt.ts # 新建 - 抽取的 prompt 生成函数 +src/slash-commands/builtin/commit.ts # 新建 - /commit 命令 +src/slash-commands/builtin/index.ts # 修改 - 注册命令 +src/nodeBridge.ts # 修改 - 改用新函数 +``` + +### 核心接口 +```typescript +// src/utils/commitPrompt.ts +export function createGenerateCommitSystemPrompt(language: string): string +``` + +### /commit 命令流程 +1. 检查是否有 staged changes +2. 获取 staged diff(排除 lock 文件) +3. 使用 `createGenerateCommitSystemPrompt(language)` 生成系统提示词 +4. AI 分析 diff 并生成 commit 信息 +5. 展示 commitMessage、branchName、summary、isBreakingChange +6. 使用 `AskUserQuestion` 让用户选择操作 +7. 执行对应的 git 命令 diff --git a/src/nodeBridge.ts b/src/nodeBridge.ts index 5e1ee813..29bc5d2d 100644 --- a/src/nodeBridge.ts +++ b/src/nodeBridge.ts @@ -20,6 +20,7 @@ import { query } from './query'; import { SessionConfigManager } from './session'; import { SlashCommandManager } from './slashCommand'; import type { ApprovalCategory, ToolUse } from './tool'; +import { createGenerateCommitSystemPrompt } from './utils/commitPrompt'; import { getFiles } from './utils/files'; import { listDirectory } from './utils/list'; import { randomUUID } from './utils/randomUUID'; @@ -2405,51 +2406,3 @@ function normalizeProviders(providers: ProvidersMap, context: Context) { }, ); } - -function createGenerateCommitSystemPrompt(language: string) { - const { isEnglish } = require('./utils/language'); - const useEnglish = isEnglish(language); - const descriptionLang = useEnglish - ? '' - : `\n - Use ${language} for the description`; - const summaryLang = useEnglish ? '' : `\n - Use ${language}`; - return ` -You are an expert software engineer that generates Git commit information based on provided diffs. - -Review the provided context and diffs which are about to be committed to a git repo. -Analyze the changes carefully and generate a JSON response with the following fields: - -1. **commitMessage**: A one-line commit message following conventional commit format - - Format: : - - Types: fix, feat, build, chore, ci, docs, style, refactor, perf, test${descriptionLang} - - Use imperative mood (e.g., "add feature" not "added feature") - - Do not exceed 72 characters - - Do not capitalize the first letter - - Do not end with a period - -2. **branchName**: A suggested Git branch name - - Format: / for conventional commits, or for regular changes - - Use only lowercase letters, numbers, and hyphens - - Maximum 50 characters - - No leading or trailing hyphens - -3. **isBreakingChange**: Boolean indicating if this is a breaking change - - Set to true if the changes break backward compatibility - - Look for removed public APIs, changed function signatures, etc. - -4. **summary**: A brief 1-2 sentence summary of the changes${summaryLang} - - Describe what was changed and why - -## Response Format - -Respond with valid JSON only, no additional text or markdown formatting. - -Example response: -{ - "commitMessage": "feat: add user authentication system", - "branchName": "feat/add-user-authentication", - "isBreakingChange": false, - "summary": "Added JWT-based authentication with login and logout endpoints." -} - `.trim(); -} diff --git a/src/slash-commands/builtin/commit.ts b/src/slash-commands/builtin/commit.ts new file mode 100644 index 00000000..256daf52 --- /dev/null +++ b/src/slash-commands/builtin/commit.ts @@ -0,0 +1,71 @@ +import { createGenerateCommitSystemPrompt } from '../../utils/commitPrompt'; +import { TOOL_NAMES } from '../../constants'; +import { isEnglish } from '../../utils/language'; +import type { PromptCommand } from '../types'; + +export function createCommitCommand(language: string): PromptCommand { + const useEnglish = isEnglish(language); + const lang = useEnglish ? '' : ` Communicate in ${language}.`; + + return { + type: 'prompt', + name: 'commit', + description: 'Generate commit message for staged changes', + progressMessage: 'Generating commit message...', + async getPromptForCommand(_args?: string) { + const systemPrompt = createGenerateCommitSystemPrompt(language); + const lockFiles = [ + 'pnpm-lock.yaml', + 'package-lock.json', + 'yarn.lock', + 'bun.lockb', + 'Gemfile.lock', + 'Cargo.lock', + ]; + const lockFilesPattern = lockFiles.map((file) => `':!${file}'`).join(' '); + + return [ + { + role: 'user', + content: `You are a Git commit assistant.${lang} + +## System Prompt for Commit Generation +${systemPrompt} + +## Instructions + +Follow these steps: + +1. First, check if there are staged changes using bash("git diff --cached --stat") + - If no staged changes, inform the user and ask if they want to stage all changes using bash("git add -A") + +2. Get the staged diff using bash("git --no-pager diff --cached -- . ${lockFilesPattern}") + +3. Analyze the diff and generate commit information (commitMessage, branchName, isBreakingChange, summary) + +4. Present the generated information to the user in a clear format: + - Commit Message: + - Branch Name: + - Summary: + - Breaking Change: Yes/No + +5. Use ${TOOL_NAMES.ASK_USER_QUESTION} tool to let the user choose an action with these options: + - "Commit" - Commit with the generated message + - "Commit & Push" - Commit and push to remote + - "Create Branch & Commit" - Create new branch with suggested name, then commit + +6. Based on user's choice, execute the corresponding git commands: + - Commit: bash("git commit -m ''") + - Push: bash("git push origin ''") + - Create Branch: bash("git checkout -b ''") + +7. If the user wants to modify the commit message or branch name before executing, allow them to provide a new value. + +8. Report the result of each operation to the user. + +Note: If any git command fails, explain the error and suggest possible solutions.`, + }, + ]; + }, + }; +} diff --git a/src/slash-commands/builtin/index.ts b/src/slash-commands/builtin/index.ts index a9dadba1..6e107ef6 100644 --- a/src/slash-commands/builtin/index.ts +++ b/src/slash-commands/builtin/index.ts @@ -2,6 +2,7 @@ import type { SlashCommand } from '../types'; import { createAddDirCommand } from './add-dir'; import { createBugCommand } from './bug'; import { clearCommand } from './clear'; +import { createCommitCommand } from './commit'; import { compactCommand } from './compact'; import { contextCommand } from './context'; import { exitCommand } from './exit'; @@ -40,6 +41,7 @@ export function createBuiltinCommands(opts: { createOutputStyleCommand(), createResumeCommand(), createReviewCommand(opts.language), + createCommitCommand(opts.language), createTerminalSetupCommand(), createBugCommand(), compactCommand, diff --git a/src/utils/commitPrompt.ts b/src/utils/commitPrompt.ts new file mode 100644 index 00000000..39081b36 --- /dev/null +++ b/src/utils/commitPrompt.ts @@ -0,0 +1,49 @@ +import { isEnglish } from './language'; + +export function createGenerateCommitSystemPrompt(language: string): string { + const useEnglish = isEnglish(language); + const descriptionLang = useEnglish + ? '' + : `\n - Use ${language} for the description`; + const summaryLang = useEnglish ? '' : `\n - Use ${language}`; + + return ` +You are an expert software engineer that generates Git commit information based on provided diffs. + +Review the provided context and diffs which are about to be committed to a git repo. +Analyze the changes carefully and generate a JSON response with the following fields: + +1. **commitMessage**: A one-line commit message following conventional commit format + - Format: : + - Types: fix, feat, build, chore, ci, docs, style, refactor, perf, test${descriptionLang} + - Use imperative mood (e.g., "add feature" not "added feature") + - Do not exceed 72 characters + - Do not capitalize the first letter + - Do not end with a period + +2. **branchName**: A suggested Git branch name + - Format: / for conventional commits, or for regular changes + - Use only lowercase letters, numbers, and hyphens + - Maximum 50 characters + - No leading or trailing hyphens + +3. **isBreakingChange**: Boolean indicating if this is a breaking change + - Set to true if the changes break backward compatibility + - Look for removed public APIs, changed function signatures, etc. + +4. **summary**: A brief 1-2 sentence summary of the changes${summaryLang} + - Describe what was changed and why + +## Response Format + +Respond with valid JSON only, no additional text or markdown formatting. + +Example response: +{ + "commitMessage": "feat: add user authentication system", + "branchName": "feat/add-user-authentication", + "isBreakingChange": false, + "summary": "Added JWT-based authentication with login and logout endpoints." +} + `.trim(); +}