Spec Kit git 扩展 speckit.git.feature 命令详解:特性分支创建、编号策略与分支命名模板
【免费下载链接】spec-kit💫 Toolkit to help you get started with Spec-Driven Development项目地址: https://gitcode.com/GitHub_Trending/sp/spec-kit
本文围绕 Spec Kit(spec-kit)中 git 扩展的核心命令speckit.git.feature展开,系统讲解它如何在 SDD(Spec-Driven Development,规格驱动开发)工作流中自动创建并切换特性分支:包括分支编号的两种模式(顺序号/时间戳)、配置解析优先级、branch_template分支命名模板的四类占位符与校验规则,以及底层脚本对编号探测、命名长度限制与无 Git 环境的降级处理机制。读完后你可以完整掌握该命令的触发方式、全部可配置参数,并能结合仓库源码理解其实现细节。
命令定位:它只负责"建分支",不负责"建规格"
speckit.git.feature是 git 扩展命令定义 描述的一条 Agent 可执行命令,其 frontmatter 中声明的职责是:
Create a feature branch with sequential or timestamp numbering(创建顺序编号或时间戳编号的特性分支)
文档开篇即明确了职责边界:该命令只处理分支创建("This command handles branch creation only"),而规格目录与规格文件的创建由核心的 specify 工作流完成。这一点在 git 扩展清单 中也能得到印证:speckit.git.feature被注册为before_specify钩子,且optional: false——也就是说,当 git 扩展被安装后,每次执行核心 specify 命令之前它会强制先运行,为本次规格创建一条专属分支。核心 specify 模板 中同样写明:Pre-Execution Checks 阶段会读取.specify/extensions.yml的hooks.before_specify配置并等待钩子执行完毕后再继续。
git 扩展在 扩展 README 中的定位是一个"可选、自包含"的 Git 操作模块,除分支创建外还提供仓库初始化、分支校验、远端检测和自动提交等能力;speckit.git.feature是其中承担特性分支生命周期管理的一环。
安装与启停方式(来自扩展 README):
# 安装内置 git 扩展(无需网络) specify extension add git # 禁用(禁用后仍会继续创建规格,只是不建分支) specify extension disable git # 重新启用 specify extension enable git扩展安装时会把 配置模板 复制为项目内的.specify/extensions/git/git-config.yml,后续所有分支行为都由该文件驱动。
用户输入与执行前置检查
命令定义中的User Input一节直接嵌入占位符$ARGUMENTS,并要求 Agent "MUST consider the user input before proceeding (if not empty)"——即用户随命令一起输入的文字(通常是特性描述)必须被纳入分支命名的考量。
执行前置条件只有一条,且非常具体:
git rev-parse --is-inside-work-tree 2>/dev/null用该命令探测当前目录是否处于 Git 工作树内。如果 Git 不可用,命令不会报错中断,而是"警告用户并跳过分支创建"(见下文"优雅降级"一节)。
GIT_BRANCH_NAME 环境变量覆盖:精确指定分支名
命令定义中的Environment Variable Override一节给出了最高优先级的分支命名通道:当用户通过环境变量、参数或请求显式提供GIT_BRANCH_NAME时,Agent 必须把该值注入环境后再调用脚本。此时脚本的行为是(来自命令文档,与 Bash 脚本实现 逐条对应):
- 使用精确值作为分支名,绕过所有前缀/后缀生成逻辑;
--short-name、--number、--timestamp三个标志全部被忽略;FEATURE_NUM的提取规则:若分支名的最后一个路径段以"数字或时间戳特性标记"开头(例如042-name、feat/042-name、jdoe/app/042-name),则提取该前缀作为特性编号;否则FEATURE_NUM取完整分支名。
脚本内部的对应实现是extract_feature_num_from_branch:先尝试匹配^[0-9]{8}-[0-9]{6}-的时间戳前缀,再退化为匹配任意数字前缀^[0-9]+-,都匹配不到则回退为完整分支名(见 create-new-feature-branch.sh 与 Python 版同构实现)。
值得注意的是,GIT_BRANCH_NAME的精确值仍需满足 GitHub 的 244 字节分支名上限,超限会直接报错退出而不是截断——因为截断一个"用户精确指定"的名字是不被允许的(详见下文长度限制一节)。
分支编号模式:四级配置解析顺序
命令定义中的Branch Numbering Mode一节规定了编号策略(顺序号或时间戳)的解析顺序,这是该命令最容易被忽略的配置细节:
- 检查
.specify/extensions/git/git-config.yml中的branch_numbering值; - 检查
.specify/init-options.json中的feature_numbering值(继承自 Spec Kit 核心); - 检查
.specify/init-options.json中的branch_numbering值(已弃用,仅为向后兼容,未来版本将移除); - 以上都不存在时,默认为
sequential。
这一"扩展配置优先、核心配置兜底"的优先级设计有测试佐证:tests/test_branch_numbering.py 验证了自 v0.10.0 起specify init的--branch-numbering命令行标志已被移除(传入会报No such option),分支编号完全交由 git 扩展的配置文件管理。
配置模板中两种模式的语义如下(来自 git-config.yml 的注释):
sequential:生成001、002… 三位零填充顺序号;timestamp:生成YYYYMMDD-HHMMSS时间戳前缀。
分支名模板:branch_template 与 branch_prefix
命令定义的Branch Name Template一节说明:脚本会读取git-config.yml中可选的branch_template。为空或缺失时使用默认形态{number}-{slug};若设置了模板,则必须满足两条结构约束——{slug}不得出现在{number}之前,且最终路径段必须以{number}-开头。脚本会展开以下四个占位符:
| 占位符 | 含义 | 取值来源(源码佐证) |
|---|---|---|
{author} | 净化后的 Git 作者名 | git config user.name,缺失时回退user.email的@前本地部分,再缺失时回退系统USER环境变量(get_author_token) |
{app} | 净化后的 Spec Kit 初始化目录名 | 取仓库根目录的 basename(get_app_token) |
{number} | 顺序号或时间戳 | 见"编号自动确定"一节 |
{slug} | 生成的短分支名 | --short-name指定或自动从特性描述提炼 |
"净化"由clean_branch_name完成:转小写、非字母数字替换为连字符、压缩连续连字符、去除首尾连字符(create-new-feature-branch.sh)。
对 monorepo 场景,文档给出模板{author}/{app}/{number}-{slug}可以生成形如jdoe/web/008-guided-tour的分支名,同时保持"每个项目独立的特性编号"。这条能力在 tests/extensions/git/test_git_extension.py 中有专门测试:test_branch_template_adds_author_and_app_namespace验证命名空间生成,test_branch_template_scopes_number_after_numeric_app_namespace与test_branch_template_scopes_existing_branch_numbers验证编号探测按模板前缀隔离——即只统计同一命名空间下的分支序号,避免 A 项目的分支抬高 B 项目的序号。
三条模板校验规则在脚本中是硬性错误(不满足直接exit 1):
- 必须包含
{number}占位符——否则生成的分支不再是合法的特性分支; {slug}不得出现在{number}之前——{slug}只能用于最终特性段;- 最终路径段必须以
{number}-开头。
对应实现见 validate_branch_template(Python 版同逻辑见 create_new_feature_branch.py),测试用例test_branch_template_requires_number_token、test_branch_template_rejects_slug_before_number、test_branch_template_requires_feature_segment_to_start_with_number分别覆盖三类错误输入。
除模板外还有一个更简单的入口:branch_prefix。它是"纯命名空间"的简写,展开规则为<branch_prefix>/{number}-{slug}(前缀以/结尾时不重复添加分隔符),见 resolve_branch_template。例如配置branch_prefix: "features/{app}"会展开为features/{app}/{number}-{slug}(配置模板注释 中的示例)。优先级上branch_template高于branch_prefix:模板非空时直接采用,只有模板为空才回退到前缀展开。
配置示例(取自 git 扩展 README 的完整配置段):
# 分支编号策略: "sequential" 或 "timestamp" branch_numbering: sequential # 可选分支名模板。留空则使用默认 "{number}-{slug}" # 支持占位符: {author}, {app}, {number}, {slug} # {slug} 不得出现在 {number} 之前; 最终路径段必须以 {number}- 开头 # monorepo 示例: "{author}/{app}/{number}-{slug}" branch_template: "" # 可选的简写命名空间。留空则按 branch_template/默认行为 # 示例: "features/{app}" 展开为 "features/{app}/{number}-{slug}" branch_prefix: "" # git init 时使用的自定义提交信息 init_commit_message: "[Spec Kit] Initial commit"执行:脚本调用方式与硬性规则
命令定义要求 Agent 先为分支生成一个简洁短名(2–4 个词):分析特性描述、提取最有意义的关键词、尽量使用"动词-名词"结构(如add-user-auth、fix-payment-bug)、保留技术术语与缩写(OAuth2、API、JWT)。随后按平台选择脚本执行(以下命令原文来自命令文档的Execution一节):
- Bash:
.specify/extensions/git/scripts/bash/create-new-feature-branch.sh --json --short-name "<short-name>" "<feature description>" - Bash(时间戳模式):
.specify/extensions/git/scripts/bash/create-new-feature-branch.sh --json --timestamp --short-name "<short-name>" "<feature description>" - PowerShell:
.specify/extensions/git/scripts/powershell/create-new-feature-branch.ps1 -Json -ShortName "<short-name>" "<feature description>" - PowerShell(时间戳模式):
.specify/extensions/git/scripts/powershell/create-new-feature-branch.ps1 -Json -Timestamp -ShortName "<short-name>" "<feature description>"
文档同时给出四条IMPORTANT规则,理解它们是正确驱动该命令的关键:
- 不要传
--number——脚本会自动确定正确的下一个编号(编号探测逻辑见下节); - 始终带 JSON 标志(Bash 为
--json,PowerShell 为-Json),保证输出可被可靠解析; - 每个特性只能运行一次该脚本——因为顺序号是"探测最大值 +1"的副作用操作,重复运行会抬高编号;
- 不要手工展开
branch_template——脚本自己读取扩展配置并一致地应用模板。
从脚本的--help输出(create-new-feature-branch.sh)可以看到完整参数面,供直接手工调用时参考:
| 参数 | 作用 |
|---|---|
--json | 以 JSON 格式输出 |
--dry-run | 只计算分支名,不实际创建分支 |
--allow-existing-branch | 分支已存在时切换到它,而不是失败 |
--short-name <name> | 提供自定义短名(2–4 个词) |
--number N | 手工指定分支编号(覆盖自动探测),必须为非负整数 |
--timestamp | 用时间戳前缀(YYYYMMDD-HHMMSS)替代顺序编号 |
环境变量GIT_BRANCH_NAME | 使用该精确分支名,绕过所有前缀/后缀生成 |
除 Bash 与 PowerShell 两个实现外,仓库中还带有一份 Python 移植版 create_new_feature_branch.py(文件头注明它是 Bash/PowerShell 双实现的 Python port),并有专门的 parity 测试(test_git_extension_python_parity.py)保证三者行为一致,适合在缺少 Bash 环境的平台上直接运行。
编号是如何"自动确定"的
"不要传--number"这条规则背后,是一套三路取最大值的编号探测机制(check_existing_branches):
- specs 目录(
get_highest_from_specs):扫描specs/下所有子目录名,匹配"3 位及以上数字前缀"(^[0-9]{3,}-)且排除时间戳目录(^[0-9]{8}-[0-9]{6}-)的形态,取最大序号。这样即便没有 Git 仓库,或某次分支被删除,规格目录也能保证编号不回退; - 本地与远端分支(
get_highest_from_branches):解析git branch -a输出,剥掉当前分支标记与remotes/<name>/前缀后按同样规则提取。默认路径会先执行git fetch --all --prune同步远端再统计;--dry-run路径则改走无副作用的git ls-remote --heads(设置GIT_TERMINAL_PROMPT=0防止交互提示),避免试算时产生网络写操作; - 模板命名空间隔离:当配置了
branch_template时,脚本从模板中{number}之前的部分渲染出scope_prefix(如jdoe/web/),编号探测只统计该前缀下的分支——这正是 monorepo 下"per-project feature numbering"得以成立的原因,测试test_branch_template_scopes_existing_branch_numbers覆盖了该行为。
最终编号取三路最大值加 1,并以printf "%03d"格式化为至少三位零填充数字(脚本主流程)。时间戳模式下则直接取date +%Y%m%d-%H%M%S,并且若同时传了--number会打印[specify] Warning: --number is ignored when --timestamp is used后清空编号。
短名自动生成的过滤逻辑
如果 Agent 没有传--short-name,脚本会自己从特性描述提炼短名(generate_branch_name,Bash / Python 逻辑一致):
- 小写化、非字母数字转空格后逐词过滤:剔除内置停用词表(the、to、for、add、get、need 等 40 余个常见虚词);
- 保留长度 ≥3 的词;短词仅在原文中以全大写形式出现时保留(用于留住 API、DB、JWT 这类缩写,恰好呼应命令文档中"保留技术术语与缩写"的要求);
- 取前 3 个有意义的词(恰好有 4 个时取 4 个)用连字符拼接。
若--short-name已提供,则跳过上述提炼,直接对指定值做clean_branch_name净化。
244 字节分支名长度限制
脚本内置了 GitHub 分支名 244 字节上限的处理(Bash / Python):
- 若分支名来自
GIT_BRANCH_NAME且超限:直接报错退出,提示"must be 244 bytes or fewer in UTF-8"; - 若分支名为自动生成且超限:逐字符截断 slug 后缀(同时去掉尾部连字符)重新渲染模板,并输出警告
[specify] Warning: Branch name exceeded GitHub's 244-byte limit,同时打印原始名与截断后的字节数; - 若模板前缀本身就超过 244 字节(截空 slug 仍超限):报错"Branch template prefix exceeds GitHub's 244-byte branch name limit"。
优雅降级:没有 Git 时也能走通
命令定义的Graceful Degradation一节与脚本行为一致:当未安装 Git 或当前目录不是 Git 仓库时——
- 跳过分支创建并打印警告:
[specify] Warning: Git repository not detected; skipped branch creation(脚本实际输出会附带计算出的目标分支名,见 警告分支); - 脚本仍然输出
BRANCH_NAME与FEATURE_NUM,调用方可以照常引用(此时编号退化为仅扫描specs/目录); - 结合 扩展 README 的降级说明:规格目录仍会创建在
specs/下,分支校验同样跳过,远端检测返回空结果——整个 specify 流程不因缺少 Git 而中断。
这与extension.yml中requires.tools: git标记为required: false的声明互为印证:Git 是该扩展的"软依赖"。
输出:JSON 契约与下游消费
命令定义的Output一节约定脚本以 JSON 输出两个字段:
BRANCH_NAME:分支名,例如顺序模式下003-user-auth,时间戳模式下20260319-143022-user-auth,模板模式下jdoe/web/003-user-auth;FEATURE_NUM:所用的数字或时间戳前缀。
脚本在 JSON 模式下优先使用jq生成(无jq时回退到手工转义拼接,见 输出段);非 JSON 模式则输出BRANCH_NAME:/FEATURE_NUM:两行纯文本,方便人工阅读。
下游消费方是核心 specify 命令:specify 模板 明确规定,before_specify钩子成功后"已创建/切换到 git 分支并输出含BRANCH_NAME和FEATURE_NUM的 JSON,记下这些值供参考,但分支名不决定规格目录名";若用户提供了GIT_BRANCH_NAME,同样要求透传给钩子。这一契约保证了"分支创建"与"规格目录创建"两个职责可以各自演进而互不耦合。
延伸阅读
- 命令定义(本文主体):speckit.git.feature.md
- git 扩展总览、钩子表与安装方式:extensions/git/README.md
- 扩展清单(命令注册、钩子声明、配置默认值):extensions/git/extension.yml
- 配置模板/运行时配置:extensions/git/config-template.yml、extensions/git/git-config.yml
- 三个平台实现:create-new-feature-branch.sh、create-new-feature-branch.ps1、create_new_feature_branch.py
- 行为测试:tests/extensions/git/test_git_extension.py、tests/test_branch_numbering.py
- 核心侧钩子消费逻辑:templates/commands/specify.md
【免费下载链接】spec-kit💫 Toolkit to help you get started with Spec-Driven Development项目地址: https://gitcode.com/GitHub_Trending/sp/spec-kit
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考