
Speckit Constitution Zh
- 271 installs
- 9 repo stars
- Updated November 28, 2025
- forztf/open-skilled-sdd
Author a Chinese project constitution for SpecKit SDD repos defining non-negotiable principles, constraints, and agent guardrails that govern every later specification and implementation decision.
About
speckit-constitution-zh creates and maintains a Chinese-language project constitution for forztf/open-skilled-sdd SpecKit workflows, defining governing principles, constraints, and agent rules that all subsequent specifications and implementations must follow consistently.
- Project governance charter
- Agent behavior guardrails
- Chinese SDD foundation
- Non-negotiable principles
- Upstream spec alignment
Speckit Constitution Zh by the numbers
- 271 all-time installs (skills.sh)
- Ranked #936 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-constitution-zhAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 271 |
|---|---|
| repo stars | ★ 9 |
| Last updated | November 28, 2025 |
| Repository | forztf/open-skilled-sdd ↗ |
What it does
Author a Chinese project constitution for SpecKit SDD repos defining non-negotiable principles, constraints, and agent guardrails that govern every later specification and implementation decision.
Files
用户输入
$ARGUMENTS在继续之前,您必须考虑用户输入(如果不为空)。
大纲
您正在更新位于 .specify/memory/constitution.md 的项目章程。此文件源自一个模板assets/constitution-template.md,包含方括号中的占位符令牌(例如 [项目名称]、[原则_1_名称])。您的工作是:(a) 收集/推导具体值,(b) 精确填充模板,以及 (c) 在依赖工件中传播任何修订。
遵循此执行流程:
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. 加载位于相对仓库根目录 .specify/memory/constitution.md 的现有章程模板。
- 识别形式为
[ALL_CAPS_IDENTIFIER]的每个占位符令牌。
重要:用户可能需要比模板中使用的更少或更多的原则。如果指定了数量,请遵守该数量 - 遵循通用模板。您将相应地更新文档。
3. 收集/推导占位符的值:
- 如果用户输入(对话)提供了值,则使用它。
- 否则从现有仓库上下文推断(README、文档、嵌入的先前章程版本)。
- 对于治理日期:
批准日期是原始采用日期(如果未知则询问或标记 TODO),如果有更改则最后修订日期是今天,否则保持之前的日期。 章程版本必须根据语义版本规则递增:- 主版本:向后不兼容的治理/原则删除或重新定义。
- 次版本:添加新原则/章节或实质性扩展指导。
- 补丁:澄清、措辞、拼写错误修复、非语义性优化。
- 如果版本升级类型不明确,在最终确定前提出理由。
4. 起草更新的章程内容:
- 用具体文本替换每个占位符(除了项目选择尚未定义而有意保留的模板槽位——明确说明任何剩余的占位符)。
- 保留标题层次结构,一旦替换可以移除注释,除非它们仍然提供澄清指导。
- 确保每个原则部分:简洁的名称行,段落(或项目符号列表)捕捉不可协商的规则,如果不是显而易见则提供明确的理由。
- 确保治理部分列出修订程序、版本策略和合规审查期望。
5. 一致性传播检查清单(将先前检查清单转换为积极验证):
- 读取
.specify/templates/plan-template.md并确保任何"章程检查"或规则与更新的原则一致。 - 读取
.specify/templates/spec-template.md以对齐范围/要求——如果章程添加/删除强制性章节或约束则更新。 - 读取
.specify/templates/tasks-template.md并确保任务分类反映新增或删除的原则驱动任务类型(例如,可观察性、版本控制、测试纪律)。 - 读取任何运行时指导文档(例如
README.md、docs/quickstart.md或存在的特定代理指导文件)。更新对已更改原则的引用。
6. 生成同步影响报告(在更新后作为 HTML 注释预置在章程文件顶部):
- 版本变更:旧 → 新
- 修改的原则列表(旧标题 → 新标题如果重命名)
- 新增章节
- 删除章节
- 需要更新的模板(✅ 已更新 / ⚠ 待处理)及文件路径
- 如果有任何占位符被故意推迟,则列出后续待办事项。
7. 最终输出前的验证:
- 没有剩余未解释的括号令牌。
- 版本行与报告匹配。
- 日期为 ISO 格式 YYYY-MM-DD。
- 原则是陈述性的、可测试的,并且没有模糊语言("应该" → 在适当地方替换为 MUST/SHOULD 理由)。
8. 将完成的章程写回到 .specify/memory/constitution.md(覆盖)。
9. 向用户输出最终摘要:
- 新版本和升级理由。
- 任何标记为手动跟进的文件。
- 建议的提交消息(例如,
docs: 修订章程至 vX.Y.Z(原则添加 + 治理更新))。
格式化和样式要求:
- 完全按照模板中的 Markdown 标题使用(不要降级/升级级别)。
- 包装长理由行以保持可读性(理想情况下 <100 个字符),但不要用生硬的断行强制执行。
- 在章节之间保持单个空行。
- 避免尾随空白。
如果用户提供部分更新(例如,仅修订一个原则),仍需执行验证和版本决策步骤。
如果关键信息缺失(例如,批准日期确实未知),插入 TODO(<FIELD_NAME>): explanation 并在同步影响报告的延期项目下包含。
不要创建新模板;始终在现有的 .specify/memory/constitution.md 文件上操作。
[项目名称] 章程
<!-- 示例:规范章程,任务流章程等 -->
核心原则
[原则_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您必须在继续之前考虑用户输入(如果不为空)。
大纲
目标:检测并减少活动功能规格中的歧义或缺失决策点,并将澄清直接记录在规格文件中。
注意:此澄清工作流程预计在调用 /speckit.plan 之前运行(并完成)。如果用户明确表示他们正在跳过澄清(例如,探索性刺探),您可以继续,但必须警告下游返工风险会增加。
执行步骤:
1. 从仓库根目录运行一次 {SCRIPT}(组合 --json --paths-only 模式 / -Json -PathsOnly)。解析最小 JSON 负载字段:
FEATURE_DIRFEATURE_SPEC- (可选捕获
IMPL_PLAN,TASKS用于未来的链式流程。) - 如果 JSON 解析失败,则中止并指示用户重新运行
/speckit.specify或验证功能分支环境。 - 对于参数中的单引号,如 "I'm Groot",使用转义语法:例如 'I'\''m Groot'(或者如果可能的话使用双引号:"I'm Groot")。
2. 加载当前规格文件。使用此分类法执行结构化歧义和覆盖扫描。对于每个类别,标记状态:清晰 / 部分 / 缺失。生成用于优先级排序的内部覆盖图(除非不问问题,否则不要输出原始图)。
功能范围和行为:
- 核心用户目标和成功标准
- 明确的范围外声明
- 用户角色 / 人物区分
领域和数据模型:
- 实体、属性、关系
- 身份和唯一性规则
- 生命周期/状态转换
- 数据量 / 规模假设
交互和用户体验流程:
- 关键用户旅程 / 序列
- 错误/空/加载状态
- 可访问性或本地化注释
非功能性质量属性:
- 性能(延迟、吞吐量目标)
- 可扩展性(水平/垂直、限制)
- 可靠性和可用性(正常运行时间、恢复期望)
- 可观察性(日志、指标、跟踪信号)
- 安全性和隐私(认证/授权、数据保护、威胁假设)
- 合规性 / 监管约束(如果有)
集成和外部依赖:
- 外部服务/API 和故障模式
- 数据导入/导出格式
- 协议/版本假设
边缘情况和故障处理:
- 负面场景
- 速率限制 / 节流
- 冲突解决(例如,并发编辑)
约束和权衡:
- 技术约束(语言、存储、托管)
- 明确的权衡或被拒绝的替代方案
术语和一致性:
- 规范术语表
- 避免的同义词 / 废弃术语
完成信号:
- 验收标准可测试性
- 可测量的完成定义风格指标
杂项 / 占位符:
- TODO 标记 / 未解决的决策
- 缺乏量化的模糊形容词("健壮的"、"直观的")
对于状态为部分或缺失的每个类别,添加一个候选问题机会,除非:
- 澄清不会实质性改变实施或验证策略
- 信息最好推迟到规划阶段(内部记录)
3. 生成(内部)优先级候选澄清问题队列(最多 5 个)。不要一次性输出所有问题。应用这些约束:
- 整个会话最多 10 个问题。
- 每个问题必须可以通过以下方式回答:
- 短的多项选择(2-5 个不同的、互斥的选项),或
- 一个单词 / 短语答案(明确约束:"答案 <=5 个单词")。
- 仅包括其答案实质性影响架构、数据建模、任务分解、测试设计、用户体验行为、运营准备或合规性验证的问题。
- 确保类别覆盖平衡:尝试首先覆盖最高影响的未解决类别;避免在单个高影响领域(例如,安全态势)未解决时问两个低影响问题。
- 排除已经回答的问题、琐碎的风格偏好或计划级执行细节(除非阻塞正确性)。
- 优先考虑减少下游返工风险或防止不一致验收测试的澄清。
- 如果超过 5 个类别仍未解决,按(影响 * 不确定性)启发式选择前 5 个。
4. 顺序提问循环(交互式):
- 一次只提出一个问题。
- 对于多项选择问题:
- 分析所有选项并根据以下确定最合适的选项:
- 项目类型的最佳实践
- 类似实现中的常见模式
- 风险降低(安全性、性能、可维护性)
- 与规格中可见的任何明确项目目标或约束对齐
- 突出显示您的推荐选项在顶部,并提供明确的理由(1-2 句解释为什么这是最佳选择)。
- 格式为:
**推荐:** 选项 [X] - <理由> - 然后将所有选项呈现为 Markdown 表格:
| 选项 | 描述 |
|---|---|
| A | <选项 A 描述> |
| B | <选项 B 描述> |
| C | <选项 C 描述>(根据需要添加 D/E 至多 5 个) |
| 简短 | 提供不同的简短答案(<=5 个单词)(仅在自由形式替代方案适当时包含) |
- 表格后添加:
您可以回复选项字母(例如,"A"),通过说"yes"或"recommended"接受推荐,或提供您自己的简短答案。 - 对于简短答案风格(无有意义的离散选项):
- 提供您的建议答案基于最佳实践和上下文。
- 格式为:
**建议:** <您的建议答案> - <简要理由> - 然后输出:
格式:简短答案(<=5 个单词)。您可以通过说"yes"或"suggested"接受建议,或提供您自己的答案。 - 用户回答后:
- 如果用户回复"yes"、"recommended"或"suggested",使用您之前声明的推荐/建议作为答案。
- 否则,验证答案映射到一个选项或符合 <=5 个单词的约束。
- 如果模糊,要求快速澄清(计数仍属于同一问题;不要前进)。
- 一旦满意,将其记录在工作内存中(尚不写入磁盘)并移至下一个排队问题。
- 停止进一步提问当:
- 所有关键歧义提前解决(剩余排队项目变得不必要),或
- 用户发出完成信号("done"、"good"、"no more"),或
- 您达到 5 个已问问题。
- 永远不要提前透露未来排队的问题。
- 如果开始时没有有效问题,立即报告没有关键歧义。
5. 每个接受答案后的集成(增量更新方法):
- 维护规格的内存表示(启动时加载一次)加上原始文件内容。
- 对于此会话中的第一个集成答案:
- 确保存在
## Clarifications部分(如果缺失,则在规格模板中最高级上下文/概述部分之后创建)。 - 在其下创建(如果不存在)一个
### Session YYYY-MM-DD子标题用于今天。 - 接受后立即追加一个项目符号行:
- Q: <问题> → A: <最终答案>。 - 然后立即将澄清应用到最合适的部分:
- 功能歧义 → 更新或在功能要求中添加项目符号。
- 用户交互 / 行为者区分 → 更新用户故事或行为者子部分(如果存在)与澄清的角色、约束或场景。
- 数据形状 / 实体 → 更新数据模型(添加字段、类型、关系)保持排序;简洁地记录添加的约束。
- 非功能性约束 → 在非功能性 / 质量属性部分添加/修改可测量标准(将模糊形容词转换为指标或明确目标)。
- 边缘情况 / 负面流程 → 在边缘情况 / 错误处理下添加新项目符号(或创建此类子部分如果模板提供占位符)。
- 术语冲突 → 规范化整个规格中的术语;仅在必要时保留原始术语,添加
(以前称为"X")一次。 - 如果澄清使早期模糊声明无效,则替换该声明而不是重复;不留过时的矛盾文本。
- 每次集成后保存规格文件以最小化上下文丢失风险(原子覆盖)。
- 保持格式:不要重新排序无关部分;保持标题层次结构完整。
- 保持每个插入的澄清最小且可测试(避免叙述性漂移)。
6. 验证(每次写入后执行加上最终通过):
- 澄清会话包含每个接受答案的一个项目符号(无重复)。
- 总问(接受)问题 ≤ 5。
- 更新部分不包含新的答案应该解决的模糊占位符。
- 无矛盾的早期声明保留(扫描移除的无效替代选择)。
- Markdown 结构有效;仅允许新标题:
## Clarifications,### Session YYYY-MM-DD。 - 术语一致性:所有更新部分使用相同的规范术语。
7. 将更新的规格写回 FEATURE_SPEC。
8. 报告完成(提问循环结束或提前终止后):
- 问和回答的问题数量。
- 更新规格的路径。
- 触及的部分(列出名称)。
- 覆盖摘要表列出每个分类类别,状态:已解决(之前部分/缺失并已解决)、推迟(超出问题配额或更适合规划)、清晰(已足够)、未解决(仍部分/缺失但影响低)。
- 如果有任何未解决或推迟的,建议是否继续到
/speckit.plan或稍后再次运行/speckit.clarify。 - 建议的下一个命令。
行为规则:
- 如果未发现有意义的歧义(或所有潜在问题都是低影响的),回应:"未检测到值得正式澄清的关键歧义。"并建议继续。
- 如果规格文件缺失,指示用户先运行
/speckit.specify(不要在此处创建新规格)。 - 永远不要超过 5 个总问问题(澄清重试单个问题不计入新问题)。
- 避免推测性技术栈问题,除非缺失会阻塞功能清晰度。
- 尊重用户提前终止信号("stop"、"done"、"proceed")。
- 如果由于完全覆盖而未问问题,输出紧凑的覆盖摘要(所有类别清晰)然后建议前进。
- 如果配额达到但仍有未解决的高影响类别,明确标记它们为推迟并附上理由。
优先级上下文:{ARGS}
用户输入
$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 文件上操作。