Spec Kit Git 扩展中的 speckit.git.validate:功能分支命名校验机制解析
在 Spec Kit 的规格驱动开发(Spec-Driven Development)流程中,每个功能对应一个带编号前缀的 Git 分支和一个 specs/ 目录。Git 扩展提供的 speckit.git.validate 命令负责回答一个关键问题:当前分支是否遵循了预期的功能分支命名约定,以及它是否还能找到与之对应的规格目录。读完本文,你将掌握该命令的两类命名规则(顺序编号与时间戳)、完整的校验执行流程、非 Git 环境下的降级策略,以及它在 Bash / PowerShell / Python 三套实现中的底层正则逻辑。
命令定位与基本职责
speckit.git.validate 是 Git Branching Workflow 扩展注册的五个命令之一,其声明见 extension.yml:
provides:
commands:
- name: speckit.git.validate
file: commands/speckit.git.validate.md
description: "Validate current branch follows feature branch naming conventions"
该命令的完整行为规范定义在 speckit.git.validate.md 中。需要说明的是,扩展文件 extension.yml 中 requires.tools 声明 git 为 required: false——也就是说 Git 是可选依赖,命令必须能在没有 Git 的环境中优雅降级(后文详述)。
它与扩展内其他命令的关系是:speckit.git.feature 按约定创建分支,speckit.git.validate 在流程中校验分支,speckit.git.commit 在各核心命令前后自动提交,speckit.git.remote 探测远端用于 GitHub 集成,speckit.git.initialize 初始化仓库。完整命令清单见 Git 扩展 README。
前置检查:Git 可用性探测
校验的第一步是确认 Git 可用。按规范文档,执行:
git rev-parse --is-inside-work-tree 2>/dev/null
如果 Git 不可用(未安装或当前目录不是 Git 仓库),命令不报错、不中断,而是输出一条警告并跳过校验:
[specify] Warning: Git repository not detected; skipped branch validation
这条警告文案并非随意指定——它在三套平台实现中是逐字一致的。从源码看,Bash 实现的 check_feature_branch() 在 git-common.sh 中,当 has_git_repo != "true" 时正是向 stderr 打印这句话后 return 0(视为通过):
check_feature_branch() {
local raw="$1"
local has_git_repo="$2"
# For non-git repos, we can't enforce branch naming but still provide output
if [[ "$has_git_repo" != "true" ]]; then
echo "[specify] Warning: Git repository not detected; skipped branch validation" >&2
return 0
fi
...
}
Python 孪生实现 git_common.py 的 check_feature_branch() 同样在非 Git 仓库时打印相同警告并返回 True,保证三种运行环境行为对齐(这一点由 test_git_extension.py 等测试覆盖)。而 has_git() 的探测逻辑本身要求三重条件同时满足:.git 目录/文件存在、git 可执行文件在 PATH 中、rev-parse --is-inside-work-tree 返回 0(见 git-common.sh 与 git_common.py)。
校验规则:两种功能分支命名模式
获得当前分支名的方式是:
git rev-parse --abbrev-ref HEAD
规则要求:分支名的最后一个路径段(final path segment)必须以以下两类特征前缀之一开头。
1. 顺序编号(Sequential)
- 模式:
[0-9]{3,}-,即至少 3 位数字加连字符 - 示例:
001-feature-name、042-fix-bug、1000-big-feature、jdoe/web/008-guided-tour
注意数字位数没有上限,4 位、5 位编号(如 1234-feature-name)同样合法——源码中的错误提示文案 001-feature-name, 1234-feature-name, ... 也印证了这一点(见 git-common.sh)。
2. 时间戳(Timestamp)
- 模式:
[0-9]{8}-[0-9]{6}-,即 8 位日期 + 6 位时间 + 连字符 - 示例:
20260319-143022-feature-name、jdoe/web/20260319-143022-feature-name
命名空间前缀是允许的
两个示例中都出现了 jdoe/web/... 这种带路径前缀的形式。这是因为 Git 扩展支持 monorepo 场景下的 branch_template(如 {author}/{app}/{number}-{slug}),生成 jdoe/web/008-guided-tour 这类分支名;校验规则因此只看“最后一段路径”。这一点在扩展 README 的配置说明中有明确记载:
# Optional branch name template. Leave empty for the default "{number}-{slug}".
# Supported tokens: {author}, {app}, {number}, {slug}; {slug} must not appear
# before {number}, and the final path segment must start with {number}-.
# Example for monorepos: "{author}/{app}/{number}-{slug}"
branch_template: ""
配置存储位置为 .specify/extensions/git/git-config.yml,模板/前缀的完整参数说明见 git-config 模板 与 README 配置章节。
源码中的“畸形时间戳”排除逻辑
规范文档给出的是简化规则,而三套实现中还包含一条防御性细节:顺序模式必须排除“畸形时间戳”。以 Python 实现 git_common.py 为例:
# Accept sequential prefix (3+ digits) but exclude malformed timestamps:
# 7-or-8 digit date + 6-digit time with no trailing slug.
is_sequential = bool(
re.match(r"^[0-9]{3,}-", feature_segment)
and not re.match(r"^[0-9]{7}-[0-9]{6}-", feature_segment)
and not re.fullmatch(r"[0-9]{7,8}-[0-9]{6}", feature_segment)
)
is_timestamp = bool(re.match(r"^[0-9]{8}-[0-9]{6}-", feature_segment))
它把两种情况排除在“顺序编号”之外:
[0-9]{7}-[0-9]{6}-开头——7 位“日期”是坏掉的时间戳(日期应为 8 位),不应被误判成合法的 7 位顺序编号;- 整段恰好是
[0-9]{7,8}-[0-9]{6}(没有后续 slug)——这是“被截断的时间戳”,例如裸的20260319-143022。
Bash 版本在 git-common.sh 中用完全对应的三条正则表达同一意图,PowerShell 版本 Test-FeatureBranch 在 git-common.ps1 中用 $hasMalformedTimestamp 变量实现。三者注释均写明“Logic aligned with the ... twin”,即有意保持跨平台语义一致。
单段命名空间的剥离:effective branch name
另一个实现细节值得注意。在取“最后路径段”之前,三套实现都会先调用一个 effective_branch_name 变换:如果分支名恰好是 a/b 两段(每段不含斜杠),则剥掉第一段——典型场景是 gitflow 风格的 feat/004-name → 004-name(见 git-common.sh 的注释与 git_common.py 的 docstring)。但注意其边界:只有恰好两段时才剥离,像 jdoe/web/008-guided-tour(三段)保持原样,然后取最后一段 008-guided-tour 参与匹配。从源码结构看,这是一种对常见分支命名生态(gitflow 等)的兼容性处理,避免 feat/001-x 这类分支被误判为非功能分支。
执行流程与输出契约
校验命中与未命中时,规范文档规定了明确的输出契约,便于其他 Agent 或脚本机器解析。
命中功能分支
若分支匹配任一模式,输出:
✓ On feature branch: <branch-name>
随后检查 specs/ 下是否存在对应的规格目录:
- 顺序分支:查找
specs/<prefix>-*,其中 prefix 为编号数字部分,忽略分支命名空间前缀; - 时间戳分支:查找
specs/<prefix>-*,其中 prefix 为YYYYMMDD-HHMMSS部分,同样忽略命名空间前缀。
结果二选一:
✓ Spec directory found: <path>
⚠ No spec directory found for prefix <prefix>
第二个输出是警告而非错误——分支合法但规格目录缺失,常见于分支刚创建、specify 流程尚未产出的情况。
未命中功能分支
若不在功能分支上(例如还在 main 或 master),输出两行:
✗ Not on a feature branch. Current branch: <branch-name>
Feature branches should be named like: 001-feature-name, 20260319-143022-feature-name, or <namespace>/001-feature-name
对照源码实现,Bash/PowerShell/Python 三者在失败分支写入 stderr 的正是这两行(Python 版本多列出了 1234-feature-name 示例):
if not is_sequential and not is_timestamp:
print(f"ERROR: Not on a feature branch. Current branch: {raw}", file=sys.stderr)
print(
"Feature branches should be named like: 001-feature-name, "
"1234-feature-name, 20260319-143022-feature-name, or "
"<prefix>/001-feature-name",
file=sys.stderr,
)
return False
(见 git_common.py。)
优雅降级:SPECIFY_FEATURE 环境变量兜底
规范文档的“Graceful Degradation”一节定义了无 Git 场景下的兜底路径:
- 检查
SPECIFY_FEATURE环境变量; - 若已设置,则用该值替代分支名,按同样的命名模式校验;
- 若未设置,则输出警告并跳过校验。
SPECIFY_FEATURE 并非凭空出现,而是扩展创建分支流程写入的环境变量。从源码看,create-new-feature-branch 的三套实现都会在成功建分支后把它设成新分支名:PowerShell 直接执行 $env:SPECIFY_FEATURE = $branchName(create-new-feature-branch.ps1),Bash/Python 则在 stderr 打印持久化提示 # To persist: export SPECIFY_FEATURE=...(create-new-feature-branch.sh、create_new_feature_branch.py)。由此可以推断:SPECIFY_FEATURE 是“会话级”的分支标记——即使后续在非 Git 目录(或 CI 的浅检出等受限环境)中运行流程,validate 仍能基于该标记完成命名一致性检查,而不是硬性失败。
这与整个扩展的降级哲学一致,Git 扩展 README 的 Graceful Degradation 一节概括为:
- 规格目录照常创建于
specs/下; - 分支创建被跳过并给出警告;
- 分支校验被跳过并给出警告(即本文命令的行为);
- 远端探测返回空结果。
安装、禁用与验证路径
在目标项目(注意是已 specify init 的业务项目,而非本仓库)中启用该命令:
# 安装内置 git 扩展(无需网络)
specify extension add git
# 禁用 / 重新启用
specify extension disable git
specify extension enable git
命令模板由扩展注册时安装到项目的命令目录;本仓库中的权威源文件即 speckit.git.validate.md。若只想阅读行为定义而不安装,直接打开该文件即可——它就是 Agent 执行时遵循的完整行为规范(frontmatter 中的 description 与正文的 Prerequisites / Validation Rules / Execution / Graceful Degradation 四节构成命令契约)。
行为测试集中在 test_git_extension.py,其中包含“模板渲染出的最终路径段必须能通过校验”(Templates must render a final path segment that validation accepts)与“配置模板必须包含 {number} 才能生成可校验分支”等断言,从测试角度保证了分支创建侧与分支校验侧的正则契约不会漂移;另有 test_git_extension_python_parity.py 同目录下的 Python 对等测试用于保证三语言实现一致。
小结:规则速查
| 输入 | 是否通过 | 输出 |
|---|---|---|
001-feature-name |
通过 | ✓ On feature branch: ... + spec 目录检查 |
jdoe/web/008-guided-tour |
通过(命名空间被忽略) | 同上 |
20260319-143022-feature-name |
通过 | 同上 |
feat/004-name |
通过(两段名先剥前缀) | 同上 |
20260319-143022(无 slug 的裸时间戳) |
不通过 | ✗ Not on a feature branch... |
main / master |
不通过 | 同上,并附命名建议 |
非 Git 目录 + SPECIFY_FEATURE 已设 |
按该值校验 | 依赖取值 |
| 非 Git 目录 + 未设 | 跳过 | [specify] Warning: Git repository not detected; skipped branch validation |
speckit.git.validate 的设计要点可以概括为:规则宽松到覆盖命名空间与两种编号策略,实现严格到排除畸形时间戳,失败路径永远给出可操作的提示而非硬错误。它是 Git 扩展把“分支名 ↔ 规格目录”这一 SDD 核心不变量落到运行时的守门人。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00