首页
/ Spec Kit 项目宪法深度解析:specify-cli 的五大治理原则与质量门禁

Spec Kit 项目宪法深度解析:specify-cli 的五大治理原则与质量门禁

2026-09-06 11:19:30作者:翟萌耘Ralph

Spec Kit 仓库中的 .specify/memory/constitution.md 是为 specify-cli 代码库正式批准的 v1.0.0 项目宪法(批准日期 2026-06-19)。它不是空泛的口号文档,而是从源码、测试套件、CI 流水线与项目约定(AGENTS.mdCONTRIBUTING.mdDEVELOPMENT.md)中多轮分析提炼出的约束性治理契约:五大核心原则(代码质量与架构纪律、测试驱动变更、CLI 用户体验一致性、离线优先性能纪律、最小依赖与幂等文件操作)外加安全/跨平台约束、开发流程质量门和治理机制。读完本文,你将理解这份宪法如何与仓库中真实存在的注册表架构、Typer 命令结构、目录栈(catalog stack)模型和离线打包机制一一对应,并掌握在贡献代码时如何满足这些 MUST 级门禁。

宪法定位:约束谁、约束什么

宪法的开篇明确了适用范围:Spec Kit 指 specify-cli 包及其捆绑资产,是一个本地、离线可用的开发者 CLI,用于为 AI 编码代理启动和运行规格驱动开发(SDD)工作流。这些原则对所有变更具有约束力——包括 specify bundle 子命令,以及任何未来的命令组、集成(integration)、扩展(extension)、预设(preset)或工作流(workflow)。

文件顶部的 HTML 注释是一份 Sync Impact Report(同步影响报告),记录了版本从 (template/unratified) 升级为 1.0.0 的判定:这是一次 MAJOR 基线,因为它在原先不存在强制治理的地方建立了有约束力的规则。报告同时确认了四个模板的对齐情况:

  • templates/plan-template.md — 通用的 "Constitution Check" 门禁保持有效,计划阶段由原则 I–V 具体填充;
  • templates/spec-template.md — 无宪法特定占位符,无需改动;
  • templates/tasks-template.md — 任务分类(setup/foundational/story/polish)已涵盖测试、性能与 UX 任务,无需改动;
  • .github/agents/speckit.*.agent.md — 命令指引与 agent 无关,无需改动。

这种"宪法修订必须同步更新依赖模板并在 Sync Impact Report 中登记"的机制,正是后文 Governance 章节的核心要求。

原则 I:代码质量与架构纪律

宪法第一条要求代码库遵循严格的、注册表驱动的、分层架构,所有变更必须保持这一结构。结合仓库源码,该原则的每一条都有可验证的落点:

1. CLI 表层与可导入逻辑分离。 面向用户的命令放在 Typer 子应用中(如 src/specify_cli/commands/、各包下的 _commands.py);业务逻辑则放在普通的、可独立导入的模块中,不带 @app.command() 装饰器。新功能的编排逻辑 MUST 能够独立于 Typer 被测试。从源码结构看,src/specify_cli/integrations/ 正是这一模式的样板:命令层在 _commands.py,而安装、状态查询、迁移等逻辑分散在 _install_commands.pymanifest.py 等纯逻辑模块中。

2. 使用既定的扩展模式。 新的 agent/integration MUST 继承标准基类之一——MarkdownIntegrationTomlIntegrationYamlIntegrationSkillsIntegration——并声明所需的类属性(keyconfigregistrar_config,以及适用时的 context_file)。只有在没有任何基类合适时才可继承 IntegrationBase,且偏差必须说明理由。这一要求在 base.py 中得到直接印证:IntegrationBase 的 docstring 明确要求子类设置 key("unique identifier, matches actual CLI tool name")、config(与 AGENT_CONFIG 条目兼容的 dict)、registrar_config(与 CommandRegistrar.AGENT_CONFIGS 兼容的 dict),并可选设置 invoke_separatormulti_install_safe

3. 尊重单一事实来源。 内建组件通过对应注册表接线,导入与注册保持字母序;重复的 key MUST 大声失败而非静默覆盖。integrations/init.py 中的 _register() 函数精确实现了这一契约——空 key 抛 ValueError,重复 key 抛 KeyError("Integration with key ... is already registered.")。而 _register_builtins() 中 38 个内建集成的导入与注册列表全部按字母序排列,kiro_cli/cursor_agent/ 等包目录用下划线、key 保持连字符形式(如 key = "kiro-cli"),与宪法"包目录用下划线、key 保持规范(通常连字符)形式"的规定完全一致。对于 CLI 型集成,key 必须与可执行文件名匹配,使 shutil.which(key) 能够解析到二进制。

4. 命名与类型不是可选项。 私有模块/函数以 _ 前缀开头且 MUST NOT 跨包边界导入;每个新模块以 from __future__ import annotations 开头并使用现代类型语法(dict[str, Any]str | None),拒绝旧式 Dict/List/Optional。仓库中 base.py 第一行导入就是 from __future__ import annotations__init__.pyINTEGRATION_REGISTRY: dict[str, IntegrationBase]get_integration(key: str) -> IntegrationBase | None 也都是现代语法实例。

宪法给出的 Rationale 解释了为什么:注册表 + 基类的架构是数十个集成、扩展、工作流能够以最小耦合共存的根本,这里的漂移会成倍放大维护成本,并打破"加一个子类、注册一次、附带一个测试"(add one subclass, register once, ship a test)的契约。

原则 II:测试驱动变更(不可协商)

第二条是宪法中标注 NON-NEGOTIABLE 的条款:每个行为变更 MUST 附带自动化测试,测试套件是硬性门禁。

1. 测试门禁合并。 CI 在操作系统 × Python 版本的矩阵上运行 pytest,变更必须通过矩阵中每一个单元格。查看当前仓库的 .github/workflows/test.yml,实际矩阵为 ubuntu-latest / windows-latest / macos-latest × Python 3.13 / 3.14(宪法批准时记录的是 3.11–3.13 组合,pyproject.tomlrequires-python = ">=3.11" 为下限),但"全矩阵通过"这一门禁语义不变。此外 ruff 检查在独立的 job 中先行执行。

2. 一致性(parity)不变量必须成立。 每个集成 MUST 存在于注册表中、在需要时拥有 CommandRegistrar 配置条目、并附带专属测试文件 tests/integrations/test_integration_<key>.py(key 中的连字符在文件名中变为下划线)。这些由参数化测试(如 tests/integrations/test_registry.py)强制,MUST NOT 被弱化。仓库中 tests/integrations/ 目录下 test_integration_claude.pytest_integration_kiro_cli.py 等数十个文件正是这一不变量的实物证据——每个内建集成一个测试文件,与 _register_builtins() 的字母序注册一一对应。

3. 遵循 pytest 约定。 测试模块/类/函数使用项目配置好的 test_* / Test* 命名(见 pyproject.toml[tool.pytest.ini_options]python_files = ["test_*.py"]python_classes = ["Test*"]python_functions = ["test_*"]addopts 包含 --strict-markers);用 tmp_pathmonkeypatch 和 autouse 的 auth 隔离 fixture 隔离状态;平台相关测试必须加守卫(如 @requires_bash),而不是放任其失败。test.yml 中的注释也证实了 Windows 上 bash 依赖测试的自动跳过策略(tests/conftest.py::_has_working_bash(),WSL launcher 因无法处理原生 Windows 路径而被拒绝)。

4. 安全与幂等性测试是强制类别。 路径穿越拒绝、清单哈希完整性/符号链接安全、不覆盖幂等性已由现有测试套件覆盖(如 tests/test_extension_add_path_traversal.pytests/test_registrar_path_traversal.pytests/test_check_prerequisites_paths_only.py);凡触及文件写入、路径处理或 setup 脚本的变更 MUST 扩展(绝不缩减)这些覆盖。

5. 网络全部 mock。 任何测试不得发起真实的外部网络调用;HTTP MUST 被 stub,使套件确定、可离线运行。

宪法的 Rationale 指出:支持 agent 的广度与离线/断网保证,只能靠详尽的参数化测试维持;parity 和安全套件正是阻止"单个新集成让整个矩阵回归"的防线。

原则 III:CLI 与用户体验一致性

第三条要求 CLI 呈现一个连贯的表层,每个命令组都必须"感觉像其他命令组":

  • 复用共享动词词汇。 面向消费者的命令组使用既定动词——listadd/installremovesearchinfoupdate,以及适用时的 enable/disableset-priority。现有动词能表达时 MUST NOT 新造动词;真正的新动词必须说明理由。
  • 镜像目录栈(catalog-stack)模型。 由 catalog 支撑的命令组 MUST 暴露 <group> catalog list|add|remove,背后是优先级有序的源栈(数字越小优先级越高)加每源安装策略(install-allowed vs discovery-only),且无项目配置时回退到内建默认栈。仓库源码完整印证了这套模型:src/specify_cli/bundler/services/catalog_stack.py 定义了 CatalogStack 类;src/specify_cli/bundler/models/catalog.py 的模块 docstring 声明优先级为 project > user > built-in,并定义策略常量 INSTALL_ALLOWED = "install-allowed"DISCOVERY_ONLY = "discovery-only";而 primitives.py 中的报错信息 "Preset '...' is from a discovery-only catalog" 则展示了策略在执行期的实际生效方式。
  • 标准方式注册子应用。 命令组是 typer.Typer(...) 实例,通过 app.add_typer(child, name="...") 挂载,最好经由模块化 register(app) 函数并在 __init__.py 中导入;嵌套 MUST 控制在约 2–3 层以内。
  • 输出一致且机器友好。 人类可读输出使用共享的 Rich 约定(如 [green]✓[/green] 表示成功、[red]Error:[/red] 加非零退出码表示失败、消息中包含可操作的修复建议);提供 --json 标志时,合法 JSON 走 stdout,其余日志重定向到 stderr。
  • 交互安全且幂等。 破坏性操作在确认前展示将发生什么变更;"already installed / already present" 的结果应当成功(退出码 0)而非报错;面向用户的命令组 MUST 在 docs/reference/ 下有文档。

Rationale 一语中的:可预测性就是产品。用户学一套动词、一套 catalog 模型、一套输出口语,然后应用到每个命令组——包括 specify bundle

原则 IV:离线优先的性能与资源纪律

第四条确立了 Spec Kit 作为本地 CLI 的性能契约:响应速度、离线可操作、优雅降级。

  • specify init 与核心脚手架 MUST 完全离线可用,基于捆绑的 core_pack 资产;资产解析 MUST 优先捆绑资产,其次源码检出,最后才考虑网络。pyproject.toml[tool.hatch.build.targets.wheel.force-include] 段是这一原则的直接实现:模板、templates/commands、三种语言的 scripts/、捆绑扩展(git、agent-context、assess、bug)、workflows/speckit、预设(lean、constitution-sync)乃至社区 bundle catalog 快照,全部被 force-include 进 wheel 的 specify_cli/core_pack/ 命名空间,注释明确写着 "Bundle core assets so specify init works without network access (air-gapped / enterprise)"。tests/contract/test_wheel_core_pack_scripts.py 则从契约层面守护 wheel 中这些核心脚本的完整性。
  • 网络使用是惰性的、有界的、可降级的。 网络调用只发生在用户显式命令下;MUST 设置超时;MUST 缓存 catalog 结果(1 小时 TTL)并在失败时回退到过期缓存;MUST 将离线/限流状况表达为清晰消息而非崩溃。
  • 保持启动开销低。 避免在 import 时加入重量级工作;新的可选子系统 SHOULD 优先惰性加载而非无条件 eager import,使无关命令(包括 --help)保持快速。
  • 文件系统写入最小且幂等。 安装 MUST 跟踪文件(SHA-256 清单)、避免破坏用户修改过的内容、只卸载哈希仍匹配的文件、绝不跟随符号链接逃出项目根目录。

Rationale:开发者在断网(air-gapped)、企业、弱网环境中运行此工具;离线优先行为和幂等的、哈希跟踪的文件操作,是它可以被反复安全且快速运行的原因。

原则 V:最小依赖与安全、幂等的文件操作

第五条守护项目的依赖面与磁盘足迹:

  • 默认零新增运行时依赖。 运行时依赖集被刻意保持精简并锁定在最低主版本。查看 pyproject.toml,当前依赖恰好是宪法列出的九个:typerclickrichplatformdirsreadcharpyyamlpackagingpathspecjson5。新增依赖需要维护者同意,且须论证现有依赖无法满足需求;新子系统 SHOULD 在进程内复用既有原语机制(见 src/specify_cli/bundler/services/primitives.py)而非重新实现或重新分发。
  • 所有路径都经过校验。 任何来自用户/清单/catalog 输入的项目相对路径 MUST 被限制在项目根内(Path.relative_to 检查)并拒绝穿越载荷;符号链接逃逸 MUST 被拒绝。
  • 错误显式且链式。 输入预先校验;抛出带可操作上下文的异常(违规字段/值加提示);用 raise ... from exc 保留原因;可能合法失败的 I/O MUST 优雅降级而非抛出原始 traceback。
  • 版本遵循 SemVer。 用户可见与打包行为变更遵循 MAJOR.MINOR.PATCH 语义;不向后兼容的变更 MUST 被明确指出并论证。

Rationale:精简且锁定的依赖集加上加固的幂等文件处理,是工具在企业与断网环境中值得信赖、且维护成本可控的根本。

安全与跨平台约束

宪法在五大原则之外单列了三项硬性约束:

  • 跨平台一致性是强制的。 代码 MUST 在 Linux、macOS、Windows 上运行,Python 范围为 3.11–3.13(pyproject.toml 声明 requires-python = ">=3.11")。Windows 细节(UTF-8 流重配置、bash 依赖测试自动跳过)MUST 被尊重;不得在没有守卫回退的情况下引入仅 POSIX 的假设。
  • 安全工具是门禁。 CodeQL 与项目安全测试套件(路径穿越、清单/符号链接加固)MUST 保持绿色——对应 .github/workflows/codeql.ymlsecurity.yml 流水线。网络访问在测试中默认关闭、运行时可选开启、超时受限、凭证隔离。
  • 格式化被强制。 .editorconfig 规则(LF 行尾、末尾换行、无行尾空格、Python 4 空格 / YAML-JSON-Markdown 2 空格)、ruff check src/markdownlint-cli2(配置见 .markdownlint-cli2.jsonc)MUST 通过。此外,pyproject.toml 的 ruff 配置锁定了 subprocess 安全姿态:S602/S604/S605 规则意味着任何 shell=Trueos.system 的重新引入都必须带显式 # noqa 注释,让偏差在评审中可见——这正是"安全测试与静态检查互为门禁"的具体体现。

开发工作流与质量门

宪法将日常开发流程也变成了契约:

  • 分支命名遵循 <type>/<number>-<short-slug>(无 issue 时 <type>/<short-slug>),<type> ∈ {feat, fix, docs, community, chore}。
  • PR 保持聚焦,MUST 满足:通过 ruffpytest(全矩阵)、markdown lint、CodeQL;为新行为新增/扩展测试;行为变化时更新面向用户的文档(README.mddocs/spec-driven.md);披露所使用的任何 AI 辅助。
  • 影响 slash 命令的变更 MUST 通过编码代理手动演练,并在 PR 中报告结果(按 CONTRIBUTING.md)。
  • 大型或横切变更(新模板、新参数、新命令组)MUST 在实现前与维护者达成一致。

治理机制:权威、修订与合规审查

宪法的 Governance 章节规定了它的自我维护方式:

  • 权威。 原则 I–V 是约束性门禁;templates/plan-template.md 中的 ## Constitution Check 小节 MUST 对照这些原则评估,/speckit.analyze 将违反 MUST 的冲突视为 CRITICAL。冲突的解决方式是修改 spec、plan 或 tasks——而不是稀释原则。plan 模板第 108 行附近的注释("Fill ONLY if Constitution Check has violations that must be justified")与这一机制呼应。
  • 修订。 对宪法本体的修改需要一个含理由的 PR、维护者批准、并按以下策略升版;任何修订 MUST 在同一变更中传播到依赖的模板与命令指引,并记录在本文件顶部的 Sync Impact Report 中——文件头部那份 v1.0.0 报告本身就是范例。
  • 版本策略(治理也用 SemVer)。 MAJOR = 不向后兼容的治理变更或原则的移除/重定义;MINOR = 新原则/新章节或实质性扩展的指引;PATCH = 澄清与非语义性润色。
  • 合规审查。 每个 PR 与每次评审 MUST 核验对原则的合规性;新增复杂度或任何偏差 MUST 在 PR 中论证(对 plan 而言则在 plan 的 Complexity Tracking 节中);未论证的违规阻塞合并。

文档末尾标记:Version 1.0.0 | Ratified 2026-06-19 | Last Amended 2026-06-19

结语

Spec Kit 的宪法展示了 SDD 方法论的一个容易被忽视的面向:不仅规格驱动功能开发,也用治理文档驱动工程纪律本身。它的独特之处在于"从现状提炼而非凭空立规"——注册表的重复 key 大声失败(integrations/init.py)、core_pack 的离线捆绑(pyproject.toml)、catalog 优先级栈(catalog_stack.py)在宪法写下之前就已存在,宪法只是把这些隐含约定升级为显式的 MUST 门禁,并配套了可执行的验证手段(CI 全矩阵、CodeQL、parity 参数化测试、Sync Impact Report)。对于贡献者而言,理解这份宪法等于拿到了 specify-cli 项目的架构地图:任何新集成、新命令组或新扩展,只要对照五大原则逐条自查,就能通过仓库设定的一切质量门。

登录后查看全文
热门项目推荐
相关项目推荐