spec-kit 的 agent-context 扩展:用 speckit.agent-context.update 让编码 Agent 始终读对最新的 plan
在 Spec-Driven Development 流程中,speckit.specify / speckit.plan 产出的 plan.md 是编码 Agent 最重要的上下文入口之一,但 Agent 自身的上下文文件(CLAUDE.md、.github/copilot-instructions.md、AGENTS.md 等)并不会自动感知 plan 路径的变化。spec-kit 的 agent-context 扩展通过 speckit.agent-context.update 命令解决这一问题:它把一段带标记的“受管区块”写入(或更新)Agent 上下文文件,并始终指向最新的 plan。本文基于该扩展的命令文档 speckit.agent-context.update.md,结合 Bash 脚本、Python 实现 与配套配置,完整讲解其配置项、plan 路径解析策略、路径安全约束和实际执行方式。
命令定位:一个只更新“受管区块”的上下文同步命令
speckit.agent-context.update 的功能定义非常克制:只刷新指定 Agent 上下文文件中由标记符包围的受管 Spec Kit 区块,文件内其余内容一律不动。该命令由 extension.yml 注册提供:
provides:
commands:
- name: speckit.agent-context.update
file: commands/speckit.agent-context.update.md
description: "Refresh the managed Spec Kit section in the coding agent context file"
除手动触发外,extension.yml 还声明了两个可选钩子,使区块能随开发流程自动保持同步:
hooks:
after_specify:
command: speckit.agent-context.update
optional: true
description: "Refresh agent context after specification"
after_plan:
command: speckit.agent-context.update
optional: true
description: "Refresh agent context after planning"
这带来一个清晰的自动化链路:speckit.specify 结束 → after_specify 钩子触发 → 更新上下文文件;speckit.plan 结束 → after_plan 钩子触发 → 再次更新,此时受管区块中的 plan 路径指向刚生成的 plan.md。
值得一提的是设计边界:Spec Kit 本体从不触碰用户的 Agent 上下文文件,只有显式安装该扩展后才有此行为。从 extension 的 README 可以看到,specify init 默认不安装它,需通过 specify extension add agent-context 显式安装;禁用后(specify extension disable agent-context),模板中的 __CONTEXT_FILE__ 占位符保持原样,扩展配置也永远不会被读取。这一“opt-in”边界在 CLI 帮助函数 的注释中也有对应说明:CLI 侧与该扩展的上下文文件解析互不干涉。
配置文件与核心参数
命令的行为完全由扩展配置驱动。脚本读取位于 .specify/extensions/agent-context/agent-context-config.yml 的配置文件(安装后位于项目内;仓库中的模板见 agent-context-config.yml),涉及三个关键字段:
| 配置项 | 说明 |
|---|---|
context_file |
单个 Agent 上下文文件的项目相对路径,例如 CLAUDE.md |
context_files |
多个上下文文件的可选列表。非空时逐项更新,且优先级高于 context_file |
context_markers.start / .end |
受管区块的起止定界符;字段缺失时回退为 <!-- SPECKIT START --> / <!-- SPECKIT END --> |
仓库自带的配置模板完整展示了这三项及注释掉的示例:
context_file: ""
context_files: []
context_markers:
start: "<!-- SPECKIT START -->"
end: "<!-- SPECKIT END -->"
未显式配置时的自播种(self-seed)逻辑
从源码看,context_file / context_files 均为空时并非直接放弃,而是有一条回退链:Python 实现的 _collect_context_files 会依次尝试——
- 读
context_files列表(去重,Windows/Cygwin/MSYS 平台下按大小写不敏感去重); - 读单个
context_file; - 读取
.specify/init-options.json中记录的活动集成键(integration或ai字段),再通过内置映射表 agent-context-defaults.json 反查该集成默认对应的上下文文件。
该映射表覆盖了主流集成,例如 claude → CLAUDE.md、copilot → .github/copilot-instructions.md、cursor-agent → .cursor/rules/specify-rules.mdc、cline → .clinerules/specify-rules.md、kilocode → .kilocode/rules/specify-rules.md,而 codex、gemini、goose 等大量集成则统一指向 AGENTS.md(完整映射见 agent-context-defaults.json)。若映射表中也没有对应键,脚本会向 stderr 提示“请显式设置 context_file”并跳过更新。
plan 路径的解析策略:feature.json 优先,mtime 兜底
受管区块的核心内容是“当前 plan 在哪里”。当调用命令时显式传入 plan_path 参数时直接使用;省略时,解析顺序在 Bash 脚本 L258-L345 与 Python 的 _resolve_plan_path 中一致:
第一步:优先读 .specify/feature.json。 该文件由 speckit.specify 写入,其中的 feature_directory 字段指向当前特性的目录。脚本会将其拼接为 <feature_directory>/plan.md 并检查文件是否存在。这里有几个实现细节值得关注:
- 路径中的反斜杠(PowerShell 在 Windows 下写入)会先归一化为正斜杠;
feature_directory允许相对或绝对路径,Windows 下还兼容C:/...这种驱动器前缀形式;- 在比较与输出前会先
resolve()符号链接,使 macOS 上/var/...与/private/var/...被视为等价; - 最终写入区块的是项目相对路径(
relative_to(root).as_posix()),保证区块内容跨机器可读。
第二步:feature.json 缺失或其 plan 尚不存在时,回退到 mtime 启发式。 脚本对 specs/ 目录做递归搜索(rglob("plan.md")),而不是旧的单级 specs/*/plan.md 通配——这一步专门兼容通过 SPECIFY_FEATURE_DIRECTORY 创建的作用域嵌套布局 specs/<scope>/<feature>/plan.md,然后按修改时间倒序取最新的一份:
candidates = []
for p in specs.rglob("plan.md"):
rel = _resolved_rel(p)
if rel is not None:
candidates.append((p, rel))
candidates.sort(key=lambda pr: pr[0].stat().st_mtime, reverse=True)
注意 _resolved_rel 会先 resolve() 符号链接再判断是否落在项目根内——注释中解释了原因:relative_to() 是纯词法比较,若 specs/ 下存在指向项目外部的符号链接,不先解析就可能把项目外的 plan 当成“最新”选入。这与命令文档中“any plan.md under specs/, including nested scoped layouts”的描述完全吻合。
路径安全约束:只允许项目内相对路径
命令文档明确声明:“Context file paths must stay project-relative; absolute paths, Windows drive paths, backslash separators, and .. path segments are rejected.” 源码中对应的强制检查在 _validate_context_file(Bash 侧等价逻辑见 update-agent-context.sh L219-L252),逐层拦截:
- 以
/开头的绝对路径,或^[A-Za-z]:形式的 Windows 盘符路径 → 直接报错退出(退出码 1); - 路径中含反斜杠 → 拒绝,强制统一使用正斜杠;
- 任一路径段为
..→ 拒绝,防止相对路径穿越; - 兜底:把
项目根/相对路径做resolve()后仍须满足relative_to(root),否则视为“解析后逃出项目根”拒绝。
四层校验同时覆盖了词法规则与真实文件系统的解析结果。而 context_files 与 context_file 均为空且自播种也失败时,命令打印 “nothing to do” 并以成功码退出(退出码 0),保证钩子链不会因为无目标而中断。
受管区块的更新语义:创建、替换、追加
区块内容本身由 _build_section 生成,形如:
<!-- SPECKIT START -->
For additional context about technologies to be used, project structure,
shell commands, and other important information, read the current plan
at specs/001-feature/plan.md
<!-- SPECKIT END -->
若解析不到 plan 路径,则省略 at <plan_path> 一行,其余引导语保留。写回逻辑在 _upsert_section 中,对目标文件按四种情形分别处理,这解释了命令文档中“creates, replaces, or appends”的表述:
- 起止标记都找到且顺序正确:整体替换标记之间的旧区块(含 CRLF 换行收尾的精确截断);
- 只有起始标记:从起始标记处截断,插入新区块;
- 只有结束标记:新区块插到文件开头,旧内容保留在区块之后;
- 都没有:追加到文件末尾(空文件则直接写入区块);文件不存在则创建(父目录不存在时先
makedirs)。
另外两个针对 Cursor 的特殊处理值得了解:写入前统一把 \r\n、\r 归一化为 \n;而文件名以 .mdc 结尾的规则文件,还会经过 ensure_mdc_frontmatter——Cursor 只自动加载带 alwaysApply: true frontmatter 的 .mdc 规则,该函数会在缺失时补上 frontmatter,或在已有 frontmatter 中修复/追加 alwaysApply: true,且尽量保留原有注释与格式。
如何执行该命令
在已初始化且已安装扩展的项目根目录下,按平台选择入口(见命令文档的 Execution 一节):
# Bash
.specify/extensions/agent-context/scripts/bash/update-agent-context.sh [plan_path]
# PowerShell
.specify/extensions/agent-context/scripts/powershell/update-agent-context.ps1 [plan_path]
plan_path 为可选参数;省略时走上文所述的 feature.json → mtime 两级解析。在 Agent 中则按各集成语法调用规范命令名 speckit.agent-context.update(点号语法 /speckit.agent-context.update、连字符语法 /speckit-agent-context-update 等,具体见 extension 的 README)。Bash 脚本执行时会先探测可用解释器:优先 SPECKIT_PYTHON 环境变量指向的解释器,其次 python3、python,要求该解释器是 Python 3 且可导入 yaml;找不到时打印提示(pip install pyyaml)并以 0 退出跳过,而非失败——这让钩子在缺依赖时不阻塞主流程。
验证与回归:测试覆盖了什么
仓库为这条命令链提供了成体系的测试,便于读者核对上文行为是否如描述一致:
- tests/extensions/test_extension_agent_context.py:断言扩展包布局完整(
extension.yml必需字段、命令文件存在且文档化了三类路径拒绝规则——“Windows drive paths”“backslash separators”均被断言在命令文档中)、Bash/PowerShell 双脚本行为等价,并验证 CLI 不再解析__CONTEXT_FILE__占位符; - tests/extensions/test_update_agent_context_python_parity.py 与 test_agent_context_cli_free.py:分别保障 Python 端口与 shell 版本的行为一致性,以及扩展不依赖 Specify CLI 运行;
- tests/extensions/test_update_agent_context_feature_json.py:专门验证 feature.json 驱动的 plan 解析路径。
这些测试与 scripts 目录下的三端实现共同构成了该命令的“实现事实”边界:配置解析、自播种、路径校验、plan 解析、upsert 与 .mdc frontmatter 修复,均可在对应文件中按本文给出的相对路径逐条对照。
小结
speckit.agent-context.update 是一个职责单一的上下文同步命令:以 context_file/context_files/context_markers 三项配置为输入,以 feature.json 优先、mtime 兜底的策略定位最新 plan,以“创建/替换/追加”的幂等语义维护标记区块,同时用四层校验保证只写项目内相对路径。对使用者而言,记住三件事即可:显式安装扩展(specify extension add agent-context)、按需编辑 .specify/extensions/agent-context/agent-context-config.yml、在 specs/ 结构变化后手动或经钩子触发一次更新——受管区块内的 plan 指针就会指向正确的位置,而你在上下文文件里手写的其余内容不会被改动。
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 StartedRust0624
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