
Speckit Specify Zh
- 296 installs
- 9 repo stars
- Updated November 28, 2025
- forztf/open-skilled-sdd
speckit-specify-zh is a Claude Code skill that produces structured Chinese-language product specifications with Speckit before implementation for developers who need clarified requirements and engineering handoff documen
About
speckit-specify-zh is a Claude Code skill from the open-skilled-sdd collection that generates structured Chinese-language product specifications using the Speckit workflow before any implementation begins. The skill clarifies requirements, acceptance criteria, and scope boundaries so engineering teams receive an explicit handoff artifact instead of ambiguous chat notes. Developers reach for it when product discussions happen in Chinese, when stakeholders need PRD-style documents aligned to Speckit conventions, or when spec-driven development (SDD) gates coding behind written acceptance tests. It fits teams practicing specification-first delivery where downstream agents or engineers implement against a frozen spec. Use it at feature kickoff, after stakeholder interviews, or when translating informal requests into formal Chinese specs with testable criteria.
- Chinese spec templates
- Requirement decomposition
- Acceptance criteria drafting
- Scope boundary definition
- Spec-driven development handoff
Speckit Specify Zh by the numbers
- 296 all-time installs (skills.sh)
- Ranked #909 of 3,282 Productivity & Planning skills by installs in the Skillselion catalog
- Data as of Jul 24, 2026 (Skillselion catalog sync)
npx skills add https://github.com/forztf/open-skilled-sdd --skill speckit-specify-zhAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 296 |
|---|---|
| repo stars | ★ 9 |
| Last updated | November 28, 2025 |
| Repository | forztf/open-skilled-sdd ↗ |
How do you write Chinese product specs with Speckit?
Produce structured Chinese-language product specifications with Speckit before implementation, clarifying requirements, acceptance criteria, and scope for engineering handoff.
Who is it for?
Chinese-speaking product and engineering leads practicing spec-driven development who need Speckit-formatted PRDs before coding.
Skip if: English-only teams already using a different spec template who do not need Chinese-language Speckit output.
When should I use this skill?
A feature needs a Chinese Speckit specification with clear acceptance criteria before any implementation skill runs.
What you get
Structured Chinese product specification, acceptance criteria list, and scoped requirements document for engineering handoff.
- Chinese product specification
- acceptance criteria document
- scoped requirements summary
Files
用户输入
$ARGUMENTS在继续之前,您必须考虑用户输入(如果非空)。
大纲
触发消息中用户在触发词后键入的文本就是功能描述。假设在此对话中始终可以使用该功能描述,即 $ARGUMENTS。除非用户提供了一个空命令,否则不要要求用户重复。
根据该功能描述,请执行以下操作:
1. 将 assets/specify/ 所有文件(包括子目录)按原目录结构复制到仓库根目录下的.specify 目录,跳过已有文件,不能覆盖原有同名文件。cp命令的 -n(--no-clobber)选项可以防止覆盖已存在的文件。 在此阶段,您的项目文件夹内容应类似于以下内容:
仓库根目录
└── .specify
├── memory
│ └── constitution.md
├── scripts
│ ├──bash
│ │ ├── check-prerequisites.sh
│ │ ├── common.sh
│ │ ├── create-new-feature.sh
│ │ ├── setup-plan.sh
│ │ └── update-claude-md.sh
│ ├──powershell
│ │ ├── check-prerequisites.ps1
│ │ ├── common.ps1
│ │ ├── create-new-feature.ps1
│ │ ├── setup-plan.ps1
│ │ └── update-claude-md.ps1
├── specs
│ └── 001-create-taskify
│ └── spec.md
└── templates
├── plan-template.md
├── spec-template.md
└── tasks-template.md2. 生成一个简洁的短名称(2-4个词)用于分支:
- 分析功能描述并提取最有意义的关键词
- 创建一个2-4个词的短名称,捕捉功能的本质
- 尽可能使用动词-名词格式(例如:"add-user-auth"、"fix-payment-bug")
- 保留技术术语和缩写(OAuth2、API、JWT等)
- 保持简洁但足够描述性,以便一目了然地理解功能
- 示例:
- "我想添加用户认证" → "user-auth"
- "为API实现OAuth2集成" → "oauth2-api-integration"
- "创建分析仪表板" → "analytics-dashboard"
- "修复支付处理超时错误" → "fix-payment-timeout"
3. 在创建新分支前检查现有分支:
a. 首先获取所有远程分支以确保拥有最新信息:
git fetch --all --pruneb. 查找短名称在所有来源中的最高功能编号:
- 远程分支:
git ls-remote --heads origin | grep -E 'refs/heads/[0-9]+-<short-name>$' - 本地分支:
git branch | grep -E '^[* ]*[0-9]+-<short-name>$' - 规范目录:检查匹配
specs/[0-9]+-<short-name>的目录
c. 确定下一个可用编号:
- 提取所有三个来源的所有数字
- 找到最大数字N
- 对于新分支使用N+1
d. 使用计算出的编号和短名称运行脚本 create-new-feature.ps1 -Json "$ARGUMENTS":
- 传递
--number N+1和--short-name "your-short-name"以及功能描述 - Bash示例:
create-new-feature.sh -Json "$ARGUMENTS" --json --number 5 --short-name "user-auth" "添加用户认证" - PowerShell示例:
create-new-feature.ps1 -Json "$ARGUMENTS" -Json -Number 5 -ShortName "user-auth" "添加用户认证"
重要:
- 检查所有三个来源(远程分支、本地分支、规范目录)以找到最高编号
- 只匹配具有确切短名称模式的分支/目录
- 如果未找到具有此短名称的现有分支/目录,则从编号1开始
- 每个功能只能运行一次此脚本
- JSON在终端中作为输出提供 - 始终参考它来获取您正在查找的实际内容
- JSON输出将包含BRANCH_NAME和SPEC_FILE路径
- 对于参数中的单引号如"I'm Groot",使用转义语法:例如'I'\''m Groot'(或者如果可能的话使用双引号:"I'm Groot")
4. 加载 .specify/templates/spec-template.md 以了解必需的部分。
5. 遵循此执行流程:
1. 解析来自输入的用户描述 如果为空:错误"未提供功能描述" 2. 从描述中提取关键概念 识别:参与者、动作、数据、约束 3. 对于不清楚的方面:
- 基于上下文和行业标准做出有根据的猜测
- 仅在以下情况下标记[需要澄清:具体问题]:
- 选择显著影响功能范围或用户体验
- 存在多种合理解释且有不同的含义
- 不存在合理的默认值
- 限制:最多总共3个[需要澄清]标记
- 按影响优先排序:范围 > 安全/隐私 > 用户体验 > 技术细节
4. 填写用户场景与测试部分 如果没有清晰的用户流程:错误"无法确定用户场景" 5. 生成功能性需求 每个需求都必须是可测试的 对未指定的详细信息使用合理的默认值(在假设部分记录假设) 6. 定义成功标准 创建可测量的、技术无关的结果 包括定量指标(时间、性能、数量)和定性措施(用户满意度、任务完成度) 每个标准必须在没有实现细节的情况下可验证 7. 识别关键实体(如果涉及数据) 8. 返回:成功(规范已准备好进行规划)
6. 使用模板结构将规范写入SPEC_FILE,用从功能描述(参数)派生的具体细节替换占位符,同时保持部分顺序和标题不变。
7. 规范质量验证:编写初始规范后,根据质量标准对其进行验证:
a. 创建规范质量检查清单:使用检查清单模板结构在 FEATURE_DIR/checklists/requirements.md 生成一个检查清单文件,参考:assets/quality-checklist-template.md
b. 运行验证检查:针对每个检查清单项目审查规范:
- 对于每个项目,确定它是通过还是失败
- 记录发现的具体问题(引用相关的规范部分)
c. 处理验证结果:
- 如果所有项目都通过:标记检查清单完成并进入步骤6
- 如果有项目失败(不包括[需要澄清]):
1. 列出失败的项目和具体问题 2. 更新规范以解决每个问题 3. 重新运行验证直到所有项目通过(最多3次迭代) 4. 如果在3次迭代后仍然失败,在检查清单注释中记录剩余问题并向用户发出警告
- 如果存在[需要澄清]标记:
1. 从规范中提取所有[需要澄清:...]标记
2. 限制检查:如果存在超过3个标记,则只保留按范围/安全/用户体验影响最重要的3个,并对其余的做出有根据的猜测
3. 对于每个需要澄清的问题(最多3个),参考assets/clarification-template.md向用户呈现选项。
4. 关键 - 表格格式化:确保markdown表格正确格式化:
- 使用一致的间距,管道对齐
- 每个单元格应在内容周围留有空格:
| 内容 |而不是|内容| - 表头分隔符必须至少有3个破折号:
|--------| - 测试表格在markdown预览中是否正确渲染
5. 按顺序编号问题(Q1、Q2、Q3 - 最多总共3个)
6. 在等待响应之前一起呈现所有问题
7. 等待用户响应他们对所有问题的选择(例如:"Q1: A, Q2: 自定义 - [详情], Q3: B")
8. 通过用用户的选定或提供的答案替换每个[需要澄清]标记来更新规范
9. 在所有澄清解决后重新运行验证
d. 更新检查清单:每次验证迭代后,使用当前通过/失败状态更新检查清单文件
8. 报告完成情况,包括分支名称、规范文件路径、检查清单结果以及下一阶段(speckit-clarify 或 speckit-plan)的准备情况。
注意:脚本会创建并检出新分支并在写入前初始化规范文件。
通用指南
快速指南
- 关注用户需要什么以及为什么
- 避免如何实现(无技术栈、API、代码结构)
- 为业务利益相关者而非开发人员编写
- 不要创建嵌入规范中的任何检查清单。那将是单独的命令
部分要求
- 必填部分:每个功能都必须完成
- 可选部分:仅当与功能相关时才包含
- 当某个部分不适用时,完全删除它(不要留下"N/A")
对于AI生成
从用户提示创建此规范时:
1. 做出有根据的猜测:使用上下文、行业标准和常见模式填补空白 2. 记录假设:在假设部分记录合理的默认值 3. 限制澄清:最多3个[需要澄清]标记 - 仅用于那些:
- 显著影响功能范围或用户体验的关键决策
- 具有多种合理解释且不同含义的情况
- 缺乏任何合理默认值的情况
4. 优先考虑澄清:范围 > 安全/隐私 > 用户体验 > 技术细节 5. 像测试人员一样思考:每个模糊的需求都应该未能通过"可测试且明确"的检查清单项 6. 常见的需要澄清区域(只有在没有合理默认值时):
- 功能范围和边界(包括/排除特定用例)
- 用户类型和权限(如果存在多个冲突的解释可能性)
- 安全/合规要求(当法律/财务上重要时)
合理默认值示例(不要询问这些):
- 数据保留:领域内的行业标准实践
- 性能目标:标准网页/移动应用期望,除非另有规定
- 错误处理:用户友好的消息和适当的回退
- 认证方法:标准基于会话或OAuth2的Web应用程序
- 集成模式:RESTful API,除非另有说明
成功标准指南
成功标准必须:
1. 可衡量:包括具体指标(时间、百分比、计数、比率) 2. 技术无关:不提及框架、语言、数据库或工具 3. 以用户为中心:从用户/业务角度描述结果,而不是系统内部机制 4. 可验证:无需知道实现细节即可测试/验证
良好示例:
- "用户可在3分钟内完成结账"
- "系统支持10,000个并发用户"
- "95%的搜索在1秒内返回结果"
- "任务完成率提高40%"
不良示例(实现导向):
- "API响应时间低于200毫秒"(过于技术性,应使用"用户立即看到结果")
- "数据库可处理1000 TPS"(实现细节,应使用面向用户的指标)
- "React组件高效渲染"(框架特定)
- "Redis缓存命中率高于80%"(技术特定)
问题[N]:[主题]
上下文:[引用相关的规范部分]
我们需要知道什么:[来自需要澄清标记的具体问题]
建议答案:
| 选项 | 答案 | 影响 |
|---|---|---|
| A | [第一个建议答案] | [这对功能意味着什么] |
| B | [第二个建议答案] | [这对功能意味着什么] |
| C | [第三个建议答案] | [这对功能意味着什么] |
| 自定义 | 提供您的答案 | [解释如何提供自定义输入] |
您的选择:_[等待用户响应]_
规范质量检查清单:[功能名称]
目的:在进入规划阶段前验证规范完整性和质量 创建时间:[日期] 功能:[链接到spec.md]
内容质量
- [ ] 不包含实现细节(语言、框架、API)
- [ ] 聚焦于用户价值和业务需求
- [ ] 为非技术利益相关者编写
- [ ] 所有必填部分已完成
需求完整性
- [ ] 没有剩余的[需要澄清]标记
- [ ] 需求是可测试且明确的
- [ ] 成功标准是可衡量的
- [ ] 成功标准是技术无关的(不包含实现细节)
- [ ] 所有验收场景已定义
- [ ] 边缘情况已识别
- [ ] 范围明确界定
- [ ] 已识别依赖关系和假设
功能就绪
- [ ] 所有功能性需求都有明确的验收标准
- [ ] 用户场景涵盖主要流程
- [ ] 功能满足成功标准中定义的可衡量结果
- [ ] 没有实现细节泄露到规范中
注释
- 标记为不完整的项目需要在
/speckit.clarify或/speckit.plan之前更新规范
功能规格:[功能名称]
功能分支:[###-feature-name] 创建时间:[日期] 状态:草案 输入:用户描述:"$ARGUMENTS"
用户场景与测试 (必填)
<!-- 重要:用户故事应按照重要性排序为用户旅程。 每个用户故事/旅程必须是独立可测试的 - 这意味着如果您只实现其中一个, 您仍应拥有一个可交付价值的可行MVP(最小可行产品)。
为每个故事分配优先级(P1, P2, P3等),其中P1是最重要的。 将每个故事视为可以:
- 独立开发
- 独立测试
- 独立部署
- 独立向用户展示
-->
用户故事 1 - [简要标题] (优先级: P1)
[用通俗语言描述此用户旅程]
为何此优先级:[解释价值以及为何具有此优先级]
独立测试:[描述如何独立测试 - 例如,"可以通过[具体操作]完全测试,并交付[具体价值]"]
验收场景:
1. 给定 [初始状态],当 [操作],则 [预期结果] 2. 给定 [初始状态],当 [操作],则 [预期结果]
---
用户故事 2 - [简要标题] (优先级: P2)
[用通俗语言描述此用户旅程]
为何此优先级:[解释价值以及为何具有此优先级]
独立测试:[描述如何独立测试]
验收场景:
1. 给定 [初始状态],当 [操作],则 [预期结果]
---
用户故事 3 - [简要标题] (优先级: P3)
[用通俗语言描述此用户旅程]
为何此优先级:[解释价值以及为何具有此优先级]
独立测试:[描述如何独立测试]
验收场景:
1. 给定 [初始状态],当 [操作],则 [预期结果]
---
[根据需要添加更多用户故事,每个都有分配的优先级]
边缘情况
<!-- 需要操作:本节中的内容是占位符。 用正确的边缘情况填充它们。 -->
- 当[边界条件]发生时会怎样?
- 系统如何处理[错误场景]?
要求 (必填)
<!-- 需要操作:本节中的内容是占位符。 用正确的功能要求填充它们。 -->
功能要求
- FR-001:系统必须[具体能力,例如,"允许用户创建账户"]
- FR-002:系统必须[具体能力,例如,"验证电子邮件地址"]
- FR-003:用户必须能够[关键交互,例如,"重置密码"]
- FR-004:系统必须[数据要求,例如,"持久化用户偏好设置"]
- FR-005:系统必须[行为,例如,"记录所有安全事件"]
标记不明确要求的示例:
- FR-006:系统必须通过[需要澄清:未指定认证方法 - 电子邮件/密码、SSO、OAuth?]
- FR-007:系统必须保留用户数据[需要澄清:未指定保留期]
关键实体 (如果功能涉及数据则包含)
- [实体 1]:[它代表什么,关键属性(无实现细节)]
- [实体 2]:[它代表什么,与其他实体的关系]
成功标准 (必填)
<!-- 需要操作:定义可衡量的成功标准。 这些必须是技术无关且可衡量的。 -->
可衡量的结果
- SC-001:[可衡量的指标,例如,"用户可以在2分钟内完成账户创建"]
- SC-002:[可衡量的指标,例如,"系统在无降级情况下处理1000个并发用户"]
- SC-003:[用户满意度指标,例如,"90%的用户首次尝试即可成功完成主要任务"]
- SC-004:[业务指标,例如,"将与[X]相关的支持工单减少50%"]
[项目名称] 章程
<!-- 示例:规范章程,任务流章程等 -->
核心原则
[原则_1_名称]
<!-- 示例:I. 库优先 --> [原则_1_描述] <!-- 示例:每个功能都以独立库开始;库必须自包含、可独立测试、有文档;需要明确目的 - 没有仅用于组织的库 -->
[原则_2_名称]
<!-- 示例:II. CLI 接口 --> [原则_2_描述] <!-- 示例:每个库都通过 CLI 暴露功能;文本输入/输出协议:stdin/args → stdout,错误 → stderr;支持 JSON + 人类可读格式 -->
[原则_3_名称]
<!-- 示例:III. 测试优先(不可协商) --> [原则_3_描述] <!-- 示例:TDD 强制:编写测试 → 用户批准 → 测试失败 → 然后实现;严格强制红-绿-重构循环 -->
[原则_4_名称]
<!-- 示例:IV. 集成测试 --> [原则_4_描述] <!-- 示例:需要集成测试的重点领域:新库契约测试、契约变更、服务间通信、共享模式 -->
[原则_5_名称]
<!-- 示例:V. 可观察性,VI. 版本控制和破坏性变更,VII. 简单性 --> [原则_5_描述] <!-- 示例:文本 I/O 确保可调试性;需要结构化日志;或:MAJOR.MINOR.BUILD 格式;或:从简单开始,YAGNI 原则 -->
[部分_2_名称]
<!-- 示例:附加约束、安全要求、性能标准等 -->
[部分_2_内容] <!-- 示例:技术栈要求、合规标准、部署策略等 -->
[部分_3_名称]
<!-- 示例:开发工作流程、审查过程、质量门等 -->
[部分_3_内容] <!-- 示例:代码审查要求、测试门、部署批准流程等 -->
治理
<!-- 示例:章程优于所有其他实践;修订需要文档、批准、迁移计划 -->
[治理规则] <!-- 示例:所有 PR/审查必须验证合规性;复杂性必须有正当理由;使用 [指导文件] 作为运行时开发指导 -->
版本:[章程版本] | 批准:[批准日期] | 最后修订:[最后修订日期] <!-- 示例:版本:2.1.1 | 批准:2025-06-13 | 最后修订:2025-07-16 -->
#!/usr/bin/env bash
# 前置条件统一校验脚本
#
# 本脚本为 Spec-Driven Development 工作流提供统一的前置条件校验。
# 用于替代此前分散在多个脚本中的校验功能。
#
# 用法: ./check-prerequisites.sh [选项]
#
# 选项:
# --json 以 JSON 格式输出
# --require-tasks 要求存在 tasks.md(实现阶段)
# --include-tasks 在 AVAILABLE_DOCS 列表中包含 tasks.md
# --paths-only 仅输出路径变量(不执行校验)
# --help, -h 显示帮助信息
#
# 输出:
# JSON 模式: {"FEATURE_DIR":"...", "AVAILABLE_DOCS":["..."]}
# 文本模式: FEATURE_DIR:... \n 可用文档: \n ✓/✗ file.md
# 仅路径: REPO_ROOT: ... \n BRANCH: ... \n FEATURE_DIR: ... 等
set -e
# 解析命令行参数
JSON_MODE=false
REQUIRE_TASKS=false
INCLUDE_TASKS=false
PATHS_ONLY=false
for arg in "$@"; do
case "$arg" in
--json)
JSON_MODE=true
;;
--require-tasks)
REQUIRE_TASKS=true
;;
--include-tasks)
INCLUDE_TASKS=true
;;
--paths-only)
PATHS_ONLY=true
;;
--help|-h)
cat << 'EOF'
用法: check-prerequisites.sh [选项]
用于 Spec-Driven Development 工作流的前置条件统一校验。
选项:
--json 以 JSON 格式输出
--require-tasks 要求存在 tasks.md(实现阶段)
--include-tasks 在 AVAILABLE_DOCS 列表中包含 tasks.md
--paths-only 仅输出路径变量(不执行校验)
--help, -h 显示帮助信息
示例:
# 校验任务阶段前置条件(要求存在 plan.md)
./check-prerequisites.sh --json
# 校验实现阶段前置条件(要求存在 plan.md + tasks.md)
./check-prerequisites.sh --json --require-tasks --include-tasks
# 仅获取特性路径(不执行校验)
./check-prerequisites.sh --paths-only
EOF
exit 0
;;
*)
echo "错误: 未知选项 '$arg'。使用 --help 查看帮助信息。" >&2
exit 1
;;
esac
done
# 加载通用函数
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/common.sh"
# 获取特性路径并校验分支
eval $(get_feature_paths)
check_feature_branch "$CURRENT_BRANCH" "$HAS_GIT" || exit 1
# 仅路径模式:输出路径并退出(支持同时使用 JSON + paths-only)
if $PATHS_ONLY; then
if $JSON_MODE; then
# 最小化的 JSON 路径负载(不执行校验)
printf '{"REPO_ROOT":"%s","BRANCH":"%s","FEATURE_DIR":"%s","FEATURE_SPEC":"%s","IMPL_PLAN":"%s","TASKS":"%s"}\n' \
"$REPO_ROOT" "$CURRENT_BRANCH" "$FEATURE_DIR" "$FEATURE_SPEC" "$IMPL_PLAN" "$TASKS"
else
echo "REPO_ROOT: $REPO_ROOT"
echo "BRANCH: $CURRENT_BRANCH"
echo "FEATURE_DIR: $FEATURE_DIR"
echo "FEATURE_SPEC: $FEATURE_SPEC"
echo "IMPL_PLAN: $IMPL_PLAN"
echo "TASKS: $TASKS"
fi
exit 0
fi
# 校验必要的目录与文件
if [[ ! -d "$FEATURE_DIR" ]]; then
echo "错误: 未找到特性目录: $FEATURE_DIR" >&2
echo "请先运行 /speckit.specify 以创建特性目录结构。" >&2
exit 1
fi
if [[ ! -f "$IMPL_PLAN" ]]; then
echo "错误: 在 $FEATURE_DIR 中未找到 plan.md" >&2
echo "请先运行 /speckit.plan 以生成实现计划。" >&2
exit 1
fi
# Check for tasks.md if required
if $REQUIRE_TASKS && [[ ! -f "$TASKS" ]]; then
echo "错误: 在 $FEATURE_DIR 中未找到 tasks.md" >&2
echo "请先运行 /speckit.tasks 以创建任务列表。" >&2
exit 1
fi
# 构建可用文档列表
docs=()
# 始终检查这些可选文档
[[ -f "$RESEARCH" ]] && docs+=("research.md")
[[ -f "$DATA_MODEL" ]] && docs+=("data-model.md")
# 检查 contracts 目录(仅在存在且包含文件时)
if [[ -d "$CONTRACTS_DIR" ]] && [[ -n "$(ls -A "$CONTRACTS_DIR" 2>/dev/null)" ]]; then
docs+=("contracts/")
fi
[[ -f "$QUICKSTART" ]] && docs+=("quickstart.md")
# 若请求且存在,则包含 tasks.md
if $INCLUDE_TASKS && [[ -f "$TASKS" ]]; then
docs+=("tasks.md")
fi
# 输出结果
if $JSON_MODE; then
# 构建文档的 JSON 数组
if [[ ${#docs[@]} -eq 0 ]]; then
json_docs="[]"
else
json_docs=$(printf '"%s",' "${docs[@]}")
json_docs="[${json_docs%,}]"
fi
printf '{"FEATURE_DIR":"%s","AVAILABLE_DOCS":%s}\n' "$FEATURE_DIR" "$json_docs"
else
# 文本输出
echo "特性目录:$FEATURE_DIR"
echo "可用文档:"
# Show status of each potential document
check_file "$RESEARCH" "research.md"
check_file "$DATA_MODEL" "data-model.md"
check_dir "$CONTRACTS_DIR" "contracts/"
check_file "$QUICKSTART" "quickstart.md"
if $INCLUDE_TASKS; then
check_file "$TASKS" "tasks.md"
fi
fi
#!/usr/bin/env bash
# 通用函数与变量(供所有脚本使用)
# 获取仓库根目录;在非 Git 仓库下回退为脚本所在路径的上级目录
get_repo_root() {
if git rev-parse --show-toplevel >/dev/null 2>&1; then
git rev-parse --show-toplevel
else
# 非 Git 仓库下回退为脚本所在路径的上级目录
local script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
(cd "$script_dir/../../.." && pwd)
fi
}
# 获取当前分支;在非 Git 仓库下通过 specs 目录推断
get_current_branch() {
# First check if SPECIFY_FEATURE environment variable is set
if [[ -n "${SPECIFY_FEATURE:-}" ]]; then
echo "$SPECIFY_FEATURE"
return
fi
# Then check git if available
if git rev-parse --abbrev-ref HEAD >/dev/null 2>&1; then
git rev-parse --abbrev-ref HEAD
return
fi
# 非 Git 仓库:尝试在 specs 目录中查找最新的特性目录
local repo_root=$(get_repo_root)
local specs_dir="$repo_root/.specify/specs"
if [[ -d "$specs_dir" ]]; then
local latest_feature=""
local highest=0
for dir in "$specs_dir"/*; do
if [[ -d "$dir" ]]; then
local dirname=$(basename "$dir")
if [[ "$dirname" =~ ^([0-9]{3})- ]]; then
local number=${BASH_REMATCH[1]}
number=$((10#$number))
if [[ "$number" -gt "$highest" ]]; then
highest=$number
latest_feature=$dirname
fi
fi
fi
done
if [[ -n "$latest_feature" ]]; then
echo "$latest_feature"
return
fi
fi
echo "main" # Final fallback
}
# 检查是否存在 Git 仓库
has_git() {
git rev-parse --show-toplevel >/dev/null 2>&1
}
check_feature_branch() {
local branch="$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] 警告: 未检测到 Git 仓库;已跳过分支校验" >&2
return 0
fi
if [[ ! "$branch" =~ ^[0-9]{3}- ]]; then
echo "错误: 当前不在特性分支。当前分支: $branch" >&2
echo "特性分支命名应为: 001-feature-name" >&2
return 1
fi
return 0
}
get_feature_dir() { echo "$1/specs/$2"; }
# 根据数字前缀查找特性目录,而非严格匹配分支名
# 允许多个分支共同使用同一个规格(如 004-fix-bug、004-add-feature)
find_feature_dir_by_prefix() {
local repo_root="$1"
local branch_name="$2"
local specs_dir="$repo_root/.specify/specs"
# Extract numeric prefix from branch (e.g., "004" from "004-whatever")
if [[ ! "$branch_name" =~ ^([0-9]{3})- ]]; then
# If branch doesn't have numeric prefix, fall back to exact match
echo "$specs_dir/$branch_name"
return
fi
local prefix="${BASH_REMATCH[1]}"
# 在 specs/ 下搜索以该前缀开头的目录
local matches=()
if [[ -d "$specs_dir" ]]; then
for dir in "$specs_dir"/"$prefix"-*; do
if [[ -d "$dir" ]]; then
matches+=("$(basename "$dir")")
fi
done
fi
# Handle results
if [[ ${#matches[@]} -eq 0 ]]; then
# No match found - return the branch name path (will fail later with clear error)
echo "$specs_dir/$branch_name"
elif [[ ${#matches[@]} -eq 1 ]]; then
# Exactly one match - perfect!
echo "$specs_dir/${matches[0]}"
else
# Multiple matches - this shouldn't happen with proper naming convention
echo "错误: 发现多个以前缀 '$prefix' 开头的规格目录: ${matches[*]}" >&2
echo "请确保每个数字前缀仅存在一个规格目录。" >&2
echo "$specs_dir/$branch_name" # Return something to avoid breaking the script
fi
}
get_feature_paths() {
local repo_root=$(get_repo_root)
local current_branch=$(get_current_branch)
local has_git_repo="false"
if has_git; then
has_git_repo="true"
fi
# Use prefix-based lookup to support multiple branches per spec
local feature_dir=$(find_feature_dir_by_prefix "$repo_root" "$current_branch")
cat <<EOF
REPO_ROOT='$repo_root'
CURRENT_BRANCH='$current_branch'
HAS_GIT='$has_git_repo'
FEATURE_DIR='$feature_dir'
FEATURE_SPEC='$feature_dir/spec.md'
IMPL_PLAN='$feature_dir/plan.md'
TASKS='$feature_dir/tasks.md'
RESEARCH='$feature_dir/research.md'
DATA_MODEL='$feature_dir/data-model.md'
QUICKSTART='$feature_dir/quickstart.md'
CONTRACTS_DIR='$feature_dir/contracts'
EOF
}
check_file() { [[ -f "$1" ]] && echo " ✓ $2" || echo " ✗ $2"; }
check_dir() { [[ -d "$1" && -n $(ls -A "$1" 2>/dev/null) ]] && echo " ✓ $2" || echo " ✗ $2"; }
#!/usr/bin/env bash
set -e
JSON_MODE=false
SHORT_NAME=""
BRANCH_NUMBER=""
ARGS=()
i=1
while [ $i -le $# ]; do
arg="${!i}"
case "$arg" in
--json)
JSON_MODE=true
;;
--short-name)
if [ $((i + 1)) -gt $# ]; then
echo '错误: --short-name 需要一个值' >&2
exit 1
fi
i=$((i + 1))
next_arg="${!i}"
# Check if the next argument is another option (starts with --)
if [[ "$next_arg" == --* ]]; then
echo '错误: --short-name 需要一个值' >&2
exit 1
fi
SHORT_NAME="$next_arg"
;;
--number)
if [ $((i + 1)) -gt $# ]; then
echo '错误: --number 需要一个值' >&2
exit 1
fi
i=$((i + 1))
next_arg="${!i}"
if [[ "$next_arg" == --* ]]; then
echo '错误: --number 需要一个值' >&2
exit 1
fi
BRANCH_NUMBER="$next_arg"
;;
--help|-h)
echo "用法: $0 [--json] [--short-name <名称>] [--number N] <特性描述>"
echo ""
echo "选项:"
echo " --json 以 JSON 格式输出"
echo " --short-name <名称> 为分支提供自定义短名(2-4 个词)"
echo " --number N 手动指定分支编号(覆盖自动检测)"
echo " --help, -h 显示此帮助信息"
echo ""
echo "示例:"
echo " $0 '添加用户认证系统' --short-name 'user-auth'"
echo " $0 '为 API 实现 OAuth2 集成' --number 5"
exit 0
;;
*)
ARGS+=("$arg")
;;
esac
i=$((i + 1))
done
FEATURE_DESCRIPTION="${ARGS[*]}"
if [ -z "$FEATURE_DESCRIPTION" ]; then
echo "用法: $0 [--json] [--short-name <名称>] [--number N] <特性描述>" >&2
exit 1
fi
# 通过查找项目标记来定位仓库根目录的函数
find_repo_root() {
local dir="$1"
while [ "$dir" != "/" ]; do
if [ -d "$dir/.git" ] || [ -d "$dir/.specify" ]; then
echo "$dir"
return 0
fi
dir="$(dirname "$dir")"
done
return 1
}
# 检查现有分支(本地与远程)并返回下一个可用编号的函数
check_existing_branches() {
local short_name="$1"
# Fetch all remotes to get latest branch info (suppress errors if no remotes)
git fetch --all --prune 2>/dev/null || true
# Find all branches matching the pattern using git ls-remote (more reliable)
local remote_branches=$(git ls-remote --heads origin 2>/dev/null | grep -E "refs/heads/[0-9]+-${short_name}$" | sed 's/.*\/\([0-9]*\)-.*/\1/' | sort -n)
# Also check local branches
local local_branches=$(git branch 2>/dev/null | grep -E "^[* ]*[0-9]+-${short_name}$" | sed 's/^[* ]*//' | sed 's/-.*//' | sort -n)
# Check specs directory as well
local spec_dirs=""
if [ -d "$SPECS_DIR" ]; then
spec_dirs=$(find "$SPECS_DIR" -maxdepth 1 -type d -name "[0-9]*-${short_name}" 2>/dev/null | xargs -n1 basename 2>/dev/null | sed 's/-.*//' | sort -n)
fi
# Combine all sources and get the highest number
local max_num=0
for num in $remote_branches $local_branches $spec_dirs; do
if [ "$num" -gt "$max_num" ]; then
max_num=$num
fi
done
# Return next number
echo $((max_num + 1))
}
# 解析仓库根目录:优先使用 Git 信息;若不可用则回退到项目标记查找
# 以确保在 --no-git 初始化的仓库中也可正常工作
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
if git rev-parse --show-toplevel >/dev/null 2>&1; then
REPO_ROOT=$(git rev-parse --show-toplevel)
HAS_GIT=true
else
REPO_ROOT="$(find_repo_root "$SCRIPT_DIR")"
if [ -z "$REPO_ROOT" ]; then
echo "错误: 无法确定仓库根目录。请在仓库内运行此脚本。" >&2
exit 1
fi
HAS_GIT=false
fi
cd "$REPO_ROOT"
SPECS_DIR="$REPO_ROOT/.specify/specs"
mkdir -p "$SPECS_DIR"
# 生成分支名称:包含停用词过滤与长度控制
generate_branch_name() {
local description="$1"
# 常见停用词(需要过滤掉)
local stop_words="^(i|a|an|the|to|for|of|in|on|at|by|with|from|is|are|was|were|be|been|being|have|has|had|do|does|did|will|would|should|could|can|may|might|must|shall|this|that|these|those|my|your|our|their|want|need|add|get|set)$"
# 转为小写并拆分为单词
local clean_name=$(echo "$description" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/ /g')
# 过滤单词:移除停用词以及长度小于 3 的词(除非原描述中为全大写的缩写)
local meaningful_words=()
for word in $clean_name; do
# 跳过空词
[ -z "$word" ] && continue
# 保留:非停用词,且(长度 ≥ 3 或可能是缩写)
if ! echo "$word" | grep -qiE "$stop_words"; then
if [ ${#word} -ge 3 ]; then
meaningful_words+=("$word")
elif echo "$description" | grep -q "\b${word^^}\b"; then
# 若原描述中为全大写(可能为缩写),保留该短词
meaningful_words+=("$word")
fi
fi
done
# 若存在有效词,取前 3-4 个作为短名
if [ ${#meaningful_words[@]} -gt 0 ]; then
local max_words=3
if [ ${#meaningful_words[@]} -eq 4 ]; then max_words=4; fi
local result=""
local count=0
for word in "${meaningful_words[@]}"; do
if [ $count -ge $max_words ]; then break; fi
if [ -n "$result" ]; then result="$result-"; fi
result="$result$word"
count=$((count + 1))
done
echo "$result"
else
# 若无有效词,回退到原始逻辑
echo "$description" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/-\+/-/g' | sed 's/^-//' | sed 's/-$//' | tr '-' '\n' | grep -v '^$' | head -3 | tr '\n' '-' | sed 's/-$//'
fi
}
# 生成分支名
if [ -n "$SHORT_NAME" ]; then
# 使用提供的短名,并进行清理
BRANCH_SUFFIX=$(echo "$SHORT_NAME" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/-\+/-/g' | sed 's/^-//' | sed 's/-$//')
else
# 基于描述生成短名(智能过滤)
BRANCH_SUFFIX=$(generate_branch_name "$FEATURE_DESCRIPTION")
fi
# 确定分支编号
if [ -z "$BRANCH_NUMBER" ]; then
if [ "$HAS_GIT" = true ]; then
# 检查远端现有分支
BRANCH_NUMBER=$(check_existing_branches "$BRANCH_SUFFIX")
else
# 回退到本地目录检查
HIGHEST=0
if [ -d "$SPECS_DIR" ]; then
for dir in "$SPECS_DIR"/*; do
[ -d "$dir" ] || continue
dirname=$(basename "$dir")
number=$(echo "$dirname" | grep -o '^[0-9]\+' || echo "0")
number=$((10#$number))
if [ "$number" -gt "$HIGHEST" ]; then HIGHEST=$number; fi
done
fi
BRANCH_NUMBER=$((HIGHEST + 1))
fi
fi
FEATURE_NUM=$(printf "%03d" "$BRANCH_NUMBER")
BRANCH_NAME="${FEATURE_NUM}-${BRANCH_SUFFIX}"
# GitHub 对分支名有 244 字节限制
# 如超限则进行校验与截断
MAX_BRANCH_LENGTH=244
if [ ${#BRANCH_NAME} -gt $MAX_BRANCH_LENGTH ]; then
# 计算需要从后缀截取的长度(特性编号 3 + 连字符 1 = 4)
MAX_SUFFIX_LENGTH=$((MAX_BRANCH_LENGTH - 4))
# 尽可能在词边界进行截断
TRUNCATED_SUFFIX=$(echo "$BRANCH_SUFFIX" | cut -c1-$MAX_SUFFIX_LENGTH)
# 若截断产生尾部连字符则移除
TRUNCATED_SUFFIX=$(echo "$TRUNCATED_SUFFIX" | sed 's/-$//')
ORIGINAL_BRANCH_NAME="$BRANCH_NAME"
BRANCH_NAME="${FEATURE_NUM}-${TRUNCATED_SUFFIX}"
>&2 echo "[specify] 警告: 分支名称超过 GitHub 的 244 字节限制"
>&2 echo "[specify] 原始: $ORIGINAL_BRANCH_NAME (${#ORIGINAL_BRANCH_NAME} 字节)"
>&2 echo "[specify] 已截断为: $BRANCH_NAME (${#BRANCH_NAME} 字节)"
fi
if [ "$HAS_GIT" = true ]; then
git checkout -b "$BRANCH_NAME"
else
>&2 echo "[specify] 警告: 未检测到 Git 仓库;已跳过分支创建: $BRANCH_NAME"
fi
FEATURE_DIR="$SPECS_DIR/$BRANCH_NAME"
mkdir -p "$FEATURE_DIR"
TEMPLATE="$REPO_ROOT/.specify/templates/spec-template.md"
SPEC_FILE="$FEATURE_DIR/spec.md"
if [ -f "$TEMPLATE" ]; then cp "$TEMPLATE" "$SPEC_FILE"; else touch "$SPEC_FILE"; fi
# 为当前会话设置 SPECIFY_FEATURE 环境变量
export SPECIFY_FEATURE="$BRANCH_NAME"
if $JSON_MODE; then
printf '{"BRANCH_NAME":"%s","SPEC_FILE":"%s","FEATURE_NUM":"%s"}\n' "$BRANCH_NAME" "$SPEC_FILE" "$FEATURE_NUM"
else
echo "分支名称: $BRANCH_NAME"
echo "规格文件: $SPEC_FILE"
echo "特性编号: $FEATURE_NUM"
echo "已设置 SPECIFY_FEATURE 环境变量为: $BRANCH_NAME"
fi
#!/usr/bin/env bash
set -e
# 解析命令行参数
JSON_MODE=false
ARGS=()
for arg in "$@"; do
case "$arg" in
--json)
JSON_MODE=true
;;
--help|-h)
echo "用法: $0 [--json]"
echo " --json 以 JSON 格式输出结果"
echo " --help 显示此帮助信息"
exit 0
;;
*)
ARGS+=("$arg")
;;
esac
done
# 获取脚本目录并加载通用函数
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/common.sh"
# 从通用函数获取所有路径与变量
eval $(get_feature_paths)
# 检查当前是否在正确的特性分支(仅在 Git 仓库中校验)
check_feature_branch "$CURRENT_BRANCH" "$HAS_GIT" || exit 1
# 确保特性目录存在
mkdir -p "$FEATURE_DIR"
# 若存在计划模板则复制
TEMPLATE="$REPO_ROOT/.specify/templates/plan-template.md"
if [[ -f "$TEMPLATE" ]]; then
cp "$TEMPLATE" "$IMPL_PLAN"
echo "已复制计划模板到 $IMPL_PLAN"
else
echo "警告: 未在 $TEMPLATE 找到计划模板"
# 若模板不存在则创建一个基础计划文件
touch "$IMPL_PLAN"
fi
# 输出结果
if $JSON_MODE; then
printf '{"FEATURE_SPEC":"%s","IMPL_PLAN":"%s","SPECS_DIR":"%s","BRANCH":"%s","HAS_GIT":"%s"}\n' \
"$FEATURE_SPEC" "$IMPL_PLAN" "$FEATURE_DIR" "$CURRENT_BRANCH" "$HAS_GIT"
else
echo "规格文件: $FEATURE_SPEC"
echo "实现计划: $IMPL_PLAN"
echo "规格目录: $FEATURE_DIR"
echo "当前分支: $CURRENT_BRANCH"
echo "是否有 Git: $HAS_GIT"
fi
#!/usr/bin/env bash
# 基于 plan.md 更新代理上下文文件
#
# 本脚本通过解析特性规格,维护各 AI 代理的上下文文件,
# 并将项目信息同步到对应的代理配置文件中。
#
# 主要功能:
# 1. 环境校验
# - 校验 Git 仓库结构与分支信息
# - 检查必要的 plan.md 与模板文件
# - 验证文件权限与可访问性
#
# 2. 计划数据解析
# - 解析 plan.md 提取项目元数据
# - 识别语言/版本、框架、数据库、项目类型
# - 友好处理缺失或不完整的规格数据
#
# 3. 代理文件管理
# - 需要时从模板创建新的代理上下文文件
# - 将新增项目信息写入已有代理文件
# - 保留手动添加的内容与自定义配置
# - 支持多种代理文件路径与目录结构
#
# 4. 内容生成
# - 生成语言对应的构建/测试命令
# - 创建合理的项目目录结构示例
# - 更新技术栈与最近变更章节
# - 保持一致的格式与时间戳
#
# 5. 多代理支持
# - 处理不同代理的文件路径与命名约定
# - 支持:Claude、Gemini、Copilot、Cursor、Qwen、opencode、Codex、Windsurf、Kilo Code、Auggie CLI、Roo Code、CodeBuddy CLI、Amp、Amazon Q Developer CLI
# - 可选择单个代理或更新所有已存在的代理文件
# - 若不存在代理文件,则默认创建 Claude 文件
#
# 用法: ./update-agent-context.sh [agent_type]
# 支持的代理类型: claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|q
# 留空则更新所有已存在的代理文件
set -e
# 启用严格错误处理
set -u
set -o pipefail
#==============================================================================
# 配置与全局变量
#==============================================================================
# 获取脚本目录并加载通用函数
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/common.sh"
# 从通用函数获取所有路径与变量
eval $(get_feature_paths)
NEW_PLAN="$IMPL_PLAN" # Alias for compatibility with existing code
AGENT_TYPE="${1:-}"
# 各代理的文件路径
CLAUDE_FILE="$REPO_ROOT/CLAUDE.md"
GEMINI_FILE="$REPO_ROOT/GEMINI.md"
COPILOT_FILE="$REPO_ROOT/.github/copilot-instructions.md"
CURSOR_FILE="$REPO_ROOT/.cursor/rules/specify-rules.mdc"
QWEN_FILE="$REPO_ROOT/QWEN.md"
AGENTS_FILE="$REPO_ROOT/AGENTS.md"
WINDSURF_FILE="$REPO_ROOT/.windsurf/rules/specify-rules.md"
KILOCODE_FILE="$REPO_ROOT/.kilocode/rules/specify-rules.md"
AUGGIE_FILE="$REPO_ROOT/.augment/rules/specify-rules.md"
ROO_FILE="$REPO_ROOT/.roo/rules/specify-rules.md"
CODEBUDDY_FILE="$REPO_ROOT/CODEBUDDY.md"
AMP_FILE="$REPO_ROOT/AGENTS.md"
Q_FILE="$REPO_ROOT/AGENTS.md"
# 模板文件
TEMPLATE_FILE="$REPO_ROOT/.specify/templates/agent-file-template.md"
# 解析后的计划数据的全局变量
NEW_LANG=""
NEW_FRAMEWORK=""
NEW_DB=""
NEW_PROJECT_TYPE=""
#==============================================================================
# 工具函数
#==============================================================================
log_info() {
echo "信息: $1"
}
log_success() {
echo "✓ $1"
}
log_error() {
echo "错误: $1" >&2
}
log_warning() {
echo "警告: $1" >&2
}
# 清理临时文件函数
cleanup() {
local exit_code=$?
rm -f /tmp/agent_update_*_$$
rm -f /tmp/manual_additions_$$
exit $exit_code
}
# 设置清理钩子
trap cleanup EXIT INT TERM
#==============================================================================
# 校验函数
#==============================================================================
validate_environment() {
# Check if we have a current branch/feature (git or non-git)
if [[ -z "$CURRENT_BRANCH" ]]; then
log_error "无法确定当前特性"
if [[ "$HAS_GIT" == "true" ]]; then
log_info "请确保当前处于特性分支"
else
log_info "请设置 SPECIFY_FEATURE 环境变量或先创建特性"
fi
exit 1
fi
# Check if plan.md exists
if [[ ! -f "$NEW_PLAN" ]]; then
log_error "未在 $NEW_PLAN 发现 plan.md"
log_info "请确保正在处理具有对应规格目录的特性"
if [[ "$HAS_GIT" != "true" ]]; then
log_info "使用: export SPECIFY_FEATURE=your-feature-name 或先创建新特性"
fi
exit 1
fi
# Check if template exists (needed for new files)
if [[ ! -f "$TEMPLATE_FILE" ]]; then
log_warning "未在 $TEMPLATE_FILE 找到模板文件"
log_warning "创建新的代理文件将失败"
fi
}
#==============================================================================
# 计划解析函数
#==============================================================================
extract_plan_field() {
local field_pattern="$1"
local plan_file="$2"
grep "^\*\*${field_pattern}\*\*: " "$plan_file" 2>/dev/null | \
head -1 | \
sed "s|^\*\*${field_pattern}\*\*: ||" | \
sed 's/^[ \t]*//;s/[ \t]*$//' | \
grep -v "NEEDS CLARIFICATION" | \
grep -v "^N/A$" || echo ""
}
parse_plan_data() {
local plan_file="$1"
if [[ ! -f "$plan_file" ]]; then
log_error "未找到计划文件: $plan_file"
return 1
fi
if [[ ! -r "$plan_file" ]]; then
log_error "计划文件不可读: $plan_file"
return 1
fi
log_info "正在解析计划数据: $plan_file"
NEW_LANG=$(extract_plan_field "Language/Version" "$plan_file")
NEW_FRAMEWORK=$(extract_plan_field "Primary Dependencies" "$plan_file")
NEW_DB=$(extract_plan_field "Storage" "$plan_file")
NEW_PROJECT_TYPE=$(extract_plan_field "Project Type" "$plan_file")
# Log what we found
if [[ -n "$NEW_LANG" ]]; then
log_info "发现语言: $NEW_LANG"
else
log_warning "在计划中未找到语言信息"
fi
if [[ -n "$NEW_FRAMEWORK" ]]; then
log_info "发现框架: $NEW_FRAMEWORK"
fi
if [[ -n "$NEW_DB" ]] && [[ "$NEW_DB" != "N/A" ]]; then
log_info "发现数据库: $NEW_DB"
fi
if [[ -n "$NEW_PROJECT_TYPE" ]]; then
log_info "发现项目类型: $NEW_PROJECT_TYPE"
fi
}
format_technology_stack() {
local lang="$1"
local framework="$2"
local parts=()
# Add non-empty parts
[[ -n "$lang" && "$lang" != "NEEDS CLARIFICATION" ]] && parts+=("$lang")
[[ -n "$framework" && "$framework" != "NEEDS CLARIFICATION" && "$framework" != "N/A" ]] && parts+=("$framework")
# Join with proper formatting
if [[ ${#parts[@]} -eq 0 ]]; then
echo ""
elif [[ ${#parts[@]} -eq 1 ]]; then
echo "${parts[0]}"
else
# Join multiple parts with " + "
local result="${parts[0]}"
for ((i=1; i<${#parts[@]}; i++)); do
result="$result + ${parts[i]}"
done
echo "$result"
fi
}
#==============================================================================
# 模板与内容生成函数
#==============================================================================
get_project_structure() {
local project_type="$1"
if [[ "$project_type" == *"web"* ]]; then
echo "backend/\\nfrontend/\\ntests/"
else
echo "src/\\ntests/"
fi
}
get_commands_for_language() {
local lang="$1"
case "$lang" in
*"Python"*)
echo "cd src && pytest && ruff check ."
;;
*"Rust"*)
echo "cargo test && cargo clippy"
;;
*"JavaScript"*|*"TypeScript"*)
echo "npm test \\&\\& npm run lint"
;;
*)
echo "# 为 $lang 添加命令"
;;
esac
}
get_language_conventions() {
local lang="$1"
echo "$lang: 遵循标准约定"
}
create_new_agent_file() {
local target_file="$1"
local temp_file="$2"
local project_name="$3"
local current_date="$4"
if [[ ! -f "$TEMPLATE_FILE" ]]; then
log_error "Template not found at $TEMPLATE_FILE"
return 1
fi
if [[ ! -r "$TEMPLATE_FILE" ]]; then
log_error "Template file is not readable: $TEMPLATE_FILE"
return 1
fi
log_info "正在从模板创建新的代理上下文文件..."
if ! cp "$TEMPLATE_FILE" "$temp_file"; then
log_error "复制模板文件失败"
return 1
fi
# 替换模板占位符
local project_structure
project_structure=$(get_project_structure "$NEW_PROJECT_TYPE")
local commands
commands=$(get_commands_for_language "$NEW_LANG")
local language_conventions
language_conventions=$(get_language_conventions "$NEW_LANG")
# 执行占位符替换(带错误检查,使用更安全方式)
# 通过选择不同分隔符或转义来处理 sed 的特殊字符
local escaped_lang=$(printf '%s\n' "$NEW_LANG" | sed 's/[\[\.*^$()+{}|]/\\&/g')
local escaped_framework=$(printf '%s\n' "$NEW_FRAMEWORK" | sed 's/[\[\.*^$()+{}|]/\\&/g')
local escaped_branch=$(printf '%s\n' "$CURRENT_BRANCH" | sed 's/[\[\.*^$()+{}|]/\\&/g')
# 根据条件构建技术栈与“最近变更”字符串
local tech_stack
if [[ -n "$escaped_lang" && -n "$escaped_framework" ]]; then
tech_stack="- $escaped_lang + $escaped_framework ($escaped_branch)"
elif [[ -n "$escaped_lang" ]]; then
tech_stack="- $escaped_lang ($escaped_branch)"
elif [[ -n "$escaped_framework" ]]; then
tech_stack="- $escaped_framework ($escaped_branch)"
else
tech_stack="- ($escaped_branch)"
fi
local recent_change
if [[ -n "$escaped_lang" && -n "$escaped_framework" ]]; then
recent_change="- $escaped_branch: Added $escaped_lang + $escaped_framework"
elif [[ -n "$escaped_lang" ]]; then
recent_change="- $escaped_branch: Added $escaped_lang"
elif [[ -n "$escaped_framework" ]]; then
recent_change="- $escaped_branch: Added $escaped_framework"
else
recent_change="- $escaped_branch: Added"
fi
local substitutions=(
"s|\[PROJECT NAME\]|$project_name|"
"s|\[DATE\]|$current_date|"
"s|\[EXTRACTED FROM ALL PLAN.MD FILES\]|$tech_stack|"
"s|\[ACTUAL STRUCTURE FROM PLANS\]|$project_structure|g"
"s|\[ONLY COMMANDS FOR ACTIVE TECHNOLOGIES\]|$commands|"
"s|\[LANGUAGE-SPECIFIC, ONLY FOR LANGUAGES IN USE\]|$language_conventions|"
"s|\[LAST 3 FEATURES AND WHAT THEY ADDED\]|$recent_change|"
)
for substitution in "${substitutions[@]}"; do
if ! sed -i.bak -e "$substitution" "$temp_file"; then
log_error "占位符替换失败: $substitution"
rm -f "$temp_file" "$temp_file.bak"
return 1
fi
done
# 将 \n 序列转换为实际换行
newline=$(printf '\n')
sed -i.bak2 "s/\\\\n/${newline}/g" "$temp_file"
# 清理备份文件
rm -f "$temp_file.bak" "$temp_file.bak2"
return 0
}
update_existing_agent_file() {
local target_file="$1"
local current_date="$2"
log_info "正在更新现有代理上下文文件..."
# 使用单个临时文件以保证原子更新
local temp_file
temp_file=$(mktemp) || {
log_error "创建临时文件失败"
return 1
}
# 单次遍历处理文件
local tech_stack=$(format_technology_stack "$NEW_LANG" "$NEW_FRAMEWORK")
local new_tech_entries=()
local new_change_entry=""
# 准备新的技术栈条目
if [[ -n "$tech_stack" ]] && ! grep -q "$tech_stack" "$target_file"; then
new_tech_entries+=("- $tech_stack ($CURRENT_BRANCH)")
fi
if [[ -n "$NEW_DB" ]] && [[ "$NEW_DB" != "N/A" ]] && [[ "$NEW_DB" != "NEEDS CLARIFICATION" ]] && ! grep -q "$NEW_DB" "$target_file"; then
new_tech_entries+=("- $NEW_DB ($CURRENT_BRANCH)")
fi
# 准备新的“最近变更”条目
if [[ -n "$tech_stack" ]]; then
new_change_entry="- $CURRENT_BRANCH: 新增 $tech_stack"
elif [[ -n "$NEW_DB" ]] && [[ "$NEW_DB" != "N/A" ]] && [[ "$NEW_DB" != "NEEDS CLARIFICATION" ]]; then
new_change_entry="- $CURRENT_BRANCH: 新增 $NEW_DB"
fi
# 检查文件中是否存在相应章节
local has_active_technologies=0
local has_recent_changes=0
if grep -q "^## Active Technologies" "$target_file" 2>/dev/null; then
has_active_technologies=1
fi
if grep -q "^## Recent Changes" "$target_file" 2>/dev/null; then
has_recent_changes=1
fi
# 按行处理文件
local in_tech_section=false
local in_changes_section=false
local tech_entries_added=false
local changes_entries_added=false
local existing_changes_count=0
local file_ended=false
while IFS= read -r line || [[ -n "$line" ]]; do
# 处理 Active Technologies 章节
if [[ "$line" == "## Active Technologies" ]]; then
echo "$line" >> "$temp_file"
in_tech_section=true
continue
elif [[ $in_tech_section == true ]] && [[ "$line" =~ ^##[[:space:]] ]]; then
# 在章节结束前追加新的技术栈条目
if [[ $tech_entries_added == false ]] && [[ ${#new_tech_entries[@]} -gt 0 ]]; then
printf '%s\n' "${new_tech_entries[@]}" >> "$temp_file"
tech_entries_added=true
fi
echo "$line" >> "$temp_file"
in_tech_section=false
continue
elif [[ $in_tech_section == true ]] && [[ -z "$line" ]]; then
# 在技术栈章节的空行前追加新条目
if [[ $tech_entries_added == false ]] && [[ ${#new_tech_entries[@]} -gt 0 ]]; then
printf '%s\n' "${new_tech_entries[@]}" >> "$temp_file"
tech_entries_added=true
fi
echo "$line" >> "$temp_file"
continue
fi
# 处理 Recent Changes 章节
if [[ "$line" == "## Recent Changes" ]]; then
echo "$line" >> "$temp_file"
# 在章节标题后立即追加新的变更条目
if [[ -n "$new_change_entry" ]]; then
echo "$new_change_entry" >> "$temp_file"
fi
in_changes_section=true
changes_entries_added=true
continue
elif [[ $in_changes_section == true ]] && [[ "$line" =~ ^##[[:space:]] ]]; then
echo "$line" >> "$temp_file"
in_changes_section=false
continue
elif [[ $in_changes_section == true ]] && [[ "$line" == "- "* ]]; then
# 仅保留前 2 条已有的变更记录
if [[ $existing_changes_count -lt 2 ]]; then
echo "$line" >> "$temp_file"
((existing_changes_count++))
fi
continue
fi
# 更新时间戳
if [[ "$line" =~ \*\*Last\ updated\*\*:.*[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] ]]; then
echo "$line" | sed "s/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/$current_date/" >> "$temp_file"
else
echo "$line" >> "$temp_file"
fi
done < "$target_file"
# 遍历结束后的检查:若仍在技术栈章节且尚未追加新条目
if [[ $in_tech_section == true ]] && [[ $tech_entries_added == false ]] && [[ ${#new_tech_entries[@]} -gt 0 ]]; then
printf '%s\n' "${new_tech_entries[@]}" >> "$temp_file"
tech_entries_added=true
fi
# 若章节不存在,则在文件结尾追加该章节
if [[ $has_active_technologies -eq 0 ]] && [[ ${#new_tech_entries[@]} -gt 0 ]]; then
echo "" >> "$temp_file"
echo "## Active Technologies" >> "$temp_file"
printf '%s\n' "${new_tech_entries[@]}" >> "$temp_file"
tech_entries_added=true
fi
if [[ $has_recent_changes -eq 0 ]] && [[ -n "$new_change_entry" ]]; then
echo "" >> "$temp_file"
echo "## Recent Changes" >> "$temp_file"
echo "$new_change_entry" >> "$temp_file"
changes_entries_added=true
fi
# 原子性地将临时文件移动到目标文件
if ! mv "$temp_file" "$target_file"; then
log_error "Failed to update target file"
rm -f "$temp_file"
return 1
fi
return 0
}
#==============================================================================
# 代理文件更新主函数
#==============================================================================
update_agent_file() {
local target_file="$1"
local agent_name="$2"
if [[ -z "$target_file" ]] || [[ -z "$agent_name" ]]; then
log_error "update_agent_file 需要 target_file 和 agent_name 参数"
return 1
fi
log_info "正在更新 $agent_name 上下文文件: $target_file"
local project_name
project_name=$(basename "$REPO_ROOT")
local current_date
current_date=$(date +%Y-%m-%d)
# Create directory if it doesn't exist
local target_dir
target_dir=$(dirname "$target_file")
if [[ ! -d "$target_dir" ]]; then
if ! mkdir -p "$target_dir"; then
log_error "创建目录失败: $target_dir"
return 1
fi
fi
if [[ ! -f "$target_file" ]]; then
# Create new file from template
local temp_file
temp_file=$(mktemp) || {
log_error "Failed to create temporary file"
return 1
}
if create_new_agent_file "$target_file" "$temp_file" "$project_name" "$current_date"; then
if mv "$temp_file" "$target_file"; then
log_success "已创建新的 $agent_name 上下文文件"
else
log_error "移动临时文件到 $target_file 失败"
rm -f "$temp_file"
return 1
fi
else
log_error "创建新的代理文件失败"
rm -f "$temp_file"
return 1
fi
else
# Update existing file
if [[ ! -r "$target_file" ]]; then
log_error "无法读取现有文件: $target_file"
return 1
fi
if [[ ! -w "$target_file" ]]; then
log_error "无法写入现有文件: $target_file"
return 1
fi
if update_existing_agent_file "$target_file" "$current_date"; then
log_success "已更新现有的 $agent_name 上下文文件"
else
log_error "更新现有代理文件失败"
return 1
fi
fi
return 0
}
#==============================================================================
# 代理类型选择与处理
#==============================================================================
update_specific_agent() {
local agent_type="$1"
case "$agent_type" in
claude)
update_agent_file "$CLAUDE_FILE" "Claude Code"
;;
gemini)
update_agent_file "$GEMINI_FILE" "Gemini CLI"
;;
copilot)
update_agent_file "$COPILOT_FILE" "GitHub Copilot"
;;
cursor-agent)
update_agent_file "$CURSOR_FILE" "Cursor IDE"
;;
qwen)
update_agent_file "$QWEN_FILE" "Qwen Code"
;;
opencode)
update_agent_file "$AGENTS_FILE" "opencode"
;;
codex)
update_agent_file "$AGENTS_FILE" "Codex CLI"
;;
windsurf)
update_agent_file "$WINDSURF_FILE" "Windsurf"
;;
kilocode)
update_agent_file "$KILOCODE_FILE" "Kilo Code"
;;
auggie)
update_agent_file "$AUGGIE_FILE" "Auggie CLI"
;;
roo)
update_agent_file "$ROO_FILE" "Roo Code"
;;
codebuddy)
update_agent_file "$CODEBUDDY_FILE" "CodeBuddy CLI"
;;
amp)
update_agent_file "$AMP_FILE" "Amp"
;;
q)
update_agent_file "$Q_FILE" "Amazon Q Developer CLI"
;;
*)
log_error "未知代理类型 '$agent_type'"
log_error "期望: claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|roo|amp|q"
exit 1
;;
esac
}
update_all_existing_agents() {
local found_agent=false
# Check each possible agent file and update if it exists
if [[ -f "$CLAUDE_FILE" ]]; then
update_agent_file "$CLAUDE_FILE" "Claude Code"
found_agent=true
fi
if [[ -f "$GEMINI_FILE" ]]; then
update_agent_file "$GEMINI_FILE" "Gemini CLI"
found_agent=true
fi
if [[ -f "$COPILOT_FILE" ]]; then
update_agent_file "$COPILOT_FILE" "GitHub Copilot"
found_agent=true
fi
if [[ -f "$CURSOR_FILE" ]]; then
update_agent_file "$CURSOR_FILE" "Cursor IDE"
found_agent=true
fi
if [[ -f "$QWEN_FILE" ]]; then
update_agent_file "$QWEN_FILE" "Qwen Code"
found_agent=true
fi
if [[ -f "$AGENTS_FILE" ]]; then
update_agent_file "$AGENTS_FILE" "Codex/opencode"
found_agent=true
fi
if [[ -f "$WINDSURF_FILE" ]]; then
update_agent_file "$WINDSURF_FILE" "Windsurf"
found_agent=true
fi
if [[ -f "$KILOCODE_FILE" ]]; then
update_agent_file "$KILOCODE_FILE" "Kilo Code"
found_agent=true
fi
if [[ -f "$AUGGIE_FILE" ]]; then
update_agent_file "$AUGGIE_FILE" "Auggie CLI"
found_agent=true
fi
if [[ -f "$ROO_FILE" ]]; then
update_agent_file "$ROO_FILE" "Roo Code"
found_agent=true
fi
if [[ -f "$CODEBUDDY_FILE" ]]; then
update_agent_file "$CODEBUDDY_FILE" "CodeBuddy CLI"
found_agent=true
fi
if [[ -f "$Q_FILE" ]]; then
update_agent_file "$Q_FILE" "Amazon Q Developer CLI"
found_agent=true
fi
# If no agent files exist, create a default Claude file
if [[ "$found_agent" == false ]]; then
log_info "未发现现有代理文件,正在创建默认的 Claude 文件..."
update_agent_file "$CLAUDE_FILE" "Claude Code"
fi
}
print_summary() {
echo
log_info "变更摘要:"
if [[ -n "$NEW_LANG" ]]; then
echo " - 新增语言: $NEW_LANG"
fi
if [[ -n "$NEW_FRAMEWORK" ]]; then
echo " - 新增框架: $NEW_FRAMEWORK"
fi
if [[ -n "$NEW_DB" ]] && [[ "$NEW_DB" != "N/A" ]]; then
echo " - 新增数据库: $NEW_DB"
fi
echo
log_info "用法: $0 [claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|codebuddy|q]"
}
#==============================================================================
# 主流程执行
#==============================================================================
main() {
# Validate environment before proceeding
validate_environment
log_info "=== 正在为特性 $CURRENT_BRANCH 更新代理上下文文件 ==="
# Parse the plan file to extract project information
if ! parse_plan_data "$NEW_PLAN"; then
log_error "解析计划数据失败"
exit 1
fi
# Process based on agent type argument
local success=true
if [[ -z "$AGENT_TYPE" ]]; then
# No specific agent provided - update all existing agent files
log_info "未指定代理类型,更新所有现有代理文件..."
if ! update_all_existing_agents; then
success=false
fi
else
# Specific agent provided - update only that agent
log_info "正在更新指定代理: $AGENT_TYPE"
if ! update_specific_agent "$AGENT_TYPE"; then
success=false
fi
fi
# Print summary
print_summary
if [[ "$success" == true ]]; then
log_success "代理上下文更新成功完成"
exit 0
else
log_error "代理上下文更新完成但存在错误"
exit 1
fi
}
# Execute main function if script is run directly
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
fi
#!/usr/bin/env pwsh
# 前置条件统一校验脚本(PowerShell)
#
# 本脚本为 Spec-Driven Development 工作流提供统一的前置条件校验。
# 用于替代此前分散在多个脚本中的校验功能。
#
# 用法: ./check-prerequisites.ps1 [选项]
#
# 选项:
# -Json 以 JSON 格式输出
# -RequireTasks 要求存在 tasks.md(实现阶段)
# -IncludeTasks 在 AVAILABLE_DOCS 列表中包含 tasks.md
# -PathsOnly 仅输出路径变量(不执行校验)
# -Help, -h 显示帮助信息
[CmdletBinding()]
param(
[switch]$Json,
[switch]$RequireTasks,
[switch]$IncludeTasks,
[switch]$PathsOnly,
[switch]$Help
)
$ErrorActionPreference = 'Stop'
# 如请求则显示帮助
if ($Help) {
Write-Output @"
用法: check-prerequisites.ps1 [选项]
用于 Spec-Driven Development 工作流的前置条件统一校验。
选项:
-Json 以 JSON 格式输出
-RequireTasks 要求存在 tasks.md(实现阶段)
-IncludeTasks 在 AVAILABLE_DOCS 列表中包含 tasks.md
-PathsOnly 仅输出路径变量(不执行校验)
-Help, -h 显示帮助信息
示例:
# 校验任务阶段前置条件(要求存在 plan.md)
.\check-prerequisites.ps1 -Json
# 校验实现阶段前置条件(要求存在 plan.md + tasks.md)
.\check-prerequisites.ps1 -Json -RequireTasks -IncludeTasks
# 仅获取特性路径(不执行校验)
.\check-prerequisites.ps1 -PathsOnly
"@
exit 0
}
# 加载通用函数
. "$PSScriptRoot/common.ps1"
# 获取特性路径并校验分支
$paths = Get-FeaturePathsEnv
if (-not (Test-FeatureBranch -Branch $paths.CURRENT_BRANCH -HasGit:$paths.HAS_GIT)) {
exit 1
}
# 仅路径模式:输出路径并退出(支持同时使用 -Json 与 -PathsOnly)
if ($PathsOnly) {
if ($Json) {
[PSCustomObject]@{
REPO_ROOT = $paths.REPO_ROOT
BRANCH = $paths.CURRENT_BRANCH
FEATURE_DIR = $paths.FEATURE_DIR
FEATURE_SPEC = $paths.FEATURE_SPEC
IMPL_PLAN = $paths.IMPL_PLAN
TASKS = $paths.TASKS
} | ConvertTo-Json -Compress
} else {
Write-Output "REPO_ROOT: $($paths.REPO_ROOT)"
Write-Output "BRANCH: $($paths.CURRENT_BRANCH)"
Write-Output "FEATURE_DIR: $($paths.FEATURE_DIR)"
Write-Output "FEATURE_SPEC: $($paths.FEATURE_SPEC)"
Write-Output "IMPL_PLAN: $($paths.IMPL_PLAN)"
Write-Output "TASKS: $($paths.TASKS)"
}
exit 0
}
# 校验必要的目录与文件
if (-not (Test-Path $paths.FEATURE_DIR -PathType Container)) {
Write-Output ('错误: 未找到特性目录: {0}' -f $paths.FEATURE_DIR)
Write-Output '请先运行 /speckit.specify 以创建特性目录结构。'
exit 1
}
if (-not (Test-Path $paths.IMPL_PLAN -PathType Leaf)) {
Write-Output ('错误: 在 {0} 中未找到 plan.md' -f $paths.FEATURE_DIR)
Write-Output '请先运行 /speckit.plan 以生成实现计划。'
exit 1
}
# 如需 tasks.md 则进行检查
if ($RequireTasks -and -not (Test-Path $paths.TASKS -PathType Leaf)) {
Write-Output ('错误: 在 {0} 中未找到 tasks.md' -f $paths.FEATURE_DIR)
Write-Output '请先运行 /speckit.tasks 以创建任务列表。'
exit 1
}
# 构建可用文档列表
$docs = @()
# 始终检查这些可选文档
if (Test-Path $paths.RESEARCH) { $docs += 'research.md' }
if (Test-Path $paths.DATA_MODEL) { $docs += 'data-model.md' }
# 检查 contracts 目录(存在且包含文件时)
if ((Test-Path $paths.CONTRACTS_DIR) -and (Get-ChildItem -Path $paths.CONTRACTS_DIR -ErrorAction SilentlyContinue | Select-Object -First 1)) {
$docs += 'contracts/'
}
if (Test-Path $paths.QUICKSTART) { $docs += 'quickstart.md' }
# 如请求且存在则包含 tasks.md
if ($IncludeTasks -and (Test-Path $paths.TASKS)) {
$docs += 'tasks.md'
}
# 输出结果
if ($Json) {
# JSON 输出
[PSCustomObject]@{
FEATURE_DIR = $paths.FEATURE_DIR
AVAILABLE_DOCS = $docs
} | ConvertTo-Json -Compress
} else {
# 文本输出
Write-Output ('特性目录:{0}' -f $paths.FEATURE_DIR)
Write-Output '可用文档:'
# 显示各可能文档的状态
Test-FileExists -Path $paths.RESEARCH -Description "research.md" | Out-Null
Test-FileExists -Path $paths.DATA_MODEL -Description "data-model.md" | Out-Null
Test-DirHasFiles -Path $paths.CONTRACTS_DIR -Description "contracts/" | Out-Null
Test-FileExists -Path $paths.QUICKSTART -Description "quickstart.md" | Out-Null
if ($IncludeTasks) {
Test-FileExists -Path $paths.TASKS -Description "tasks.md" | Out-Null
}
}
#!/usr/bin/env pwsh
# 通用 PowerShell 函数(与 common.sh 等价)
function Get-RepoRoot {
try {
$result = git rev-parse --show-toplevel 2>$null
if ($LASTEXITCODE -eq 0) {
return $result
}
} catch {
# Git 命令执行失败
}
# 非 Git 仓库下回退为脚本所在路径的上级目录
return (Resolve-Path (Join-Path $PSScriptRoot "../../..")).Path
}
function Get-CurrentBranch {
# 优先检查 SPECIFY_FEATURE 环境变量是否已设置
if ($env:SPECIFY_FEATURE) {
return $env:SPECIFY_FEATURE
}
# 若可用则使用 Git 获取分支名
try {
$result = git rev-parse --abbrev-ref HEAD 2>$null
if ($LASTEXITCODE -eq 0) {
return $result
}
} catch {
# Git 命令执行失败
}
# 非 Git 仓库:尝试在 specs 目录中查找最新的特性目录
$repoRoot = Get-RepoRoot
$specsDir = Join-Path $repoRoot "specs"
if (Test-Path $specsDir) {
$latestFeature = ""
$highest = 0
Get-ChildItem -Path $specsDir -Directory | ForEach-Object {
if ($_.Name -match '^(\d{3})-') {
$num = [int]$matches[1]
if ($num -gt $highest) {
$highest = $num
$latestFeature = $_.Name
}
}
}
if ($latestFeature) {
return $latestFeature
}
}
# 最终回退
return "main"
}
function Test-HasGit {
try {
git rev-parse --show-toplevel 2>$null | Out-Null
return ($LASTEXITCODE -eq 0)
} catch {
return $false
}
}
function Test-FeatureBranch {
param(
[string]$Branch,
[bool]$HasGit = $true
)
# 非 Git 仓库:不强制分支命名,但仍给出提示
if (-not $HasGit) {
Write-Warning "[specify] 警告: 未检测到 Git 仓库;已跳过分支校验"
return $true
}
if ($Branch -notmatch '^[0-9]{3}-') {
Write-Output "错误: 当前不在特性分支。当前分支: $Branch"
Write-Output "特性分支命名应为: 001-feature-name"
return $false
}
return $true
}
function Get-FeatureDir {
param([string]$RepoRoot, [string]$Branch)
Join-Path $RepoRoot "specs/$Branch"
}
function Get-FeaturePathsEnv {
$repoRoot = Get-RepoRoot
$currentBranch = Get-CurrentBranch
$hasGit = Test-HasGit
$featureDir = Get-FeatureDir -RepoRoot $repoRoot -Branch $currentBranch
[PSCustomObject]@{
REPO_ROOT = $repoRoot
CURRENT_BRANCH = $currentBranch
HAS_GIT = $hasGit
FEATURE_DIR = $featureDir
FEATURE_SPEC = Join-Path $featureDir 'spec.md'
IMPL_PLAN = Join-Path $featureDir 'plan.md'
TASKS = Join-Path $featureDir 'tasks.md'
RESEARCH = Join-Path $featureDir 'research.md'
DATA_MODEL = Join-Path $featureDir 'data-model.md'
QUICKSTART = Join-Path $featureDir 'quickstart.md'
CONTRACTS_DIR = Join-Path $featureDir 'contracts'
}
}
function Test-FileExists {
param([string]$Path, [string]$Description)
if (Test-Path -Path $Path -PathType Leaf) {
Write-Output " ✓ $Description"
return $true
} else {
Write-Output " ✗ $Description"
return $false
}
}
function Test-DirHasFiles {
param([string]$Path, [string]$Description)
if ((Test-Path -Path $Path -PathType Container) -and (Get-ChildItem -Path $Path -ErrorAction SilentlyContinue | Where-Object { -not $_.PSIsContainer } | Select-Object -First 1)) {
Write-Output " ✓ $Description"
return $true
} else {
Write-Output " ✗ $Description"
return $false
}
}
#!/usr/bin/env pwsh
# 创建一个新的特性
[CmdletBinding()]
param(
[switch]$Json,
[string]$ShortName,
[int]$Number = 0,
[switch]$Help,
[Parameter(ValueFromRemainingArguments = $true)]
[string[]]$FeatureDescription
)
$ErrorActionPreference = 'Stop'
# 如请求则显示帮助
if ($Help) {
Write-Host '用法: ./create-new-feature.ps1 [-Json] [-ShortName <名称>] [-Number N] <特性描述>'
Write-Host ""
Write-Host '选项:'
Write-Host ' -Json 以 JSON 格式输出'
Write-Host ' -ShortName <名称> 为分支提供自定义短名(2-4 个词)'
Write-Host ' -Number N 手动指定分支编号(覆盖自动检测)'
Write-Host ' -Help 显示此帮助信息'
Write-Host ""
Write-Host '示例:'
Write-Host " ./create-new-feature.ps1 '添加用户认证系统' -ShortName 'user-auth'"
Write-Host " ./create-new-feature.ps1 '为 API 实现 OAuth2 集成'"
exit 0
}
# Check if feature description provided
if (-not $FeatureDescription -or $FeatureDescription.Count -eq 0) {
Write-Error '用法: ./create-new-feature.ps1 [-Json] [-ShortName <名称>] <特性描述>'
exit 1
}
$featureDesc = ($FeatureDescription -join ' ').Trim()
# 解析仓库根目录:优先使用 Git 信息;若不可用则回退到项目标记查找
# 确保在使用 --no-git 初始化的仓库中也能正常工作。
function Find-RepositoryRoot {
param(
[string]$StartDir,
[string[]]$Markers = @('.git', '.specify')
)
$current = Resolve-Path $StartDir
while ($true) {
foreach ($marker in $Markers) {
if (Test-Path (Join-Path $current $marker)) {
return $current
}
}
$parent = Split-Path $current -Parent
if ($parent -eq $current) {
# 到达文件系统根目录仍未找到标记
return $null
}
$current = $parent
}
}
function Get-NextBranchNumber {
param(
[string]$ShortName,
[string]$SpecsDir
)
# 拉取所有远端以获取最新分支信息(无远端时忽略错误)
try {
git fetch --all --prune 2>$null | Out-Null
} catch {
# 忽略拉取错误
}
# 使用 git ls-remote 查找符合模式的远端分支
$remoteBranches = @()
try {
$remoteRefs = git ls-remote --heads origin 2>$null
if ($remoteRefs) {
$remoteBranches = $remoteRefs | Where-Object { $_ -match "refs/heads/(\d+)-$([regex]::Escape($ShortName))$" } | ForEach-Object {
if ($_ -match "refs/heads/(\d+)-") {
[int]$matches[1]
}
}
}
} catch {
# 忽略错误
}
# 检查本地分支
$localBranches = @()
try {
$allBranches = git branch 2>$null
if ($allBranches) {
$localBranches = $allBranches | Where-Object { $_ -match "^\*?\s*(\d+)-$([regex]::Escape($ShortName))$" } | ForEach-Object {
if ($_ -match "(\d+)-") {
[int]$matches[1]
}
}
}
} catch {
# Ignore errors
}
# 检查 specs 目录
$specDirs = @()
if (Test-Path $SpecsDir) {
try {
$specDirs = Get-ChildItem -Path $SpecsDir -Directory | Where-Object { $_.Name -match "^(\d+)-$([regex]::Escape($ShortName))$" } | ForEach-Object {
if ($_.Name -match "^(\d+)-") {
[int]$matches[1]
}
}
} catch {
# 忽略错误
}
}
# 合并各来源并获取最大编号
$maxNum = 0
foreach ($num in ($remoteBranches + $localBranches + $specDirs)) {
if ($num -gt $maxNum) {
$maxNum = $num
}
}
# 返回下一个编号
return $maxNum + 1
}
$fallbackRoot = (Find-RepositoryRoot -StartDir $PSScriptRoot)
if (-not $fallbackRoot) {
Write-Error '错误: 无法确定仓库根目录。请在仓库内运行此脚本。'
exit 1
}
try {
$repoRoot = git rev-parse --show-toplevel 2>$null
if ($LASTEXITCODE -eq 0) {
$hasGit = $true
} else {
throw "Git not available"
}
} catch {
$repoRoot = $fallbackRoot
$hasGit = $false
}
Set-Location $repoRoot
$specsDir = Join-Path $repoRoot 'specs'
New-Item -ItemType Directory -Path $specsDir -Force | Out-Null
# 生成分支名称(带停用词过滤与长度控制)
function Get-BranchName {
param([string]$Description)
# 常见停用词(需要过滤掉)
$stopWords = @(
'i', 'a', 'an', 'the', 'to', 'for', 'of', 'in', 'on', 'at', 'by', 'with', 'from',
'is', 'are', 'was', 'were', 'be', 'been', 'being', 'have', 'has', 'had',
'do', 'does', 'did', 'will', 'would', 'should', 'could', 'can', 'may', 'might', 'must', 'shall',
'this', 'that', 'these', 'those', 'my', 'your', 'our', 'their',
'want', 'need', 'add', 'get', 'set'
)
# 转为小写并提取单词(仅字母数字)
$cleanName = $Description.ToLower() -replace '[^a-z0-9\s]', ' '
$words = $cleanName -split '\s+' | Where-Object { $_ }
# 过滤单词:移除停用词与长度小于 3 的词(除非原描述中为全大写缩写)
$meaningfulWords = @()
foreach ($word in $words) {
# 跳过停用词
if ($stopWords -contains $word) { continue }
# 保留长度 ≥ 3 的词,或原描述中以全大写形式出现的词(可能为缩写)
if ($word.Length -ge 3) {
$meaningfulWords += $word
} elseif ($Description -match "\b$($word.ToUpper())\b") {
# 若原描述中为全大写(可能为缩写),保留该短词
$meaningfulWords += $word
}
}
# 若存在有效词,取前 3-4 个组成短名
if ($meaningfulWords.Count -gt 0) {
$maxWords = if ($meaningfulWords.Count -eq 4) { 4 } else { 3 }
$result = ($meaningfulWords | Select-Object -First $maxWords) -join '-'
return $result
} else {
# 若无有效词,回退到原始逻辑
$result = $Description.ToLower() -replace '[^a-z0-9]', '-' -replace '-{2,}', '-' -replace '^-', '' -replace '-$', ''
$fallbackWords = ($result -split '-') | Where-Object { $_ } | Select-Object -First 3
return [string]::Join('-', $fallbackWords)
}
}
# 生成分支名称
if ($ShortName) {
# 使用提供的短名并清理
$branchSuffix = $ShortName.ToLower() -replace '[^a-z0-9]', '-' -replace '-{2,}', '-' -replace '^-', '' -replace '-$', ''
} else {
# 基于描述生成短名(智能过滤)
$branchSuffix = Get-BranchName -Description $featureDesc
}
# 确定分支编号
if ($Number -eq 0) {
if ($hasGit) {
# 检查远端现有分支
$Number = Get-NextBranchNumber -ShortName $branchSuffix -SpecsDir $specsDir
} else {
# 回退到本地目录检查
$highest = 0
if (Test-Path $specsDir) {
Get-ChildItem -Path $specsDir -Directory | ForEach-Object {
if ($_.Name -match '^(\d{3})') {
$num = [int]$matches[1]
if ($num -gt $highest) { $highest = $num }
}
}
}
$Number = $highest + 1
}
}
$featureNum = ('{0:000}' -f $Number)
$branchName = "$featureNum-$branchSuffix"
# GitHub 对分支名有 244 字节限制,如超限则进行截断
$maxBranchLength = 244
if ($branchName.Length -gt $maxBranchLength) {
# 计算需要从后缀截取的长度(特性编号 3 + 连字符 1 = 4)
$maxSuffixLength = $maxBranchLength - 4
# 截断后缀
$truncatedSuffix = $branchSuffix.Substring(0, [Math]::Min($branchSuffix.Length, $maxSuffixLength))
# 若截断产生尾部连字符则移除
$truncatedSuffix = $truncatedSuffix -replace '-$', ''
$originalBranchName = $branchName
$branchName = "$featureNum-$truncatedSuffix"
Write-Warning "[specify] 警告: 分支名称超过 GitHub 的 244 字节限制"
Write-Warning "[specify] 原始: $originalBranchName ($($originalBranchName.Length) 字节)"
Write-Warning "[specify] 已截断为: $branchName ($($branchName.Length) 字节)"
}
if ($hasGit) {
try {
git checkout -b $branchName | Out-Null
} catch {
Write-Warning ('创建 Git 分支失败: {0}' -f $branchName)
}
} else {
Write-Warning ('[specify] 警告: 未检测到 Git 仓库;已跳过分支创建: {0}' -f $branchName)
}
$featureDir = Join-Path $specsDir $branchName
New-Item -ItemType Directory -Path $featureDir -Force | Out-Null
$template = Join-Path $repoRoot '.specify/templates/spec-template.md'
$specFile = Join-Path $featureDir 'spec.md'
if (Test-Path $template) {
Copy-Item $template $specFile -Force
} else {
New-Item -ItemType File -Path $specFile | Out-Null
}
# 为当前会话设置 SPECIFY_FEATURE 环境变量
$env:SPECIFY_FEATURE = $branchName
if ($Json) {
$obj = [PSCustomObject]@{
BRANCH_NAME = $branchName
SPEC_FILE = $specFile
FEATURE_NUM = $featureNum
HAS_GIT = $hasGit
}
$obj | ConvertTo-Json -Compress
} else {
Write-Output ('分支名称: {0}' -f $branchName)
Write-Output ('规格文件: {0}' -f $specFile)
Write-Output ('特性编号: {0}' -f $featureNum)
Write-Output ('是否有 Git: {0}' -f $hasGit)
Write-Output ('已设置 SPECIFY_FEATURE 环境变量为: {0}' -f $branchName)
}
#!/usr/bin/env pwsh
# 为特性设置实现计划
[CmdletBinding()]
param(
[switch]$Json,
[switch]$Help
)
$ErrorActionPreference = 'Stop'
# 如请求则显示帮助
if ($Help) {
Write-Output '用法: ./setup-plan.ps1 [-Json] [-Help]'
Write-Output ' -Json 以 JSON 格式输出结果'
Write-Output ' -Help 显示此帮助信息'
exit 0
}
# 加载通用函数
. "$PSScriptRoot/common.ps1"
# 从通用函数获取所有路径与变量
$paths = Get-FeaturePathsEnv
# 检查当前是否在正确的特性分支(仅在 Git 仓库中校验)
if (-not (Test-FeatureBranch -Branch $paths.CURRENT_BRANCH -HasGit $paths.HAS_GIT)) {
exit 1
}
# 确保特性目录存在
New-Item -ItemType Directory -Path $paths.FEATURE_DIR -Force | Out-Null
# 若存在计划模板则复制,否则记录并创建空文件
$template = Join-Path $paths.REPO_ROOT '.specify/templates/plan-template.md'
if (Test-Path $template) {
Copy-Item $template $paths.IMPL_PLAN -Force
Write-Output ('已复制计划模板到 {0}' -f $paths.IMPL_PLAN)
} else {
Write-Warning ('未在 {0} 找到计划模板' -f $template)
# 若模板不存在则创建一个基础计划文件
New-Item -ItemType File -Path $paths.IMPL_PLAN -Force | Out-Null
}
# 输出结果
if ($Json) {
$result = [PSCustomObject]@{
FEATURE_SPEC = $paths.FEATURE_SPEC
IMPL_PLAN = $paths.IMPL_PLAN
SPECS_DIR = $paths.FEATURE_DIR
BRANCH = $paths.CURRENT_BRANCH
HAS_GIT = $paths.HAS_GIT
}
$result | ConvertTo-Json -Compress
} else {
Write-Output ('规格文件: {0}' -f $paths.FEATURE_SPEC)
Write-Output ('实现计划: {0}' -f $paths.IMPL_PLAN)
Write-Output ('规格目录: {0}' -f $paths.FEATURE_DIR)
Write-Output ('当前分支: {0}' -f $paths.CURRENT_BRANCH)
Write-Output ('是否有 Git: {0}' -f $paths.HAS_GIT)
}
#!/usr/bin/env pwsh
<#!
.SYNOPSIS
基于 plan.md 更新代理上下文文件(PowerShell 版本)
.DESCRIPTION
与 scripts/bash/update-agent-context.sh 行为一致:
1. 环境校验
2. 计划数据解析
3. 代理文件管理(从模板创建或更新现有文件)
4. 内容生成(技术栈、最近变更、时间戳)
5. 多代理支持(claude、gemini、copilot、cursor-agent、qwen、opencode、codex、windsurf、kilocode、auggie、roo、amp、q)
.PARAMETER AgentType
可选代理类型,仅更新指定代理。若省略则更新所有已存在代理(若不存在则创建默认 Claude 文件)。
.EXAMPLE
./update-agent-context.ps1 -AgentType claude
.EXAMPLE
./update-agent-context.ps1 # 更新所有已存在代理文件
.NOTES
依赖 common.ps1 中的通用辅助函数
#>
param(
[Parameter(Position=0)]
[ValidateSet('claude','gemini','copilot','cursor-agent','qwen','opencode','codex','windsurf','kilocode','auggie','roo','codebuddy','amp','q')]
[string]$AgentType
)
$ErrorActionPreference = 'Stop'
# 导入通用辅助函数
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
. (Join-Path $ScriptDir 'common.ps1')
# 获取环境路径
$envData = Get-FeaturePathsEnv
$REPO_ROOT = $envData.REPO_ROOT
$CURRENT_BRANCH = $envData.CURRENT_BRANCH
$HAS_GIT = $envData.HAS_GIT
$IMPL_PLAN = $envData.IMPL_PLAN
$NEW_PLAN = $IMPL_PLAN
# 代理文件路径
$CLAUDE_FILE = Join-Path $REPO_ROOT 'CLAUDE.md'
$GEMINI_FILE = Join-Path $REPO_ROOT 'GEMINI.md'
$COPILOT_FILE = Join-Path $REPO_ROOT '.github/copilot-instructions.md'
$CURSOR_FILE = Join-Path $REPO_ROOT '.cursor/rules/specify-rules.mdc'
$QWEN_FILE = Join-Path $REPO_ROOT 'QWEN.md'
$AGENTS_FILE = Join-Path $REPO_ROOT 'AGENTS.md'
$WINDSURF_FILE = Join-Path $REPO_ROOT '.windsurf/rules/specify-rules.md'
$KILOCODE_FILE = Join-Path $REPO_ROOT '.kilocode/rules/specify-rules.md'
$AUGGIE_FILE = Join-Path $REPO_ROOT '.augment/rules/specify-rules.md'
$ROO_FILE = Join-Path $REPO_ROOT '.roo/rules/specify-rules.md'
$CODEBUDDY_FILE = Join-Path $REPO_ROOT 'CODEBUDDY.md'
$AMP_FILE = Join-Path $REPO_ROOT 'AGENTS.md'
$Q_FILE = Join-Path $REPO_ROOT 'AGENTS.md'
$TEMPLATE_FILE = Join-Path $REPO_ROOT '.specify/templates/agent-file-template.md'
# 计划解析占位变量
$script:NEW_LANG = ''
$script:NEW_FRAMEWORK = ''
$script:NEW_DB = ''
$script:NEW_PROJECT_TYPE = ''
function Write-Info {
param(
[Parameter(Mandatory=$true)]
[string]$Message
)
Write-Host ("信息: {0}" -f $Message)
}
function Write-Success {
param(
[Parameter(Mandatory=$true)]
[string]$Message
)
Write-Host ("$([char]0x2713) {0}" -f $Message)
}
function Write-WarningMsg {
param(
[Parameter(Mandatory=$true)]
[string]$Message
)
Write-Warning $Message
}
function Write-Err {
param(
[Parameter(Mandatory=$true)]
[string]$Message
)
Write-Host ("错误: {0}" -f $Message) -ForegroundColor Red
}
function Validate-Environment {
if (-not $CURRENT_BRANCH) {
Write-Err '无法确定当前特性'
if ($HAS_GIT) { Write-Info '请确保当前处于特性分支' } else { Write-Info '请设置 SPECIFY_FEATURE 环境变量或先创建特性' }
exit 1
}
if (-not (Test-Path $NEW_PLAN)) {
Write-Err ("未在 {0} 发现 plan.md" -f $NEW_PLAN)
Write-Info '请确保正在处理具有对应规格目录的特性'
if (-not $HAS_GIT) { Write-Info '使用: $env:SPECIFY_FEATURE=your-feature-name 或先创建新特性' }
exit 1
}
if (-not (Test-Path $TEMPLATE_FILE)) {
Write-Err ("未在 {0} 找到模板文件" -f $TEMPLATE_FILE)
Write-Info '运行 specify init 以生成 .specify/templates,或手动添加 agent-file-template.md'
exit 1
}
}
function Extract-PlanField {
param(
[Parameter(Mandatory=$true)]
[string]$FieldPattern,
[Parameter(Mandatory=$true)]
[string]$PlanFile
)
if (-not (Test-Path $PlanFile)) { return '' }
# 示例行格式:**Language/Version**: Python 3.12
$regex = "^\*\*$([Regex]::Escape($FieldPattern))\*\*: (.+)$"
Get-Content -LiteralPath $PlanFile -Encoding utf8 | ForEach-Object {
if ($_ -match $regex) {
$val = $Matches[1].Trim()
if ($val -notin @('NEEDS CLARIFICATION','N/A')) { return $val }
}
} | Select-Object -First 1
}
function Parse-PlanData {
param(
[Parameter(Mandatory=$true)]
[string]$PlanFile
)
if (-not (Test-Path $PlanFile)) { Write-Err "Plan file not found: $PlanFile"; return $false }
Write-Info ("正在解析计划数据: {0}" -f $PlanFile)
$script:NEW_LANG = Extract-PlanField -FieldPattern 'Language/Version' -PlanFile $PlanFile
$script:NEW_FRAMEWORK = Extract-PlanField -FieldPattern 'Primary Dependencies' -PlanFile $PlanFile
$script:NEW_DB = Extract-PlanField -FieldPattern 'Storage' -PlanFile $PlanFile
$script:NEW_PROJECT_TYPE = Extract-PlanField -FieldPattern 'Project Type' -PlanFile $PlanFile
if ($NEW_LANG) { Write-Info ("发现语言: {0}" -f $NEW_LANG) } else { Write-WarningMsg '在计划中未找到语言信息' }
if ($NEW_FRAMEWORK) { Write-Info ("发现框架: {0}" -f $NEW_FRAMEWORK) }
if ($NEW_DB -and $NEW_DB -ne 'N/A') { Write-Info ("发现数据库: {0}" -f $NEW_DB) }
if ($NEW_PROJECT_TYPE) { Write-Info ("发现项目类型: {0}" -f $NEW_PROJECT_TYPE) }
return $true
}
function Format-TechnologyStack {
param(
[Parameter(Mandatory=$false)]
[string]$Lang,
[Parameter(Mandatory=$false)]
[string]$Framework
)
$parts = @()
if ($Lang -and $Lang -ne 'NEEDS CLARIFICATION') { $parts += $Lang }
if ($Framework -and $Framework -notin @('NEEDS CLARIFICATION','N/A')) { $parts += $Framework }
if (-not $parts) { return '' }
return ($parts -join ' + ')
}
function Get-ProjectStructure {
param(
[Parameter(Mandatory=$false)]
[string]$ProjectType
)
if ($ProjectType -match 'web') { return "backend/`nfrontend/`ntests/" } else { return "src/`ntests/" }
}
function Get-CommandsForLanguage {
param(
[Parameter(Mandatory=$false)]
[string]$Lang
)
switch -Regex ($Lang) {
'Python' { return "cd src; pytest; ruff check ." }
'Rust' { return "cargo test; cargo clippy" }
'JavaScript|TypeScript' { return "npm test; npm run lint" }
default { return "# Add commands for $Lang" }
}
}
function Get-LanguageConventions {
param(
[Parameter(Mandatory=$false)]
[string]$Lang
)
if ($Lang) { "${Lang}: Follow standard conventions" } else { 'General: Follow standard conventions' }
}
function New-AgentFile {
param(
[Parameter(Mandatory=$true)]
[string]$TargetFile,
[Parameter(Mandatory=$true)]
[string]$ProjectName,
[Parameter(Mandatory=$true)]
[datetime]$Date
)
if (-not (Test-Path $TEMPLATE_FILE)) { Write-Err ("未在 {0} 找到模板" -f $TEMPLATE_FILE); return $false }
$temp = New-TemporaryFile
Copy-Item -LiteralPath $TEMPLATE_FILE -Destination $temp -Force
$projectStructure = Get-ProjectStructure -ProjectType $NEW_PROJECT_TYPE
$commands = Get-CommandsForLanguage -Lang $NEW_LANG
$languageConventions = Get-LanguageConventions -Lang $NEW_LANG
$escaped_lang = $NEW_LANG
$escaped_framework = $NEW_FRAMEWORK
$escaped_branch = $CURRENT_BRANCH
$content = Get-Content -LiteralPath $temp -Raw -Encoding utf8
$content = $content -replace '\[PROJECT NAME\]',$ProjectName
$content = $content -replace '\[DATE\]',$Date.ToString('yyyy-MM-dd')
# 安全构建技术栈字符串
$techStackForTemplate = ""
if ($escaped_lang -and $escaped_framework) {
$techStackForTemplate = "- $escaped_lang + $escaped_framework ($escaped_branch)"
} elseif ($escaped_lang) {
$techStackForTemplate = "- $escaped_lang ($escaped_branch)"
} elseif ($escaped_framework) {
$techStackForTemplate = "- $escaped_framework ($escaped_branch)"
}
$content = $content -replace '\[EXTRACTED FROM ALL PLAN.MD FILES\]',$techStackForTemplate
# 项目结构手动嵌入(保留换行)
$escapedStructure = [Regex]::Escape($projectStructure)
$content = $content -replace '\[ACTUAL STRUCTURE FROM PLANS\]',$escapedStructure
# 完成替换后,将转义的换行占位符替换为真实换行
$content = $content -replace '\[ONLY COMMANDS FOR ACTIVE TECHNOLOGIES\]',$commands
$content = $content -replace '\[LANGUAGE-SPECIFIC, ONLY FOR LANGUAGES IN USE\]',$languageConventions
# 安全构建“最近变更”字符串
$recentChangesForTemplate = ""
if ($escaped_lang -and $escaped_framework) {
$recentChangesForTemplate = "- ${escaped_branch}: Added ${escaped_lang} + ${escaped_framework}"
} elseif ($escaped_lang) {
$recentChangesForTemplate = "- ${escaped_branch}: Added ${escaped_lang}"
} elseif ($escaped_framework) {
$recentChangesForTemplate = "- ${escaped_branch}: Added ${escaped_framework}"
}
$content = $content -replace '\[LAST 3 FEATURES AND WHAT THEY ADDED\]',$recentChangesForTemplate
# 将转义产生的 \n 转换为真实换行
$content = $content -replace '\\n',[Environment]::NewLine
$parent = Split-Path -Parent $TargetFile
if (-not (Test-Path $parent)) { New-Item -ItemType Directory -Path $parent | Out-Null }
Set-Content -LiteralPath $TargetFile -Value $content -NoNewline -Encoding utf8
Remove-Item $temp -Force
return $true
}
function Update-ExistingAgentFile {
param(
[Parameter(Mandatory=$true)]
[string]$TargetFile,
[Parameter(Mandatory=$true)]
[datetime]$Date
)
if (-not (Test-Path $TargetFile)) { return (New-AgentFile -TargetFile $TargetFile -ProjectName (Split-Path $REPO_ROOT -Leaf) -Date $Date) }
$techStack = Format-TechnologyStack -Lang $NEW_LANG -Framework $NEW_FRAMEWORK
$newTechEntries = @()
if ($techStack) {
$escapedTechStack = [Regex]::Escape($techStack)
if (-not (Select-String -Pattern $escapedTechStack -Path $TargetFile -Quiet)) {
$newTechEntries += "- $techStack ($CURRENT_BRANCH)"
}
}
if ($NEW_DB -and $NEW_DB -notin @('N/A','NEEDS CLARIFICATION')) {
$escapedDB = [Regex]::Escape($NEW_DB)
if (-not (Select-String -Pattern $escapedDB -Path $TargetFile -Quiet)) {
$newTechEntries += "- $NEW_DB ($CURRENT_BRANCH)"
}
}
$newChangeEntry = ''
if ($techStack) { $newChangeEntry = "- ${CURRENT_BRANCH}: 新增 ${techStack}" }
elseif ($NEW_DB -and $NEW_DB -notin @('N/A','NEEDS CLARIFICATION')) { $newChangeEntry = "- ${CURRENT_BRANCH}: 新增 ${NEW_DB}" }
$lines = Get-Content -LiteralPath $TargetFile -Encoding utf8
$output = New-Object System.Collections.Generic.List[string]
$inTech = $false; $inChanges = $false; $techAdded = $false; $changeAdded = $false; $existingChanges = 0
for ($i=0; $i -lt $lines.Count; $i++) {
$line = $lines[$i]
if ($line -eq '## Active Technologies') {
$output.Add($line)
$inTech = $true
continue
}
if ($inTech -and $line -match '^##\s') {
if (-not $techAdded -and $newTechEntries.Count -gt 0) { $newTechEntries | ForEach-Object { $output.Add($_) }; $techAdded = $true }
$output.Add($line); $inTech = $false; continue
}
if ($inTech -and [string]::IsNullOrWhiteSpace($line)) {
if (-not $techAdded -and $newTechEntries.Count -gt 0) { $newTechEntries | ForEach-Object { $output.Add($_) }; $techAdded = $true }
$output.Add($line); continue
}
if ($line -eq '## Recent Changes') {
$output.Add($line)
if ($newChangeEntry) { $output.Add($newChangeEntry); $changeAdded = $true }
$inChanges = $true
continue
}
if ($inChanges -and $line -match '^##\s') { $output.Add($line); $inChanges = $false; continue }
if ($inChanges -and $line -match '^- ') {
if ($existingChanges -lt 2) { $output.Add($line); $existingChanges++ }
continue
}
if ($line -match '\*\*Last updated\*\*: .*\d{4}-\d{2}-\d{2}') {
$output.Add(($line -replace '\d{4}-\d{2}-\d{2}',$Date.ToString('yyyy-MM-dd')))
continue
}
$output.Add($line)
}
# 循环后检查:若仍处于“Active Technologies”章节且尚未追加新条目
if ($inTech -and -not $techAdded -and $newTechEntries.Count -gt 0) {
$newTechEntries | ForEach-Object { $output.Add($_) }
}
Set-Content -LiteralPath $TargetFile -Value ($output -join [Environment]::NewLine) -Encoding utf8
return $true
}
function Update-AgentFile {
param(
[Parameter(Mandatory=$true)]
[string]$TargetFile,
[Parameter(Mandatory=$true)]
[string]$AgentName
)
if (-not $TargetFile -or -not $AgentName) { Write-Err 'Update-AgentFile 需要 TargetFile 和 AgentName 参数'; return $false }
Write-Info ("正在更新 {0} 上下文文件: {1}" -f $AgentName, $TargetFile)
$projectName = Split-Path $REPO_ROOT -Leaf
$date = Get-Date
$dir = Split-Path -Parent $TargetFile
if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir | Out-Null }
if (-not (Test-Path $TargetFile)) {
if (New-AgentFile -TargetFile $TargetFile -ProjectName $projectName -Date $date) { Write-Success ("已创建新的 {0} 上下文文件" -f $AgentName) } else { Write-Err '创建新的代理文件失败'; return $false }
} else {
try {
if (Update-ExistingAgentFile -TargetFile $TargetFile -Date $date) { Write-Success ("已更新现有的 {0} 上下文文件" -f $AgentName) } else { Write-Err '更新代理文件失败'; return $false }
} catch {
Write-Err ("无法访问或更新现有文件: {0}. {1}" -f $TargetFile, $_)
return $false
}
}
return $true
}
function Update-SpecificAgent {
param(
[Parameter(Mandatory=$true)]
[string]$Type
)
switch ($Type) {
'claude' { Update-AgentFile -TargetFile $CLAUDE_FILE -AgentName 'Claude Code' }
'gemini' { Update-AgentFile -TargetFile $GEMINI_FILE -AgentName 'Gemini CLI' }
'copilot' { Update-AgentFile -TargetFile $COPILOT_FILE -AgentName 'GitHub Copilot' }
'cursor-agent' { Update-AgentFile -TargetFile $CURSOR_FILE -AgentName 'Cursor IDE' }
'qwen' { Update-AgentFile -TargetFile $QWEN_FILE -AgentName 'Qwen Code' }
'opencode' { Update-AgentFile -TargetFile $AGENTS_FILE -AgentName 'opencode' }
'codex' { Update-AgentFile -TargetFile $AGENTS_FILE -AgentName 'Codex CLI' }
'windsurf' { Update-AgentFile -TargetFile $WINDSURF_FILE -AgentName 'Windsurf' }
'kilocode' { Update-AgentFile -TargetFile $KILOCODE_FILE -AgentName 'Kilo Code' }
'auggie' { Update-AgentFile -TargetFile $AUGGIE_FILE -AgentName 'Auggie CLI' }
'roo' { Update-AgentFile -TargetFile $ROO_FILE -AgentName 'Roo Code' }
'codebuddy' { Update-AgentFile -TargetFile $CODEBUDDY_FILE -AgentName 'CodeBuddy CLI' }
'amp' { Update-AgentFile -TargetFile $AMP_FILE -AgentName 'Amp' }
'q' { Update-AgentFile -TargetFile $Q_FILE -AgentName 'Amazon Q Developer CLI' }
default { Write-Err ("未知代理类型 '{0}'" -f $Type); Write-Err '期望: claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|roo|codebuddy|amp|q'; return $false }
}
}
function Update-AllExistingAgents {
$found = $false
$ok = $true
if (Test-Path $CLAUDE_FILE) { if (-not (Update-AgentFile -TargetFile $CLAUDE_FILE -AgentName 'Claude Code')) { $ok = $false }; $found = $true }
if (Test-Path $GEMINI_FILE) { if (-not (Update-AgentFile -TargetFile $GEMINI_FILE -AgentName 'Gemini CLI')) { $ok = $false }; $found = $true }
if (Test-Path $COPILOT_FILE) { if (-not (Update-AgentFile -TargetFile $COPILOT_FILE -AgentName 'GitHub Copilot')) { $ok = $false }; $found = $true }
if (Test-Path $CURSOR_FILE) { if (-not (Update-AgentFile -TargetFile $CURSOR_FILE -AgentName 'Cursor IDE')) { $ok = $false }; $found = $true }
if (Test-Path $QWEN_FILE) { if (-not (Update-AgentFile -TargetFile $QWEN_FILE -AgentName 'Qwen Code')) { $ok = $false }; $found = $true }
if (Test-Path $AGENTS_FILE) { if (-not (Update-AgentFile -TargetFile $AGENTS_FILE -AgentName 'Codex/opencode')) { $ok = $false }; $found = $true }
if (Test-Path $WINDSURF_FILE) { if (-not (Update-AgentFile -TargetFile $WINDSURF_FILE -AgentName 'Windsurf')) { $ok = $false }; $found = $true }
if (Test-Path $KILOCODE_FILE) { if (-not (Update-AgentFile -TargetFile $KILOCODE_FILE -AgentName 'Kilo Code')) { $ok = $false }; $found = $true }
if (Test-Path $AUGGIE_FILE) { if (-not (Update-AgentFile -TargetFile $AUGGIE_FILE -AgentName 'Auggie CLI')) { $ok = $false }; $found = $true }
if (Test-Path $ROO_FILE) { if (-not (Update-AgentFile -TargetFile $ROO_FILE -AgentName 'Roo Code')) { $ok = $false }; $found = $true }
if (Test-Path $CODEBUDDY_FILE) { if (-not (Update-AgentFile -TargetFile $CODEBUDDY_FILE -AgentName 'CodeBuddy CLI')) { $ok = $false }; $found = $true }
if (Test-Path $Q_FILE) { if (-not (Update-AgentFile -TargetFile $Q_FILE -AgentName 'Amazon Q Developer CLI')) { $ok = $false }; $found = $true }
if (-not $found) {
Write-Info '未发现现有代理文件,正在创建默认的 Claude 文件...'
if (-not (Update-AgentFile -TargetFile $CLAUDE_FILE -AgentName 'Claude Code')) { $ok = $false }
}
return $ok
}
function Print-Summary {
Write-Host ''
Write-Info '变更摘要:'
if ($NEW_LANG) { Write-Host " - Added language: $NEW_LANG" }
if ($NEW_FRAMEWORK) { Write-Host " - Added framework: $NEW_FRAMEWORK" }
if ($NEW_DB -and $NEW_DB -ne 'N/A') { Write-Host " - Added database: $NEW_DB" }
Write-Host ''
Write-Info '用法: ./update-agent-context.ps1 [-AgentType claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|roo|codebuddy|amp|q]'
}
function Main {
Validate-Environment
Write-Info ("=== 正在为特性 {0} 更新代理上下文文件 ===" -f $CURRENT_BRANCH)
if (-not (Parse-PlanData -PlanFile $NEW_PLAN)) { Write-Err '解析计划数据失败'; exit 1 }
$success = $true
if ($AgentType) {
Write-Info ("正在更新指定代理: {0}" -f $AgentType)
if (-not (Update-SpecificAgent -Type $AgentType)) { $success = $false }
}
else {
Write-Info '未指定代理类型,更新所有现有代理文件...'
if (-not (Update-AllExistingAgents)) { $success = $false }
}
Print-Summary
if ($success) { Write-Success '代理上下文更新成功完成'; exit 0 } else { Write-Err '代理上下文更新完成但存在错误'; exit 1 }
}
Main
[项目名称] 开发指南
自动生成自所有功能计划。最后更新:[日期]
活动技术
[从所有计划文件中提取]
项目结构
[来自计划的实际结构]命令
[仅限活动技术的命令]
代码风格
[特定语言的规范,仅适用于正在使用的语言]
最近变更
[最近3个功能及其添加内容]
<!-- 手动添加开始 --> <!-- 手动添加结束 -->
[检查表类型] 检查表:[功能名称]
目的:[此检查表涵盖内容的简要描述] 创建时间:[日期] 功能:[链接到 spec.md 或相关文档]
注意:此检查表由 /speckit.checklist 命令根据功能上下文和要求生成。
<!-- ============================================================================ 重要提示:下面的检查表项目仅为示例项目,仅用于说明。
/speckit.checklist 命令必须根据以下内容替换这些项目:
- 用户的具体检查表请求
- 来自 spec.md 的功能要求
- 来自 plan.md 的技术上下文
- 来自 tasks.md 的实现细节
不要在生成的检查表文件中保留这些示例项目。 ============================================================================ -->
[类别 1]
- [ ] CHK001 第一个检查表项目,带有明确的操作
- [ ] CHK002 第二个检查表项目
- [ ] CHK003 第三个检查表项目
[类别 2]
- [ ] CHK004 另一个类别的项目
- [ ] CHK005 带有特定标准的项目
- [ ] CHK006 此类别中的最后一个项目
备注
- 完成后勾选项目:
[x] - 在线添加评论或发现
- 链接到相关资源或文档
- 项目按顺序编号以便于参考
用户输入
$ARGUMENTS您必须在继续之前考虑用户输入(如果不为空)。
目标
在实现之前,识别三个核心工件(spec.md、plan.md、tasks.md)之间的不一致、重复、歧义和未充分说明的项目。此命令必须仅在 /speckit.tasks 成功生成完整的 tasks.md 后运行。
操作约束
严格只读:不要修改任何文件。输出结构化分析报告。提供可选的补救计划(用户必须明确批准后才能手动调用任何后续编辑命令)。
宪章权威性:项目宪章(/memory/constitution.md)在此分析范围内是不可协商的。宪章冲突自动为关键级别,需要调整规格、计划或任务——而不是稀释、重新解释或默默忽略原则。如果原则本身需要更改,必须在 /speckit.analyze 之外的单独、明确的宪章更新中进行。
执行步骤
1. 初始化分析上下文
从仓库根目录运行一次 {SCRIPT} 并解析 JSON 以获取 FEATURE_DIR 和 AVAILABLE_DOCS。推导绝对路径:
- SPEC = FEATURE_DIR/spec.md
- PLAN = FEATURE_DIR/plan.md
- TASKS = FEATURE_DIR/tasks.md
如果缺少任何必需文件,则中止并显示错误消息(指示用户运行缺少的先决条件命令)。 对于参数中的单引号,如 "I'm Groot",使用转义语法:例如 'I'\''m Groot'(或者如果可能的话使用双引号:"I'm Groot")。
2. 加载工件(渐进式披露)
仅加载每个工件的最小必要上下文:
来自 spec.md:
- 概述/上下文
- 功能要求
- 非功能要求
- 用户故事
- 边缘情况(如果存在)
来自 plan.md:
- 架构/技术栈选择
- 数据模型引用
- 阶段
- 技术约束
来自 tasks.md:
- 任务 ID
- 描述
- 阶段分组
- 并行标记 [P]
- 引用的文件路径
来自 constitution:
- 加载
/memory/constitution.md用于原则验证
3. 构建语义模型
创建内部表示(不要在输出中包含原始工件):
- 要求清单:每个功能+非功能要求带有一个稳定键(根据祈使句派生 slug;例如,"用户可以上传文件" →
user-can-upload-file) - 用户故事/动作清单:具有验收标准的离散用户动作
- 任务覆盖映射:将每个任务映射到一个或多个要求或故事(通过关键词/显式引用模式如 ID 或关键词进行推断)
- 宪章规则集:提取原则名称和 MUST/SHOULD 规范性陈述
4. 检测过程(高效令牌分析)
专注于高信号发现。限制总数为 50 个发现;在溢出摘要中聚合其余发现。
A. 重复检测
- 识别近似重复的要求
- 标记质量较低的措辞以进行合并
B. 歧义检测
- 标记缺乏可测量标准的模糊形容词(快速、可扩展、安全、直观、健壮)
- 标记未解决的占位符(TODO、TKTK、???、
<placeholder>等)
C. 未充分说明
- 有动词但缺少对象或可测量结果的要求
- 缺少验收标准对齐的用户故事
- 引用在规格/计划中未定义的文件或组件的任务
D. 宪章对齐
- 任何与 MUST 原则冲突的要求或计划元素
- 缺少宪章中规定的章节或质量门
E. 覆盖差距
- 没有关联任务的要求
- 没有映射要求/故事的任务
- 未在任务中体现的非功能要求(例如,性能、安全性)
F. 不一致
- 术语漂移(同一概念在不同文件中有不同名称)
- 计划中引用但在规格中缺失的数据实体(反之亦然)
- 任务排序矛盾(例如,集成任务在基础设置任务之前但没有依赖注释)
- 冲突的要求(例如,一个要求 Next.js 而另一个指定 Vue)
5. 严重性分配
使用此启发式方法来优先处理发现:
- 关键:违反宪章 MUST、缺少核心规格工件,或阻塞基本功能的零覆盖要求
- 高:重复或冲突的要求、模糊的安全/性能属性、不可测试的验收标准
- 中:术语漂移、缺少非功能任务覆盖、未充分说明的边缘情况
- 低:样式/措辞改进、不影响执行顺序的次要冗余
6. 生成紧凑分析报告
输出一个 Markdown 报告(不写入文件)具有以下结构:
规格分析报告
| ID | 类别 | 严重性 | 位置 | 摘要 | 建议 |
|---|---|---|---|---|---|
| A1 | 重复 | 高 | spec.md:L120-134 | 两个相似的要求 ... | 合并措辞;保留更清晰的版本 |
(每项发现添加一行;生成以类别首字母为前缀的稳定 ID。)
覆盖摘要表:
| 要求键 | 有任务? | 任务 ID | 备注 |
|---|
宪章对齐问题:(如果有)
未映射的任务:(如果有)
指标:
- 总要求
- 总任务
- 覆盖率%(有>=1个任务的要求)
- 歧义计数
- 重复计数
- 关键问题计数
7. 提供下一步行动
在报告末尾,输出一个简洁的下一步行动块:
- 如果存在关键问题:建议在
/speckit.implement之前解决 - 如果只有低/中等:用户可以继续,但提供改进建议
- 提供明确的命令建议:例如,"使用改进运行 /speckit.specify","运行 /speckit.plan 调整架构","手动编辑 tasks.md 添加 'performance-metrics' 的覆盖"
8. 提供补救措施
询问用户:"您希望我为前 N 个问题建议具体的补救编辑吗?"(不要自动应用它们。)
操作原则
上下文效率
- 最小高信号令牌:专注于可操作的发现,而不是详尽的文档
- 渐进式披露:增量加载工件;不要将所有内容倒入分析
- 高效令牌输出:限制发现表为 50 行;总结溢出
- 确定性结果:在没有更改的情况下重新运行应产生一致的 ID 和计数
分析指南
- 永不修改文件(这是只读分析)
- 永不虚构缺失部分(如果缺失,准确报告)
- 优先处理宪章违规(这些总是关键的)
- 使用示例而非详尽规则(引用具体实例,而非通用模式)
- 优雅报告零问题(发出带有覆盖统计的成功报告)
上下文
{ARGS}
检查表目的:"中文的单元测试"
关键概念:检查表是要求编写的单元测试 - 它们验证特定领域中要求的质量、清晰度和完整性。
不用于验证/测试:
- ❌ 不是"验证按钮正确点击"
- ❌ 不是"测试错误处理是否有效"
- ❌ 不是"确认 API 返回 200"
- ❌ 不是检查代码/实现是否符合规格
用于要求质量验证:
- ✅ "是否为所有卡片类型定义了视觉层次要求?"(完整性)
- ✅ "是否用特定的尺寸/定位量化了'显著显示'?"(清晰度)
- ✅ "所有交互元素的悬停状态要求是否一致?"(一致性)
- ✅ "是否为键盘导航定义了可访问性要求?"(覆盖范围)
- ✅ "规格是否定义了徽标图像加载失败时的情况?"(边缘情况)
比喻:如果您的规格是用英语编写的代码,那么检查表就是它的单元测试套件。您正在测试要求是否编写良好、完整、明确并准备好实施 - 而不是测试实现是否有效。
用户输入
$ARGUMENTS您必须在继续之前考虑用户输入(如果不为空)。
执行步骤
1. 设置:从仓库根目录运行 {SCRIPT} 并解析 JSON 以获取 FEATURE_DIR 和 AVAILABLE_DOCS 列表。
- 所有文件路径必须是绝对的。
- 对于参数中的单引号,如 "I'm Groot",使用转义语法:例如 'I'\''m Groot'(或者如果可能的话使用双引号:"I'm Groot")。
2. 澄清意图(动态):推导出最多三个初始上下文澄清问题(无预设目录)。它们必须:
- 从用户的措辞 + 从规格/计划/任务中提取的信号生成
- 仅询问会实质性改变检查表内容的信息
- 如果在
$ARGUMENTS中已经明确,则单独跳过 - 优先考虑精确性而非广度
生成算法: 1. 提取信号:功能领域关键词(例如,auth, latency, UX, API),风险指标("critical", "must", "compliance"),利益相关者提示("QA", "review", "security team")和明确的交付物("a11y", "rollback", "contracts")。 2. 将信号聚类到候选关注领域(最多 4 个)按相关性排序。 3. 识别可能的受众和时机(作者、审阅者、QA、发布)如果不明确。 4. 检测缺失的维度:范围广度、深度/严谨性、风险重点、排除边界、可测量的验收标准。 5. 从这些原型中制定问题:
- 范围细化(例如,"这应该包括与 X 和 Y 的集成接触点还是仅限于本地模块正确性?")
- 风险优先级(例如,"这些潜在风险领域中哪些应该接受强制门控检查?")
- 深度校准(例如,"这是一个轻量级的预提交健全性列表还是正式的发布门?")
- 受众框架(例如,"这将仅由作者使用还是在 PR 审阅期间由同行使用?")
- 边界排除(例如,"我们应该明确排除本轮的性能调优项目吗?")
- 场景类别差距(例如,"未检测到恢复流程——回滚/部分故障路径是否在范围内?")
问题格式规则:
- 如果提供选项,生成一个紧凑的表格,列:选项 | 候选 | 重要性原因
- 限制最多 A-E 个选项;如果自由形式答案更清晰则省略表格
- 永远不要要求用户重述他们已经说过的话
- 避免推测性类别(无幻觉)。如果不确定,明确询问:"确认 X 是否在范围内。"
无法交互时的默认值:
- 深度:标准
- 受众:如果与代码相关则为审阅者(PR);否则为作者
- 关注:前 2 个相关性聚类
输出问题(标记 Q1/Q2/Q3)。回答后:如果≥2 个场景类别(替代/异常/恢复/非功能性领域)仍不清楚,您可以要求最多两个更有针对性的后续问题(Q4/Q5),每个问题附带一行理由(例如,"未解决的恢复路径风险")。不要超过五个总问题。如果用户明确拒绝更多问题则跳过升级。
3. 理解用户请求:结合 $ARGUMENTS + 澄清答案:
- 推导检查表主题(例如,安全、审阅、部署、用户体验)
- 整合用户提到的明确必备项目
- 将焦点选择映射到类别脚手架
- 从规格/计划/任务中推断任何缺失的上下文(不要幻觉)
4. 加载功能上下文:从 FEATURE_DIR 读取:
- spec.md:功能要求和范围
- plan.md(如果存在):技术细节、依赖关系
- tasks.md(如果存在):实施任务
上下文加载策略:
- 仅加载与活跃关注领域相关的必要部分(避免完整文件转储)
- 更喜欢将长段落总结为简洁的场景/要求要点
- 使用渐进式披露:仅在检测到差距时添加后续检索
- 如果源文档很大,生成中间摘要项目而不是嵌入原始文本
5. 生成检查表 - 创建"要求的单元测试":
- 如果不存在则创建
FEATURE_DIR/checklists/目录 - 生成唯一的检查表文件名:
- 使用基于领域的简短描述性名称(例如,
ux.md,api.md,security.md) - 格式:
[domain].md - 如果文件存在,则追加到现有文件
- 从 CHK001 开始顺序编号项目
- 每个
/speckit.checklist运行创建一个新文件(从不覆盖现有检查表)
核心原则 - 测试要求,而不是实现: 每个检查表项目必须评估要求本身:
- 完整性:所有必要的要求是否存在?
- 清晰度:要求是否明确且具体?
- 一致性:要求是否相互对齐?
- 可测量性:要求是否可以客观验证?
- 覆盖范围:是否解决了所有场景/边缘情况?
类别结构 - 按要求质量维度分组项目:
- 要求完整性(是否记录了所有必要的要求?)
- 要求清晰度(要求是否具体且明确?)
- 要求一致性(要求是否对齐而无冲突?)
- 验收标准质量(成功标准是否可测量?)
- 场景覆盖(是否解决了所有流程/案例?)
- 边缘情况覆盖(是否定义了边界条件?)
- 非功能性要求(性能、安全性、可访问性等 - 是否指定?)
- 依赖关系和假设(是否记录和验证?)
- 歧义和冲突(需要澄清什么?)
如何编写检查表项目 - "英语的单元测试":
❌ 错误(测试实现):
- "验证着陆页显示 3 个剧集卡片"
- "测试桌面端悬停状态是否有效"
- "确认徽标点击导航到主页"
✅ 正确(测试要求质量):
- "是否明确指定了特色剧集的确切数量和布局?" [完整性]
- "是否用特定的尺寸/定位量化了'显著显示'?" [清晰度]
- "所有交互元素的悬停状态要求是否一致?" [一致性]
- "是否为所有交互式 UI 定义了键盘导航要求?" [覆盖范围]
- "当徽标图像加载失败时是否指定了回退行为?" [边缘情况]
- "是否为异步剧集数据定义了加载状态?" [完整性]
- "规格是否定义了竞争 UI 元素的视觉层次?" [清晰度]
项目结构: 每个项目应遵循此模式:
- 询问要求质量的问题格式
- 关注规格/计划中编写(或未编写)的内容
- 包括质量维度在括号中 [完整性/清晰度/一致性等]
- 检查现有要求时引用规格部分
[Spec §X.Y] - 使用
[Gap]标记检查缺失的要求
按质量维度的示例:
完整性:
- "是否为所有 API 故障模式定义了错误处理要求? [Gap]"
- "是否为所有交互元素指定了可访问性要求? [完整性]"
- "是否为响应式布局定义了移动断点要求? [Gap]"
清晰度:
- "是否用特定的时间阈值量化了'快速加载'? [清晰度, Spec §NFR-2]"
- "是否明确定义了'相关剧集'的选择标准? [清晰度, Spec §FR-5]"
- "是否用可测量的视觉属性定义了'显著'? [歧义, Spec §FR-4]"
一致性:
- "所有页面的导航要求是否对齐? [一致性, Spec §FR-10]"
- "着陆页和详情页的卡片组件要求是否一致? [一致性]"
覆盖范围:
- "是否为零状态场景(无剧集)定义了要求? [覆盖范围, 边缘情况]"
- "是否解决了并发用户交互场景? [覆盖范围, Gap]"
- "是否为部分数据加载失败指定了要求? [覆盖范围, 异常流程]"
可测量性:
- "视觉层次要求是否可测量/可测试? [验收标准, Spec §FR-1]"
- "是否可以客观验证'平衡的视觉权重'? [可测量性, Spec §FR-2]"
场景分类和覆盖(要求质量重点):
- 检查是否存在要求:主要、替代、异常/错误、恢复、非功能性场景
- 对于每个场景类别,询问:"[场景类型] 要求是否完整、清晰且一致?"
- 如果场景类别缺失:"[场景类型] 要求是故意排除还是缺失? [Gap]"
- 包括状态变更时的弹性/回滚:"是否为迁移失败定义了回滚要求? [Gap]"
可追溯性要求:
- 最低要求:≥80% 的项目必须至少包含一个可追溯性引用
- 每个项目应引用:规格部分
[Spec §X.Y],或使用标记:[Gap]、[Ambiguity]、[Conflict]、[Assumption] - 如果不存在 ID 系统:"是否建立了要求和验收标准 ID 方案? [可追溯性]"
表面和解决问题(要求质量问题): 询问有关要求本身的问题:
- 歧义:"'快速' 一词是否用具体指标量化? [歧义, Spec §NFR-1]"
- 冲突:"§FR-10 和 §FR-10a 中的导航要求是否冲突? [冲突]"
- 假设:"'始终可用的播客 API' 假设是否已验证? [假设]"
- 依赖关系:"是否记录了外部播客 API 要求? [依赖关系, Gap]"
- 缺失定义:"是否用可测量的标准定义了'视觉层次'? [Gap]"
内容整合:
- 软上限:如果原始候选项目 > 40,按风险/影响优先排序
- 合并检查相同要求方面的近似重复项
- 如果 >5 个低影响边缘情况,创建一个项目:"边缘情况 X、Y、Z 是否在要求中解决? [覆盖范围]"
🚫 绝对禁止 - 这些使其成为实现测试,而不是要求测试:
- ❌ 任何以"验证"、"测试"、"确认"、"检查" + 实现行为开头的项目
- ❌ 引用代码执行、用户操作、系统行为
- ❌ "正确显示"、"正常工作"、"按预期功能"
- ❌ "点击"、"导航"、"渲染"、"加载"、"执行"
- ❌ 测试用例、测试计划、QA 程序
- ❌ 实现细节(框架、API、算法)
✅ 必需模式 - 这些测试要求质量:
- ✅ "是否为 [场景] 定义/指定/记录了 [要求类型]?"
- ✅ "是否用具体标准量化/澄清了 [模糊术语]?"
- ✅ "[部分 A] 和 [部分 B] 的要求是否一致?"
- ✅ "是否可以客观测量/验证 [要求]?"
- ✅ "要求中是否解决了 [边缘情况/场景]?"
- ✅ "规格是否定义了 [缺失方面]?"
6. 结构参考:按照 templates/checklist-template.md 中的规范模板生成检查表,包括标题、元部分、类别标题和 ID 格式。如果模板不可用,使用:H1 标题、目的/创建的元行、包含 - [ ] CHK### <要求项目> 行的 ## 类别部分,全局递增 ID 从 CHK001 开始。
7. 报告:输出创建的检查表的完整路径、项目计数,并提醒用户每次运行都会创建一个新文件。总结:
- 选择的关注领域
- 深度级别
- 参与者/时机
- 任何包含的用户明确指定的必备项目
重要:每个 /speckit.checklist 命令调用都使用简短的描述性名称创建检查表文件,除非文件已存在。这允许:
- 不同类型的多个检查表(例如,
ux.md,test.md,security.md) - 简单、易记的文件名,指示检查表目的
- 在
checklists/文件夹中轻松识别和导航
为避免混乱,使用描述性类型并在完成后清理过时的检查表。
示例检查表类型和示例项目
用户体验要求质量: ux.md
示例项目(测试要求,而不是实现):
- "是否用可测量的标准定义了视觉层次要求? [清晰度, Spec §FR-1]"
- "是否明确定义了 UI 元素的数量和定位? [完整性, Spec §FR-1]"
- "交互状态要求(悬停、焦点、活动)是否一致定义? [一致性]"
- "是否为所有交互元素指定了可访问性要求? [覆盖范围, Gap]"
- "图像加载失败时是否定义了回退行为? [边缘情况, Gap]"
- "是否可以客观测量'显著显示'? [可测量性, Spec §FR-4]"
API 要求质量: api.md
示例项目:
- "是否为所有故障场景指定了错误响应格式? [完整性]"
- "是否用具体阈值量化了速率限制要求? [清晰度]"
- "所有端点的身份验证要求是否一致? [一致性]"
- "是否为外部依赖关系定义了重试/超时要求? [覆盖范围, Gap]"
- "版本控制策略是否在要求中记录? [Gap]"
性能要求质量: performance.md
示例项目:
- "是否用具体指标量化了性能要求? [清晰度]"
- "是否为所有关键用户旅程定义了性能目标? [覆盖范围]"
- "是否为不同负载条件指定了性能要求? [完整性]"
- "是否可以客观测量性能要求? [可测量性]"
- "是否为高负载场景定义了降级要求? [边缘情况, Gap]"
安全要求质量: security.md
示例项目:
- "是否为所有受保护资源指定了身份验证要求? [覆盖范围]"
- "是否为敏感信息定义了数据保护要求? [完整性]"
- "威胁模型是否记录并与要求对齐? [可追溯性]"
- "安全要求是否与合规义务一致? [一致性]"
- "是否定义了安全故障/违规响应要求? [Gap, 异常流程]"
反例:不要做的事情
❌ 错误 - 这些测试实现,而不是要求:
- [ ] CHK001 - 验证着陆页显示 3 个剧集卡片 [Spec §FR-001]
- [ ] CHK002 - 测试桌面端悬停状态是否正确工作 [Spec §FR-003]
- [ ] CHK003 - 确认徽标点击导航到主页 [Spec §FR-010]
- [ ] CHK004 - 检查相关剧集部分显示 3-5 个项目 [Spec §FR-005]✅ 正确 - 这些测试要求质量:
- [ ] CHK001 - 是否明确定义了特色剧集的数量和布局? [完整性, Spec §FR-001]
- [ ] CHK002 - 是否为所有交互元素一致定义了悬停状态要求? [一致性, Spec §FR-003]
- [ ] CHK003 - 是否为所有可点击品牌元素明确了导航要求? [清晰度, Spec §FR-010]
- [ ] CHK004 - 是否记录了相关剧集的选择标准? [Gap, Spec §FR-005]
- [ ] CHK005 - 是否为异步剧集数据定义了加载状态要求? [Gap]
- [ ] CHK006 - 是否可以客观测量"视觉层次"要求? [可测量性, Spec §FR-001]主要区别:
- 错误:测试系统是否正常工作
- 正确:测试要求是否编写正确
- 错误:行为验证
- 正确:要求质量验证
- 错误:"它是否做 X?"
- 正确:"X 是否明确定义?"
用户输入
$ARGUMENTS您必须在继续之前考虑用户输入(如果不为空)。
大纲
您正在更新位于 /memory/constitution.md 的项目宪章。此文件是一个模板,包含方括号中的占位符标记(例如 [PROJECT_NAME], [PRINCIPLE_1_NAME])。您的工作是 (a) 收集/推导具体值,(b) 精确填充模板,以及 (c) 传播任何修订到依赖工件。
遵循此执行流程:
1. 加载位于 /memory/constitution.md 的现有宪章模板。
- 识别形式为
[ALL_CAPS_IDENTIFIER]的每个占位符标记。
重要:用户可能需要比模板中使用的更少或更多的原则。如果指定了数量,请尊重 - 遵循通用模板。您将相应地更新文档。
2. 收集/推导占位符的值:
- 如果用户输入(对话)提供了值,则使用它。
- 否则从现有仓库上下文(README、文档、先前的宪章版本(如果嵌入))推断。
- 对于治理日期:
RATIFICATION_DATE是原始采用日期(如果未知则询问或标记 TODO),LAST_AMENDED_DATE是今天如果进行了更改,否则保持先前日期。 CONSTITUTION_VERSION必须根据语义版本规则递增:- MAJOR:向后不兼容的治理/原则删除或重新定义。
- MINOR:添加新原则/部分或实质性扩展指导。
- PATCH:澄清、措辞、拼写错误修复、非语义性改进。
- 如果版本提升类型不明确,在最终确定前提出理由。
3. 起草更新的宪章内容:
- 用具体文本替换每个占位符(除了项目选择尚未定义的故意保留的模板槽位 - 明确说明任何保留的槽位)。
- 保持标题层次结构,注释可以在替换后删除,除非它们仍然提供澄清指导。
- 确保每个原则部分:简洁的名称行,段落(或项目符号列表)捕获不可协商的规则,如果不是显而易见则提供明确的理由。
- 确保治理部分列出修订程序、版本政策和合规性审查期望。
4. 一致性传播检查表(将先前的检查表转换为积极验证):
- 读取
/templates/plan-template.md并确保任何"宪章检查"或规则与更新的原则对齐。 - 读取
/templates/spec-template.md以对齐范围/要求 - 如果宪章添加/删除了强制性部分或约束则更新。 - 读取
/templates/tasks-template.md并确保任务分类反映新增或删除的原则驱动任务类型(例如,可观察性、版本控制、测试纪律)。 - 读取
/templates/commands/*.md中的每个命令文件(包括此文件)以验证没有过时的引用(仅当需要通用指导时保留特定代理名称如 CLAUDE)。 - 读取任何运行时指导文档(例如,
README.md,docs/quickstart.md,或特定代理指导文件(如果存在))。更新对更改原则的引用。
5. 生成同步影响报告(在更新后作为 HTML 注释预置在宪章文件顶部):
- 版本变更:旧 → 新
- 修改的原则列表(旧标题 → 新标题如果重命名)
- 添加的部分
- 删除的部分
- 需要更新的模板(✅ 已更新 / ⚠ 待处理)及文件路径
- 如果有任何占位符故意推迟则列出。
6. 最终输出前的验证:
- 没有剩余的未解释括号标记。
- 版本行与报告匹配。
- 日期为 ISO 格式 YYYY-MM-DD。
- 原则是陈述性的、可测试的,并且没有模糊语言("应该" → 在适当时替换为 MUST/SHOULD 理由)。
7. 将完成的宪章写回 /memory/constitution.md(覆盖)。
8. 向用户输出最终摘要:
- 新版本和提升理由。
- 任何标记为手动跟进的文件。
- 建议的提交消息(例如,
docs: 修订宪章至 vX.Y.Z(原则添加 + 治理更新))。
格式和样式要求:
- 完全按照模板中的 Markdown 标题使用(不要降级/升级级别)。
- 包装长理由行以保持可读性(理想情况下 <100 个字符),但不要用尴尬的断行强制执行。
- 在部分之间保持单个空行。
- 避免尾随空格。
如果用户提供部分更新(例如,仅一个原则修订),仍执行验证和版本决策步骤。
如果关键信息缺失(例如,批准日期真正未知),插入 TODO(<FIELD_NAME>): explanation 并在同步影响报告的推迟项目下列出。
不要创建新模板;始终在现有的 /memory/constitution.md 文件上操作。
Related skills
FAQ
What language does speckit-specify-zh output?
speckit-specify-zh produces structured Chinese-language product specifications using Speckit. Developers use it to clarify requirements, acceptance criteria, and scope before implementation and engineering handoff.
When should teams run speckit-specify-zh?
speckit-specify-zh runs during feature kickoff when requirements are still informal. The skill creates a formal Chinese spec with testable acceptance criteria so later implementation steps follow spec-driven development.