首页
/ spec-kit constitution-sync 预设详解:用 wrap 策略包装 /constitution 命令,让宪法变更自动同步到依赖模板与命令文件

spec-kit constitution-sync 预设详解:用 wrap 策略包装 /constitution 命令,让宪法变更自动同步到依赖模板与命令文件

2026-09-06 15:05:46作者:咎竹峻Karen

spec-kit 的 constitution-sync 预设通过 wrap 组合策略在核心 /constitution 命令之上追加一段"一致性传播"流程:每次宪法(constitution)更新后,自动对齐 .specify/templates/ 下的 plan/spec/tasks 模板、项目本地命令文件和指导文档,并在宪法文件的 Sync Impact Report 中记录变更。读完本文,你将理解该预设的 frontmatter 结构与传播步骤、{CORE_TEMPLATE} 占位符在源码中的解析机制、安装期"有守卫的宪法物化"如何依据生成内容哈希触发,以及这个 opt-in 预设带来的漂移与覆盖风险。

一、这是什么:一个包装核心命令的 opt-in 预设文件

presets/constitution-sync/ 是 spec-kit 内置的预设包,其唯一的交付物就是本文的主角 presets/constitution-sync/commands/speckit.constitution.md。它不是取代核心宪法命令,而是包装(wrap)它:命令体第一行正文是占位符 {CORE_TEMPLATE},运行时被替换为当前核心 speckit.constitution 命令的正文,然后在其后追加"Constitution Template Sync"传播章节。

文件的 frontmatter 声明了关键元数据:

description: Create or update the project constitution, then propagate the amended guidance into dependent templates and installed command files (opt-in template sync).
strategy: wrap
handoffs:
  - label: Build Specification
    agent: speckit.specify
    prompt: Implement the feature specification based on the updated constitution. I want to build...
  • strategy: wrap 告诉预设解析器该命令使用包装组合,而非整体替换;
  • handoffs 定义了完成宪法更新后向 speckit.specify 的交接提示,与核心命令模板 templates/commands/constitution.md 中的 handoff 保持一致,保证用户更新完宪法后可直接进入规格编写环节。

preset.yml 中对应的声明进一步确认了交付方式与适用前提:

requires:
  speckit_version: ">=0.14.4"   # 0.14.4 起核心 /constitution 不再传播,避免双重传播

provides:
  templates:
    - type: "command"
      name: "speckit.constitution"
      file: "commands/speckit.constitution.md"
      strategy: "wrap"

注意 requires.speckit_version: ">=0.14.4":从源码注释看,0.14.4 引入了 runtime-resolution 基线,核心 /constitution 命令移除了传播逻辑;把这个预设装到更老的核心上会导致传播逻辑被双重应用

二、核心命令行为:先完整执行宪法更新({CORE_TEMPLATE} 展开)

包装体的第一行是 {CORE_TEMPLATE},展开后即核心命令的完整正文(来源 templates/commands/constitution.md)。理解这一部分,才能理解预设为何"对核心变更保持前向兼容"——它没有复制核心命令的任何内容。

核心命令的主要结构(展开后包含在包装命令内):

  1. User Input:读取 $ARGUMENTS 注入的用户输入(新增/修改的宪法原则描述);

  2. Scope Guard(作用域守卫):这是理解预设意义的关键段落。核心命令明确规定——

    "This command's own work is limited to updating the project constitution itself. Dependent templates and commands read the constitution at runtime and are not modified here."

    即:默认模型下,依赖模板和命令在每次运行时读取活动宪法.specify/memory/constitution.md),因此 /constitution 只改宪法本身,不碰模板和命令文件。这是 spec-kit 推荐的"单一事实源 + 运行时解析"治理模型;

  3. Pre-Execution Checks:检查 .specify/extensions.ymlhooks.before_constitution 扩展钩子,按 optional 标志输出可选预钩子提示;

  4. 核心更新流程:读取现有宪法、应用用户输入、写入更新后的宪法、生成 Sync Impact Report。

三、预设追加的传播流程:Constitution Template Sync 五步走

{CORE_TEMPLATE} 之后是预设追加的核心章节,其开篇就明确宣告了与核心命令的关系:

This section supersedes the "Scope Guard" above for template and command propagation. The core Scope Guard states that dependent templates and commands "are not modified here" — that restriction is intentionally lifted by this preset.

也就是说,"模板/命令不在此处修改"的限制被有意解除——但只解除"传播"这一条,Scope Guard 的其余约束(不实现功能、不生成应用代码、不触碰与宪法/模板工作流无关的产物)仍然有效。

宪法写完后,命令执行以下五步一致性传播(consistency propagation):

第 1 步:对齐 plan-template.md 的 Constitution Check

读取 .specify/templates/plan-template.md,检查其中 "Constitution Check" 或规则是否与更新后的原则一致。这里有一个关键设计抉择:

  • 只有当团队打算把具体 gate 文本作为提交内容审查时,才在这里固化具体的 gate 文本;
  • 否则保留运行时指针 [Gates determined based on constitution file],让 /plan 每次运行时从活动宪法动态填充。

这与 presets/constitution-sync/README.md 中的告诫一致:预填的 Constitution Check 会让首次 /plan 锚定在冻结文本上,可能引入偏见。

第 2 步:对齐 spec-template.md 的范围与需求约束

读取 .specify/templates/spec-template.md,检查范围/需求章节的一致性——如果宪法增删了强制章节或约束(mandatory sections / constraints),模板需同步更新。

第 3 步:对齐 tasks-template.md 的任务分类

读取 .specify/templates/tasks-template.md,确保任务分类反映新增或移除的"原则驱动型任务类别",例如可观测性(observability)、版本管理(versioning)、测试纪律(testing discipline)等。

第 4 步:巡检已安装的 speckit 命令文件

读取当前 agent 下每个已安装的 Spec Kit 命令文件(包括该命令自身),命名形态有三种:

  • speckit.*(点号,如 Copilot 类集成);
  • speckit-*(连字符,视 agent 而定);
  • skills 式集成的 speckit-<name>/SKILL.md,例如位于 .github/agents/.github/skills/.claude/skills/ 或 agent 等价的命令目录。

巡检目标是确认在需要通用指导的地方,不再残留过时的 agent 专属命名(例如 CLAUDE-only 的叫法)。此步有一条硬性边界

  • 只有项目本地、且不由任何 preset/extension 管理的命令文件才允许手工编辑;
  • 凡是由解析栈(resolution stack)组合出来的命令文件(preset/extension 提供或包装的),一律不得就地编辑——必须通过栈重新生成。原因:任何对账操作(specify integration usespecify integration upgrade、preset/extension 的安装或移除)都会用栈的重新合成结果覆盖(clobber)手工编辑。

第 5 步:更新运行时指导文档

读取项目中的运行时指导文档(如 README.mddocs/quickstart.md 或 agent 专属指导文件),更新其中引用了已变更原则的表述。

收尾:扩展 Sync Impact Report

完成传播后,在 .specify/memory/constitution.md 顶部的 Sync Impact Report 中追加记录,格式要求列出:

  • 需要更新的模板(标注 ✅ updated / ⚠ pending)及其文件路径。

最后一条红线再次强调:不要直接编辑由 preset/extension 提供的版本化模板或命令文件。这些产物归其所属包所有,会在包的下一次更新或栈对账时重新合成,手工编辑必然被覆盖。传播只能写入项目自有的 .specify/templates/ 脚手架,以及不受 preset/extension 管理的命令文件。

四、源码纵深:wrap 策略如何在解析期替换 {CORE_TEMPLATE}

{CORE_TEMPLATE} 不是普通的文本模板变量,它在预设命令注册时被 CommandRegistrar 处理。核心实现在 src/specify_cli/presets/init.py#L163-L229_substitute_core_template

  1. 占位符检测:若 body 中不含 {CORE_TEMPLATE} 直接原样返回——因此核心命令被 wrap 后,其原有行为一字不差地保留,预设只是"追加";
  2. 核心模板的三级解析顺序PresetResolver):
    • resolve_core(cmd_name):覆盖 tier-1 项目级覆盖与按文件名的 tier-3/4 查找,本地覆盖永远优先
    • resolve_extension_command_via_manifest(cmd_name):扩展命令的 manifest 回退(例如 speckit.selftest.extension 对应文件名 commands/selftest.md 这类命令名与文件名不一致的情形);
    • resolve_core(short_name):用去掉 speckit. 前缀的短名回退到核心模板目录(如 constitutiontemplates/commands/constitution.md)。
  3. 防嵌套设计resolve_core() 会跳过已安装预设(tier 2),避免另一个预设的 wrap 输出被误认为"真正的核心",从而造成无限/错误嵌套;
  4. 健壮性:核心模板文件不可读(OSError/UnicodeDecodeError)时按缺失处理并给出 warning,不会让单个损坏的项目覆盖文件崩溃整个命令注册;替换成功后还会返回核心的 frontmatter,让调用方继承其 scripts/agent_scripts 字段。

Bash/PowerShell 侧脚本实现了对称的占位符校验,例如 scripts/bash/common.sh#L902-L907:wrap 策略下若缺少 {CORE_TEMPLATE} 占位符直接报错返回 2;Python 侧对应 scripts/python/common.py#L462。策略总表见 presets/ARCHITECTURE.mdwrap 即"内容中的 {CORE_TEMPLATE}(模板/命令)或 $CORE_SCRIPT(脚本)占位符被低优先级内容替换"。

五、源码纵深:安装期"有守卫的宪法物化"(reconcile)

命令文件的 wrap 只是这个预设的一半能力。另半部分在 CLI 安装/变更链路中:presets 的存在会激活核心的安装期宪法调和(install-time constitution reconciliation)。入口是 src/specify_cli/presets/init.py#L3702-L3732reconcile_constitution / _reconcile_constitution

def _reconcile_constitution(self, *, create_if_missing: bool = False) -> None:
    """Materialize the winning layer when constitution-sync is enabled."""
    sync_metadata = self.registry.get(_CONSTITUTION_SYNC_PRESET_ID)
    if sync_metadata is None or not sync_metadata.get("enabled", True):
        return                                   # 未安装/未启用 → 什么都不做
    memory_constitution = self.project_root / ".specify" / "memory" / "constitution.md"
    if not memory_constitution.exists() and not create_if_missing:
        return
    resolver = PresetResolver(self.project_root)
    if memory_constitution.exists() and not _constitution_is_generated(
        self.project_root, memory_constitution, resolver
    ):
        return                                   # 人工编辑过 → 不自动替换
    _materialize_constitution_template(self.project_root, memory_constitution)

三层守卫清晰可见:

  1. opt-in 守卫:registry 中没有 constitution-sync 或其 enabled 为 False 时直接返回——未安装该预设的项目行为完全不变;
  2. 缺失守卫.specify/memory/constitution.md 不存在且未要求创建时不动作;
  3. 生成内容哈希守卫_constitution_is_generated 校验活动文件是否仍与记录的生成内容一致——只有"未被人改过的生成文件"才会被重新物化,人工编辑会自动关闭替换行为

触发点分布在 src/specify_cli/presets/_commands.py:preset 优先级变更(约 L599)、启用(约 L635)、禁用(约 L672)之后都会调用 manager.reconcile_constitution(...),失败仅发 warning 不阻断变更。这正是 presets/constitution-sync/README.md 所述:"安装该预设会物化当前解析出的 constitution-template;之后的栈变更仅在活动文件仍与其记录的生成内容哈希匹配时重新物化"。

六、安装、验证与回退

安装(bundled 预设,无需下载)

# constitution-sync 是 bundled preset —— no download needed
specify preset add constitution-sync

本地开发验证

# 从本地目录测试
specify preset add --dev ./presets/constitution-sync

# 验证被包装的命令解析结果
specify preset resolve speckit.constitution

# 完成后移除
specify preset remove constitution-sync

specify preset resolve speckit.constitution 的价值在于直接展示 wrap 合成后的最终命令文本,可核对 {CORE_TEMPLATE} 是否已展开为核心正文、传播章节是否位于其后。

迁移回默认运行时解析模型

若决定回到"活动宪法为唯一事实源"的默认模型,先把 .specify/templates/plan-template.md 中每个物化过的 ## Constitution Check 段落重置为运行时指针:

## Constitution Check

*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*

[Gates determined based on constitution file]

然后再移除预设(详见 docs/upgrade.md)。

七、适用边界与三条必须接受的取舍

该预设只应在团队把物化的模板和命令当作"已审查的提交产物"(reviewed, committed artifacts)时使用——例如 plan-template.md 的 Constitution Check 在 PR 里被当作"我们当前的 gate 清单"来阅读,并期望它跟随宪法变化。若你的流程依赖默认运行时解析模型,则不需要它:活动宪法已是唯一事实源,没有东西需要同步。

安装前必须理解的三条风险(来源 presets/constitution-sync/README.md):

  1. 物化副本会漂移:任何被传播的文件都是快照。修改宪法后若未重跑 /constitution,副本即与宪法失同步;默认运行时模型每次运行读取活动宪法,天然无漂移。
  2. 对组合文件的手动编辑无法存活对账:如果 SDD 流程的其余部分由其他 preset/extension 管理,那么 speckit.planspeckit.specifyspeckit.tasksspeckit.analyzespeckit.implement 等命令都来自栈的合成,传播进其中的指导会在下一次 specify integration use/switchspecify integration upgrade 或 preset/extension 安装/移除时被覆盖。这也解释了预设为何自我限制在项目本地文件——传播只对完全属于你的产物可靠
  3. 预填的 Constitution Check 可能影响 /plan:把具体 gate 物化进 plan-template.md 会替换运行时指针,首次 /plan 可能锚定在冻结文本上。除非你确实想要"已提交的 gate",否则应保留指针。

八、总结:预设模型下"治理"与"组合"的分界线

speckit.constitution.md 这个包装文件浓缩了 spec-kit 预设体系的一个核心张力:**组合栈(composed stack)主张模板与命令是"分层、包所有、按需重新合成"的活文件,运行时解析保持宪法为单一事实源;而传播(propagation)**是相反的理念——把指导物化进文件并冻结下来。constitution-sync 预设就是官方支持的逃生舱:wrap 策略保证核心命令升级时自动继承(前向兼容),五步传播 + Sync Impact Report 保证物化产物与宪法的一致性可追溯,而哈希守卫与"只写项目自有文件"的边界则把覆盖风险收敛到可控范围。对你而言的决策路径很明确:plan/specify/tasks/analyze 等命令或模板若来自其他 preset/extension,坚持默认运行时解析模型;只有当受治理的模板与命令确属项目本地的受审产物时,再 specify preset add constitution-sync 换回物化工作流。

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