Spec Kit 扩展自测指南:用speckit.selftest.extension验证扩展的目录发现、安装与注册全生命周期
【免费下载链接】spec-kit💫 Toolkit to help you get started with Spec-Driven Development项目地址: https://gitcode.com/GitHub_Trending/sp/spec-kit
Spec Kit(spec-kit)内置了一个名为selftest的官方扩展,它把"扩展从目录中被发现、被安装、被注册"这一完整生命周期封装成了一条可交给 AI 代理执行的自测命令speckit.selftest.extension。本文以 selftest 命令模板 为主线,逐步骤解析其四步验证流程(目录发现校验、安装模拟、注册校验、测试报告),并结合 扩展管理器源码 与 扩展子命令实现 讲清每一步背后的真实机制,包括install_allowed的目录分级设计、--from安装通道的安全加固,以及.specify/extensions/.registry注册表的结构与容错策略。读完本文,你可以直接在自己的 Spec Kit 项目上对任意扩展(无论是官方目录还是社区目录中的条目)跑一遍端到端生命周期验证,并能读懂 CLI 每一步输出背后的实现原理。
一、selftest 扩展是什么:一份命令模板 + 一个扩展清单
selftest 扩展由两部分组成:
- extensions/selftest/extension.yml:扩展清单,声明身份信息与提供的命令;
- extensions/selftest/commands/selftest.md:自测命令的 Markdown 提示词模板,由代理解释执行。
清单中的关键字段如下(摘自 extension.yml):
schema_version: "1.0" extension: id: selftest name: Spec Kit Self-Test Utility version: 1.0.0 description: Verifies catalog extensions by programmatically walking through the discovery, installation, and registration lifecycle. author: spec-kit-core repository: https://github.com/github/spec-kit license: MIT requires: speckit_version: ">=0.2.0" provides: commands: - name: speckit.selftest.extension file: commands/selftest.md description: Validate the lifecycle of an extension from the catalog.几个要点:
requires.speckit_version: ">=0.2.0"声明了运行该扩展所需的最低 Spec Kit 版本,这是扩展系统对版本兼容性的显式约束;provides.commands将命令speckit.selftest.extension绑定到模板文件commands/selftest.md;- 模板文件首部的 frontmatter
description: "Validate the lifecycle of an extension from the catalog."是命令在目录与帮助输出中展示的说明文字。
使用方式:在已安装 selftest 扩展的 Spec Kit 项目中,通过代理调用/speckit.selftest.extension <扩展名>(例如/speckit.selftest.extension linear)。模板中的$ARGUMENTS占位符即为用户传入的扩展名。模板明确约定:若$ARGUMENTS为空,必须提示用户提供一个扩展名,而不是自行猜测目标——这是代理驱动命令常见的"防幻觉"约束。
自测的总目标(模板中 Goal 一节原文语义)是:针对$ARGUMENTS这个扩展,验证其端到端生命周期(discovery、installation、registration),即"目录能否发现它、CLI 能否把它装进工作区、安装后项目配置中是否留下注册记录"。
二、四步验证流程逐条解析
Step 1:目录发现校验(Catalog Discovery Validation)
模板要求执行:
specify extension info "$ARGUMENTS"判定标准有二:命令成功完成,且返回的扩展 ID 与$ARGUMENTS完全一致。任何一条不满足即判该步失败。
从源码看这一步对应extension info子命令(见 extension_info 命令)。它在解析用户输入时有一套值得注意的消歧逻辑(消歧实现):
- 输入若直接命中某个扩展 ID,则直接采用;
- 若输入命中的是展示名(name),且恰好只有一个候选,则按该候选解析;
- 若展示名命中多个扩展(多个目录条目共用同名展示名),CLI 会打印一张"Matching extensions"表格(含 ID、Name、Version、Catalog 列)并以错误退出,要求改用扩展 ID 重跑。
因此自测报告中"返回的扩展 ID 必须精确匹配$ARGUMENTS"这一判定,正好覆盖了展示名歧义被拒绝的边界情形——用 ID 传参是这条命令最稳妥的调用方式。
Step 2:安装模拟(Simulate Installation),含install_allowed分级与--from回退
模板要求先直接尝试:
specify extension add "$ARGUMENTS"并明确指出:如果目录中该扩展来自install_allowed: false的目录(discovery-only,仅可发现),这一步预期会失败。随后要求回退到第二条通道:从目录元数据中取出该扩展的download_url(模板注明"可通过 catalog info 命令或 UI 获取"),再执行:
specify extension add "$ARGUMENTS" --from "<download_url>"这条"预期失败 + 显式回退"的写法,正是 Spec Kit 目录安全模型的直接体现。CLI 对 catalog 子系统的官方说明(catalog_app 帮助文本)把目录分成两类:
- 安装源(
install_allowed: true):你信任、可从中安装扩展的目录;内置的default(官方)目录属于此类,你自己编写并审核过的目录也应当标记为install_allowed: true; - 仅发现目录(
install_allowed: false):只用于"找到"扩展、不可安装的检索面;内置的community社区目录即为此类,开箱即可被search检索,但不能被add安装。
该说明同时强调"不要把 discovery-only 目录翻成install_allowed",并给出两条合规的安装路径:审核后用specify extension add <name> --from <url>直接安装,或自己策展一个受控目录。这与 扩展系统 RFC 中的默认目录栈描述一致:官方catalog.json(install_allowed: true)+ 社区catalog.community.json(install_allowed: false)。仓库内 官方目录 中的 4 个扩展(agent-context、assess、bug、git)均带bundled: true标记;而 社区目录 的每个条目都携带download_url等元数据——这正是 Step 2 回退通道所需要的download_url字段来源。
--from通道本身在源码中有完整的安全加固。install_extension_from_url 函数 的文档字符串列出了它的防护清单:
- 协议约束:URL 必须使用 HTTPS(仅 localhost 允许 HTTP),否则抛出
ExtensionError; - 下载上限:响应读取被限定在 50 MiB 以内,防止超大响应拖垮下载目录;
- 归档格式探测:自动识别 ZIP 或 tar.gz/tgz 归档;
- TOCTOU 防护:下载落在一个"临时 inode"文件(POSIX 下 unlink、Windows 下 O_TEMPORARY)上,下载完直接交给
install_from_zip消费,路径不会被二次打开。
此外,CLI 在打印任何建议用户复制执行的命令前,还会经过_command_safe_id过滤:目录条目属于不可信输入,只有当 ID 匹配^[a-z0-9-]+$(小写字母、数字、连字符)且不以连字符开头时才被原样嵌入命令,否则回退为占位符,避免目录数据被注入为 shell 元字符。仓库测试中也有对应的路径穿越防护用例(见 test_registrar_path_traversal.py),覆盖安装/注册阶段对恶意路径的拦截。
对自测而言,Step 2 的通过标准是"直接 add 在 discovery-only 场景下预期失败、--from回退成功",而不是两步都必须成功——模板用*expected* to fail的措辞显式区分了"失败是符合设计的行为"。
Step 3:注册校验(Registration Verification)
模板要求add完成后,用cat之类工具检查项目配置中存在$ARGUMENTS的注册记录,模板给出的示例路径是:
cat .specify/extensions/.registry/$ARGUMENTS.json这里结合源码做一个重要澄清。从 ExtensionRegistry 实现 看,注册表是单文件结构:REGISTRY_FILE = ".registry",注册表路径为extensions_dir / ".registry",即.specify/extensions/.registry——一个 JSON 文件,内部是{"schema_version": "1.0", "extensions": {<id>: {…元数据…}}}的结构。也就是说,当前源码实现中并不存在按扩展 ID 拆分的.registry/<id>.json文件,模板里的按 ID 路径应理解为"该扩展注册记录所在位置"的示意;执行自测的代理实际会校验.specify/extensions/.registry中是否包含目标 ID 的记录(或其等价落盘形态),判定本质是"安装是否留下了可查询的注册条目"。
注册记录本身的信息来自 registry.add 实现:写入时深拷贝传入元数据(版本、来源等),并自动补充installed_at字段(UTC ISO 时间戳)。因此 Step 3 的验证除了"记录存在",还可以顺带核对installed_at时间戳是否落在本次自测时间窗口内,作为"新安装生效"的佐证。
注册表实现还有两个健壮性设计值得了解:
- 损坏恢复:
_load在文件缺失、非普通文件、JSON 解析失败或结构不符时一律回退为空注册表,保证安装/启用/禁用流程不因脏数据崩溃; - fail-closed 探测:
is_corrupt则供解析路径使用,区分"注册表不存在"(安全)与"注册表存在但不可读"(危险)——后者若被静默当空表处理,会把磁盘上所有未注册扩展目录误判为"已注册启用",因此解析路径必须显式失败。
Step 4:验证报告(Verification Report)
模板要求汇总前三步的标准输出,直接生成一份终端风格的测试报告返回给用户。模板给出的标准输出格式如下(完整继承原文):
============================= test session starts ============================== collected 3 items test_selftest_discovery.py::test_catalog_search [PASS/FAIL] Details: [Provide execution result of specify extension search] test_selftest_installation.py::test_extension_add [PASS/FAIL] Details: [Provide execution result of specify extension add] test_selftest_registration.py::test_config_verification [PASS/FAIL] Details: [Provide execution result of registry record verification] ============================== [X] passed in ... ==============================报告刻意采用 pytest 风格的视觉语言(test session starts/collected 3 items/::用例命名 /passed in ...收尾),让"发现、安装、注册"三个用例各自独立判级(PASS/FAIL + Details 摘录原始命令输出),即使读者不看执行过程也能在报告里定位失败环节。命名中的test_selftest_discovery.py::test_catalog_search只是报告格式中的用例标签,并非要求仓库中真的存在这些测试文件。
三、一次完整自测的实战走查
以 社区目录 中真实存在的ascii-diagram扩展(v1.1.0,带download_url)为例,把四个步骤串起来:
发现:
specify extension info ascii-diagram期望:成功返回 ID 为ascii-diagram的条目。注意该条目来自install_allowed: false的社区目录——可以用specify extension catalog list核对各活跃目录的 install_allowed 状态(该命令对 discovery-only 目录会打印 "discovery only" 并附上审核提示)。安装(直接通道,预期失败):
specify extension add ascii-diagram期望:被拒。这是设计使然——社区目录只是检索面。CLI 在相关路径上的提示文案会指向specify extension add <name> --from <url>这条合规出口(见 _commands.py 中的安装提示)。安装(
--from回退通道):specify extension add ascii-diagram --from "https://github.com/MRZHUH/spec-kit-ascii-diagram/archive/refs/tags/v1.1.0.zip"期望:HTTPS 下载、归档探测、解包安装均成功。此步骤会触发 install_extension_from_url 的加固下载链路。
注册校验:检查
.specify/extensions/.registry(或模板中指示的记录位置)存在ascii-diagram的记录,且含installed_at时间戳;随后可用specify extension list复核——该命令(extension_list 实现)会列出已安装扩展的名称、版本、描述、命令数、钩子数、优先级与启用状态。输出报告:按 Step 4 的 pytest 风格模板汇总三步结果,例如
2 passed, 1 expected-fail (discovery-only)这类结论需要如实标注——直接add在 discovery-only 目录条目上失败属于 Step 2 的预期分支,不应被简单计为整体失败。
四、适用前提与注意事项
- 运行环境:命令依赖已安装的
specifyCLI 与一个已初始化的 Spec Kit 项目(存在.specify/目录结构);selftest 扩展自身要求speckit_version >= 0.2.0。extension add/info等命令在源码入口处会先通过_require_specify_project校验当前目录是合法的 Spec Kit 项目根。 - 用 ID 而不是展示名传参:如 Step 1 分析所述,展示名在跨目录检索时可能歧义并导致命令直接报错退出,自测传参建议使用扩展 ID。
- 网络依赖:目录发现与
--from安装均需要联网访问目录 URL 或下载 URL(源码中定义了官方与社区两个目录的默认远端地址,见 catalog URL 常量;项目级目录栈可通过.specify/extension-catalogs.yml配置,SPECKIT_CATALOG_URL环境变量亦可指定)。离线环境下该自测的 Step 1/Step 2 无法完整执行。 - 注册表结构以源码为准:模板中
.registry/$ARGUMENTS.json的按 ID 路径与源码实现的单文件.registry注册表在字面上不一致,执行校验时应以"目标 ID 的记录是否存在于注册表"为判定基准,而非严格匹配该文件路径;若未来注册表格式变更(schema_version目前为1.0),报告中的注册校验步骤应同步调整。 - 不要把自测当作安装决策:
--from通道的设计前提是"你先审核了该扩展"(vetted),自测验证的是生命周期机制是否通畅,不代表对被测扩展本身的安全背书。
五、小结
selftest 扩展用最少的表面(一条命令模板)覆盖了扩展系统最关键的三条链路:目录发现(specify extension info)、分级安装(add与add --from的双通道)、注册落盘(.specify/extensions/.registry)。它的价值在于把扩展系统的"约定行为"——尤其是 discovery-only 目录预期失败、--from合规回退——固化成了可重复执行的验证脚本,并借用 pytest 风格报告让每一步都有独立的可判定结论。对扩展作者而言,它是发布前验证自己扩展条目元数据(ID、download_url、install_allowed归属目录)是否正确的现成工具;对平台使用者而言,它是理解 Spec Kit 目录安全模型(扩展用户指南、API 参考)的最佳实践入口。
【免费下载链接】spec-kit💫 Toolkit to help you get started with Spec-Driven Development项目地址: https://gitcode.com/GitHub_Trending/sp/spec-kit
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考