15-commit命令
15. commit 命令
所属分组:命令系统
概述
/commit是 Claude Code 中最具代表性的 prompt 类斜杠命令。它本身不含任何 git 操作代码——既不调用simple-git,也不直接child_process.exec——而是通过一份精心设计的 prompt 模板,把"读 status、读 diff、读最近 commit、起草 message、执行 commit"整条链路完全交给模型与 Bash 工具完成。这种"prompt-as-command"的设计哲学是 Claude Code 命令系统的核心:命令作者只需要描述目标和约束,执行细节由模型在运行时编排。
本文拆解commands/commit.ts这 90 行源码,看它如何用一段模板字符串、一份 allowedTools 白名单、以及executeShellCommandsInPrompt提供的"内联 shell 占位符"机制,完成一次安全的 git 提交。
源码位置
- [commands/commit.ts](file:///e:/2026plan/AI_Lab/claude-code-sourcemap-main/restored-src/src/commands/commit.ts)
- [utils/promptShellExecution.ts](file:///e:/2026plan/AI_Lab/claude-code-sourcemap-main/restored-src/src/utils/promptShellExecution.ts)
- [utils/attribution.ts](file:///e:/2026plan/AI_Lab/claude-code-sourcemap-main/restored-src/src/utils/attribution.ts)
- [utils/undercover.ts](file:///e:/2026plan/AI_Lab/claude-code-sourcemap-main/restored-src/src/utils/undercover.ts)
核心实现分析
1. 命令骨架:极简的 PromptCommand
commit.ts导出的command对象是上一篇介绍的PromptCommand形态的典型样本:
constcommand={type:'prompt',name:'commit',description:'Create a git commit',allowedTools:ALLOWED_TOOLS,contentLength:0,// Dynamic contentprogressMessage:'creating commit',source:'builtin',asyncgetPromptForCommand(_args,context){/* ... */},}satisfies Command几个关键字段的含义:
type: 'prompt':声明这是 prompt 类命令,REPL 不会直接执行它,而是把getPromptForCommand返回的ContentBlockParam[]注入到模型上下文,让模型用工具完成任务。allowedTools: ALLOWED_TOOLS:本次命令执行期间模型只能使用这些工具,其他工具一律拒绝。这是 prompt 命令的"权限收敛"机制。contentLength: 0:注释写明 “Dynamic content”,因为真实 prompt 内容包含git status、git diff等命令输出,长度在运行时才确定。该字段用于 token 估算,0 表示"动态"。progressMessage: 'creating commit':UI 上显示的进度文案。source: 'builtin':标记为内置命令,影响 typeahead 中的来源标注(参考formatDescriptionWithSource)。
2. allowedTools:最小权限白名单
ALLOWED_TOOLS是一个三元素数组:
constALLOWED_TOOLS=['Bash(git add:*)','Bash(git status:*)','Bash(git commit:*)',]字符串形如Bash(git add:*),是 Claude Code 工具权限语法中"前缀匹配 + 通配"的写法:允许任何以git add开头的 Bash 命令,但git push、git reset、rm等一律被拦截。这一白名单在两处生效:
getPromptForCommand中通过getAppState()临时覆盖toolPermissionContext.alwaysAllowRules.command,让这些 git 子命令免确认直接放行;- REPL 在命令执行期间把
allowedTools作为整体工具白名单应用,模型即使想调用其他工具也调不动。
这种"双重锁定"让/commit在便利性和安全性之间取得平衡:用户输入/commit后无需为每一条git add、git status、git commit单独确认,但又无法借/commit之名做git push --force之类的危险操作。
3. Prompt 模板:Context + Safety Protocol + Task
getPromptContent()函数返回的模板是整个命令的灵魂。它由三部分组成:
Context(上下文采集)
## Context-Current git status:!`git status`-Current gitdiff(staged and unstaged changes):!`git diff HEAD`-Current branch:!`git branch --show-current`-Recent commits:!`git log --oneline -10`注意!`git status`这种内联反引号语法——它是executeShellCommandsInPrompt提供的"内联 shell 占位符"。在 prompt 真正发给模型之前,executeShellCommandsInPrompt会扫描整段文本,把这些!`...`替换成对应命令的实际输出。这意味着模型看到的是已经填好的真实 git status、真实 diff、真实最近 10 条 commit,而不是"请你自己运行 git status"的指令。
这种"占位符预执行"模式有两个好处:第一,模型不需要消耗工具调用额度去采集上下文,一次 commit 任务的工具调用次数大幅下降;第二,prompt 长度更可预测,避免模型因为漏跑 status 而瞎猜。
Git Safety Protocol(安全约束)
- NEVER update the git config - NEVER skip hooks (--no-verify, --no-gpg-sign, etc) unless the user explicitly requests it - CRITICAL: ALWAYS create NEW commits. NEVER use git commit --amend, unless the user explicitly requests it - Do not commit files that likely contain secrets (.env, credentials.json, etc). Warn the user if they specifically request to commit those files - If there are no changes to commit (i.e., no untracked files and no modifications), do not create an empty commit - Never use git commands with the -i flag (like git rebase -i or git add -i) since they require interactive input which is not supported这段是给模型的"硬性禁令"。其中"NEVER use--amend"和"NEVER skip hooks"是两条最关键的红线——它们防止模型为了让 commit 看起来更整洁而改写历史或绕过 CI 检查。"-iflag"那条则是技术约束:交互式 git 命令在 Claude Code 的非交互 Bash 工具下无法工作。
Your task(任务步骤)
1. Analyze all staged changes and draft a commit message: - Look at the recent commits above to follow this repository's commit message style - Summarize the nature of the changes (new feature, enhancement, bug fix, refactoring, test, docs, etc.) - Ensure the message accurately reflects the changes and their purpose - Draft a concise (1-2 sentences) commit message that focuses on the "why" rather than the "what" 2. Stage relevant files and create the commit using HEREDOC syntax: git commit -m "$(cat <<'EOF' Commit message here.${commitAttribution ? `\n\n${commitAttribution}` : ''} EOF )"任务部分把 commit 拆成"起草 message"和"执行 commit"两步,并显式要求用 HEREDOC 语法。HEREDOC 是为了避免多行 commit message 在 shell 中被截断或转义出错——这是模型生成 shell 命令时最容易翻车的地方,所以模板直接给出固定模板,让模型只填 message 内容。
模板末尾的${commitAttribution}是动态拼接的"署名"文本,由getAttributionTexts()返回。比如对外发行版可能附加Generated with Claude Code,内部 undercover 模式则返回空字符串以隐藏来源。
4.getPromptForCommand:占位符预执行 + 权限注入
getPromptForCommand是PromptCommand接口的实现,它做了三件事:
asyncgetPromptForCommand(_args,context){constpromptContent=getPromptContent()constfinalContent=awaitexecuteShellCommandsInPrompt(promptContent,{...context,getAppState(){constappState=context.getAppState()return{...appState,toolPermissionContext:{...appState.toolPermissionContext,alwaysAllowRules:{...appState.toolPermissionContext.alwaysAllowRules,command:ALLOWED_TOOLS,},},}},},'/commit',)return[{type:'text',text:finalContent}]}第一,调用getPromptContent()拿到模板字符串。第二,把模板交给executeShellCommandsInPrompt,让它把!`git status`等占位符替换成真实输出。第三,返回[{ type: 'text', text: finalContent }],这是ContentBlockParam[]格式,REPL 会把它作为用户消息注入对话。
值得注意的是中间那段getAppState()覆盖:它不是修改全局 app state,而是返回一个局部副本,把alwaysAllowRules.command替换成ALLOWED_TOOLS。这样executeShellCommandsInPrompt在执行git status、git diff等占位符命令时就不会触发权限确认弹窗——因为占位符命令也是通过 Bash 工具跑的,没有这道覆盖,用户会被四次权限弹窗打断。
executeShellCommandsInPrompt的实现位于utils/promptShellExecution.ts:它用BLOCK_PATTERN与INLINE_PATTERN两个正则扫描文本,把所有匹配到的命令通过BashTool执行,再把输出回填到原文中。注释里还提到一个性能细节:INLINE_PATTERN在大文本上比BLOCK_PATTERN慢 100 倍,所以先用text.includes('!')做廉价预筛,93% 没有 `` !`` 的 skill 直接跳过昂贵扫描。
5. Undercover 模式:内部用户的隐藏身份
getPromptContent()开头有一段条件分支:
letprefix=''if(process.env.USER_TYPE==='ant'&&isUndercover()){prefix=getUndercoverInstructions()+'\n'}USER_TYPE === 'ant'表示 Anthropic 内部用户,isUndercover()则是一种"隐藏身份"开关。开启时,prompt 头部会拼上getUndercoverInstructions()返回的额外指令,要求模型在 commit message、PR 描述等对外可见的产物中不暴露"由 Claude 生成"的信息。与之配合,getAttributionTexts()在 undercover 模式下返回{ commit: '', pr: '' },让模板里的${commitAttribution}拼接出空字符串。
这套机制让 Anthropic 内部工程师在用 Claude Code 处理对外开源仓库的 commit 时,不会自动带上"Generated with Claude Code"的署名,避免污染开源项目的 commit 历史。
关键设计要点
- Prompt-as-command:
/commit不写任何 git 逻辑,只写一份 prompt 模板。命令作者专注于"目标和约束",执行细节由模型编排。这让命令极薄(90 行)、极容易维护,也极容易复制改造(用户可以写自己的/my-commitskill)。 - 占位符预执行:
!`git status`语法让上下文采集发生在 prompt 注入之前,模型拿到的是已填好的真实数据,省下大量工具调用,也避免了模型"忘记看 diff"这类失误。 - 最小权限白名单:
allowedTools收敛到git add/status/commit三个前缀,加上alwaysAllowRules临时覆盖,既免确认又防越权——git push、git reset --hard等危险操作在/commit期间根本调不动。 - HEREDOC 模板:commit message 多行场景下,shell 转义是模型最容易出错的地方。模板直接给出
git commit -m "$(cat <<'EOF' ... EOF)"固定骨架,模型只填消息正文,把 shell 注入风险降到最低。 - Safety Protocol 写进 prompt:除了工具白名单这种"硬约束",模板还把"–amend/–no-verify 不许用""secrets 文件要警告"等"软约束"直接写进 prompt 文本。这是 prompt-engineering 视角的安全防线,与工具白名单形成纵深防御。
与其他模块的关系
utils/promptShellExecution.ts:/commit的占位符预执行完全依赖它的BLOCK_PATTERN/INLINE_PATTERN扫描与BashTool调用。同一套机制也是所有 skill 模板的执行基础。utils/attribution.ts:提供commit、pr两种署名文本,决定 commit message 末尾是否附加"Generated with Claude Code"。utils/undercover.ts:内部 undercover 模式的开关与指令来源,被commit.ts与attribution.ts共同消费。commands.ts:commit被列入INTERNAL_ONLY_COMMANDS,意味着它仅在USER_TYPE === 'ant'的内部构建中可见,外部发行版不会出现/commit命令。tools/BashTool/:占位符命令与最终的git commit都通过 BashTool 执行,allowedTools的Bash(git add:*)语法正是 BashTool 权限规则的消费方。commands/commit-push-pr.ts、commands/autofix-pr/:同属 git 工作流命令家族,复用相同的 prompt 模式与 attribution 机制。
小结
/commit是理解 Claude Code 命令系统设计哲学的最佳样本。它用 90 行代码展示了"prompt-as-command"的三件套:一份带占位符的模板、一份 allowedTools 白名单、一段写进 prompt 的安全约束。命令本身不碰 git,却能让模型安全地完成"看 diff → 仿风格 → 写 message → 跑 commit"全流程。这种把命令作者从"写执行逻辑"解放到"写目标和约束"的范式,是后续/review、/plan、/security-review等命令的共同底座,也是用户自定义 skill 的标准范式。