feat: 添加Specify框架模板与脚本
添加Specify框架的核心模板文件,包括: - 功能规格说明模板(spec-template.md) - 实现计划模板(plan-template.md) - 任务清单模板(tasks-template.md) - 检查清单模板(checklist-template.md) - 代理文件模板(agent-file-template.md) 添加支持脚本: - 创建新功能的create-new-feature.sh - 检查前置条件的check-prerequisites.sh - 设置计划的setup-plan.sh - 通用函数库common.sh 添加Cursor命令文件: - speckit.specify - speckit.plan - speckit.tasks - speckit.checklist - speckit.clarify - speckit.implement - speckit.analyze - speckit.constitution - speckit.taskstoissues 更新项目宪法文件(constitution.md),增加治理与管理章节
Showing
20 changed files
with
3444 additions
and
1 deletions
.cursor/commands/speckit.analyze.md
0 → 100644
| 1 | +--- | ||
| 2 | +description: 在生成 tasks.md 之后,对 spec.md、plan.md、tasks.md 做一次非破坏性的跨文件一致性与质量分析。 | ||
| 3 | +--- | ||
| 4 | + | ||
| 5 | +## 用户输入 | ||
| 6 | + | ||
| 7 | +```text | ||
| 8 | +$ARGUMENTS | ||
| 9 | +``` | ||
| 10 | + | ||
| 11 | +在继续之前,你**必须**先考虑用户输入(如果不为空)。 | ||
| 12 | + | ||
| 13 | +## 目标 | ||
| 14 | + | ||
| 15 | +在开始实现之前,识别三份核心产物(`spec.md`、`plan.md`、`tasks.md`)之间的不一致、重复、歧义和说明不足之处。本命令**必须**在 `/speckit.tasks` 成功生成完整的 `tasks.md` 之后才运行。 | ||
| 16 | + | ||
| 17 | +## 运行约束 | ||
| 18 | + | ||
| 19 | +**严格只读**:**不要**修改任何文件。输出结构化的分析报告。可以提供可选的修复方案(用户必须明确同意后,才可以人工触发后续的编辑命令)。 | ||
| 20 | + | ||
| 21 | +**宪法优先级**:在本分析范围内,项目宪法(`.specify/memory/constitution.md`)**不可协商**。任何与宪法冲突的问题都自动视为 CRITICAL,并要求调整 spec/plan/tasks,而不是淡化、重新解释或悄悄忽略原则。如果确实需要修改原则本身,必须在 `/speckit.analyze` 之外,通过单独、明确的宪法更新来完成。 | ||
| 22 | + | ||
| 23 | +## 执行步骤 | ||
| 24 | + | ||
| 25 | +### 1. 初始化分析上下文 | ||
| 26 | + | ||
| 27 | +在仓库根目录只运行一次 `.specify/scripts/bash/check-prerequisites.sh --json --require-tasks --include-tasks`,并解析 JSON 中的 FEATURE_DIR 和 AVAILABLE_DOCS。推导出绝对路径: | ||
| 28 | + | ||
| 29 | +- SPEC = FEATURE_DIR/spec.md | ||
| 30 | +- PLAN = FEATURE_DIR/plan.md | ||
| 31 | +- TASKS = FEATURE_DIR/tasks.md | ||
| 32 | + | ||
| 33 | +如果缺少任何必需文件,直接报错并提示用户运行缺失的前置命令。 | ||
| 34 | +当参数里包含单引号(例如 "I'm Groot")时,使用转义写法:如 'I'\''m Groot'(或尽量用双引号:"I'm Groot")。 | ||
| 35 | + | ||
| 36 | +### 2. 加载产物(渐进式披露) | ||
| 37 | + | ||
| 38 | +每个产物只加载最少且必要的上下文: | ||
| 39 | + | ||
| 40 | +**来自 spec.md:** | ||
| 41 | + | ||
| 42 | +- 概览/背景 | ||
| 43 | +- 功能需求 | ||
| 44 | +- 非功能需求 | ||
| 45 | +- 用户故事 | ||
| 46 | +- 边界情况(如存在) | ||
| 47 | + | ||
| 48 | +**来自 plan.md:** | ||
| 49 | + | ||
| 50 | +- 架构/技术栈选择 | ||
| 51 | +- 数据模型引用 | ||
| 52 | +- 阶段划分 | ||
| 53 | +- 技术约束 | ||
| 54 | + | ||
| 55 | +**来自 tasks.md:** | ||
| 56 | + | ||
| 57 | +- 任务 ID | ||
| 58 | +- 任务描述 | ||
| 59 | +- 阶段分组 | ||
| 60 | +- 并行标记 [P] | ||
| 61 | +- 引用到的文件路径 | ||
| 62 | + | ||
| 63 | +**来自宪法:** | ||
| 64 | + | ||
| 65 | +- 加载 `.specify/memory/constitution.md` 用于原则校验 | ||
| 66 | + | ||
| 67 | +### 3. 构建语义模型 | ||
| 68 | + | ||
| 69 | +创建内部表示(输出中不要包含原始产物全文): | ||
| 70 | + | ||
| 71 | +- **需求清单**:每条功能/非功能需求都要有稳定的 key(根据祈使句短语生成 slug;例如 “用户可以上传文件” → `user-can-upload-file`) | ||
| 72 | +- **用户故事/动作清单**:离散的用户动作及其验收标准 | ||
| 73 | +- **任务覆盖映射**:将每个任务映射到一个或多个需求/故事(通过关键字推断或显式引用模式,比如 ID、关键短语) | ||
| 74 | +- **宪法规则集**:提取原则名称以及 MUST/SHOULD 等规范性语句 | ||
| 75 | + | ||
| 76 | +### 4. 检测遍历(Token 高效分析) | ||
| 77 | + | ||
| 78 | +聚焦高信号问题。总计最多输出 50 条发现;其余聚合到溢出摘要中。 | ||
| 79 | + | ||
| 80 | +#### A. 重复检测 | ||
| 81 | + | ||
| 82 | +- 识别近似重复的需求 | ||
| 83 | +- 标记表达质量较低的需求,建议合并收敛表述 | ||
| 84 | + | ||
| 85 | +#### B. 歧义检测 | ||
| 86 | + | ||
| 87 | +- 标记缺少可度量标准的模糊形容词(fast、scalable、secure、intuitive、robust 等) | ||
| 88 | +- 标记未解决的占位符(TODO、TKTK、???、`<placeholder>` 等) | ||
| 89 | + | ||
| 90 | +#### C. 说明不足 | ||
| 91 | + | ||
| 92 | +- 有动词但缺少对象或可度量结果的需求 | ||
| 93 | +- 用户故事缺少与验收标准对齐的说明 | ||
| 94 | +- 任务引用了 spec/plan 中未定义的文件或组件 | ||
| 95 | + | ||
| 96 | +#### D. 宪法对齐 | ||
| 97 | + | ||
| 98 | +- 任意与宪法 MUST 原则冲突的需求或计划要素 | ||
| 99 | +- 缺失宪法要求的必填章节或质量门禁 | ||
| 100 | + | ||
| 101 | +#### E. 覆盖缺口 | ||
| 102 | + | ||
| 103 | +- 没有关联任务的需求 | ||
| 104 | +- 未映射到任何需求/故事的任务 | ||
| 105 | +- 未在任务中体现的非功能需求(例如性能、安全) | ||
| 106 | + | ||
| 107 | +#### F. 不一致 | ||
| 108 | + | ||
| 109 | +- 术语漂移(同一概念在不同文件中用不同名称) | ||
| 110 | +- plan 引用的数据实体在 spec 中缺失(或反之) | ||
| 111 | +- 任务顺序自相矛盾(例如未注明依赖却在基础初始化之前安排集成任务) | ||
| 112 | +- 需求相互冲突(例如一处要求 Next.js,另一处指定 Vue) | ||
| 113 | + | ||
| 114 | +### 5. 严重级别判定 | ||
| 115 | + | ||
| 116 | +使用以下启发式规则为发现排序: | ||
| 117 | + | ||
| 118 | +- **CRITICAL**: 违反宪法 MUST、缺失核心产物,或关键需求零覆盖导致基线功能无法实现 | ||
| 119 | +- **HIGH**: 重复或冲突的需求、含糊的安全/性能属性、不可测试的验收标准 | ||
| 120 | +- **MEDIUM**: 术语漂移、缺少非功能需求的任务覆盖、边界情况说明不足 | ||
| 121 | +- **LOW**: 风格/措辞改进、不影响执行顺序的轻微冗余 | ||
| 122 | + | ||
| 123 | +### 6. 产出精简分析报告 | ||
| 124 | + | ||
| 125 | +按以下结构输出 Markdown 报告(不写文件): | ||
| 126 | + | ||
| 127 | +## 规格说明分析报告 | ||
| 128 | + | ||
| 129 | +| ID | 类别 | 严重级别 | 位置 | 摘要 | 建议 | | ||
| 130 | +|----|----------|----------|-------------|---------|----------------| | ||
| 131 | +| A1 | 重复 | HIGH | spec.md:L120-134 | 两条相似需求…… | 合并表述;保留更清晰的版本 | | ||
| 132 | + | ||
| 133 | +(每条发现一行;生成稳定 ID,并以类别首字母作为前缀。) | ||
| 134 | + | ||
| 135 | +**覆盖摘要表:** | ||
| 136 | + | ||
| 137 | +| 需求 Key | 是否有任务 | 任务 ID | 备注 | | ||
| 138 | +|-----------------|-----------|----------|-------| | ||
| 139 | + | ||
| 140 | +**宪法对齐问题:**(如有) | ||
| 141 | + | ||
| 142 | +**未映射任务:**(如有) | ||
| 143 | + | ||
| 144 | +**指标:** | ||
| 145 | + | ||
| 146 | +- 需求总数 | ||
| 147 | +- 任务总数 | ||
| 148 | +- 覆盖率(至少关联 1 个任务的需求占比) | ||
| 149 | +- 歧义数量 | ||
| 150 | +- 重复数量 | ||
| 151 | +- 严重问题数量 | ||
| 152 | + | ||
| 153 | +### 7. 给出下一步动作 | ||
| 154 | + | ||
| 155 | +在报告末尾输出精炼的“下一步动作”区块: | ||
| 156 | + | ||
| 157 | +- 若存在 CRITICAL:建议在进入 `/speckit.implement` 之前先解决 | ||
| 158 | +- 若只有 LOW/MEDIUM:可继续推进,但应给出改进建议 | ||
| 159 | +- 给出明确命令建议:例如“用更精炼描述重新运行 /speckit.specify”“运行 /speckit.plan 调整架构”“手动编辑 tasks.md 为 ‘performance-metrics’ 补齐覆盖” | ||
| 160 | + | ||
| 161 | +### 8. 提供修复建议 | ||
| 162 | + | ||
| 163 | +询问用户:“你希望我为前 N 个问题给出具体的修复编辑建议吗?”(不要自动应用。) | ||
| 164 | + | ||
| 165 | +## 运行原则 | ||
| 166 | + | ||
| 167 | +### 上下文效率 | ||
| 168 | + | ||
| 169 | +- **最小高信号 token**:聚焦可执行发现,不做穷举式文档复述 | ||
| 170 | +- **渐进式披露**:逐步加载产物,不把全文倾倒进分析 | ||
| 171 | +- **token 高效输出**:发现表最多 50 行,其余做溢出摘要 | ||
| 172 | +- **确定性结果**:在无变更前提下重复运行应得到一致的 ID 与统计 | ||
| 173 | + | ||
| 174 | +### 分析准则 | ||
| 175 | + | ||
| 176 | +- **禁止修改文件**(严格只读分析) | ||
| 177 | +- **禁止臆造缺失章节**(缺失就如实报告) | ||
| 178 | +- **优先处理宪法违规则**(一律 CRITICAL) | ||
| 179 | +- **用例子胜过穷举规则**(引用具体实例而不是泛泛而谈) | ||
| 180 | +- **零问题也要输出报告**(给出覆盖统计并明确通过) | ||
| 181 | + | ||
| 182 | +## 上下文 | ||
| 183 | + | ||
| 184 | +$ARGUMENTS |
.cursor/commands/speckit.checklist.md
0 → 100644
| 1 | +--- | ||
| 2 | +description: 基于用户需求为当前功能生成一份定制检查清单。 | ||
| 3 | +--- | ||
| 4 | + | ||
| 5 | +## 清单目的:“英文需求的单元测试” | ||
| 6 | + | ||
| 7 | +**关键概念(CRITICAL)**:检查清单是**需求写作的单元测试**——用于验证某个领域里需求的质量、清晰度与完整性。 | ||
| 8 | + | ||
| 9 | +**不是用来做验证/测试**: | ||
| 10 | + | ||
| 11 | +- ❌ 不是“验证按钮能否正确点击” | ||
| 12 | +- ❌ 不是“测试错误处理是否有效” | ||
| 13 | +- ❌ 不是“确认 API 返回 200” | ||
| 14 | +- ❌ 不是检查代码/实现是否符合 spec | ||
| 15 | + | ||
| 16 | +**用于验证需求质量**: | ||
| 17 | + | ||
| 18 | +- ✅ “是否为所有卡片类型定义了视觉层级需求?”(完整性) | ||
| 19 | +- ✅ “‘突出展示’是否用具体的尺寸/位置进行了量化?”(清晰度) | ||
| 20 | +- ✅ “悬停态需求在所有可交互元素之间是否一致?”(一致性) | ||
| 21 | +- ✅ “是否定义了键盘导航的无障碍需求?”(覆盖度) | ||
| 22 | +- ✅ “spec 是否定义了当 logo 图片加载失败时的行为?”(边界情况) | ||
| 23 | + | ||
| 24 | +**类比**:如果把 spec 看作用英文写成的代码,那么检查清单就是它的单元测试套件。你要测试的是需求是否写得好、是否完整、是否无歧义、是否能直接进入实现阶段——而不是测试实现是否正确。 | ||
| 25 | + | ||
| 26 | +## 用户输入 | ||
| 27 | + | ||
| 28 | +```text | ||
| 29 | +$ARGUMENTS | ||
| 30 | +``` | ||
| 31 | + | ||
| 32 | +在继续之前,你**必须**先考虑用户输入(如果不为空)。 | ||
| 33 | + | ||
| 34 | +## 执行步骤 | ||
| 35 | + | ||
| 36 | +1. **准备**:在仓库根目录运行 `.specify/scripts/bash/check-prerequisites.sh --json`,并解析 JSON 中的 FEATURE_DIR 与 AVAILABLE_DOCS 列表。 | ||
| 37 | + - 所有文件路径必须是绝对路径。 | ||
| 38 | + - 当参数里包含单引号(例如 "I'm Groot")时,使用转义写法:如 'I'\''m Groot'(或尽量用双引号:"I'm Groot")。 | ||
| 39 | + | ||
| 40 | +2. **澄清意图(动态)**:最多生成三个初始的上下文澄清问题(不要使用预制问题库)。这些问题必须: | ||
| 41 | + - 基于用户的表述 + 从 spec/plan/tasks 中提取到的信号生成 | ||
| 42 | + - 只询问会实质性改变检查清单内容的信息 | ||
| 43 | + - 如果 `$ARGUMENTS` 已经明确,则对应问题要逐个跳过 | ||
| 44 | + - 宁可精确,不求覆盖面 | ||
| 45 | + | ||
| 46 | + 生成算法: | ||
| 47 | + 1. 提取信号:功能领域关键词(例如 auth、latency、UX、API)、风险指示词(“critical”“must”“compliance”)、干系人提示(“QA”“review”“security team”)以及显式交付物(“a11y”“rollback”“contracts”)。 | ||
| 48 | + 2. 将信号聚类为候选关注方向(最多 4 个),并按相关性排序。 | ||
| 49 | + 3. 如果未明确,推断可能的受众与使用时机(作者、审阅者、QA、发布)。 | ||
| 50 | + 4. 识别缺失维度:范围宽度、深度/严谨度、风险侧重、排除边界、可量化的验收标准。 | ||
| 51 | + 5. 从以下原型中选择并组织问题: | ||
| 52 | + - 范围细化(例如:“是否需要包含与 X、Y 的集成触点,还是仅限本地模块正确性?”) | ||
| 53 | + - 风险优先级(例如:“以下哪些潜在风险区域需要强制门禁检查?”) | ||
| 54 | + - 深度校准(例如:“这是轻量的 pre-commit 自查清单,还是正式发布门禁?”) | ||
| 55 | + - 受众框定(例如:“这份清单仅供作者使用,还是用于 PR 同行评审?”) | ||
| 56 | + - 边界排除(例如:“这一轮是否明确排除性能调优条目?”) | ||
| 57 | + - 场景类别缺口(例如:“未检测到恢复流程——回滚/部分失败路径是否在范围内?”) | ||
| 58 | + | ||
| 59 | + 问题格式规则: | ||
| 60 | + - 如果提供选项,生成紧凑表格,列为:选项 | 候选项 | 为什么重要 | ||
| 61 | + - 选项最多 A–E;如果自由回答更清晰,则不输出表格 | ||
| 62 | + - 不要让用户重复已经说过的话 | ||
| 63 | + - 避免臆测分类(不要编造)。不确定时,直接问:“请确认 X 是否属于本次范围。” | ||
| 64 | + | ||
| 65 | + 无法互动时的默认值: | ||
| 66 | + - 深度:标准 | ||
| 67 | + - 受众:若与代码相关则默认评审者(PR),否则默认作者 | ||
| 68 | + - 聚焦:相关性最高的 2 个聚类方向 | ||
| 69 | + | ||
| 70 | + 输出问题(标记为 Q1/Q2/Q3)。用户回答后:如果仍有 ≥2 类场景(Alternate / Exception / Recovery / Non-Functional domain)不清晰,你可以再追问最多 2 个更有针对性的后续问题(Q4/Q5),并为每个问题给出一行理由(例如:“恢复路径风险仍未明确”)。总问题数不要超过 5 个。若用户明确拒绝更多问题,则跳过加问。 | ||
| 71 | + | ||
| 72 | +3. **理解用户诉求**:结合 `$ARGUMENTS` 与澄清答案: | ||
| 73 | + - 提炼清单主题(例如 security、review、deploy、ux) | ||
| 74 | + - 汇总用户明确要求必须包含的条目 | ||
| 75 | + - 将关注方向映射到分类骨架 | ||
| 76 | + - 从 spec/plan/tasks 推断缺失上下文(不要编造) | ||
| 77 | + | ||
| 78 | +4. **加载功能上下文**:从 FEATURE_DIR 读取: | ||
| 79 | + - spec.md:功能需求与范围 | ||
| 80 | + - plan.md(如果存在):技术细节与依赖 | ||
| 81 | + - tasks.md(如果存在):实现任务 | ||
| 82 | + | ||
| 83 | + **上下文加载策略**: | ||
| 84 | + - 只加载与当前关注方向相关且必要的部分(避免整文件倾倒) | ||
| 85 | + - 长段落优先汇总为简短的场景/需求要点 | ||
| 86 | + - 使用渐进式披露:仅当发现缺口时再做补充读取 | ||
| 87 | + - 源文档很大时,用中间摘要条目替代直接嵌入原文 | ||
| 88 | + | ||
| 89 | +5. **生成检查清单**——创建“需求的单元测试”: | ||
| 90 | + - 若 `FEATURE_DIR/checklists/` 不存在,则创建该目录 | ||
| 91 | + - 生成唯一的检查清单文件名: | ||
| 92 | + - 根据领域使用短且有描述性的名称(例如 `ux.md`、`api.md`、`security.md`) | ||
| 93 | + - 格式:`[domain].md` | ||
| 94 | + - 如果文件已存在,则追加到现有文件 | ||
| 95 | + - 条目 ID 从 CHK001 开始顺序编号 | ||
| 96 | + - 每次运行 `/speckit.checklist` 都创建一个新文件(绝不覆盖已有清单) | ||
| 97 | + | ||
| 98 | + **核心原则:测需求,而不是测实现**: | ||
| 99 | + 每条清单项都**必须**从“需求文本本身”出发,评估: | ||
| 100 | + - **Completeness(完整性)**:是否包含所有必要需求? | ||
| 101 | + - **Clarity(清晰度)**:需求是否明确且具体、无歧义? | ||
| 102 | + - **Consistency(一致性)**:需求之间是否一致、无冲突? | ||
| 103 | + - **Measurability(可度量性)**:需求是否能客观验证? | ||
| 104 | + - **Coverage(覆盖度)**:是否覆盖所有场景/边界情况? | ||
| 105 | + | ||
| 106 | + **分类结构**——按需求质量维度分组: | ||
| 107 | + - **需求完整性**(Requirement Completeness):是否记录了所有必要需求? | ||
| 108 | + - **需求清晰度**(Requirement Clarity):需求是否具体明确、无歧义? | ||
| 109 | + - **需求一致性**(Requirement Consistency):需求是否相互对齐且无冲突? | ||
| 110 | + - **验收标准质量**(Acceptance Criteria Quality):成功标准是否可度量? | ||
| 111 | + - **场景覆盖**(Scenario Coverage):是否覆盖所有流程/用例? | ||
| 112 | + - **边界情况覆盖**(Edge Case Coverage):是否定义了边界条件? | ||
| 113 | + - **非功能需求**(Non-Functional Requirements):性能/安全/无障碍等是否被明确规定? | ||
| 114 | + - **依赖与假设**(Dependencies & Assumptions):是否记录并验证? | ||
| 115 | + - **歧义与冲突**(Ambiguities & Conflicts):哪些点需要澄清? | ||
| 116 | + | ||
| 117 | + **如何编写清单项——“英文需求的单元测试”**: | ||
| 118 | + | ||
| 119 | + ❌ **错误示例**(在测实现): | ||
| 120 | + - “验证落地页展示 3 张剧集卡片” | ||
| 121 | + - “测试桌面端悬停态是否生效” | ||
| 122 | + - “确认点击 logo 会跳回首页” | ||
| 123 | + | ||
| 124 | + ✅ **正确示例**(在测需求质量): | ||
| 125 | + - “是否明确规定了精选剧集的数量与布局?”[完整性] | ||
| 126 | + - “‘突出展示’是否用具体的尺寸/位置进行了量化?”[清晰度] | ||
| 127 | + - “悬停态需求在所有可交互元素之间是否一致?”[一致性] | ||
| 128 | + - “是否为所有可交互 UI 定义了键盘导航需求?”[覆盖度] | ||
| 129 | + - “当 logo 图片加载失败时,是否规定了兜底行为?”[边界情况] | ||
| 130 | + - “是否为异步剧集数据定义了加载态?”[完整性] | ||
| 131 | + - “spec 是否定义了存在竞争 UI 元素时的视觉层级?”[清晰度] | ||
| 132 | + | ||
| 133 | + **条目结构(结构规范)**: | ||
| 134 | + 每条清单项应遵循以下模式: | ||
| 135 | + - 用问题句式询问需求质量 | ||
| 136 | + - 聚焦 spec/plan 中“写了什么/没写什么” | ||
| 137 | + - 在方括号中标注质量维度 [Completeness/Clarity/Consistency/etc.] | ||
| 138 | + - 检查既有需求时引用 spec 章节 `[Spec §X.Y]` | ||
| 139 | + - 检查缺失需求时使用 `[Gap]` 标记 | ||
| 140 | + | ||
| 141 | + **按质量维度的示例(维度示例)**: | ||
| 142 | + | ||
| 143 | + Completeness: | ||
| 144 | + - “是否为所有 API 失败模式定义了错误处理需求?[Gap]” | ||
| 145 | + - “是否为所有可交互元素规定了无障碍需求?[Completeness]” | ||
| 146 | + - “是否为响应式布局定义了移动端断点需求?[Gap]” | ||
| 147 | + | ||
| 148 | + Clarity: | ||
| 149 | + - “‘加载很快’是否用具体时间阈值量化?[Clarity, Spec §NFR-2]” | ||
| 150 | + - “‘相关剧集’的筛选条件是否明确规定?[Clarity, Spec §FR-5]” | ||
| 151 | + - “‘突出’是否用可衡量的视觉属性定义?[Ambiguity, Spec §FR-4]” | ||
| 152 | + | ||
| 153 | + Consistency: | ||
| 154 | + - “所有页面的导航需求是否一致?[Consistency, Spec §FR-10]” | ||
| 155 | + - “落地页与详情页的卡片组件需求是否一致?[Consistency]” | ||
| 156 | + | ||
| 157 | + Coverage: | ||
| 158 | + - “是否定义了空状态场景(没有剧集)的需求?[Coverage, Edge Case]” | ||
| 159 | + - “是否覆盖了并发用户交互场景?[Coverage, Gap]” | ||
| 160 | + - “是否规定了部分数据加载失败时的需求?[Coverage, Exception Flow]” | ||
| 161 | + | ||
| 162 | + Measurability: | ||
| 163 | + - “视觉层级需求是否可测量/可验证?[Acceptance Criteria, Spec §FR-1]” | ||
| 164 | + - “‘视觉重量均衡’是否能客观验证?[Measurability, Spec §FR-2]” | ||
| 165 | + | ||
| 166 | + **场景分类与覆盖(聚焦需求质量)**: | ||
| 167 | + - 检查是否存在以下场景的需求:Primary、Alternate、Exception/Error、Recovery、Non-Functional | ||
| 168 | + - 对每个场景类别提问:“[scenario type] 的需求是否完整、清晰且一致?” | ||
| 169 | + - 若缺失某类场景:提问“[scenario type] 的需求是有意排除还是遗漏?[Gap]” | ||
| 170 | + - 当存在状态变更时,包含韧性/回滚要求:例如“是否定义了迁移失败时的回滚需求?[Gap]” | ||
| 171 | + | ||
| 172 | + **可追溯性要求(Traceability Requirements)**: | ||
| 173 | + - 最低要求:≥80% 的条目必须包含至少一个可追溯引用 | ||
| 174 | + - 每条应引用 spec 章节 `[Spec §X.Y]`,或使用标记:`[Gap]`、`[Ambiguity]`、`[Conflict]`、`[Assumption]` | ||
| 175 | + - 若没有 ID 体系:提问“是否建立了需求与验收标准的 ID 体系?[Traceability]” | ||
| 176 | + | ||
| 177 | + **暴露并推动解决问题(需求质量问题)**: | ||
| 178 | + 围绕“需求文本本身”提问: | ||
| 179 | + - 歧义:"'fast' 是否用具体指标量化?[Ambiguity, Spec §NFR-1]" | ||
| 180 | + - 冲突:“§FR-10 与 §FR-10a 的导航需求是否冲突?[Conflict]” | ||
| 181 | + - 假设:“‘播客 API 永远可用’这一假设是否得到验证?[Assumption]” | ||
| 182 | + - 依赖:“是否记录了外部播客 API 的需求?[Dependency, Gap]” | ||
| 183 | + - 定义缺失:“‘visual hierarchy’ 是否用可度量标准定义?[Gap]” | ||
| 184 | + | ||
| 185 | + **内容收敛(Content Consolidation)**: | ||
| 186 | + - 软上限:若候选条目 > 40,按风险/影响优先级筛选 | ||
| 187 | + - 合并检查同一需求点的近似重复条目 | ||
| 188 | + - 若低影响边界情况 > 5 个,合并为一条:“边界情况 X、Y、Z 是否在需求中被覆盖?[Coverage]” | ||
| 189 | + | ||
| 190 | + **🚫 绝对禁止**——这些会把它变成“实现测试”,而不是“需求测试”: | ||
| 191 | + - ❌ 任何以 “Verify/Test/Confirm/Check” 开头并描述实现行为的条目 | ||
| 192 | + - ❌ 引用代码执行、用户操作或系统行为 | ||
| 193 | + - ❌ “显示正确”“工作正常”“符合预期”等模糊描述 | ||
| 194 | + - ❌ “点击”“跳转”“渲染”“加载”“执行”等实现层动作 | ||
| 195 | + - ❌ 测试用例、测试计划、QA 流程 | ||
| 196 | + - ❌ 实现细节(框架、API、算法) | ||
| 197 | + | ||
| 198 | + **✅ 必须使用的句式**——这些才是在测需求质量: | ||
| 199 | + - ✅ “是否为 [scenario] 定义/规定/记录了 [requirement type]?” | ||
| 200 | + - ✅ “[vague term] 是否用具体标准量化/澄清?” | ||
| 201 | + - ✅ “[section A] 与 [section B] 的需求是否一致?” | ||
| 202 | + - ✅ “[requirement] 是否能客观测量/验证?” | ||
| 203 | + - ✅ “需求是否覆盖了 [edge cases/scenarios]?” | ||
| 204 | + - ✅ “spec 是否定义了 [missing aspect]?” | ||
| 205 | + | ||
| 206 | +6. **结构参考**:按 `.specify/templates/checklist-template.md` 里的权威模板生成清单(标题、元信息、分类标题、ID 格式)。如果模板不可用,使用:一级标题(H1)+ purpose/created 元信息行 + `##` 分类段落;段落内使用 `- [ ] CHK### <清单项>`,ID 从 CHK001 全局递增。 | ||
| 207 | + | ||
| 208 | +7. **报告(Report)**:输出生成的清单完整路径、条目数量,并提醒用户每次运行都会创建新文件。总结: | ||
| 209 | + - 已选择的关注方向 | ||
| 210 | + - 深度等级 | ||
| 211 | + - 角色/时机 | ||
| 212 | + - 已纳入的用户显式 must-have 条目 | ||
| 213 | + | ||
| 214 | +**重要**:每次执行 `/speckit.checklist` 都会创建一个用短且有描述性的名称命名的清单文件(如果文件已存在则追加)。这使得: | ||
| 215 | + | ||
| 216 | +- 多种类型的检查清单可并存(例如 `ux.md`、`test.md`、`security.md`) | ||
| 217 | +- 文件名简短易记,并能表达用途 | ||
| 218 | +- 在 `checklists/` 目录中易于识别与定位 | ||
| 219 | + | ||
| 220 | +为避免杂乱,请使用清晰的类型命名,并在完成后清理过时的清单文件。 | ||
| 221 | + | ||
| 222 | +## 清单类型示例与样例条目 | ||
| 223 | + | ||
| 224 | +**UX 需求质量:** `ux.md` | ||
| 225 | + | ||
| 226 | +样例条目(测需求,不测实现): | ||
| 227 | + | ||
| 228 | +- “视觉层级需求是否用可度量标准定义?[Clarity, Spec §FR-1]” | ||
| 229 | +- “UI 元素的数量与位置是否被明确规定?[Completeness, Spec §FR-1]” | ||
| 230 | +- “交互状态(hover/focus/active)的需求是否一致地被规定?[Consistency]” | ||
| 231 | +- “是否为所有可交互元素规定了无障碍需求?[Coverage, Gap]” | ||
| 232 | +- “图片加载失败时的兜底行为是否被规定?[Edge Case, Gap]” | ||
| 233 | +- “‘突出展示’是否能被客观度量?[Measurability, Spec §FR-4]” | ||
| 234 | + | ||
| 235 | +**API 需求质量:** `api.md` | ||
| 236 | + | ||
| 237 | +样例条目: | ||
| 238 | + | ||
| 239 | +- “是否为所有失败场景规定了错误响应格式?[Completeness]” | ||
| 240 | +- “限流需求是否用具体阈值量化?[Clarity]” | ||
| 241 | +- “所有端点的认证需求是否一致?[Consistency]” | ||
| 242 | +- “是否为外部依赖定义了重试/超时需求?[Coverage, Gap]” | ||
| 243 | +- “需求中是否记录了版本管理策略?[Gap]” | ||
| 244 | + | ||
| 245 | +**性能需求质量:** `performance.md` | ||
| 246 | + | ||
| 247 | +样例条目: | ||
| 248 | + | ||
| 249 | +- “性能需求是否用具体指标量化?[Clarity]” | ||
| 250 | +- “是否为所有关键用户旅程定义了性能目标?[Coverage]” | ||
| 251 | +- “是否规定了不同负载条件下的性能需求?[Completeness]” | ||
| 252 | +- “性能需求是否能被客观测量?[Measurability]” | ||
| 253 | +- “高负载场景下的降级需求是否被规定?[Edge Case, Gap]” | ||
| 254 | + | ||
| 255 | +**安全需求质量:** `security.md` | ||
| 256 | + | ||
| 257 | +样例条目: | ||
| 258 | + | ||
| 259 | +- “是否为所有受保护资源规定了认证需求?[Coverage]” | ||
| 260 | +- “是否为敏感信息定义了数据保护需求?[Completeness]” | ||
| 261 | +- “是否已文档化威胁模型,并且需求与之对齐?[Traceability]” | ||
| 262 | +- “安全需求是否与合规义务保持一致?[Consistency]” | ||
| 263 | +- “是否定义了安全失败/泄露后的响应需求?[Gap, Exception Flow]” | ||
| 264 | + | ||
| 265 | +## 反例:不要这样做 | ||
| 266 | + | ||
| 267 | +**❌ 错误——这些在测实现,不是在测需求:** | ||
| 268 | + | ||
| 269 | +```markdown | ||
| 270 | +- [ ] CHK001 - 验证落地页展示 3 张剧集卡片 [Spec §FR-001] | ||
| 271 | +- [ ] CHK002 - 测试桌面端悬停态是否正常 [Spec §FR-003] | ||
| 272 | +- [ ] CHK003 - 确认点击 logo 会跳转回首页 [Spec §FR-010] | ||
| 273 | +- [ ] CHK004 - 检查相关剧集区域是否展示 3-5 条 [Spec §FR-005] | ||
| 274 | +``` | ||
| 275 | + | ||
| 276 | +**✅ 正确——这些在测需求质量:** | ||
| 277 | + | ||
| 278 | +```markdown | ||
| 279 | +- [ ] CHK001 - 是否明确规定了精选剧集的数量与布局?[Completeness, Spec §FR-001] | ||
| 280 | +- [ ] CHK002 - 是否为所有可交互元素一致地规定了悬停态需求?[Consistency, Spec §FR-003] | ||
| 281 | +- [ ] CHK003 - 是否为所有可点击的品牌元素明确规定了导航需求?[Clarity, Spec §FR-010] | ||
| 282 | +- [ ] CHK004 - 是否记录了相关剧集的筛选条件?[Gap, Spec §FR-005] | ||
| 283 | +- [ ] CHK005 - 是否为异步剧集数据定义了加载态需求?[Gap] | ||
| 284 | +- [ ] CHK006 - “视觉层级”需求是否能被客观测量?[Measurability, Spec §FR-001] | ||
| 285 | +``` | ||
| 286 | + | ||
| 287 | +**关键差异:** | ||
| 288 | + | ||
| 289 | +- 错误:测试系统是否工作正常 | ||
| 290 | +- 正确:测试需求是否写得正确 | ||
| 291 | +- 错误:验证行为 | ||
| 292 | +- 正确:验证需求质量 | ||
| 293 | +- 错误:“它是否做了 X?” | ||
| 294 | +- 正确:“X 是否被清晰规定?” |
.cursor/commands/speckit.clarify.md
0 → 100644
| 1 | +--- | ||
| 2 | +description: 通过最多 5 个高针对性的澄清问题,识别当前功能 spec 中说明不足的部分,并将答案写回 spec。 | ||
| 3 | +handoffs: | ||
| 4 | + - label: 生成技术计划 | ||
| 5 | + agent: speckit.plan | ||
| 6 | + prompt: 为该 spec 生成实现计划。我将使用…… | ||
| 7 | +--- | ||
| 8 | + | ||
| 9 | +## 用户输入 | ||
| 10 | + | ||
| 11 | +```text | ||
| 12 | +$ARGUMENTS | ||
| 13 | +``` | ||
| 14 | + | ||
| 15 | +在继续之前,你**必须**先考虑用户输入(如果不为空)。 | ||
| 16 | + | ||
| 17 | +## 概述 | ||
| 18 | + | ||
| 19 | +目标:检测并减少当前功能说明中的歧义与缺失决策点,并将澄清内容直接记录到 spec 文件中。 | ||
| 20 | + | ||
| 21 | +注意:本澄清流程预计应在调用 `/speckit.plan` 之前运行并完成。如果用户明确表示跳过澄清(例如做探索性验证),你可以继续,但必须提醒下游返工风险会增加。 | ||
| 22 | + | ||
| 23 | +执行步骤: | ||
| 24 | + | ||
| 25 | +1. 在仓库根目录只运行一次 `.specify/scripts/bash/check-prerequisites.sh --json --paths-only`(组合模式:`--json --paths-only` / `-Json -PathsOnly`)。解析最小 JSON 字段: | ||
| 26 | + - `FEATURE_DIR` | ||
| 27 | + - `FEATURE_SPEC` | ||
| 28 | + - (可选)为后续链路保留 `IMPL_PLAN`、`TASKS` | ||
| 29 | + - 若 JSON 解析失败,立即终止并提示用户重新运行 `/speckit.specify` 或检查当前功能分支环境 | ||
| 30 | + - 当参数里包含单引号(例如 "I'm Groot")时,使用转义写法:如 'I'\''m Groot'(或尽量用双引号:"I'm Groot")。 | ||
| 31 | + | ||
| 32 | +2. 加载当前 spec 文件,并按以下分类做结构化的歧义与覆盖扫描。对每个类别标记状态:清晰 / 部分 / 缺失。生成一份用于排序的内部覆盖图(除非必要,不要输出原始覆盖图)。 | ||
| 33 | + | ||
| 34 | + 功能范围与行为(Functional Scope & Behavior): | ||
| 35 | + - 核心用户目标与成功标准 | ||
| 36 | + - 明确的范围外(out-of-scope)声明 | ||
| 37 | + - 用户角色/人物画像的区分 | ||
| 38 | + | ||
| 39 | + 领域与数据模型(Domain & Data Model): | ||
| 40 | + - 实体、属性、关系 | ||
| 41 | + - 身份/唯一性规则 | ||
| 42 | + - 生命周期/状态流转 | ||
| 43 | + - 数据量/规模假设 | ||
| 44 | + | ||
| 45 | + 交互与 UX 流程(Interaction & UX Flow): | ||
| 46 | + - 关键用户旅程/操作序列 | ||
| 47 | + - 错误/空/加载状态 | ||
| 48 | + - 无障碍或本地化说明 | ||
| 49 | + | ||
| 50 | + 非功能质量属性(Non-Functional Quality Attributes): | ||
| 51 | + - 性能(延迟、吞吐目标) | ||
| 52 | + - 可扩展性(水平/垂直、上限) | ||
| 53 | + - 可靠性与可用性(可用率、恢复预期) | ||
| 54 | + - 可观测性(日志、指标、链路追踪信号) | ||
| 55 | + - 安全与隐私(authN/Z、数据保护、威胁假设) | ||
| 56 | + - 合规/监管约束(如有) | ||
| 57 | + | ||
| 58 | + 集成与外部依赖(Integration & External Dependencies): | ||
| 59 | + - 外部服务/API 与失败模式 | ||
| 60 | + - 数据导入/导出格式 | ||
| 61 | + - 协议/版本假设 | ||
| 62 | + | ||
| 63 | + 边界情况与失败处理(Edge Cases & Failure Handling): | ||
| 64 | + - 负向场景 | ||
| 65 | + - 限流/节流 | ||
| 66 | + - 冲突解决(例如并发编辑) | ||
| 67 | + | ||
| 68 | + 约束与取舍(Constraints & Tradeoffs): | ||
| 69 | + - 技术约束(语言、存储、托管) | ||
| 70 | + - 明确的取舍或被拒绝的备选方案 | ||
| 71 | + | ||
| 72 | + 术语与一致性(Terminology & Consistency): | ||
| 73 | + - 规范术语表词条 | ||
| 74 | + - 避免使用的同义词/废弃术语 | ||
| 75 | + | ||
| 76 | + 完成信号(Completion Signals): | ||
| 77 | + - 验收标准可测试性 | ||
| 78 | + - 可量化的完成定义(Definition of Done)风格指标 | ||
| 79 | + | ||
| 80 | + 杂项/占位符(Misc / Placeholders): | ||
| 81 | + - TODO 标记/未决决策 | ||
| 82 | + - 未量化的模糊形容词(例如 “robust”“intuitive”) | ||
| 83 | + | ||
| 84 | + 对于状态为“部分”或“缺失”的类别,除非满足以下情况,否则应加入一个候选提问点: | ||
| 85 | + - 澄清不会实质性改变实现或验证策略 | ||
| 86 | + - 信息更适合延后到规划阶段再确定(内部记录即可) | ||
| 87 | + | ||
| 88 | +3. 在内部生成一个候选澄清问题的优先队列(最多 5 个)。不要一次性全部输出。约束如下: | ||
| 89 | + - 全会话最多 10 个问题。 | ||
| 90 | + - 每个问题必须能用以下任一方式回答: | ||
| 91 | + - 简短的多选项(2–5 个明确且互斥的选项),或 | ||
| 92 | + - 一个词/短语(明确限制:“回答不超过 5 个词”)。 | ||
| 93 | + - 只包含那些会实质影响架构、数据建模、任务拆分、测试设计、UX 行为、运维就绪或合规验证的问题。 | ||
| 94 | + - 保持类别覆盖均衡:优先覆盖高影响且未解决的类别;不要在一个高影响领域(例如安全策略)未解决时,去问两个低影响问题。 | ||
| 95 | + - 排除已回答的问题、琐碎的风格偏好、或 plan 层面的执行细节(除非阻塞正确性)。 | ||
| 96 | + - 优先提出能降低下游返工风险或避免验收测试错位的问题。 | ||
| 97 | + - 若未解决类别超过 5 个,按(Impact * Uncertainty)启发式选取前 5 个。 | ||
| 98 | + | ||
| 99 | +4. 顺序提问循环(交互式): | ||
| 100 | + - 每次**只**呈现一个问题。 | ||
| 101 | + - 对于多选题: | ||
| 102 | + - **分析所有选项**,并基于以下因素确定**最合适的选项**: | ||
| 103 | + - 该项目类型的最佳实践 | ||
| 104 | + - 类似实现的常见模式 | ||
| 105 | + - 风险降低(安全、性能、可维护性) | ||
| 106 | + - spec 中可见的项目目标或约束是否匹配 | ||
| 107 | + - 在顶部突出展示你的**推荐选项**并给出清晰理由(1–2 句说明为何这是最佳选择)。 | ||
| 108 | + - 格式:`**推荐:** 选项 [X] - <理由>` | ||
| 109 | + - 然后用 Markdown 表格列出所有选项: | ||
| 110 | + | ||
| 111 | + | 选项 | 描述 | | ||
| 112 | + |------|------| | ||
| 113 | + | A | <选项 A 描述> | | ||
| 114 | + | B | <选项 B 描述> | | ||
| 115 | + | C | <选项 C 描述>(如需可加 D/E,最多 5 个) | | ||
| 116 | + | Short | 提供不同的短答案(<=5 个词)(仅在需要自由回答时保留) | | ||
| 117 | + | ||
| 118 | + - 表格后追加:`你可以回复选项字母(例如 “A”),也可以回复 “yes” 或 “recommended” 来接受推荐,或给出你自己的短答案。` | ||
| 119 | + - 对于短答案形式(没有有意义的离散选项): | ||
| 120 | + - 基于最佳实践与上下文给出你的**建议答案**。 | ||
| 121 | + - 格式:`**建议:** <你的建议答案> - <简短理由>` | ||
| 122 | + - 然后输出:`格式:短答案(<=5 个词)。你可以回复 “yes” 或 “suggested” 来接受建议,或提供你自己的答案。` | ||
| 123 | + - 用户回答后: | ||
| 124 | + - 若用户回复 “yes”“recommended” 或 “suggested”,使用你之前给出的推荐/建议作为最终答案。 | ||
| 125 | + - 否则,校验回答是否能映射到某个选项或满足 <=5 word 的限制。 | ||
| 126 | + - 若仍有歧义,快速追问以消歧(仍算同一个问题,不要推进到下一题)。 | ||
| 127 | + - 一旦答案满足要求,将其记录到工作记忆(先不要写盘),并进入队列里的下一个问题。 | ||
| 128 | + - 在以下条件下停止继续提问: | ||
| 129 | + - 关键歧义已提前全部解决(剩余问题不再必要),或 | ||
| 130 | + - 用户表示结束(“done”“good”“no more”),或 | ||
| 131 | + - 已问满 5 个问题。 | ||
| 132 | + - 不要提前泄露后续排队的问题。 | ||
| 133 | + - 如果一开始就没有有效问题,立即报告“未发现值得正式澄清的关键歧义”。 | ||
| 134 | + | ||
| 135 | +5. 每次采纳答案后的集成(增量更新方式): | ||
| 136 | + - 在内存中维护一份 spec 表示(会话开始时只加载一次)以及原始文件内容。 | ||
| 137 | + - 本次会话第一次写入时: | ||
| 138 | + - 确保存在 `## Clarifications` 章节(若缺失,则按 spec 模板在最高层级的背景/概览段落之后创建)。 | ||
| 139 | + - 在其下创建(若不存在)今日对应的 `### Session YYYY-MM-DD` 小标题。 | ||
| 140 | + - 每次采纳后立即追加一行 bullet:`- Q: <问题> → A: <最终答案>`。 | ||
| 141 | + - 随后立即将澄清内容应用到最合适的章节: | ||
| 142 | + - 功能歧义 → 更新/新增 Functional Requirements 下的 bullet。 | ||
| 143 | + - 用户交互/角色区分 → 在 User Stories 或 Actors 子章节(如存在)写入明确的角色、约束或场景。 | ||
| 144 | + - 数据形态/实体 → 更新 Data Model(新增字段/类型/关系时保持原有顺序),并用简短语句注明新增约束。 | ||
| 145 | + - 非功能约束 → 在 Non-Functional / Quality Attributes 中新增或修改可度量的标准(把模糊形容词转换为指标或明确目标)。 | ||
| 146 | + - 边界情况/负向流程 → 在 Edge Cases / Error Handling 下新增 bullet(或按模板占位创建对应小节)。 | ||
| 147 | + - 术语冲突 → 统一 spec 全文用词;仅在必要时保留旧称,并只在一个地方追加 `(此前称为“X”)`。 | ||
| 148 | + - 若澄清答案使先前的模糊表述失效,应替换旧表述而不是重复追加;不要留下过时的矛盾文本。 | ||
| 149 | + - 每次集成后都保存 spec 文件,尽量降低上下文丢失风险(原子覆盖写入)。 | ||
| 150 | + - 保持格式:不要重排无关章节;保持标题层级不变。 | ||
| 151 | + - 每次插入的澄清内容都要尽量精炼且可验证(避免叙事化扩散)。 | ||
| 152 | + | ||
| 153 | +6. 校验(每次写入后执行,最终再做一次整体检查): | ||
| 154 | + - Clarifications 会话中每个被采纳答案只对应一条 bullet(不重复)。 | ||
| 155 | + - 总提问(被采纳)数量 ≤ 5。 | ||
| 156 | + - 更新后的章节中不应残留原本要被新答案解决的模糊占位符。 | ||
| 157 | + - 不应遗留自相矛盾的旧表述(扫描并移除已失效的备选说法)。 | ||
| 158 | + - Markdown 结构合法;仅允许新增标题:`## Clarifications`、`### Session YYYY-MM-DD`。 | ||
| 159 | + - 术语一致:所有更新章节使用同一套规范术语。 | ||
| 160 | + | ||
| 161 | +7. 将更新后的 spec 写回 `FEATURE_SPEC`。 | ||
| 162 | + | ||
| 163 | +8. 报告完成(当提问循环结束或被提前终止后): | ||
| 164 | + - 已提问并采纳的数量。 | ||
| 165 | + - 更新后的 spec 路径。 | ||
| 166 | + - 涉及修改的章节(列出名称)。 | ||
| 167 | + - 覆盖摘要表:列出每个分类及状态:Resolved(原为 Partial/Missing 且已补齐)、Deferred(超出配额或更适合留到规划阶段)、Clear(已足够清晰)、Outstanding(仍 Partial/Missing 但影响低)。 | ||
| 168 | + - 若仍存在 Outstanding 或 Deferred:建议是直接进入 `/speckit.plan`,还是在 plan 之后再跑一次 `/speckit.clarify`。 | ||
| 169 | + - 建议下一条命令。 | ||
| 170 | + | ||
| 171 | +行为规则: | ||
| 172 | + | ||
| 173 | +- 如果未发现有意义的歧义(或所有候选问题影响都很低),则回复:“未发现值得正式澄清的关键歧义。”并建议继续。 | ||
| 174 | +- 如果缺少 spec 文件,提示用户先运行 `/speckit.specify`(不要在这里新建 spec)。 | ||
| 175 | +- 总提问数不要超过 5 个(同一问题的澄清重试不算新问题)。 | ||
| 176 | +- 除非缺失信息会阻塞功能澄清,否则避免提出臆测性的技术栈问题。 | ||
| 177 | +- 尊重用户的提前终止信号(“stop”“done”“proceed”)。 | ||
| 178 | +- 若因覆盖完整而未提问,输出一份精简覆盖摘要(所有分类为 Clear)并建议继续推进。 | ||
| 179 | +- 若配额用尽但仍有未解决的高影响分类,在 Deferred 下明确标记并说明原因。 | ||
| 180 | + | ||
| 181 | +用于优先级判断的上下文:$ARGUMENTS |
.cursor/commands/speckit.constitution.md
0 → 100644
| 1 | +--- | ||
| 2 | +description: 通过交互式或提供的原则输入创建/更新项目宪法,并确保所有依赖模板保持同步。 | ||
| 3 | +handoffs: | ||
| 4 | + - label: 生成规格说明 | ||
| 5 | + agent: speckit.specify | ||
| 6 | + prompt: 基于更新后的宪法生成功能规格说明。我想要构建…… | ||
| 7 | +--- | ||
| 8 | + | ||
| 9 | +## 用户输入 | ||
| 10 | + | ||
| 11 | +```text | ||
| 12 | +$ARGUMENTS | ||
| 13 | +``` | ||
| 14 | + | ||
| 15 | +在继续之前,你**必须**先考虑用户输入(如果不为空)。 | ||
| 16 | + | ||
| 17 | +## 概述 | ||
| 18 | + | ||
| 19 | +你正在更新 `.specify/memory/constitution.md` 中的项目宪法。该文件是一个模板(Template),包含方括号占位符(例如 `[PROJECT_NAME]`、`[PRINCIPLE_1_NAME]`)。你的工作是:(a)收集/推导具体值,(b)精准填充模板,(c)将所有修订同步传播到依赖产物中。 | ||
| 20 | + | ||
| 21 | +按以下流程执行: | ||
| 22 | + | ||
| 23 | +1. 加载 `.specify/memory/constitution.md` 中现有的宪法模板。 | ||
| 24 | + - 识别所有形如 `[ALL_CAPS_IDENTIFIER]` 的占位符 token。 | ||
| 25 | + **重要**:用户需要的原则数量可能少于或多于模板默认数量。如果用户指定了数量,必须尊重该数量,并在通用模板结构下调整文档内容。 | ||
| 26 | + | ||
| 27 | +2. 收集/推导占位符的具体值: | ||
| 28 | + - 若用户输入(对话中)已提供取值,直接使用。 | ||
| 29 | + - 否则从仓库上下文推断(README、文档、若存在则参考仓库内嵌的旧宪法内容)。 | ||
| 30 | + - 治理日期规则:`RATIFICATION_DATE` 为原始生效日期(未知则询问或标记 TODO);`LAST_AMENDED_DATE` 若本次有修改则为今天,否则保留原值。 | ||
| 31 | + - `CONSTITUTION_VERSION` 必须按语义化版本规则递增: | ||
| 32 | + - MAJOR:不向后兼容的治理/原则移除或重定义。 | ||
| 33 | + - MINOR:新增原则/章节,或对既有指导做实质性扩展。 | ||
| 34 | + - PATCH:澄清、措辞、错别字修正、非语义性的精炼。 | ||
| 35 | + - 若版本升级类型不明确,在最终落盘前先给出判断理由。 | ||
| 36 | + | ||
| 37 | +3. 起草更新后的宪法内容: | ||
| 38 | + - 用具体文本替换每个占位符(不应残留方括号 token;除非项目明确决定暂不定义,并对每个保留项给出理由)。 | ||
| 39 | + - 保持标题层级不变;占位符被替换后,注释可移除,除非该注释仍对理解有帮助。 | ||
| 40 | + - 确保每条原则包含:简短名称行、概括不可协商规则的段落(或要点列表),若理由不明显则补充明确的理由说明。 | ||
| 41 | + - 确保治理(Governance)章节包含:修订流程、版本策略、合规审查期望。 | ||
| 42 | + | ||
| 43 | +4. 一致性传播清单(把旧清单转为可执行校验项): | ||
| 44 | + - 阅读 `.specify/templates/plan-template.md`,确保其中的“宪法检查”或规则与更新后的原则一致。 | ||
| 45 | + - 阅读 `.specify/templates/spec-template.md`,校验范围/需求对齐;若宪法新增/移除必填章节或约束,则同步更新模板。 | ||
| 46 | + - 阅读 `.specify/templates/tasks-template.md`,确保任务分类能体现新增/移除的原则驱动任务类型(例如可观测性、版本策略、测试纪律)。 | ||
| 47 | + - 阅读 `.specify/templates/commands/*.md` 下的每个命令文件(包含本文件),确保通用指导中不残留过时的 agent 特定引用(例如仅写 CLAUDE)。 | ||
| 48 | + - 阅读运行/使用指导文档(例如 `README.md`、`docs/quickstart.md`,或存在的 agent 专属指导文件),同步更新与宪法变更相关的引用。 | ||
| 49 | + | ||
| 50 | +5. 生成“同步影响报告”(更新后,作为 HTML 注释插入到宪法文件顶部): | ||
| 51 | + - 版本变更:旧版本 → 新版本 | ||
| 52 | + - 修改过的原则列表(若重命名则写 旧标题 → 新标题) | ||
| 53 | + - 新增章节 | ||
| 54 | + - 移除章节 | ||
| 55 | + - 需要更新的模板列表(✅ 已更新 / ⚠ 待处理),并附文件路径 | ||
| 56 | + - 若有占位符被刻意延期,列出后续 TODO | ||
| 57 | + | ||
| 58 | +6. 最终输出前校验: | ||
| 59 | + - 不残留任何无法解释的方括号占位符 token。 | ||
| 60 | + - 版本行与同步影响报告一致。 | ||
| 61 | + - 日期使用 ISO 格式 YYYY-MM-DD。 | ||
| 62 | + - 原则陈述应可验证、可测试,避免含糊措辞(必要时将“should”替换为 MUST/SHOULD,并给出理由)。 | ||
| 63 | + | ||
| 64 | +7. 将完成的宪法内容写回 `.specify/memory/constitution.md`(覆盖写入)。 | ||
| 65 | + | ||
| 66 | +8. 输出给用户的最终总结应包含: | ||
| 67 | + - 新版本号与升级理由。 | ||
| 68 | + - 需要人工跟进的文件(如有)。 | ||
| 69 | + - 建议的提交信息(例如 `docs: amend constitution to vX.Y.Z (principle additions + governance update)`)。 | ||
| 70 | + | ||
| 71 | +格式与样式要求: | ||
| 72 | + | ||
| 73 | +- 标题层级必须与模板完全一致(不要升/降级标题)。 | ||
| 74 | +- 适度换行以保持可读性(理想情况下 <100 字符),但不要为了硬性限制造成生硬断行。 | ||
| 75 | +- 章节之间保持一个空行。 | ||
| 76 | +- 避免行尾空格。 | ||
| 77 | + | ||
| 78 | +如果用户只提供部分更新(例如只修改一条原则),也要照样执行校验与版本决策步骤。 | ||
| 79 | + | ||
| 80 | +如果缺少关键信息(例如确实不知道 ratification date),插入 `TODO(<FIELD_NAME>): explanation`,并在同步影响报告的延期项(deferred)中列出。 | ||
| 81 | + | ||
| 82 | +不要创建新的模板;始终在现有的 `.specify/memory/constitution.md` 文件上操作。 |
.cursor/commands/speckit.implement.md
0 → 100644
| 1 | +--- | ||
| 2 | +description: 通过处理并执行 tasks.md 中定义的所有任务,落实实现计划 | ||
| 3 | +--- | ||
| 4 | + | ||
| 5 | +## 用户输入 | ||
| 6 | + | ||
| 7 | +```text | ||
| 8 | +$ARGUMENTS | ||
| 9 | +``` | ||
| 10 | + | ||
| 11 | +在继续之前,你**必须**先考虑用户输入(如果不为空)。 | ||
| 12 | + | ||
| 13 | +## 概述 | ||
| 14 | + | ||
| 15 | +1. 在仓库根目录运行一次 `.specify/scripts/bash/check-prerequisites.sh --json --require-tasks --include-tasks`,并解析 JSON 中的 FEATURE_DIR 与 AVAILABLE_DOCS 列表。所有路径必须是绝对路径。当参数里包含单引号(例如 "I'm Groot")时,使用转义写法:如 'I'\''m Groot'(或尽量用双引号:"I'm Groot")。 | ||
| 16 | + | ||
| 17 | +2. **检查检查清单状态**(如果存在 FEATURE_DIR/checklists/): | ||
| 18 | + - 扫描 checklists/ 目录下的所有清单文件 | ||
| 19 | + - 对每个清单统计: | ||
| 20 | + - 总条目:匹配 `- [ ]` 或 `- [X]` 或 `- [x]` 的所有行 | ||
| 21 | + - 已完成条目:匹配 `- [X]` 或 `- [x]` 的行 | ||
| 22 | + - 未完成条目:匹配 `- [ ]` 的行 | ||
| 23 | + - 生成状态表: | ||
| 24 | + | ||
| 25 | + ```text | ||
| 26 | + | Checklist | Total | Completed | Incomplete | Status | | ||
| 27 | + |-----------|-------|-----------|------------|--------| | ||
| 28 | + | ux.md | 12 | 12 | 0 | ✓ PASS | | ||
| 29 | + | test.md | 8 | 5 | 3 | ✗ FAIL | | ||
| 30 | + | security.md | 6 | 6 | 0 | ✓ PASS | | ||
| 31 | + ``` | ||
| 32 | + | ||
| 33 | + - 计算总体状态: | ||
| 34 | + - **PASS**:所有清单的未完成条目为 0 | ||
| 35 | + - **FAIL**:存在至少一个清单有未完成条目 | ||
| 36 | + | ||
| 37 | + - **如果存在未完成清单**: | ||
| 38 | + - 展示状态表与未完成数量 | ||
| 39 | + - **停止**并询问:“有些检查清单尚未完成。是否仍要继续实现?(yes/no)” | ||
| 40 | + - 等待用户回复后再继续 | ||
| 41 | + - 若用户回复 “no”“wait” 或 “stop”,则终止执行 | ||
| 42 | + - 若用户回复 “yes”“proceed” 或 “continue”,则进入第 3 步 | ||
| 43 | + | ||
| 44 | + - **如果所有清单都已完成**: | ||
| 45 | + - 展示所有清单通过的状态表 | ||
| 46 | + - 自动进入第 3 步 | ||
| 47 | + | ||
| 48 | +3. 加载并分析实现上下文: | ||
| 49 | + - **必须**:读取 tasks.md 获取完整任务列表与执行计划 | ||
| 50 | + - **必须**:读取 plan.md 获取技术栈、架构与文件结构 | ||
| 51 | + - **如果存在**:读取 data-model.md 获取实体与关系 | ||
| 52 | + - **如果存在**:读取 contracts/ 获取 API 规范与测试要求 | ||
| 53 | + - **如果存在**:读取 research.md 获取技术决策与约束 | ||
| 54 | + - **如果存在**:读取 quickstart.md 获取集成场景 | ||
| 55 | + | ||
| 56 | +4. **项目初始化校验**: | ||
| 57 | + - **必须**:基于实际项目情况创建/校验 ignore 文件: | ||
| 58 | + | ||
| 59 | + **检测与创建逻辑**: | ||
| 60 | + - 通过下列命令是否成功来判断仓库是否是 git 仓库(如果是,则创建/校验 .gitignore): | ||
| 61 | + | ||
| 62 | + ```sh | ||
| 63 | + git rev-parse --git-dir 2>/dev/null | ||
| 64 | + ``` | ||
| 65 | + | ||
| 66 | + - 检查是否存在 Dockerfile* 或 plan.md 中提到 Docker → 创建/校验 .dockerignore | ||
| 67 | + - 检查是否存在 .eslintrc* → 创建/校验 .eslintignore | ||
| 68 | + - 检查是否存在 eslint.config.* → 确保配置中的 `ignores` 覆盖所需模式 | ||
| 69 | + - 检查是否存在 .prettierrc* → 创建/校验 .prettierignore | ||
| 70 | + - 检查是否存在 .npmrc 或 package.json →(若要发布)创建/校验 .npmignore | ||
| 71 | + - 检查是否存在 terraform 文件(*.tf)→ 创建/校验 .terraformignore | ||
| 72 | + - 检查是否需要 .helmignore(存在 helm charts)→ 创建/校验 .helmignore | ||
| 73 | + | ||
| 74 | + **如果 ignore 文件已存在**:校验其包含关键模式,只追加缺失且重要的模式 | ||
| 75 | + **如果 ignore 文件不存在**:按检测到的技术栈创建包含完整模式集合的文件 | ||
| 76 | + | ||
| 77 | + **按技术栈的常见模式**(来自 plan.md 的技术栈): | ||
| 78 | + - **Node.js/JavaScript/TypeScript**: `node_modules/`, `dist/`, `build/`, `*.log`, `.env*` | ||
| 79 | + - **Python**: `__pycache__/`, `*.pyc`, `.venv/`, `venv/`, `dist/`, `*.egg-info/` | ||
| 80 | + - **Java**: `target/`, `*.class`, `*.jar`, `.gradle/`, `build/` | ||
| 81 | + - **C#/.NET**: `bin/`, `obj/`, `*.user`, `*.suo`, `packages/` | ||
| 82 | + - **Go**: `*.exe`, `*.test`, `vendor/`, `*.out` | ||
| 83 | + - **Ruby**: `.bundle/`, `log/`, `tmp/`, `*.gem`, `vendor/bundle/` | ||
| 84 | + - **PHP**: `vendor/`, `*.log`, `*.cache`, `*.env` | ||
| 85 | + - **Rust**: `target/`, `debug/`, `release/`, `*.rs.bk`, `*.rlib`, `*.prof*`, `.idea/`, `*.log`, `.env*` | ||
| 86 | + - **Kotlin**: `build/`, `out/`, `.gradle/`, `.idea/`, `*.class`, `*.jar`, `*.iml`, `*.log`, `.env*` | ||
| 87 | + - **C++**: `build/`, `bin/`, `obj/`, `out/`, `*.o`, `*.so`, `*.a`, `*.exe`, `*.dll`, `.idea/`, `*.log`, `.env*` | ||
| 88 | + - **C**: `build/`, `bin/`, `obj/`, `out/`, `*.o`, `*.a`, `*.so`, `*.exe`, `Makefile`, `config.log`, `.idea/`, `*.log`, `.env*` | ||
| 89 | + - **Swift**: `.build/`, `DerivedData/`, `*.swiftpm/`, `Packages/` | ||
| 90 | + - **R**: `.Rproj.user/`, `.Rhistory`, `.RData`, `.Ruserdata`, `*.Rproj`, `packrat/`, `renv/` | ||
| 91 | + - **Universal**: `.DS_Store`, `Thumbs.db`, `*.tmp`, `*.swp`, `.vscode/`, `.idea/` | ||
| 92 | + | ||
| 93 | + **工具特定模式**: | ||
| 94 | + - **Docker**: `node_modules/`, `.git/`, `Dockerfile*`, `.dockerignore`, `*.log*`, `.env*`, `coverage/` | ||
| 95 | + - **ESLint**: `node_modules/`, `dist/`, `build/`, `coverage/`, `*.min.js` | ||
| 96 | + - **Prettier**: `node_modules/`, `dist/`, `build/`, `coverage/`, `package-lock.json`, `yarn.lock`, `pnpm-lock.yaml` | ||
| 97 | + - **Terraform**: `.terraform/`, `*.tfstate*`, `*.tfvars`, `.terraform.lock.hcl` | ||
| 98 | + - **Kubernetes/k8s**: `*.secret.yaml`, `secrets/`, `.kube/`, `kubeconfig*`, `*.key`, `*.crt` | ||
| 99 | + | ||
| 100 | +5. 解析 tasks.md 结构并提取: | ||
| 101 | + - **任务阶段**:初始化、测试、核心、集成、打磨 | ||
| 102 | + - **任务依赖**:串行与并行的执行规则 | ||
| 103 | + - **任务细节**:ID、描述、文件路径、并行标记 [P] | ||
| 104 | + - **执行流程**:顺序与依赖要求 | ||
| 105 | + | ||
| 106 | +6. 按任务计划执行实现: | ||
| 107 | + - **按阶段执行**:完成当前阶段后再进入下一阶段 | ||
| 108 | + - **遵守依赖**:串行任务按顺序执行;并行任务 [P] 可同时执行 | ||
| 109 | + - **遵循 TDD**:若存在测试任务,先执行测试任务再执行对应实现任务 | ||
| 110 | + - **按文件协调**:影响同一文件的任务必须串行执行 | ||
| 111 | + - **阶段校验点**:进入下一阶段前先校验当前阶段是否完成 | ||
| 112 | + | ||
| 113 | +7. 实现执行规则: | ||
| 114 | + - **先做初始化**:初始化项目结构、依赖、配置 | ||
| 115 | + - **先写测试再写代码**:如果需要为契约、实体与集成场景编写测试 | ||
| 116 | + - **核心开发**:实现模型、服务、CLI 命令、端点 | ||
| 117 | + - **集成工作**:数据库连接、中间件、日志、外部服务 | ||
| 118 | + - **打磨与校验**:单元测试、性能优化、文档 | ||
| 119 | + | ||
| 120 | +8. 进度跟踪与错误处理: | ||
| 121 | + - 每完成一个任务都汇报进度 | ||
| 122 | + - 任意非并行任务失败时立即停止执行 | ||
| 123 | + - 并行任务 [P] 允许继续已成功任务,并汇报失败项 | ||
| 124 | + - 提供带上下文的清晰错误信息,便于定位 | ||
| 125 | + - 若实现无法继续,给出明确的下一步建议 | ||
| 126 | + - **重要**:任务完成后,务必在 tasks 文件中用 [X] 勾掉该任务 | ||
| 127 | + | ||
| 128 | +9. 完成校验: | ||
| 129 | + - 确认所有必需任务都已完成 | ||
| 130 | + - 检查实现的功能是否与原始 spec 一致 | ||
| 131 | + - 校验测试通过且覆盖率满足要求 | ||
| 132 | + - 确认实现遵循技术计划 | ||
| 133 | + - 汇报最终状态并总结已完成工作 | ||
| 134 | + | ||
| 135 | +注意:本命令假设 tasks.md 中已存在完整的任务拆分。如果任务不完整或缺失,应建议先运行 `/speckit.tasks` 重新生成任务列表。 |
.cursor/commands/speckit.plan.md
0 → 100644
| 1 | +--- | ||
| 2 | +description: 使用计划模板执行实现规划流程,并生成设计产物。 | ||
| 3 | +handoffs: | ||
| 4 | + - label: 生成任务列表 | ||
| 5 | + agent: speckit.tasks | ||
| 6 | + prompt: 将实现计划拆分为可执行任务 | ||
| 7 | + send: true | ||
| 8 | + - label: 生成检查清单 | ||
| 9 | + agent: speckit.checklist | ||
| 10 | + prompt: 为以下领域生成一份检查清单…… | ||
| 11 | +--- | ||
| 12 | + | ||
| 13 | +## 用户输入 | ||
| 14 | + | ||
| 15 | +```text | ||
| 16 | +$ARGUMENTS | ||
| 17 | +``` | ||
| 18 | + | ||
| 19 | +在继续之前,你**必须**先考虑用户输入(如果不为空)。 | ||
| 20 | + | ||
| 21 | +## 概述 | ||
| 22 | + | ||
| 23 | +1. **准备**:在仓库根目录运行 `.specify/scripts/bash/setup-plan.sh --json`,并解析 JSON 中的 FEATURE_SPEC、IMPL_PLAN、SPECS_DIR、BRANCH。当参数里包含单引号(例如 "I'm Groot")时,使用转义写法:如 'I'\''m Groot'(或尽量用双引号:"I'm Groot")。 | ||
| 24 | + | ||
| 25 | +2. **加载上下文**:读取 FEATURE_SPEC 与 `.specify/memory/constitution.md`。加载 IMPL_PLAN 模板(已复制到目标位置)。 | ||
| 26 | + | ||
| 27 | +3. **执行规划流程**:按 IMPL_PLAN 模板结构完成: | ||
| 28 | + - 填写技术上下文(未知项标记为 “NEEDS CLARIFICATION”) | ||
| 29 | + - 从宪法中填写“宪法检查”小节 | ||
| 30 | + - 评估门禁(若存在未经合理解释的违规则 ERROR) | ||
| 31 | + - Phase 0:生成 research.md(解决所有 NEEDS CLARIFICATION) | ||
| 32 | + - Phase 1:生成 data-model.md、contracts/、quickstart.md | ||
| 33 | + - Phase 1:通过运行 agent 脚本更新 agent 上下文 | ||
| 34 | + - 设计完成后重新评估“宪法检查” | ||
| 35 | + | ||
| 36 | +4. **停止并汇报**:命令在 Phase 2 规划后结束。汇报分支名、IMPL_PLAN 路径与生成的产物。 | ||
| 37 | + | ||
| 38 | +## 阶段 | ||
| 39 | + | ||
| 40 | +### Phase 0:大纲与调研 | ||
| 41 | + | ||
| 42 | +1. **从上面的技术上下文中提取未知项**: | ||
| 43 | + - 每个 NEEDS CLARIFICATION → 一条调研任务 | ||
| 44 | + - 每个依赖 → 一条最佳实践任务 | ||
| 45 | + - 每个集成点 → 一条模式/方案任务 | ||
| 46 | + | ||
| 47 | +2. **生成并分发调研 agent**: | ||
| 48 | + | ||
| 49 | + ```text | ||
| 50 | + 对于技术上下文中的每个未知项: | ||
| 51 | + 任务:"为 {feature context} 调研 {unknown}" | ||
| 52 | + 对于每个技术选型: | ||
| 53 | + 任务:"为 {domain} 场景寻找 {tech} 的最佳实践" | ||
| 54 | + ``` | ||
| 55 | + | ||
| 56 | +3. **在 `research.md` 中汇总结论**,格式为: | ||
| 57 | + - 决策(Decision):[选择了什么] | ||
| 58 | + - 理由(Rationale):[为什么这样选] | ||
| 59 | + - 备选方案(Alternatives):[还评估了什么] | ||
| 60 | + | ||
| 61 | +**输出**:research.md,且所有 NEEDS CLARIFICATION 已解决 | ||
| 62 | + | ||
| 63 | +### Phase 1:设计与契约 | ||
| 64 | + | ||
| 65 | +**前置条件:** `research.md` 已完成 | ||
| 66 | + | ||
| 67 | +1. **从功能 spec 中提取实体** → `data-model.md`: | ||
| 68 | + - 实体名称、字段、关系 | ||
| 69 | + - 来自需求的校验规则 | ||
| 70 | + - 若适用:状态流转 | ||
| 71 | + | ||
| 72 | +2. **从功能需求生成 API 契约**: | ||
| 73 | + - 每个用户动作 → 一个 endpoint | ||
| 74 | + - 使用标准 REST/GraphQL 模式 | ||
| 75 | + - 输出 OpenAPI/GraphQL schema 到 `/contracts/` | ||
| 76 | + | ||
| 77 | +3. **更新 agent 上下文**: | ||
| 78 | + - 运行 `.specify/scripts/bash/update-agent-context.sh cursor-agent` | ||
| 79 | + - 脚本会检测当前使用的是哪个 AI agent | ||
| 80 | + - 更新对应的 agent 专属上下文文件 | ||
| 81 | + - 只追加本次计划中新引入的技术 | ||
| 82 | + - 保留标记区间内的人工补充内容 | ||
| 83 | + | ||
| 84 | +**输出**:data-model.md、/contracts/*、quickstart.md、agent 专属上下文文件 | ||
| 85 | + | ||
| 86 | +## 关键规则 | ||
| 87 | + | ||
| 88 | +- 使用绝对路径 | ||
| 89 | +- 门禁失败或存在未解决澄清项时直接 ERROR |
.cursor/commands/speckit.specify.md
0 → 100644
| 1 | +--- | ||
| 2 | +description: 根据自然语言的功能描述创建或更新功能规格说明(spec)。 | ||
| 3 | +handoffs: | ||
| 4 | + - label: 生成技术计划 | ||
| 5 | + agent: speckit.plan | ||
| 6 | + prompt: 为该 spec 生成实现计划。我将使用…… | ||
| 7 | + - label: 澄清 spec 需求 | ||
| 8 | + agent: speckit.clarify | ||
| 9 | + prompt: 澄清规格说明需求 | ||
| 10 | + send: true | ||
| 11 | +--- | ||
| 12 | + | ||
| 13 | +## 用户输入 | ||
| 14 | + | ||
| 15 | +```text | ||
| 16 | +$ARGUMENTS | ||
| 17 | +``` | ||
| 18 | + | ||
| 19 | +在继续之前,你**必须**先考虑用户输入(如果不为空)。 | ||
| 20 | + | ||
| 21 | +## 概述 | ||
| 22 | + | ||
| 23 | +触发消息里,用户在 `/speckit.specify` 之后输入的文本**就是**功能描述。即使下面字面出现 `$ARGUMENTS`,也要假设在本次对话中你始终能拿到该描述。除非用户执行了空命令,否则不要让用户重复描述。 | ||
| 24 | + | ||
| 25 | +基于该功能描述,执行以下流程: | ||
| 26 | + | ||
| 27 | +1. **为分支生成一个精炼短名**(2–4 个词): | ||
| 28 | + - 分析功能描述并提取最有意义的关键词 | ||
| 29 | + - 生成一个 2–4 词的 short name,能够概括功能核心 | ||
| 30 | + - 尽量使用“动作-名词”格式(例如 "add-user-auth"、"fix-payment-bug") | ||
| 31 | + - 保留技术术语与缩写(OAuth2、API、JWT 等) | ||
| 32 | + - 保持简洁,但要足够描述性,能一眼看懂功能概念 | ||
| 33 | + - 示例: | ||
| 34 | + - “我想增加用户认证功能” → "user-auth" | ||
| 35 | + - “为 API 接入 OAuth2 集成” → "oauth2-api-integration" | ||
| 36 | + - “创建一个数据分析看板” → "analytics-dashboard" | ||
| 37 | + - “修复支付处理超时问题” → "fix-payment-timeout" | ||
| 38 | + | ||
| 39 | +2. **创建新分支前检查是否已存在同名分支**: | ||
| 40 | + | ||
| 41 | + a. 先拉取所有远端分支,确保信息最新: | ||
| 42 | + | ||
| 43 | + ```bash | ||
| 44 | + git fetch --all --prune | ||
| 45 | + ``` | ||
| 46 | + | ||
| 47 | + b. 在所有来源中查找该 short-name 的最大功能编号: | ||
| 48 | + - 远端分支:`git ls-remote --heads origin | grep -E 'refs/heads/[0-9]+-<short-name>$'` | ||
| 49 | + - 本地分支:`git branch | grep -E '^[* ]*[0-9]+-<short-name>$'` | ||
| 50 | + - Specs 目录:检查匹配 `specs/[0-9]+-<short-name>` 的目录 | ||
| 51 | + | ||
| 52 | + c. 计算下一个可用编号: | ||
| 53 | + - 从三个来源中提取所有编号 | ||
| 54 | + - 找到最大编号 N | ||
| 55 | + - 新分支编号使用 N+1 | ||
| 56 | + | ||
| 57 | + d. 使用计算出的编号与 short-name 运行脚本 `.specify/scripts/bash/create-new-feature.sh --json "$ARGUMENTS"`: | ||
| 58 | + - 传入 `--number N+1` 与 `--short-name "your-short-name"`,并携带功能描述 | ||
| 59 | + - Bash 示例:`.specify/scripts/bash/create-new-feature.sh --json "$ARGUMENTS" --json --number 5 --short-name "user-auth" "Add user authentication"` | ||
| 60 | + - PowerShell 示例:`.specify/scripts/bash/create-new-feature.sh --json "$ARGUMENTS" -Json -Number 5 -ShortName "user-auth" "Add user authentication"` | ||
| 61 | + | ||
| 62 | + **重要**: | ||
| 63 | + - 必须检查三处来源(远端分支、本地分支、specs 目录)以找到最大编号 | ||
| 64 | + - 只匹配与 short-name 完全一致的分支/目录模式 | ||
| 65 | + - 如果没有找到与 short-name 匹配的分支/目录,从编号 1 开始 | ||
| 66 | + - 每个功能只允许运行该脚本一次 | ||
| 67 | + - JSON 会作为终端输出打印出来 —— 获取路径等信息时必须以该 JSON 为准 | ||
| 68 | + - JSON 输出里会包含 BRANCH_NAME 与 SPEC_FILE 路径 | ||
| 69 | + - 当参数里包含单引号(例如 "I'm Groot")时,使用转义写法:如 'I'\''m Groot'(或尽量用双引号:"I'm Groot") | ||
| 70 | + | ||
| 71 | +3. 加载 `.specify/templates/spec-template.md` 以了解必需章节。 | ||
| 72 | + | ||
| 73 | +4. 按以下执行流程生成 spec: | ||
| 74 | + | ||
| 75 | + 1. 从输入中解析用户描述 | ||
| 76 | + 如果为空:ERROR "未提供功能描述" | ||
| 77 | + 2. 从描述中提取关键概念 | ||
| 78 | + 识别:参与者(actors)、动作(actions)、数据(data)、约束(constraints) | ||
| 79 | + 3. 对于不清晰的点: | ||
| 80 | + - 基于上下文与行业常见做法做出有依据的默认判断 | ||
| 81 | + - 仅在以下情况下用 [NEEDS CLARIFICATION: 具体问题] 标记: | ||
| 82 | + - 选择会显著影响功能范围或用户体验 | ||
| 83 | + - 存在多个合理解释且影响不同 | ||
| 84 | + - 不存在合理默认值 | ||
| 85 | + - **限制:总计最多 3 个 [NEEDS CLARIFICATION] 标记** | ||
| 86 | + - 按影响优先级排序澄清:范围 > 安全/隐私 > 用户体验 > 技术细节 | ||
| 87 | + 4. 填写“用户场景与验收”章节 | ||
| 88 | + 如果没有明确用户流程:ERROR "无法确定用户场景" | ||
| 89 | + 5. 生成“需求 → 功能需求” | ||
| 90 | + 每条需求都必须可测试 | ||
| 91 | + 对未明确的细节使用合理默认值(并在“假设(Assumptions)”章节记录假设) | ||
| 92 | + 6. 定义“成功标准” | ||
| 93 | + 产出可度量、与具体技术无关的结果 | ||
| 94 | + 同时包含量化指标(时间、性能、容量)与定性指标(用户满意度、任务完成率) | ||
| 95 | + 每条标准必须在不依赖实现细节的情况下可验证 | ||
| 96 | + 7. 识别关键实体(当功能涉及数据时) | ||
| 97 | + 8. 返回:SUCCESS(spec 已可进入规划阶段) | ||
| 98 | + | ||
| 99 | +5. 按模板结构将 spec 写入 SPEC_FILE:用功能描述(arguments)推导的具体内容替换占位符,同时保持章节顺序与标题不变。 | ||
| 100 | + | ||
| 101 | +6. **spec 质量校验**:初版 spec 写入后,按质量标准进行校验: | ||
| 102 | + | ||
| 103 | + a. **创建 spec 质量清单**:按检查清单模板结构,在 `FEATURE_DIR/checklists/requirements.md` 生成清单文件,并包含以下校验项: | ||
| 104 | + | ||
| 105 | + ```markdown | ||
| 106 | + # spec 质量清单:[FEATURE NAME] | ||
| 107 | + | ||
| 108 | + **目的**:在进入规划阶段前,校验 spec 的完整性与质量 | ||
| 109 | + **创建时间**:[DATE] | ||
| 110 | + **功能**:[Link to spec.md] | ||
| 111 | + | ||
| 112 | + ## 内容质量 | ||
| 113 | + | ||
| 114 | + - [ ] 不包含实现细节(语言、框架、API) | ||
| 115 | + - [ ] 聚焦用户价值与业务需求 | ||
| 116 | + - [ ] 面向非技术干系人撰写 | ||
| 117 | + - [ ] 所有必填章节已完成 | ||
| 118 | + | ||
| 119 | + ## 需求完整性 | ||
| 120 | + | ||
| 121 | + - [ ] 不再遗留 [NEEDS CLARIFICATION] 标记 | ||
| 122 | + - [ ] 需求可测试且无歧义 | ||
| 123 | + - [ ] 成功标准可度量 | ||
| 124 | + - [ ] 成功标准与技术实现无关(不包含实现细节) | ||
| 125 | + - [ ] 所有验收场景已定义 | ||
| 126 | + - [ ] 已识别边界情况 | ||
| 127 | + - [ ] 范围边界清晰 | ||
| 128 | + - [ ] 已识别依赖与假设 | ||
| 129 | + | ||
| 130 | + ## 功能就绪度 | ||
| 131 | + | ||
| 132 | + - [ ] 所有功能需求都有清晰的验收标准 | ||
| 133 | + - [ ] 用户场景覆盖主流程 | ||
| 134 | + - [ ] 功能满足“成功标准”中定义的可度量结果 | ||
| 135 | + - [ ] spec 中没有渗入实现细节 | ||
| 136 | + | ||
| 137 | + ## 备注 | ||
| 138 | + | ||
| 139 | + - 标记为未完成的条目,需要先更新 spec,再进入 `/speckit.clarify` 或 `/speckit.plan` | ||
| 140 | + ``` | ||
| 141 | + | ||
| 142 | + b. **执行校验**:逐条对照清单检查 spec: | ||
| 143 | + - 对每条清单项判定通过/失败 | ||
| 144 | + - 记录发现的具体问题(引用 spec 的相关段落) | ||
| 145 | + | ||
| 146 | + c. **处理校验结果**: | ||
| 147 | + | ||
| 148 | + - **如果全部通过**:将清单标记为完成,并进入下一步 | ||
| 149 | + | ||
| 150 | + - **如果存在失败项(不含 [NEEDS CLARIFICATION])**: | ||
| 151 | + 1. 列出失败项与具体问题 | ||
| 152 | + 2. 更新 spec 逐项修复 | ||
| 153 | + 3. 重新执行校验直到全部通过(最多 3 次迭代) | ||
| 154 | + 4. 若 3 次后仍未通过,在清单备注中记录剩余问题并提醒用户 | ||
| 155 | + | ||
| 156 | + - **如果仍存在 [NEEDS CLARIFICATION] 标记**: | ||
| 157 | + 1. 从 spec 中提取所有 [NEEDS CLARIFICATION: ...] 标记 | ||
| 158 | + 2. **限制检查**:如果标记超过 3 个,只保留最关键的 3 个(按范围/安全/UX 影响排序),其余做有依据的默认判断 | ||
| 159 | + 3. 对每个需要澄清的问题(最多 3 个),按以下格式向用户给出选项: | ||
| 160 | + | ||
| 161 | + ```markdown | ||
| 162 | + ## 问题 [N]:[Topic] | ||
| 163 | + | ||
| 164 | + **上下文**:[引用 spec 相关段落] | ||
| 165 | + | ||
| 166 | + **需要确认的信息**:[来自 NEEDS CLARIFICATION 标记的具体问题] | ||
| 167 | + | ||
| 168 | + **建议答案**: | ||
| 169 | + | ||
| 170 | + | 选项 | 答案 | 影响 | | ||
| 171 | + |------|------|------| | ||
| 172 | + | A | [建议答案 1] | [对该功能的影响] | | ||
| 173 | + | B | [建议答案 2] | [对该功能的影响] | | ||
| 174 | + | C | [建议答案 3] | [对该功能的影响] | | ||
| 175 | + | Custom | 提供你自己的答案 | [说明如何提供自定义输入] | | ||
| 176 | + | ||
| 177 | + **你的选择**:_[等待用户回复]_ | ||
| 178 | + ``` | ||
| 179 | + | ||
| 180 | + 4. **关键(CRITICAL)- 表格格式**:确保 Markdown 表格格式正确: | ||
| 181 | + - 使用一致的空格并对齐竖线 | ||
| 182 | + - 每个单元格内容两侧要有空格:`| Content |` 而不是 `|Content|` | ||
| 183 | + - 表头分隔线至少 3 个短横线:`|--------|` | ||
| 184 | + - 确认表格在 Markdown 预览中能正确渲染 | ||
| 185 | + 5. 问题编号按顺序排列(Q1、Q2、Q3,最多 3 个) | ||
| 186 | + 6. 在等待用户回复之前,一次性展示全部问题 | ||
| 187 | + 7. 等待用户按问题编号回复选项(例如 “Q1: A, Q2: Custom - [details], Q3: B”) | ||
| 188 | + 8. 用用户选择/提供的答案替换每个 [NEEDS CLARIFICATION] 标记并更新 spec | ||
| 189 | + 9. 当所有澄清完成后重新执行校验 | ||
| 190 | + | ||
| 191 | + d. **更新清单**:每次校验迭代后,更新清单文件中的通过/失败状态 | ||
| 192 | + | ||
| 193 | +7. 汇报完成情况:分支名、spec 文件路径、清单结果,以及进入下一阶段(`/speckit.clarify` 或 `/speckit.plan`)的就绪情况。 | ||
| 194 | + | ||
| 195 | +**注意(NOTE)**:脚本会先创建并切换到新分支,并在写入前初始化 spec 文件。 | ||
| 196 | + | ||
| 197 | +## 通用指南 | ||
| 198 | + | ||
| 199 | +## 快速指南 | ||
| 200 | + | ||
| 201 | +- 聚焦用户需要**什么(WHAT)**与**为什么(WHY)**。 | ||
| 202 | +- 避免描述如何实现(不写技术栈、API、代码结构)。 | ||
| 203 | +- 面向业务/非技术干系人撰写,而不是只写给开发看。 | ||
| 204 | +- 不要在 spec 里内嵌任何检查清单;清单由单独命令生成。 | ||
| 205 | + | ||
| 206 | +### 章节要求 | ||
| 207 | + | ||
| 208 | +- **必填章节**:每个功能都必须完成 | ||
| 209 | +- **可选章节**:只在与功能相关时保留 | ||
| 210 | +- 如果某个章节不适用,直接删除(不要留下 “N/A”) | ||
| 211 | + | ||
| 212 | +### 面向 AI 生成 | ||
| 213 | + | ||
| 214 | +当根据用户输入生成该 spec 时: | ||
| 215 | + | ||
| 216 | +1. **做出有依据的默认判断**:用上下文、行业标准与常见模式补齐缺口 | ||
| 217 | +2. **记录假设**:在 Assumptions 章节写下合理默认值 | ||
| 218 | +3. **限制澄清点**:最多 3 个 [NEEDS CLARIFICATION] 标记,只用于以下关键决策: | ||
| 219 | + - 会显著影响功能范围或用户体验 | ||
| 220 | + - 存在多种合理解释且影响不同 | ||
| 221 | + - 没有合理默认值 | ||
| 222 | +4. **澄清优先级**:范围 > 安全/隐私 > 用户体验 > 技术细节 | ||
| 223 | +5. **像测试人员一样思考**:任何模糊需求都应在“可测试且无歧义”项上失败 | ||
| 224 | +6. **常见需要澄清的领域**(仅当不存在合理默认值时才问): | ||
| 225 | + - 功能范围与边界(包含/排除哪些用例) | ||
| 226 | + - 用户类型与权限(存在多种冲突解释时) | ||
| 227 | + - 安全/合规要求(法律/财务层面影响显著时) | ||
| 228 | + | ||
| 229 | +**合理默认值示例**(这些通常不必询问用户): | ||
| 230 | + | ||
| 231 | +- 数据保留:该领域的行业通用做法 | ||
| 232 | +- 性能目标:除非用户明确要求,否则按常规 web/移动端应用预期 | ||
| 233 | +- 错误处理:用户友好的提示,并提供合适兜底 | ||
| 234 | +- 认证方式:web 应用默认使用 session 或 OAuth2 的常见方式 | ||
| 235 | +- 集成模式:除非另有说明,默认使用 RESTful API | ||
| 236 | + | ||
| 237 | +### “成功标准”编写指南 | ||
| 238 | + | ||
| 239 | +成功标准必须满足: | ||
| 240 | + | ||
| 241 | +1. **可度量**:包含具体指标(时间、百分比、数量、比率等) | ||
| 242 | +2. **与技术无关**:不提框架、语言、数据库或工具 | ||
| 243 | +3. **以用户为中心**:从用户/业务视角描述结果,而不是系统内部指标 | ||
| 244 | +4. **可验证**:不依赖实现细节也能测试/验证 | ||
| 245 | + | ||
| 246 | +**好的示例**: | ||
| 247 | + | ||
| 248 | +- "用户可在 3 分钟内完成结账" | ||
| 249 | +- "系统支持 10,000 并发用户" | ||
| 250 | +- "95% 的搜索在 1 秒内返回结果" | ||
| 251 | +- "任务完成率提升 40%" | ||
| 252 | + | ||
| 253 | +**不好的示例**(偏实现细节): | ||
| 254 | + | ||
| 255 | +- "API 响应时间低于 200ms"(太技术化;应改为“用户几乎瞬时看到结果”这类用户侧指标) | ||
| 256 | +- "数据库可处理 1000 TPS"(实现细节;应改为用户侧可感知/可验收的指标) | ||
| 257 | +- "React 组件渲染高效"(框架相关) | ||
| 258 | +- "Redis 缓存命中率高于 80%"(技术相关) |
.cursor/commands/speckit.tasks.md
0 → 100644
| 1 | +--- | ||
| 2 | +description: 基于现有设计产物,为该功能生成可执行、按依赖排序的 tasks.md。 | ||
| 3 | +handoffs: | ||
| 4 | + - label: 一致性分析 | ||
| 5 | + agent: speckit.analyze | ||
| 6 | + prompt: 执行项目一致性分析 | ||
| 7 | + send: true | ||
| 8 | + - label: 分阶段实现 | ||
| 9 | + agent: speckit.implement | ||
| 10 | + prompt: 按阶段开始实现 | ||
| 11 | + send: true | ||
| 12 | +--- | ||
| 13 | + | ||
| 14 | +## 用户输入 | ||
| 15 | + | ||
| 16 | +```text | ||
| 17 | +$ARGUMENTS | ||
| 18 | +``` | ||
| 19 | + | ||
| 20 | +在继续之前,你**必须**先考虑用户输入(如果不为空)。 | ||
| 21 | + | ||
| 22 | +## 概述 | ||
| 23 | + | ||
| 24 | +1. **准备**:在仓库根目录运行 `.specify/scripts/bash/check-prerequisites.sh --json` 并解析 FEATURE_DIR 与 AVAILABLE_DOCS 列表。所有路径必须是绝对路径。当参数里包含单引号(例如 "I'm Groot")时,使用转义写法:如 'I'\''m Groot'(或尽量用双引号:"I'm Groot")。 | ||
| 25 | + | ||
| 26 | +2. **加载设计文档**:从 FEATURE_DIR 读取: | ||
| 27 | + - **必需**:plan.md(技术栈、依赖库、结构)、spec.md(带优先级的用户故事) | ||
| 28 | + - **可选**:data-model.md(实体)、contracts/(API 端点)、research.md(决策)、quickstart.md(测试场景) | ||
| 29 | + - 注意:并非所有项目都有全部文档。应基于当前可用文档生成任务。 | ||
| 30 | + | ||
| 31 | +3. **执行任务生成流程**: | ||
| 32 | + - 加载 plan.md,提取技术栈、依赖库与项目结构 | ||
| 33 | + - 加载 spec.md,提取带优先级的用户故事(P1、P2、P3 等) | ||
| 34 | + - 如果存在 data-model.md:提取实体并映射到用户故事 | ||
| 35 | + - 如果存在 contracts/:将端点映射到用户故事 | ||
| 36 | + - 如果存在 research.md:提取决策用于初始化相关任务 | ||
| 37 | + - 按用户故事组织生成任务(见下方“任务生成规则”) | ||
| 38 | + - 生成依赖图,展示用户故事的完成顺序 | ||
| 39 | + - 为每个用户故事提供并行执行示例 | ||
| 40 | + - 校验任务完整性(每个用户故事包含所需任务,且可独立测试) | ||
| 41 | + | ||
| 42 | +4. **生成 tasks.md**:以 `.specify/templates/tasks-template.md` 为结构,填充: | ||
| 43 | + - 从 plan.md 读取正确的功能名称 | ||
| 44 | + - Phase 1:初始化任务(项目初始化) | ||
| 45 | + - Phase 2:基础任务(所有用户故事的阻塞前置) | ||
| 46 | + - Phase 3+:按 spec.md 的优先级为每个用户故事建一个阶段 | ||
| 47 | + - 每个阶段包含:故事目标、独立测试标准、测试(如有要求)、实现任务 | ||
| 48 | + - 最终阶段:打磨与横切关注点 | ||
| 49 | + - 所有任务必须遵循严格的清单格式(见下方“任务生成规则”) | ||
| 50 | + - 每个任务都要写清楚文件路径 | ||
| 51 | + - Dependencies 章节展示故事完成顺序 | ||
| 52 | + - 每个故事提供并行执行示例 | ||
| 53 | + - Implementation strategy 章节说明实现策略(先 MVP,逐步交付) | ||
| 54 | + | ||
| 55 | +5. **报告(Report)**:输出生成的 tasks.md 路径与摘要: | ||
| 56 | + - 任务总数 | ||
| 57 | + - 每个用户故事的任务数 | ||
| 58 | + - 识别到的并行机会 | ||
| 59 | + - 每个故事的独立测试标准 | ||
| 60 | + - 建议的 MVP 范围(通常只包含用户故事 1) | ||
| 61 | + - 格式校验:确认所有任务都遵循清单格式(checkbox、ID、标签、文件路径) | ||
| 62 | + | ||
| 63 | +用于任务生成的上下文:$ARGUMENTS | ||
| 64 | + | ||
| 65 | +tasks.md 应当可以直接执行——每个任务都必须足够具体,使得 LLM 在不依赖额外上下文的情况下也能完成。 | ||
| 66 | + | ||
| 67 | +## 任务生成规则 | ||
| 68 | + | ||
| 69 | +**关键(CRITICAL)**:任务必须按用户故事组织,以支持独立实现与独立测试。 | ||
| 70 | + | ||
| 71 | +**测试是可选项**:只有在 spec 明确要求,或用户要求 TDD 时才生成测试任务。 | ||
| 72 | + | ||
| 73 | +### 清单格式(必须) | ||
| 74 | + | ||
| 75 | +每个任务都必须严格遵循以下格式: | ||
| 76 | + | ||
| 77 | +```text | ||
| 78 | +- [ ] [TaskID] [P?] [Story?] 包含文件路径的任务描述 | ||
| 79 | +``` | ||
| 80 | + | ||
| 81 | +**格式组成**: | ||
| 82 | + | ||
| 83 | +1. **勾选框(Checkbox)**:必须以 `- [ ]` 开头(Markdown checkbox) | ||
| 84 | +2. **任务 ID(Task ID)**:按执行顺序递增编号(T001、T002、T003...) | ||
| 85 | +3. **[P] 标记**:仅当任务可并行时才包含(不同文件,且不依赖未完成任务) | ||
| 86 | +4. **[Story] 标签**:仅用于“用户故事阶段”的任务,且必须包含 | ||
| 87 | + - 格式:[US1]、[US2]、[US3] 等(映射 spec.md 的用户故事) | ||
| 88 | + - 初始化阶段:不加 story 标签 | ||
| 89 | + - 基础阶段:不加 story 标签 | ||
| 90 | + - 用户故事阶段:必须有 story 标签 | ||
| 91 | + - 打磨阶段:不加 story 标签 | ||
| 92 | +5. **描述(Description)**:清晰描述动作,并包含准确文件路径 | ||
| 93 | + | ||
| 94 | +**示例**: | ||
| 95 | + | ||
| 96 | +- ✅ 正确:`- [ ] T001 按实现计划创建项目结构` | ||
| 97 | +- ✅ 正确:`- [ ] T005 [P] 在 src/middleware/auth.py 实现认证中间件` | ||
| 98 | +- ✅ 正确:`- [ ] T012 [P] [US1] 在 src/models/user.py 创建 User 模型` | ||
| 99 | +- ✅ 正确:`- [ ] T014 [US1] 在 src/services/user_service.py 实现 UserService` | ||
| 100 | +- ❌ 错误:`- [ ] 创建用户模型`(缺少 ID 与 Story 标签) | ||
| 101 | +- ❌ 错误:`T001 [US1] 创建模型`(缺少 checkbox) | ||
| 102 | +- ❌ 错误:`- [ ] [US1] 创建用户模型`(缺少 Task ID) | ||
| 103 | +- ❌ 错误:`- [ ] T001 [US1] 创建模型`(缺少文件路径) | ||
| 104 | + | ||
| 105 | +### 任务组织方式 | ||
| 106 | + | ||
| 107 | +1. **来自用户故事(spec.md)——主要组织方式**: | ||
| 108 | + - 每个用户故事(P1、P2、P3...)对应一个阶段 | ||
| 109 | + - 将所有相关组件映射到对应的故事: | ||
| 110 | + - 该故事需要的模型 | ||
| 111 | + - 该故事需要的服务 | ||
| 112 | + - 该故事需要的端点/UI | ||
| 113 | + - 若需要测试:该故事专属的测试任务 | ||
| 114 | + - 标记故事依赖(大多数故事应尽量独立) | ||
| 115 | + | ||
| 116 | +2. **来自 contracts**: | ||
| 117 | + - 将每个 contract/endpoint 映射到它服务的用户故事 | ||
| 118 | + - 若需要测试:每个 contract 在该故事阶段中,先生成 contract 测试任务 [P],再实现 | ||
| 119 | + | ||
| 120 | +3. **来自数据模型**: | ||
| 121 | + - 将每个实体映射到需要它的用户故事 | ||
| 122 | + - 若实体服务多个故事:放入最早的故事阶段或初始化阶段 | ||
| 123 | + - 关系处理 → 放到对应故事阶段的服务层任务中 | ||
| 124 | + | ||
| 125 | +4. **来自初始化/基础设施**: | ||
| 126 | + - 共享基础设施 → 初始化阶段(Phase 1) | ||
| 127 | + - 基础/阻塞任务 → 基础阶段(Phase 2) | ||
| 128 | + - 故事特定的初始化 → 放到该故事阶段内 | ||
| 129 | + | ||
| 130 | +### 阶段结构 | ||
| 131 | + | ||
| 132 | +- **Phase 1**:初始化(项目初始化) | ||
| 133 | +- **Phase 2**:基础(阻塞前置;必须在用户故事之前完成) | ||
| 134 | +- **Phase 3+**:按优先级顺序的用户故事(P1、P2、P3...) | ||
| 135 | + - 每个故事内:测试(如需)→ 模型 → 服务 → 端点 → 集成 | ||
| 136 | + - 每个阶段都应是一个完整且可独立测试的增量 | ||
| 137 | +- **最终阶段**:打磨与横切关注点 |
.cursor/commands/speckit.taskstoissues.md
0 → 100644
| 1 | +--- | ||
| 2 | +description: 基于现有设计产物,将已有任务转换为可执行、按依赖排序的 GitHub issue。 | ||
| 3 | +tools: ['github/github-mcp-server/issue_write'] | ||
| 4 | +--- | ||
| 5 | + | ||
| 6 | +## 用户输入 | ||
| 7 | + | ||
| 8 | +```text | ||
| 9 | +$ARGUMENTS | ||
| 10 | +``` | ||
| 11 | + | ||
| 12 | +在继续之前,你**必须**先考虑用户输入(如果不为空)。 | ||
| 13 | + | ||
| 14 | +## 概述 | ||
| 15 | + | ||
| 16 | +1. 在仓库根目录运行 `.specify/scripts/bash/check-prerequisites.sh --json --require-tasks --include-tasks` 并解析 FEATURE_DIR 与 AVAILABLE_DOCS 列表。所有路径必须是绝对路径。当参数里包含单引号(例如 "I'm Groot")时,使用转义写法:如 'I'\''m Groot'(或尽量用双引号:"I'm Groot")。 | ||
| 17 | +2. 从脚本输出中提取 **tasks** 文件路径。 | ||
| 18 | +3. 通过以下命令获取 Git 远端地址(remote): | ||
| 19 | + | ||
| 20 | +```bash | ||
| 21 | +git config --get remote.origin.url | ||
| 22 | +``` | ||
| 23 | + | ||
| 24 | +> [!CAUTION] | ||
| 25 | +> 只有当 remote 是 GitHub URL 时才允许继续后续步骤 | ||
| 26 | + | ||
| 27 | +4. 对任务列表中的每个任务,使用 GitHub MCP server 在与该 remote 相匹配的仓库中创建一个新 issue。 | ||
| 28 | + | ||
| 29 | +> [!CAUTION] | ||
| 30 | +> 任何情况下都不要在与 remote URL 不匹配的仓库里创建 issue |
| 1 | -## 项目原则 | 1 | +# mlaj 宪法 |
| 2 | + | ||
| 3 | +## 核心原则 | ||
| 4 | + | ||
| 5 | +### 用户需求为中心 | ||
| 2 | 6 | ||
| 3 | - 以用户需求为中心,优先实现最小可用闭环,避免过度设计 | 7 | - 以用户需求为中心,优先实现最小可用闭环,避免过度设计 |
| 8 | + | ||
| 9 | +### 修改尽量小且可回滚 | ||
| 10 | + | ||
| 4 | - 修改应尽量小且可回滚,避免大范围重构 | 11 | - 修改应尽量小且可回滚,避免大范围重构 |
| 12 | + | ||
| 13 | +### 风格一致与复用优先 | ||
| 14 | + | ||
| 5 | - 保持现有代码风格与目录结构一致,优先复用已有工具与组件 | 15 | - 保持现有代码风格与目录结构一致,优先复用已有工具与组件 |
| 16 | + | ||
| 17 | +### 生产代码完毕后不要重启服务 | ||
| 18 | + | ||
| 6 | - 生产代码完毕后不要重启服务 | 19 | - 生产代码完毕后不要重启服务 |
| 20 | + | ||
| 21 | +### 操作完成后不要自动打开预览 | ||
| 22 | + | ||
| 7 | - 操作完成后不要自动打开预览 | 23 | - 操作完成后不要自动打开预览 |
| 8 | 24 | ||
| 9 | ## 技术与工程约束 | 25 | ## 技术与工程约束 |
| ... | @@ -26,3 +42,9 @@ | ... | @@ -26,3 +42,9 @@ |
| 26 | 42 | ||
| 27 | - 若图片域名为 cdn.ipadbiz.cn,需要自动追加 ?imageMogr2/thumbnail/200x/strip/quality/70 | 43 | - 若图片域名为 cdn.ipadbiz.cn,需要自动追加 ?imageMogr2/thumbnail/200x/strip/quality/70 |
| 28 | 44 | ||
| 45 | +## 治理与管理 | ||
| 46 | + | ||
| 47 | +- 本宪法优先级高于临时约定与个人习惯;出现冲突时以本宪法为准 | ||
| 48 | +- 宪法变更应同步更新版本/日期信息,并确保对现有工程约束的兼容性 | ||
| 49 | + | ||
| 50 | +**版本**: [CONSTITUTION_VERSION] | **批准**: [RATIFICATION_DATE] | **最后修订**: [LAST_AMENDED_DATE] | ... | ... |
.specify/scripts/bash/check-prerequisites.sh
0 → 100755
| 1 | +#!/usr/bin/env bash | ||
| 2 | + | ||
| 3 | +# 前置条件检查脚本(整合版) | ||
| 4 | +# | ||
| 5 | +# 本脚本为 Spec-Driven Development 工作流提供统一的前置条件检查, | ||
| 6 | +# 用于替代此前分散在多个脚本中的同类功能。 | ||
| 7 | +# | ||
| 8 | +# 用法:./check-prerequisites.sh [OPTIONS] | ||
| 9 | +# | ||
| 10 | +# OPTIONS: | ||
| 11 | +# --json 以 JSON 格式输出 | ||
| 12 | +# --require-tasks 要求 tasks.md 必须存在(实现阶段使用) | ||
| 13 | +# --include-tasks 在 AVAILABLE_DOCS 列表中包含 tasks.md | ||
| 14 | +# --paths-only 仅输出路径变量(不做校验) | ||
| 15 | +# --help, -h 显示帮助信息 | ||
| 16 | +# | ||
| 17 | +# 输出: | ||
| 18 | +# JSON 模式:{"FEATURE_DIR":"...", "AVAILABLE_DOCS":["..."]} | ||
| 19 | +# 文本模式:FEATURE_DIR:... \n AVAILABLE_DOCS: \n ✓/✗ file.md | ||
| 20 | +# 仅路径:REPO_ROOT: ... \n BRANCH: ... \n FEATURE_DIR: ... 等 | ||
| 21 | + | ||
| 22 | +set -e | ||
| 23 | + | ||
| 24 | +# 解析命令行参数 | ||
| 25 | +JSON_MODE=false | ||
| 26 | +REQUIRE_TASKS=false | ||
| 27 | +INCLUDE_TASKS=false | ||
| 28 | +PATHS_ONLY=false | ||
| 29 | + | ||
| 30 | +for arg in "$@"; do | ||
| 31 | + case "$arg" in | ||
| 32 | + --json) | ||
| 33 | + JSON_MODE=true | ||
| 34 | + ;; | ||
| 35 | + --require-tasks) | ||
| 36 | + REQUIRE_TASKS=true | ||
| 37 | + ;; | ||
| 38 | + --include-tasks) | ||
| 39 | + INCLUDE_TASKS=true | ||
| 40 | + ;; | ||
| 41 | + --paths-only) | ||
| 42 | + PATHS_ONLY=true | ||
| 43 | + ;; | ||
| 44 | + --help|-h) | ||
| 45 | + cat << 'EOF' | ||
| 46 | +用法:check-prerequisites.sh [OPTIONS] | ||
| 47 | + | ||
| 48 | +用于 Spec-Driven Development 工作流的前置条件检查(整合版)。 | ||
| 49 | + | ||
| 50 | +选项: | ||
| 51 | + --json 以 JSON 格式输出 | ||
| 52 | + --require-tasks 要求 tasks.md 必须存在(实现阶段使用) | ||
| 53 | + --include-tasks 在 AVAILABLE_DOCS 列表中包含 tasks.md | ||
| 54 | + --paths-only 仅输出路径变量(不做前置条件校验) | ||
| 55 | + --help, -h 显示帮助信息 | ||
| 56 | + | ||
| 57 | +示例: | ||
| 58 | + # 检查任务前置条件(需要 plan.md) | ||
| 59 | + ./check-prerequisites.sh --json | ||
| 60 | + | ||
| 61 | + # 检查实现前置条件(需要 plan.md + tasks.md) | ||
| 62 | + ./check-prerequisites.sh --json --require-tasks --include-tasks | ||
| 63 | + | ||
| 64 | + # 仅获取功能路径(不做校验) | ||
| 65 | + ./check-prerequisites.sh --paths-only | ||
| 66 | + | ||
| 67 | +EOF | ||
| 68 | + exit 0 | ||
| 69 | + ;; | ||
| 70 | + *) | ||
| 71 | + echo "错误:未知选项 '$arg'。使用 --help 查看用法。" >&2 | ||
| 72 | + exit 1 | ||
| 73 | + ;; | ||
| 74 | + esac | ||
| 75 | +done | ||
| 76 | + | ||
| 77 | +# 引入通用函数 | ||
| 78 | +SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | ||
| 79 | +source "$SCRIPT_DIR/common.sh" | ||
| 80 | + | ||
| 81 | +# 获取功能路径并校验分支 | ||
| 82 | +eval $(get_feature_paths) | ||
| 83 | +check_feature_branch "$CURRENT_BRANCH" "$HAS_GIT" || exit 1 | ||
| 84 | + | ||
| 85 | +# 若为仅路径模式:输出路径后退出(支持 --json 与 --paths-only 组合) | ||
| 86 | +if $PATHS_ONLY; then | ||
| 87 | + if $JSON_MODE; then | ||
| 88 | + # 最小 JSON 路径负载(不做校验) | ||
| 89 | + printf '{"REPO_ROOT":"%s","BRANCH":"%s","FEATURE_DIR":"%s","FEATURE_SPEC":"%s","IMPL_PLAN":"%s","TASKS":"%s"}\n' \ | ||
| 90 | + "$REPO_ROOT" "$CURRENT_BRANCH" "$FEATURE_DIR" "$FEATURE_SPEC" "$IMPL_PLAN" "$TASKS" | ||
| 91 | + else | ||
| 92 | + echo "REPO_ROOT: $REPO_ROOT" | ||
| 93 | + echo "BRANCH: $CURRENT_BRANCH" | ||
| 94 | + echo "FEATURE_DIR: $FEATURE_DIR" | ||
| 95 | + echo "FEATURE_SPEC: $FEATURE_SPEC" | ||
| 96 | + echo "IMPL_PLAN: $IMPL_PLAN" | ||
| 97 | + echo "TASKS: $TASKS" | ||
| 98 | + fi | ||
| 99 | + exit 0 | ||
| 100 | +fi | ||
| 101 | + | ||
| 102 | +# 校验必要的目录与文件 | ||
| 103 | +if [[ ! -d "$FEATURE_DIR" ]]; then | ||
| 104 | + echo "错误:未找到功能目录:$FEATURE_DIR" >&2 | ||
| 105 | + echo "请先运行 /speckit.specify 以创建功能目录结构。" >&2 | ||
| 106 | + exit 1 | ||
| 107 | +fi | ||
| 108 | + | ||
| 109 | +if [[ ! -f "$IMPL_PLAN" ]]; then | ||
| 110 | + echo "错误:在 $FEATURE_DIR 中未找到 plan.md" >&2 | ||
| 111 | + echo "请先运行 /speckit.plan 以创建实现计划。" >&2 | ||
| 112 | + exit 1 | ||
| 113 | +fi | ||
| 114 | + | ||
| 115 | +# 如果需要则检查 tasks.md | ||
| 116 | +if $REQUIRE_TASKS && [[ ! -f "$TASKS" ]]; then | ||
| 117 | + echo "错误:在 $FEATURE_DIR 中未找到 tasks.md" >&2 | ||
| 118 | + echo "请先运行 /speckit.tasks 以创建任务清单。" >&2 | ||
| 119 | + exit 1 | ||
| 120 | +fi | ||
| 121 | + | ||
| 122 | +# 构建可用文档列表 | ||
| 123 | +docs=() | ||
| 124 | + | ||
| 125 | +# 固定检查这些可选文档 | ||
| 126 | +[[ -f "$RESEARCH" ]] && docs+=("research.md") | ||
| 127 | +[[ -f "$DATA_MODEL" ]] && docs+=("data-model.md") | ||
| 128 | + | ||
| 129 | +# 检查 contracts 目录(仅当存在且非空) | ||
| 130 | +if [[ -d "$CONTRACTS_DIR" ]] && [[ -n "$(ls -A "$CONTRACTS_DIR" 2>/dev/null)" ]]; then | ||
| 131 | + docs+=("contracts/") | ||
| 132 | +fi | ||
| 133 | + | ||
| 134 | +[[ -f "$QUICKSTART" ]] && docs+=("quickstart.md") | ||
| 135 | + | ||
| 136 | +# 若要求包含 tasks.md,且文件存在,则加入列表 | ||
| 137 | +if $INCLUDE_TASKS && [[ -f "$TASKS" ]]; then | ||
| 138 | + docs+=("tasks.md") | ||
| 139 | +fi | ||
| 140 | + | ||
| 141 | +# 输出结果 | ||
| 142 | +if $JSON_MODE; then | ||
| 143 | + # 构建文档 JSON 数组 | ||
| 144 | + if [[ ${#docs[@]} -eq 0 ]]; then | ||
| 145 | + json_docs="[]" | ||
| 146 | + else | ||
| 147 | + json_docs=$(printf '"%s",' "${docs[@]}") | ||
| 148 | + json_docs="[${json_docs%,}]" | ||
| 149 | + fi | ||
| 150 | + | ||
| 151 | + printf '{"FEATURE_DIR":"%s","AVAILABLE_DOCS":%s}\n' "$FEATURE_DIR" "$json_docs" | ||
| 152 | +else | ||
| 153 | + # 文本输出 | ||
| 154 | + echo "FEATURE_DIR:$FEATURE_DIR" | ||
| 155 | + echo "AVAILABLE_DOCS:" | ||
| 156 | + | ||
| 157 | + # 展示各文档的存在状态 | ||
| 158 | + check_file "$RESEARCH" "research.md" | ||
| 159 | + check_file "$DATA_MODEL" "data-model.md" | ||
| 160 | + check_dir "$CONTRACTS_DIR" "contracts/" | ||
| 161 | + check_file "$QUICKSTART" "quickstart.md" | ||
| 162 | + | ||
| 163 | + if $INCLUDE_TASKS; then | ||
| 164 | + check_file "$TASKS" "tasks.md" | ||
| 165 | + fi | ||
| 166 | +fi |
.specify/scripts/bash/common.sh
0 → 100755
| 1 | +#!/usr/bin/env bash | ||
| 2 | +# 所有脚本共用的函数与变量 | ||
| 3 | + | ||
| 4 | +# 获取仓库根目录(非 git 仓库时提供兜底) | ||
| 5 | +get_repo_root() { | ||
| 6 | + if git rev-parse --show-toplevel >/dev/null 2>&1; then | ||
| 7 | + git rev-parse --show-toplevel | ||
| 8 | + else | ||
| 9 | + # 非 git 仓库:回退为脚本所在目录向上查找 | ||
| 10 | + local script_dir="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | ||
| 11 | + (cd "$script_dir/../../.." && pwd) | ||
| 12 | + fi | ||
| 13 | +} | ||
| 14 | + | ||
| 15 | +# 获取当前分支(非 git 仓库时提供兜底) | ||
| 16 | +get_current_branch() { | ||
| 17 | + # 优先使用 SPECIFY_FEATURE 环境变量 | ||
| 18 | + if [[ -n "${SPECIFY_FEATURE:-}" ]]; then | ||
| 19 | + echo "$SPECIFY_FEATURE" | ||
| 20 | + return | ||
| 21 | + fi | ||
| 22 | + | ||
| 23 | + # 其次尝试从 git 读取 | ||
| 24 | + if git rev-parse --abbrev-ref HEAD >/dev/null 2>&1; then | ||
| 25 | + git rev-parse --abbrev-ref HEAD | ||
| 26 | + return | ||
| 27 | + fi | ||
| 28 | + | ||
| 29 | + # 非 git 仓库:尝试找到最新的功能目录 | ||
| 30 | + local repo_root=$(get_repo_root) | ||
| 31 | + local specs_dir="$repo_root/specs" | ||
| 32 | + | ||
| 33 | + if [[ -d "$specs_dir" ]]; then | ||
| 34 | + local latest_feature="" | ||
| 35 | + local highest=0 | ||
| 36 | + | ||
| 37 | + for dir in "$specs_dir"/*; do | ||
| 38 | + if [[ -d "$dir" ]]; then | ||
| 39 | + local dirname=$(basename "$dir") | ||
| 40 | + if [[ "$dirname" =~ ^([0-9]{3})- ]]; then | ||
| 41 | + local number=${BASH_REMATCH[1]} | ||
| 42 | + number=$((10#$number)) | ||
| 43 | + if [[ "$number" -gt "$highest" ]]; then | ||
| 44 | + highest=$number | ||
| 45 | + latest_feature=$dirname | ||
| 46 | + fi | ||
| 47 | + fi | ||
| 48 | + fi | ||
| 49 | + done | ||
| 50 | + | ||
| 51 | + if [[ -n "$latest_feature" ]]; then | ||
| 52 | + echo "$latest_feature" | ||
| 53 | + return | ||
| 54 | + fi | ||
| 55 | + fi | ||
| 56 | + | ||
| 57 | + echo "main" # 最终兜底 | ||
| 58 | +} | ||
| 59 | + | ||
| 60 | +# 检查是否为 git 仓库 | ||
| 61 | +has_git() { | ||
| 62 | + git rev-parse --show-toplevel >/dev/null 2>&1 | ||
| 63 | +} | ||
| 64 | + | ||
| 65 | +check_feature_branch() { | ||
| 66 | + local branch="$1" | ||
| 67 | + local has_git_repo="$2" | ||
| 68 | + | ||
| 69 | + # 非 git 仓库无法强制分支命名,但仍输出提示 | ||
| 70 | + if [[ "$has_git_repo" != "true" ]]; then | ||
| 71 | + echo "[specify] 警告:未检测到 Git 仓库;已跳过分支校验" >&2 | ||
| 72 | + return 0 | ||
| 73 | + fi | ||
| 74 | + | ||
| 75 | + if [[ ! "$branch" =~ ^[0-9]{3}- ]]; then | ||
| 76 | + echo "错误:当前不在功能分支。当前分支:$branch" >&2 | ||
| 77 | + echo "功能分支命名应类似:001-feature-name" >&2 | ||
| 78 | + return 1 | ||
| 79 | + fi | ||
| 80 | + | ||
| 81 | + return 0 | ||
| 82 | +} | ||
| 83 | + | ||
| 84 | +get_feature_dir() { echo "$1/specs/$2"; } | ||
| 85 | + | ||
| 86 | +# 按数字前缀查找功能目录,而不是严格按分支名匹配 | ||
| 87 | +# 这样允许多个分支指向同一份规格(例如 004-fix-bug、004-add-feature) | ||
| 88 | +find_feature_dir_by_prefix() { | ||
| 89 | + local repo_root="$1" | ||
| 90 | + local branch_name="$2" | ||
| 91 | + local specs_dir="$repo_root/specs" | ||
| 92 | + | ||
| 93 | + # 从分支名提取数字前缀(例如从 "004-whatever" 提取 "004") | ||
| 94 | + if [[ ! "$branch_name" =~ ^([0-9]{3})- ]]; then | ||
| 95 | + # 分支名不含数字前缀:回退为按分支名拼路径 | ||
| 96 | + echo "$specs_dir/$branch_name" | ||
| 97 | + return | ||
| 98 | + fi | ||
| 99 | + | ||
| 100 | + local prefix="${BASH_REMATCH[1]}" | ||
| 101 | + | ||
| 102 | + # 在 specs/ 下查找以该前缀开头的目录 | ||
| 103 | + local matches=() | ||
| 104 | + if [[ -d "$specs_dir" ]]; then | ||
| 105 | + for dir in "$specs_dir"/"$prefix"-*; do | ||
| 106 | + if [[ -d "$dir" ]]; then | ||
| 107 | + matches+=("$(basename "$dir")") | ||
| 108 | + fi | ||
| 109 | + done | ||
| 110 | + fi | ||
| 111 | + | ||
| 112 | + # 处理查找结果 | ||
| 113 | + if [[ ${#matches[@]} -eq 0 ]]; then | ||
| 114 | + # 未找到:返回按分支名拼的路径(后续会给出更明确的错误) | ||
| 115 | + echo "$specs_dir/$branch_name" | ||
| 116 | + elif [[ ${#matches[@]} -eq 1 ]]; then | ||
| 117 | + # 仅一个匹配:直接返回 | ||
| 118 | + echo "$specs_dir/${matches[0]}" | ||
| 119 | + else | ||
| 120 | + # 多个匹配:命名规范正确时不应发生 | ||
| 121 | + echo "错误:发现多个同前缀规格目录 '$prefix':${matches[*]}" >&2 | ||
| 122 | + echo "请确保每个数字前缀只对应一个规格目录。" >&2 | ||
| 123 | + echo "$specs_dir/$branch_name" # 返回一个路径以避免脚本中断 | ||
| 124 | + fi | ||
| 125 | +} | ||
| 126 | + | ||
| 127 | +get_feature_paths() { | ||
| 128 | + local repo_root=$(get_repo_root) | ||
| 129 | + local current_branch=$(get_current_branch) | ||
| 130 | + local has_git_repo="false" | ||
| 131 | + | ||
| 132 | + if has_git; then | ||
| 133 | + has_git_repo="true" | ||
| 134 | + fi | ||
| 135 | + | ||
| 136 | + # 使用前缀匹配以支持“多分支对应同一份规格”的场景 | ||
| 137 | + local feature_dir=$(find_feature_dir_by_prefix "$repo_root" "$current_branch") | ||
| 138 | + | ||
| 139 | + cat <<EOF | ||
| 140 | +REPO_ROOT='$repo_root' | ||
| 141 | +CURRENT_BRANCH='$current_branch' | ||
| 142 | +HAS_GIT='$has_git_repo' | ||
| 143 | +FEATURE_DIR='$feature_dir' | ||
| 144 | +FEATURE_SPEC='$feature_dir/spec.md' | ||
| 145 | +IMPL_PLAN='$feature_dir/plan.md' | ||
| 146 | +TASKS='$feature_dir/tasks.md' | ||
| 147 | +RESEARCH='$feature_dir/research.md' | ||
| 148 | +DATA_MODEL='$feature_dir/data-model.md' | ||
| 149 | +QUICKSTART='$feature_dir/quickstart.md' | ||
| 150 | +CONTRACTS_DIR='$feature_dir/contracts' | ||
| 151 | +EOF | ||
| 152 | +} | ||
| 153 | + | ||
| 154 | +check_file() { [[ -f "$1" ]] && echo " ✓ $2" || echo " ✗ $2"; } | ||
| 155 | +check_dir() { [[ -d "$1" && -n $(ls -A "$1" 2>/dev/null) ]] && echo " ✓ $2" || echo " ✗ $2"; } |
.specify/scripts/bash/create-new-feature.sh
0 → 100755
| 1 | +#!/usr/bin/env bash | ||
| 2 | + | ||
| 3 | +set -e | ||
| 4 | + | ||
| 5 | +JSON_MODE=false | ||
| 6 | +SHORT_NAME="" | ||
| 7 | +BRANCH_NUMBER="" | ||
| 8 | +ARGS=() | ||
| 9 | +i=1 | ||
| 10 | +while [ $i -le $# ]; do | ||
| 11 | + arg="${!i}" | ||
| 12 | + case "$arg" in | ||
| 13 | + --json) | ||
| 14 | + JSON_MODE=true | ||
| 15 | + ;; | ||
| 16 | + --short-name) | ||
| 17 | + if [ $((i + 1)) -gt $# ]; then | ||
| 18 | + echo '错误:--short-name 需要提供值' >&2 | ||
| 19 | + exit 1 | ||
| 20 | + fi | ||
| 21 | + i=$((i + 1)) | ||
| 22 | + next_arg="${!i}" | ||
| 23 | + # 检查下一个参数是否为另一个选项(以 -- 开头) | ||
| 24 | + if [[ "$next_arg" == --* ]]; then | ||
| 25 | + echo '错误:--short-name 需要提供值' >&2 | ||
| 26 | + exit 1 | ||
| 27 | + fi | ||
| 28 | + SHORT_NAME="$next_arg" | ||
| 29 | + ;; | ||
| 30 | + --number) | ||
| 31 | + if [ $((i + 1)) -gt $# ]; then | ||
| 32 | + echo '错误:--number 需要提供值' >&2 | ||
| 33 | + exit 1 | ||
| 34 | + fi | ||
| 35 | + i=$((i + 1)) | ||
| 36 | + next_arg="${!i}" | ||
| 37 | + if [[ "$next_arg" == --* ]]; then | ||
| 38 | + echo '错误:--number 需要提供值' >&2 | ||
| 39 | + exit 1 | ||
| 40 | + fi | ||
| 41 | + BRANCH_NUMBER="$next_arg" | ||
| 42 | + ;; | ||
| 43 | + --help|-h) | ||
| 44 | + echo "用法:$0 [--json] [--short-name <短名>] [--number N] <功能描述>" | ||
| 45 | + echo "" | ||
| 46 | + echo "选项:" | ||
| 47 | + echo " --json 以 JSON 格式输出" | ||
| 48 | + echo " --short-name <短名> 为分支提供自定义短名(2-4 个单词)" | ||
| 49 | + echo " --number N 手动指定分支编号(覆盖自动检测)" | ||
| 50 | + echo " --help, -h 显示帮助信息" | ||
| 51 | + echo "" | ||
| 52 | + echo "示例:" | ||
| 53 | + echo " $0 '新增用户登录与鉴权能力' --short-name 'user-auth'" | ||
| 54 | + echo " $0 '为 API 接入 OAuth2 登录' --number 5" | ||
| 55 | + exit 0 | ||
| 56 | + ;; | ||
| 57 | + *) | ||
| 58 | + ARGS+=("$arg") | ||
| 59 | + ;; | ||
| 60 | + esac | ||
| 61 | + i=$((i + 1)) | ||
| 62 | +done | ||
| 63 | + | ||
| 64 | +FEATURE_DESCRIPTION="${ARGS[*]}" | ||
| 65 | +if [ -z "$FEATURE_DESCRIPTION" ]; then | ||
| 66 | + echo "用法:$0 [--json] [--short-name <短名>] [--number N] <功能描述>" >&2 | ||
| 67 | + exit 1 | ||
| 68 | +fi | ||
| 69 | + | ||
| 70 | +# 通过查找项目标记来定位仓库根目录 | ||
| 71 | +find_repo_root() { | ||
| 72 | + local dir="$1" | ||
| 73 | + while [ "$dir" != "/" ]; do | ||
| 74 | + if [ -d "$dir/.git" ] || [ -d "$dir/.specify" ]; then | ||
| 75 | + echo "$dir" | ||
| 76 | + return 0 | ||
| 77 | + fi | ||
| 78 | + dir="$(dirname "$dir")" | ||
| 79 | + done | ||
| 80 | + return 1 | ||
| 81 | +} | ||
| 82 | + | ||
| 83 | +# 从 specs 目录中获取最大编号 | ||
| 84 | +get_highest_from_specs() { | ||
| 85 | + local specs_dir="$1" | ||
| 86 | + local highest=0 | ||
| 87 | + | ||
| 88 | + if [ -d "$specs_dir" ]; then | ||
| 89 | + for dir in "$specs_dir"/*; do | ||
| 90 | + [ -d "$dir" ] || continue | ||
| 91 | + dirname=$(basename "$dir") | ||
| 92 | + number=$(echo "$dirname" | grep -o '^[0-9]\+' || echo "0") | ||
| 93 | + number=$((10#$number)) | ||
| 94 | + if [ "$number" -gt "$highest" ]; then | ||
| 95 | + highest=$number | ||
| 96 | + fi | ||
| 97 | + done | ||
| 98 | + fi | ||
| 99 | + | ||
| 100 | + echo "$highest" | ||
| 101 | +} | ||
| 102 | + | ||
| 103 | +# 从 git 分支中获取最大编号 | ||
| 104 | +get_highest_from_branches() { | ||
| 105 | + local highest=0 | ||
| 106 | + | ||
| 107 | + # 获取所有分支(本地 + 远端) | ||
| 108 | + branches=$(git branch -a 2>/dev/null || echo "") | ||
| 109 | + | ||
| 110 | + if [ -n "$branches" ]; then | ||
| 111 | + while IFS= read -r branch; do | ||
| 112 | + # 清理分支名:移除前缀标记与远端前缀 | ||
| 113 | + clean_branch=$(echo "$branch" | sed 's/^[* ]*//; s|^remotes/[^/]*/||') | ||
| 114 | + | ||
| 115 | + # 若分支符合 ###-*,则提取功能编号 | ||
| 116 | + if echo "$clean_branch" | grep -q '^[0-9]\{3\}-'; then | ||
| 117 | + number=$(echo "$clean_branch" | grep -o '^[0-9]\{3\}' || echo "0") | ||
| 118 | + number=$((10#$number)) | ||
| 119 | + if [ "$number" -gt "$highest" ]; then | ||
| 120 | + highest=$number | ||
| 121 | + fi | ||
| 122 | + fi | ||
| 123 | + done <<< "$branches" | ||
| 124 | + fi | ||
| 125 | + | ||
| 126 | + echo "$highest" | ||
| 127 | +} | ||
| 128 | + | ||
| 129 | +# 检查现有分支(本地 + 远端)并返回下一个可用编号 | ||
| 130 | +check_existing_branches() { | ||
| 131 | + local specs_dir="$1" | ||
| 132 | + | ||
| 133 | + # 拉取所有远端以获取最新分支信息(无远端时忽略错误) | ||
| 134 | + git fetch --all --prune 2>/dev/null || true | ||
| 135 | + | ||
| 136 | + # 获取所有分支中的最大编号(不局限于匹配 short name) | ||
| 137 | + local highest_branch=$(get_highest_from_branches) | ||
| 138 | + | ||
| 139 | + # 获取所有 specs 中的最大编号(不局限于匹配 short name) | ||
| 140 | + local highest_spec=$(get_highest_from_specs "$specs_dir") | ||
| 141 | + | ||
| 142 | + # 两者取最大值 | ||
| 143 | + local max_num=$highest_branch | ||
| 144 | + if [ "$highest_spec" -gt "$max_num" ]; then | ||
| 145 | + max_num=$highest_spec | ||
| 146 | + fi | ||
| 147 | + | ||
| 148 | + # 返回下一个编号 | ||
| 149 | + echo $((max_num + 1)) | ||
| 150 | +} | ||
| 151 | + | ||
| 152 | +# 清理并格式化分支名 | ||
| 153 | +clean_branch_name() { | ||
| 154 | + local name="$1" | ||
| 155 | + echo "$name" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/-\+/-/g' | sed 's/^-//' | sed 's/-$//' | ||
| 156 | +} | ||
| 157 | + | ||
| 158 | +# 解析仓库根目录:优先使用 git 信息;如不可用则回退为查找仓库标记, | ||
| 159 | +# 以兼容使用 --no-git 初始化的仓库。 | ||
| 160 | +SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | ||
| 161 | + | ||
| 162 | +if git rev-parse --show-toplevel >/dev/null 2>&1; then | ||
| 163 | + REPO_ROOT=$(git rev-parse --show-toplevel) | ||
| 164 | + HAS_GIT=true | ||
| 165 | +else | ||
| 166 | + REPO_ROOT="$(find_repo_root "$SCRIPT_DIR")" | ||
| 167 | + if [ -z "$REPO_ROOT" ]; then | ||
| 168 | + echo "错误:无法确定仓库根目录。请在仓库内运行本脚本。" >&2 | ||
| 169 | + exit 1 | ||
| 170 | + fi | ||
| 171 | + HAS_GIT=false | ||
| 172 | +fi | ||
| 173 | + | ||
| 174 | +cd "$REPO_ROOT" | ||
| 175 | + | ||
| 176 | +SPECS_DIR="$REPO_ROOT/specs" | ||
| 177 | +mkdir -p "$SPECS_DIR" | ||
| 178 | + | ||
| 179 | +# 生成分支名:包含停用词过滤与长度过滤 | ||
| 180 | +generate_branch_name() { | ||
| 181 | + local description="$1" | ||
| 182 | + | ||
| 183 | + # 常见停用词(用于过滤) | ||
| 184 | + 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)$" | ||
| 185 | + | ||
| 186 | + # 转小写并按单词拆分 | ||
| 187 | + local clean_name=$(echo "$description" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/ /g') | ||
| 188 | + | ||
| 189 | + # 过滤单词:移除停用词与长度 < 3 的词(除非在原描述中以大写出现,通常为缩写) | ||
| 190 | + local meaningful_words=() | ||
| 191 | + for word in $clean_name; do | ||
| 192 | + # 跳过空词 | ||
| 193 | + [ -z "$word" ] && continue | ||
| 194 | + | ||
| 195 | + # 保留:不属于停用词,并且(长度 >= 3 或为可能的缩写) | ||
| 196 | + if ! echo "$word" | grep -qiE "$stop_words"; then | ||
| 197 | + if [ ${#word} -ge 3 ]; then | ||
| 198 | + meaningful_words+=("$word") | ||
| 199 | + elif echo "$description" | grep -q "\b${word^^}\b"; then | ||
| 200 | + # 原描述中若以大写出现则保留(很可能是缩写) | ||
| 201 | + meaningful_words+=("$word") | ||
| 202 | + fi | ||
| 203 | + fi | ||
| 204 | + done | ||
| 205 | + | ||
| 206 | + # 若有有效词,则取前 3-4 个 | ||
| 207 | + if [ ${#meaningful_words[@]} -gt 0 ]; then | ||
| 208 | + local max_words=3 | ||
| 209 | + if [ ${#meaningful_words[@]} -eq 4 ]; then max_words=4; fi | ||
| 210 | + | ||
| 211 | + local result="" | ||
| 212 | + local count=0 | ||
| 213 | + for word in "${meaningful_words[@]}"; do | ||
| 214 | + if [ $count -ge $max_words ]; then break; fi | ||
| 215 | + if [ -n "$result" ]; then result="$result-"; fi | ||
| 216 | + result="$result$word" | ||
| 217 | + count=$((count + 1)) | ||
| 218 | + done | ||
| 219 | + echo "$result" | ||
| 220 | + else | ||
| 221 | + # 若未找到有效词:回退为原始逻辑 | ||
| 222 | + local cleaned=$(clean_branch_name "$description") | ||
| 223 | + echo "$cleaned" | tr '-' '\n' | grep -v '^$' | head -3 | tr '\n' '-' | sed 's/-$//' | ||
| 224 | + fi | ||
| 225 | +} | ||
| 226 | + | ||
| 227 | +# 生成分支名 | ||
| 228 | +if [ -n "$SHORT_NAME" ]; then | ||
| 229 | + # 使用用户提供的 short name,并做清理 | ||
| 230 | + BRANCH_SUFFIX=$(clean_branch_name "$SHORT_NAME") | ||
| 231 | +else | ||
| 232 | + # 根据描述智能过滤生成 | ||
| 233 | + BRANCH_SUFFIX=$(generate_branch_name "$FEATURE_DESCRIPTION") | ||
| 234 | +fi | ||
| 235 | + | ||
| 236 | +# 确定分支编号 | ||
| 237 | +if [ -z "$BRANCH_NUMBER" ]; then | ||
| 238 | + if [ "$HAS_GIT" = true ]; then | ||
| 239 | + # 检查远端已有分支 | ||
| 240 | + BRANCH_NUMBER=$(check_existing_branches "$SPECS_DIR") | ||
| 241 | + else | ||
| 242 | + # 回退为本地目录检查 | ||
| 243 | + HIGHEST=$(get_highest_from_specs "$SPECS_DIR") | ||
| 244 | + BRANCH_NUMBER=$((HIGHEST + 1)) | ||
| 245 | + fi | ||
| 246 | +fi | ||
| 247 | + | ||
| 248 | +# 强制按十进制解释,避免八进制转换(例如 010 在八进制会变为 8,但这里应是十进制 10) | ||
| 249 | +FEATURE_NUM=$(printf "%03d" "$((10#$BRANCH_NUMBER))") | ||
| 250 | +BRANCH_NAME="${FEATURE_NUM}-${BRANCH_SUFFIX}" | ||
| 251 | + | ||
| 252 | +# GitHub 对分支名有 244 字节限制:必要时校验并截断 | ||
| 253 | +MAX_BRANCH_LENGTH=244 | ||
| 254 | +if [ ${#BRANCH_NAME} -gt $MAX_BRANCH_LENGTH ]; then | ||
| 255 | + # 计算需要从后缀中截断的长度 | ||
| 256 | + # 分支名包含:功能编号(3)+ 连字符(1)= 4 个字符 | ||
| 257 | + MAX_SUFFIX_LENGTH=$((MAX_BRANCH_LENGTH - 4)) | ||
| 258 | + | ||
| 259 | + # 尽量按单词边界截断后缀 | ||
| 260 | + TRUNCATED_SUFFIX=$(echo "$BRANCH_SUFFIX" | cut -c1-$MAX_SUFFIX_LENGTH) | ||
| 261 | + # 若截断产生了末尾连字符则移除 | ||
| 262 | + TRUNCATED_SUFFIX=$(echo "$TRUNCATED_SUFFIX" | sed 's/-$//') | ||
| 263 | + | ||
| 264 | + ORIGINAL_BRANCH_NAME="$BRANCH_NAME" | ||
| 265 | + BRANCH_NAME="${FEATURE_NUM}-${TRUNCATED_SUFFIX}" | ||
| 266 | + | ||
| 267 | + >&2 echo "[specify] Warning: 分支名超过 GitHub 的 244 字节限制" | ||
| 268 | + >&2 echo "[specify] 原始:$ORIGINAL_BRANCH_NAME(${#ORIGINAL_BRANCH_NAME} 字节)" | ||
| 269 | + >&2 echo "[specify] 已截断为:$BRANCH_NAME(${#BRANCH_NAME} 字节)" | ||
| 270 | +fi | ||
| 271 | + | ||
| 272 | +if [ "$HAS_GIT" = true ]; then | ||
| 273 | + git checkout -b "$BRANCH_NAME" | ||
| 274 | +else | ||
| 275 | + >&2 echo "[specify] Warning: 未检测到 Git 仓库;已跳过创建分支 $BRANCH_NAME" | ||
| 276 | +fi | ||
| 277 | + | ||
| 278 | +FEATURE_DIR="$SPECS_DIR/$BRANCH_NAME" | ||
| 279 | +mkdir -p "$FEATURE_DIR" | ||
| 280 | + | ||
| 281 | +TEMPLATE="$REPO_ROOT/.specify/templates/spec-template.md" | ||
| 282 | +SPEC_FILE="$FEATURE_DIR/spec.md" | ||
| 283 | +if [ -f "$TEMPLATE" ]; then cp "$TEMPLATE" "$SPEC_FILE"; else touch "$SPEC_FILE"; fi | ||
| 284 | + | ||
| 285 | +# 为当前会话设置 SPECIFY_FEATURE 环境变量 | ||
| 286 | +export SPECIFY_FEATURE="$BRANCH_NAME" | ||
| 287 | + | ||
| 288 | +if $JSON_MODE; then | ||
| 289 | + printf '{"BRANCH_NAME":"%s","SPEC_FILE":"%s","FEATURE_NUM":"%s"}\n' "$BRANCH_NAME" "$SPEC_FILE" "$FEATURE_NUM" | ||
| 290 | +else | ||
| 291 | + echo "BRANCH_NAME: $BRANCH_NAME" | ||
| 292 | + echo "SPEC_FILE: $SPEC_FILE" | ||
| 293 | + echo "FEATURE_NUM: $FEATURE_NUM" | ||
| 294 | + echo "已设置 SPECIFY_FEATURE 环境变量为:$BRANCH_NAME" | ||
| 295 | +fi |
.specify/scripts/bash/setup-plan.sh
0 → 100755
| 1 | +#!/usr/bin/env bash | ||
| 2 | + | ||
| 3 | +set -e | ||
| 4 | + | ||
| 5 | +# 解析命令行参数 | ||
| 6 | +JSON_MODE=false | ||
| 7 | +ARGS=() | ||
| 8 | + | ||
| 9 | +for arg in "$@"; do | ||
| 10 | + case "$arg" in | ||
| 11 | + --json) | ||
| 12 | + JSON_MODE=true | ||
| 13 | + ;; | ||
| 14 | + --help|-h) | ||
| 15 | + echo "用法:$0 [--json]" | ||
| 16 | + echo " --json 以 JSON 格式输出结果" | ||
| 17 | + echo " --help 显示帮助信息" | ||
| 18 | + exit 0 | ||
| 19 | + ;; | ||
| 20 | + *) | ||
| 21 | + ARGS+=("$arg") | ||
| 22 | + ;; | ||
| 23 | + esac | ||
| 24 | +done | ||
| 25 | + | ||
| 26 | +# 获取脚本目录并加载通用函数 | ||
| 27 | +SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | ||
| 28 | +source "$SCRIPT_DIR/common.sh" | ||
| 29 | + | ||
| 30 | +# 从通用函数获取所有路径与变量 | ||
| 31 | +eval $(get_feature_paths) | ||
| 32 | + | ||
| 33 | +# 检查是否在合规的功能分支上(仅 git 仓库) | ||
| 34 | +check_feature_branch "$CURRENT_BRANCH" "$HAS_GIT" || exit 1 | ||
| 35 | + | ||
| 36 | +# 确保功能目录存在 | ||
| 37 | +mkdir -p "$FEATURE_DIR" | ||
| 38 | + | ||
| 39 | +# 如果存在计划模板,则复制 | ||
| 40 | +TEMPLATE="$REPO_ROOT/.specify/templates/plan-template.md" | ||
| 41 | +if [[ -f "$TEMPLATE" ]]; then | ||
| 42 | + cp "$TEMPLATE" "$IMPL_PLAN" | ||
| 43 | + echo "已复制 plan 模板到:$IMPL_PLAN" | ||
| 44 | +else | ||
| 45 | + echo "警告:未找到 plan 模板:$TEMPLATE" | ||
| 46 | + # 如果不存在模板,则创建空的 plan 文件 | ||
| 47 | + touch "$IMPL_PLAN" | ||
| 48 | +fi | ||
| 49 | + | ||
| 50 | +# 输出结果 | ||
| 51 | +if $JSON_MODE; then | ||
| 52 | + printf '{"FEATURE_SPEC":"%s","IMPL_PLAN":"%s","SPECS_DIR":"%s","BRANCH":"%s","HAS_GIT":"%s"}\n' \ | ||
| 53 | + "$FEATURE_SPEC" "$IMPL_PLAN" "$FEATURE_DIR" "$CURRENT_BRANCH" "$HAS_GIT" | ||
| 54 | +else | ||
| 55 | + echo "FEATURE_SPEC: $FEATURE_SPEC" | ||
| 56 | + echo "IMPL_PLAN: $IMPL_PLAN" | ||
| 57 | + echo "SPECS_DIR: $FEATURE_DIR" | ||
| 58 | + echo "BRANCH: $CURRENT_BRANCH" | ||
| 59 | + echo "HAS_GIT: $HAS_GIT" | ||
| 60 | +fi |
| 1 | +#!/usr/bin/env bash | ||
| 2 | + | ||
| 3 | +# 根据 plan.md 更新各类智能体的上下文文件 | ||
| 4 | +# | ||
| 5 | +# 本脚本通过解析功能规格与实现计划,更新不同智能体对应的上下文/规则文件, | ||
| 6 | +# 以便在后续命令(如 /speckit.tasks、/speckit.implement)中提供一致的项目背景信息。 | ||
| 7 | +# | ||
| 8 | +# 主要功能: | ||
| 9 | +# 1. 环境校验 | ||
| 10 | +# - 校验 git 仓库结构与分支信息 | ||
| 11 | +# - 校验所需的 plan.md 与模板文件是否存在 | ||
| 12 | +# - 校验文件权限与可访问性 | ||
| 13 | +# | ||
| 14 | +# 2. 计划数据提取 | ||
| 15 | +# - 解析 plan.md 提取项目元信息 | ||
| 16 | +# - 识别语言/版本、主要依赖、存储与项目类型 | ||
| 17 | +# - 对缺失或不完整数据做容错处理 | ||
| 18 | +# | ||
| 19 | +# 3. 智能体文件管理 | ||
| 20 | +# - 需要时基于模板创建新的上下文文件 | ||
| 21 | +# - 更新既有上下文文件并追加新的项目信息 | ||
| 22 | +# - 保留手工补充内容与自定义配置 | ||
| 23 | +# - 支持多种智能体格式与目录结构 | ||
| 24 | +# | ||
| 25 | +# 4. 内容生成 | ||
| 26 | +# - 生成与语言相关的构建/测试命令 | ||
| 27 | +# - 生成合理的项目目录结构占位 | ||
| 28 | +# - 更新“当前技术栈”和“最近变更”等章节 | ||
| 29 | +# - 保持格式与时间戳一致 | ||
| 30 | +# | ||
| 31 | +# 5. 多智能体支持 | ||
| 32 | +# - 处理不同智能体的路径与命名约定 | ||
| 33 | +# - 支持:Claude、Gemini、Copilot、Cursor、Qwen、opencode、Codex、Windsurf、Kilo Code、Auggie CLI、Roo Code、CodeBuddy CLI、Qoder CLI、Amp、SHAI、Amazon Q Developer CLI 等 | ||
| 34 | +# - 支持更新指定智能体或更新所有已存在的智能体文件 | ||
| 35 | +# - 若当前没有任何智能体文件,则创建默认的 Claude 文件 | ||
| 36 | +# | ||
| 37 | +# 用法:./update-agent-context.sh [agent_type] | ||
| 38 | +# agent_type:claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|shai|q|bob|qoder | ||
| 39 | +# 不传参则更新所有已存在的智能体文件 | ||
| 40 | + | ||
| 41 | +set -e | ||
| 42 | + | ||
| 43 | +# 开启更严格的错误处理 | ||
| 44 | +set -u | ||
| 45 | +set -o pipefail | ||
| 46 | + | ||
| 47 | +#============================================================================== | ||
| 48 | +# 配置与全局变量 | ||
| 49 | +#============================================================================== | ||
| 50 | + | ||
| 51 | +# 获取脚本目录并加载通用函数 | ||
| 52 | +SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | ||
| 53 | +source "$SCRIPT_DIR/common.sh" | ||
| 54 | + | ||
| 55 | +# 从通用函数获取所有路径与变量 | ||
| 56 | +eval $(get_feature_paths) | ||
| 57 | + | ||
| 58 | +NEW_PLAN="$IMPL_PLAN" # 兼容历史代码的别名 | ||
| 59 | +AGENT_TYPE="${1:-}" | ||
| 60 | + | ||
| 61 | +# 不同智能体的文件路径 | ||
| 62 | +CLAUDE_FILE="$REPO_ROOT/CLAUDE.md" | ||
| 63 | +GEMINI_FILE="$REPO_ROOT/GEMINI.md" | ||
| 64 | +COPILOT_FILE="$REPO_ROOT/.github/agents/copilot-instructions.md" | ||
| 65 | +CURSOR_FILE="$REPO_ROOT/.cursor/rules/specify-rules.mdc" | ||
| 66 | +QWEN_FILE="$REPO_ROOT/QWEN.md" | ||
| 67 | +AGENTS_FILE="$REPO_ROOT/AGENTS.md" | ||
| 68 | +WINDSURF_FILE="$REPO_ROOT/.windsurf/rules/specify-rules.md" | ||
| 69 | +KILOCODE_FILE="$REPO_ROOT/.kilocode/rules/specify-rules.md" | ||
| 70 | +AUGGIE_FILE="$REPO_ROOT/.augment/rules/specify-rules.md" | ||
| 71 | +ROO_FILE="$REPO_ROOT/.roo/rules/specify-rules.md" | ||
| 72 | +CODEBUDDY_FILE="$REPO_ROOT/CODEBUDDY.md" | ||
| 73 | +QODER_FILE="$REPO_ROOT/QODER.md" | ||
| 74 | +AMP_FILE="$REPO_ROOT/AGENTS.md" | ||
| 75 | +SHAI_FILE="$REPO_ROOT/SHAI.md" | ||
| 76 | +Q_FILE="$REPO_ROOT/AGENTS.md" | ||
| 77 | +BOB_FILE="$REPO_ROOT/AGENTS.md" | ||
| 78 | + | ||
| 79 | +# 模板文件 | ||
| 80 | +TEMPLATE_FILE="$REPO_ROOT/.specify/templates/agent-file-template.md" | ||
| 81 | + | ||
| 82 | +# 从 plan.md 解析得到的全局变量 | ||
| 83 | +NEW_LANG="" | ||
| 84 | +NEW_FRAMEWORK="" | ||
| 85 | +NEW_DB="" | ||
| 86 | +NEW_PROJECT_TYPE="" | ||
| 87 | + | ||
| 88 | +#============================================================================== | ||
| 89 | +# 工具函数 | ||
| 90 | +#============================================================================== | ||
| 91 | + | ||
| 92 | +log_info() { | ||
| 93 | + echo "信息:$1" | ||
| 94 | +} | ||
| 95 | + | ||
| 96 | +log_success() { | ||
| 97 | + echo "✓ $1" | ||
| 98 | +} | ||
| 99 | + | ||
| 100 | +log_error() { | ||
| 101 | + echo "错误:$1" >&2 | ||
| 102 | +} | ||
| 103 | + | ||
| 104 | +log_warning() { | ||
| 105 | + echo "警告:$1" >&2 | ||
| 106 | +} | ||
| 107 | + | ||
| 108 | +# 清理临时文件 | ||
| 109 | +cleanup() { | ||
| 110 | + local exit_code=$? | ||
| 111 | + rm -f /tmp/agent_update_*_$$ | ||
| 112 | + rm -f /tmp/manual_additions_$$ | ||
| 113 | + exit $exit_code | ||
| 114 | +} | ||
| 115 | + | ||
| 116 | +# 设置清理 trap | ||
| 117 | +trap cleanup EXIT INT TERM | ||
| 118 | + | ||
| 119 | +#============================================================================== | ||
| 120 | +# 校验函数 | ||
| 121 | +#============================================================================== | ||
| 122 | + | ||
| 123 | +validate_environment() { | ||
| 124 | + # 检查是否能获取当前分支/功能(git 或非 git) | ||
| 125 | + if [[ -z "$CURRENT_BRANCH" ]]; then | ||
| 126 | + log_error "无法确定当前功能/分支" | ||
| 127 | + if [[ "$HAS_GIT" == "true" ]]; then | ||
| 128 | + log_info "请确认当前在功能分支上" | ||
| 129 | + else | ||
| 130 | + log_info "请设置 SPECIFY_FEATURE 环境变量,或先创建功能目录" | ||
| 131 | + fi | ||
| 132 | + exit 1 | ||
| 133 | + fi | ||
| 134 | + | ||
| 135 | + # 检查 plan.md 是否存在 | ||
| 136 | + if [[ ! -f "$NEW_PLAN" ]]; then | ||
| 137 | + log_error "未找到 plan.md:$NEW_PLAN" | ||
| 138 | + log_info "请确认当前功能存在对应的 specs 目录" | ||
| 139 | + if [[ "$HAS_GIT" != "true" ]]; then | ||
| 140 | + log_info "可使用:export SPECIFY_FEATURE=your-feature-name,或先创建新功能" | ||
| 141 | + fi | ||
| 142 | + exit 1 | ||
| 143 | + fi | ||
| 144 | + | ||
| 145 | + # 检查模板文件是否存在(创建新文件时需要) | ||
| 146 | + if [[ ! -f "$TEMPLATE_FILE" ]]; then | ||
| 147 | + log_warning "未找到模板文件:$TEMPLATE_FILE" | ||
| 148 | + log_warning "创建新的智能体文件将会失败" | ||
| 149 | + fi | ||
| 150 | +} | ||
| 151 | + | ||
| 152 | +#============================================================================== | ||
| 153 | +# plan.md 解析函数 | ||
| 154 | +#============================================================================== | ||
| 155 | + | ||
| 156 | +extract_plan_field() { | ||
| 157 | + local field_pattern="$1" | ||
| 158 | + local plan_file="$2" | ||
| 159 | + | ||
| 160 | + grep "^\*\*${field_pattern}\*\*: " "$plan_file" 2>/dev/null | \ | ||
| 161 | + head -1 | \ | ||
| 162 | + sed "s|^\*\*${field_pattern}\*\*: ||" | \ | ||
| 163 | + sed 's/^[ \t]*//;s/[ \t]*$//' | \ | ||
| 164 | + grep -v "NEEDS CLARIFICATION" | \ | ||
| 165 | + grep -v "^N/A$" || echo "" | ||
| 166 | +} | ||
| 167 | + | ||
| 168 | +extract_plan_field_any() { | ||
| 169 | + local plan_file="$1" | ||
| 170 | + shift | ||
| 171 | + | ||
| 172 | + local field_pattern="" | ||
| 173 | + for field_pattern in "$@"; do | ||
| 174 | + local value | ||
| 175 | + value=$(extract_plan_field "$field_pattern" "$plan_file") | ||
| 176 | + if [[ -n "$value" ]]; then | ||
| 177 | + echo "$value" | ||
| 178 | + return 0 | ||
| 179 | + fi | ||
| 180 | + done | ||
| 181 | + | ||
| 182 | + echo "" | ||
| 183 | +} | ||
| 184 | + | ||
| 185 | +parse_plan_data() { | ||
| 186 | + local plan_file="$1" | ||
| 187 | + | ||
| 188 | + if [[ ! -f "$plan_file" ]]; then | ||
| 189 | + log_error "未找到 plan 文件:$plan_file" | ||
| 190 | + return 1 | ||
| 191 | + fi | ||
| 192 | + | ||
| 193 | + if [[ ! -r "$plan_file" ]]; then | ||
| 194 | + log_error "plan 文件不可读:$plan_file" | ||
| 195 | + return 1 | ||
| 196 | + fi | ||
| 197 | + | ||
| 198 | + log_info "正在解析 plan 数据:$plan_file" | ||
| 199 | + | ||
| 200 | + NEW_LANG=$(extract_plan_field_any "$plan_file" "语言/版本" "Language/Version") | ||
| 201 | + NEW_FRAMEWORK=$(extract_plan_field_any "$plan_file" "主要依赖" "Primary Dependencies") | ||
| 202 | + NEW_DB=$(extract_plan_field_any "$plan_file" "存储" "Storage") | ||
| 203 | + NEW_PROJECT_TYPE=$(extract_plan_field_any "$plan_file" "工程类型" "Project Type") | ||
| 204 | + | ||
| 205 | + # 输出解析结果 | ||
| 206 | + if [[ -n "$NEW_LANG" ]]; then | ||
| 207 | + log_info "识别到语言:$NEW_LANG" | ||
| 208 | + else | ||
| 209 | + log_warning "plan 中未找到语言信息" | ||
| 210 | + fi | ||
| 211 | + | ||
| 212 | + if [[ -n "$NEW_FRAMEWORK" ]]; then | ||
| 213 | + log_info "识别到主要依赖:$NEW_FRAMEWORK" | ||
| 214 | + fi | ||
| 215 | + | ||
| 216 | + if [[ -n "$NEW_DB" ]] && [[ "$NEW_DB" != "N/A" ]]; then | ||
| 217 | + log_info "识别到存储:$NEW_DB" | ||
| 218 | + fi | ||
| 219 | + | ||
| 220 | + if [[ -n "$NEW_PROJECT_TYPE" ]]; then | ||
| 221 | + log_info "识别到项目类型:$NEW_PROJECT_TYPE" | ||
| 222 | + fi | ||
| 223 | +} | ||
| 224 | + | ||
| 225 | +format_technology_stack() { | ||
| 226 | + local lang="$1" | ||
| 227 | + local framework="$2" | ||
| 228 | + local parts=() | ||
| 229 | + | ||
| 230 | + # 追加非空项 | ||
| 231 | + [[ -n "$lang" && "$lang" != "NEEDS CLARIFICATION" ]] && parts+=("$lang") | ||
| 232 | + [[ -n "$framework" && "$framework" != "NEEDS CLARIFICATION" && "$framework" != "N/A" ]] && parts+=("$framework") | ||
| 233 | + | ||
| 234 | + # 按合适的格式拼接 | ||
| 235 | + if [[ ${#parts[@]} -eq 0 ]]; then | ||
| 236 | + echo "" | ||
| 237 | + elif [[ ${#parts[@]} -eq 1 ]]; then | ||
| 238 | + echo "${parts[0]}" | ||
| 239 | + else | ||
| 240 | + # 多项用 " + " 连接 | ||
| 241 | + local result="${parts[0]}" | ||
| 242 | + for ((i=1; i<${#parts[@]}; i++)); do | ||
| 243 | + result="$result + ${parts[i]}" | ||
| 244 | + done | ||
| 245 | + echo "$result" | ||
| 246 | + fi | ||
| 247 | +} | ||
| 248 | + | ||
| 249 | +#============================================================================== | ||
| 250 | +# 模板与内容生成函数 | ||
| 251 | +#============================================================================== | ||
| 252 | + | ||
| 253 | +get_project_structure() { | ||
| 254 | + local project_type="$1" | ||
| 255 | + | ||
| 256 | + if [[ "$project_type" == *"web"* ]]; then | ||
| 257 | + echo "backend/\\nfrontend/\\ntests/" | ||
| 258 | + else | ||
| 259 | + echo "src/\\ntests/" | ||
| 260 | + fi | ||
| 261 | +} | ||
| 262 | + | ||
| 263 | +get_commands_for_language() { | ||
| 264 | + local lang="$1" | ||
| 265 | + | ||
| 266 | + case "$lang" in | ||
| 267 | + *"Python"*) | ||
| 268 | + echo "cd src && pytest && ruff check ." | ||
| 269 | + ;; | ||
| 270 | + *"Rust"*) | ||
| 271 | + echo "cargo test && cargo clippy" | ||
| 272 | + ;; | ||
| 273 | + *"JavaScript"*|*"TypeScript"*) | ||
| 274 | + echo "npm test \\&\\& npm run lint" | ||
| 275 | + ;; | ||
| 276 | + *) | ||
| 277 | + echo "# 请补充 $lang 对应的命令" | ||
| 278 | + ;; | ||
| 279 | + esac | ||
| 280 | +} | ||
| 281 | + | ||
| 282 | +get_language_conventions() { | ||
| 283 | + local lang="$1" | ||
| 284 | + echo "$lang:遵循该语言的通用约定" | ||
| 285 | +} | ||
| 286 | + | ||
| 287 | +create_new_agent_file() { | ||
| 288 | + local target_file="$1" | ||
| 289 | + local temp_file="$2" | ||
| 290 | + local project_name="$3" | ||
| 291 | + local current_date="$4" | ||
| 292 | + | ||
| 293 | + if [[ ! -f "$TEMPLATE_FILE" ]]; then | ||
| 294 | + log_error "未找到模板文件:$TEMPLATE_FILE" | ||
| 295 | + return 1 | ||
| 296 | + fi | ||
| 297 | + | ||
| 298 | + if [[ ! -r "$TEMPLATE_FILE" ]]; then | ||
| 299 | + log_error "模板文件不可读:$TEMPLATE_FILE" | ||
| 300 | + return 1 | ||
| 301 | + fi | ||
| 302 | + | ||
| 303 | + log_info "正在从模板创建新的智能体上下文文件..." | ||
| 304 | + | ||
| 305 | + if ! cp "$TEMPLATE_FILE" "$temp_file"; then | ||
| 306 | + log_error "复制模板文件失败" | ||
| 307 | + return 1 | ||
| 308 | + fi | ||
| 309 | + | ||
| 310 | + # 替换模板占位符 | ||
| 311 | + local project_structure | ||
| 312 | + project_structure=$(get_project_structure "$NEW_PROJECT_TYPE") | ||
| 313 | + | ||
| 314 | + local commands | ||
| 315 | + commands=$(get_commands_for_language "$NEW_LANG") | ||
| 316 | + | ||
| 317 | + local language_conventions | ||
| 318 | + language_conventions=$(get_language_conventions "$NEW_LANG") | ||
| 319 | + | ||
| 320 | + # 使用更安全的方式执行替换,并处理潜在的特殊字符 | ||
| 321 | + local escaped_lang=$(printf '%s\n' "$NEW_LANG" | sed 's/[\[\.*^$()+{}|]/\\&/g') | ||
| 322 | + local escaped_framework=$(printf '%s\n' "$NEW_FRAMEWORK" | sed 's/[\[\.*^$()+{}|]/\\&/g') | ||
| 323 | + local escaped_branch=$(printf '%s\n' "$CURRENT_BRANCH" | sed 's/[\[\.*^$()+{}|]/\\&/g') | ||
| 324 | + | ||
| 325 | + # 按条件构建“当前技术栈”和“最近变更”的写入内容 | ||
| 326 | + local tech_stack | ||
| 327 | + if [[ -n "$escaped_lang" && -n "$escaped_framework" ]]; then | ||
| 328 | + tech_stack="- $escaped_lang + $escaped_framework ($escaped_branch)" | ||
| 329 | + elif [[ -n "$escaped_lang" ]]; then | ||
| 330 | + tech_stack="- $escaped_lang ($escaped_branch)" | ||
| 331 | + elif [[ -n "$escaped_framework" ]]; then | ||
| 332 | + tech_stack="- $escaped_framework ($escaped_branch)" | ||
| 333 | + else | ||
| 334 | + tech_stack="- ($escaped_branch)" | ||
| 335 | + fi | ||
| 336 | + | ||
| 337 | + local recent_change | ||
| 338 | + if [[ -n "$escaped_lang" && -n "$escaped_framework" ]]; then | ||
| 339 | + recent_change="- $escaped_branch:新增 $escaped_lang + $escaped_framework" | ||
| 340 | + elif [[ -n "$escaped_lang" ]]; then | ||
| 341 | + recent_change="- $escaped_branch:新增 $escaped_lang" | ||
| 342 | + elif [[ -n "$escaped_framework" ]]; then | ||
| 343 | + recent_change="- $escaped_branch:新增 $escaped_framework" | ||
| 344 | + else | ||
| 345 | + recent_change="- $escaped_branch:新增" | ||
| 346 | + fi | ||
| 347 | + | ||
| 348 | + local substitutions=( | ||
| 349 | + "s|\[PROJECT NAME\]|$project_name|" | ||
| 350 | + "s|\[项目名称\]|$project_name|" | ||
| 351 | + "s|\[DATE\]|$current_date|" | ||
| 352 | + "s|\[EXTRACTED FROM ALL PLAN.MD FILES\]|$tech_stack|" | ||
| 353 | + "s|\[从所有 plan.md 中提取\]|$tech_stack|" | ||
| 354 | + "s|\[ACTUAL STRUCTURE FROM PLANS\]|$project_structure|g" | ||
| 355 | + "s|\[从计划中提取的真实结构\]|$project_structure|g" | ||
| 356 | + "s|\[ONLY COMMANDS FOR ACTIVE TECHNOLOGIES\]|$commands|" | ||
| 357 | + "s|\[仅保留与当前技术栈相关的命令\]|$commands|" | ||
| 358 | + "s|\[LANGUAGE-SPECIFIC, ONLY FOR LANGUAGES IN USE\]|$language_conventions|" | ||
| 359 | + "s|\[语言相关规范(仅保留当前使用的语言)\]|$language_conventions|" | ||
| 360 | + "s|\[LAST 3 FEATURES AND WHAT THEY ADDED\]|$recent_change|" | ||
| 361 | + "s|\[最近 3 个功能及其变更\]|$recent_change|" | ||
| 362 | + ) | ||
| 363 | + | ||
| 364 | + for substitution in "${substitutions[@]}"; do | ||
| 365 | + if ! sed -i.bak -e "$substitution" "$temp_file"; then | ||
| 366 | + log_error "执行替换失败:$substitution" | ||
| 367 | + rm -f "$temp_file" "$temp_file.bak" | ||
| 368 | + return 1 | ||
| 369 | + fi | ||
| 370 | + done | ||
| 371 | + | ||
| 372 | + # 将 \n 序列转换为真实换行 | ||
| 373 | + newline=$(printf '\n') | ||
| 374 | + sed -i.bak2 "s/\\\\n/${newline}/g" "$temp_file" | ||
| 375 | + | ||
| 376 | + # 清理备份文件 | ||
| 377 | + rm -f "$temp_file.bak" "$temp_file.bak2" | ||
| 378 | + | ||
| 379 | + return 0 | ||
| 380 | +} | ||
| 381 | + | ||
| 382 | + | ||
| 383 | + | ||
| 384 | + | ||
| 385 | +update_existing_agent_file() { | ||
| 386 | + local target_file="$1" | ||
| 387 | + local current_date="$2" | ||
| 388 | + | ||
| 389 | + log_info "正在更新已有的智能体上下文文件..." | ||
| 390 | + | ||
| 391 | + # 使用单个临时文件实现原子更新 | ||
| 392 | + local temp_file | ||
| 393 | + temp_file=$(mktemp) || { | ||
| 394 | + log_error "创建临时文件失败" | ||
| 395 | + return 1 | ||
| 396 | + } | ||
| 397 | + | ||
| 398 | + # 单次扫描处理文件 | ||
| 399 | + local tech_stack=$(format_technology_stack "$NEW_LANG" "$NEW_FRAMEWORK") | ||
| 400 | + local new_tech_entries=() | ||
| 401 | + local new_change_entry="" | ||
| 402 | + | ||
| 403 | + # 准备需要追加的技术条目 | ||
| 404 | + if [[ -n "$tech_stack" ]] && ! grep -q "$tech_stack" "$target_file"; then | ||
| 405 | + new_tech_entries+=("- $tech_stack ($CURRENT_BRANCH)") | ||
| 406 | + fi | ||
| 407 | + | ||
| 408 | + if [[ -n "$NEW_DB" ]] && [[ "$NEW_DB" != "N/A" ]] && [[ "$NEW_DB" != "NEEDS CLARIFICATION" ]] && ! grep -q "$NEW_DB" "$target_file"; then | ||
| 409 | + new_tech_entries+=("- $NEW_DB ($CURRENT_BRANCH)") | ||
| 410 | + fi | ||
| 411 | + | ||
| 412 | + # 准备需要追加的变更条目 | ||
| 413 | + if [[ -n "$tech_stack" ]]; then | ||
| 414 | + new_change_entry="- $CURRENT_BRANCH:新增 $tech_stack" | ||
| 415 | + elif [[ -n "$NEW_DB" ]] && [[ "$NEW_DB" != "N/A" ]] && [[ "$NEW_DB" != "NEEDS CLARIFICATION" ]]; then | ||
| 416 | + new_change_entry="- $CURRENT_BRANCH:新增 $NEW_DB" | ||
| 417 | + fi | ||
| 418 | + | ||
| 419 | + # 检查文件中是否已存在对应章节 | ||
| 420 | + local has_active_technologies=0 | ||
| 421 | + local has_recent_changes=0 | ||
| 422 | + | ||
| 423 | + if grep -qE "^(## Active Technologies|## 当前技术栈)$" "$target_file" 2>/dev/null; then | ||
| 424 | + has_active_technologies=1 | ||
| 425 | + fi | ||
| 426 | + | ||
| 427 | + if grep -qE "^(## Recent Changes|## 最近变更)$" "$target_file" 2>/dev/null; then | ||
| 428 | + has_recent_changes=1 | ||
| 429 | + fi | ||
| 430 | + | ||
| 431 | + # 逐行处理文件内容 | ||
| 432 | + local in_tech_section=false | ||
| 433 | + local in_changes_section=false | ||
| 434 | + local tech_entries_added=false | ||
| 435 | + local changes_entries_added=false | ||
| 436 | + local existing_changes_count=0 | ||
| 437 | + | ||
| 438 | + while IFS= read -r line || [[ -n "$line" ]]; do | ||
| 439 | + # 处理“当前技术栈 / Active Technologies”章节 | ||
| 440 | + if [[ "$line" == "## Active Technologies" ]] || [[ "$line" == "## 当前技术栈" ]]; then | ||
| 441 | + echo "$line" >> "$temp_file" | ||
| 442 | + in_tech_section=true | ||
| 443 | + continue | ||
| 444 | + elif [[ $in_tech_section == true ]] && [[ "$line" =~ ^##[[:space:]] ]]; then | ||
| 445 | + # 章节结束前补充新条目 | ||
| 446 | + if [[ $tech_entries_added == false ]] && [[ ${#new_tech_entries[@]} -gt 0 ]]; then | ||
| 447 | + printf '%s\n' "${new_tech_entries[@]}" >> "$temp_file" | ||
| 448 | + tech_entries_added=true | ||
| 449 | + fi | ||
| 450 | + echo "$line" >> "$temp_file" | ||
| 451 | + in_tech_section=false | ||
| 452 | + continue | ||
| 453 | + elif [[ $in_tech_section == true ]] && [[ -z "$line" ]]; then | ||
| 454 | + # 在章节内遇到空行前补充新条目 | ||
| 455 | + if [[ $tech_entries_added == false ]] && [[ ${#new_tech_entries[@]} -gt 0 ]]; then | ||
| 456 | + printf '%s\n' "${new_tech_entries[@]}" >> "$temp_file" | ||
| 457 | + tech_entries_added=true | ||
| 458 | + fi | ||
| 459 | + echo "$line" >> "$temp_file" | ||
| 460 | + continue | ||
| 461 | + fi | ||
| 462 | + | ||
| 463 | + # 处理“最近变更 / Recent Changes”章节 | ||
| 464 | + if [[ "$line" == "## Recent Changes" ]] || [[ "$line" == "## 最近变更" ]]; then | ||
| 465 | + echo "$line" >> "$temp_file" | ||
| 466 | + # 在标题行后立刻写入新的变更条目 | ||
| 467 | + if [[ -n "$new_change_entry" ]]; then | ||
| 468 | + echo "$new_change_entry" >> "$temp_file" | ||
| 469 | + fi | ||
| 470 | + in_changes_section=true | ||
| 471 | + changes_entries_added=true | ||
| 472 | + continue | ||
| 473 | + elif [[ $in_changes_section == true ]] && [[ "$line" =~ ^##[[:space:]] ]]; then | ||
| 474 | + echo "$line" >> "$temp_file" | ||
| 475 | + in_changes_section=false | ||
| 476 | + continue | ||
| 477 | + elif [[ $in_changes_section == true ]] && [[ "$line" == "- "* ]]; then | ||
| 478 | + # 仅保留原有的前 2 条变更(加上新写入的 1 条,总计最多 3 条) | ||
| 479 | + if [[ $existing_changes_count -lt 2 ]]; then | ||
| 480 | + echo "$line" >> "$temp_file" | ||
| 481 | + ((existing_changes_count++)) | ||
| 482 | + fi | ||
| 483 | + continue | ||
| 484 | + fi | ||
| 485 | + | ||
| 486 | + # 更新时间戳 | ||
| 487 | + if [[ "$line" =~ (Last[[:space:]]updated:|最近更新:).*([0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]) ]]; then | ||
| 488 | + echo "$line" | sed "s/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/$current_date/" >> "$temp_file" | ||
| 489 | + else | ||
| 490 | + echo "$line" >> "$temp_file" | ||
| 491 | + fi | ||
| 492 | + done < "$target_file" | ||
| 493 | + | ||
| 494 | + # 循环结束后检查:若仍在“当前技术栈 / Active Technologies”章节且未追加,则补充新条目 | ||
| 495 | + if [[ $in_tech_section == true ]] && [[ $tech_entries_added == false ]] && [[ ${#new_tech_entries[@]} -gt 0 ]]; then | ||
| 496 | + printf '%s\n' "${new_tech_entries[@]}" >> "$temp_file" | ||
| 497 | + tech_entries_added=true | ||
| 498 | + fi | ||
| 499 | + | ||
| 500 | + # 若章节不存在,则在文件末尾补充 | ||
| 501 | + if [[ $has_active_technologies -eq 0 ]] && [[ ${#new_tech_entries[@]} -gt 0 ]]; then | ||
| 502 | + echo "" >> "$temp_file" | ||
| 503 | + echo "## 当前技术栈" >> "$temp_file" | ||
| 504 | + printf '%s\n' "${new_tech_entries[@]}" >> "$temp_file" | ||
| 505 | + tech_entries_added=true | ||
| 506 | + fi | ||
| 507 | + | ||
| 508 | + if [[ $has_recent_changes -eq 0 ]] && [[ -n "$new_change_entry" ]]; then | ||
| 509 | + echo "" >> "$temp_file" | ||
| 510 | + echo "## 最近变更" >> "$temp_file" | ||
| 511 | + echo "$new_change_entry" >> "$temp_file" | ||
| 512 | + changes_entries_added=true | ||
| 513 | + fi | ||
| 514 | + | ||
| 515 | + # 原子替换写入 | ||
| 516 | + if ! mv "$temp_file" "$target_file"; then | ||
| 517 | + log_error "更新目标文件失败" | ||
| 518 | + rm -f "$temp_file" | ||
| 519 | + return 1 | ||
| 520 | + fi | ||
| 521 | + | ||
| 522 | + return 0 | ||
| 523 | +} | ||
| 524 | +#============================================================================== | ||
| 525 | +# 智能体文件更新入口 | ||
| 526 | +#============================================================================== | ||
| 527 | + | ||
| 528 | +update_agent_file() { | ||
| 529 | + local target_file="$1" | ||
| 530 | + local agent_name="$2" | ||
| 531 | + | ||
| 532 | + if [[ -z "$target_file" ]] || [[ -z "$agent_name" ]]; then | ||
| 533 | + log_error "update_agent_file 需要 target_file 与 agent_name 参数" | ||
| 534 | + return 1 | ||
| 535 | + fi | ||
| 536 | + | ||
| 537 | + log_info "正在更新 $agent_name 上下文文件:$target_file" | ||
| 538 | + | ||
| 539 | + local project_name | ||
| 540 | + project_name=$(basename "$REPO_ROOT") | ||
| 541 | + local current_date | ||
| 542 | + current_date=$(date +%Y-%m-%d) | ||
| 543 | + | ||
| 544 | + # 目录不存在则创建 | ||
| 545 | + local target_dir | ||
| 546 | + target_dir=$(dirname "$target_file") | ||
| 547 | + if [[ ! -d "$target_dir" ]]; then | ||
| 548 | + if ! mkdir -p "$target_dir"; then | ||
| 549 | + log_error "创建目录失败:$target_dir" | ||
| 550 | + return 1 | ||
| 551 | + fi | ||
| 552 | + fi | ||
| 553 | + | ||
| 554 | + if [[ ! -f "$target_file" ]]; then | ||
| 555 | + # 基于模板创建新文件 | ||
| 556 | + local temp_file | ||
| 557 | + temp_file=$(mktemp) || { | ||
| 558 | + log_error "创建临时文件失败" | ||
| 559 | + return 1 | ||
| 560 | + } | ||
| 561 | + | ||
| 562 | + if create_new_agent_file "$target_file" "$temp_file" "$project_name" "$current_date"; then | ||
| 563 | + if mv "$temp_file" "$target_file"; then | ||
| 564 | + log_success "已创建新的 $agent_name 上下文文件" | ||
| 565 | + else | ||
| 566 | + log_error "移动临时文件到目标路径失败:$target_file" | ||
| 567 | + rm -f "$temp_file" | ||
| 568 | + return 1 | ||
| 569 | + fi | ||
| 570 | + else | ||
| 571 | + log_error "创建新的智能体文件失败" | ||
| 572 | + rm -f "$temp_file" | ||
| 573 | + return 1 | ||
| 574 | + fi | ||
| 575 | + else | ||
| 576 | + # 更新已有文件 | ||
| 577 | + if [[ ! -r "$target_file" ]]; then | ||
| 578 | + log_error "无法读取已有文件:$target_file" | ||
| 579 | + return 1 | ||
| 580 | + fi | ||
| 581 | + | ||
| 582 | + if [[ ! -w "$target_file" ]]; then | ||
| 583 | + log_error "无法写入已有文件:$target_file" | ||
| 584 | + return 1 | ||
| 585 | + fi | ||
| 586 | + | ||
| 587 | + if update_existing_agent_file "$target_file" "$current_date"; then | ||
| 588 | + log_success "已更新 $agent_name 上下文文件" | ||
| 589 | + else | ||
| 590 | + log_error "更新已有智能体文件失败" | ||
| 591 | + return 1 | ||
| 592 | + fi | ||
| 593 | + fi | ||
| 594 | + | ||
| 595 | + return 0 | ||
| 596 | +} | ||
| 597 | + | ||
| 598 | +#============================================================================== | ||
| 599 | +# 智能体选择与处理 | ||
| 600 | +#============================================================================== | ||
| 601 | + | ||
| 602 | +update_specific_agent() { | ||
| 603 | + local agent_type="$1" | ||
| 604 | + | ||
| 605 | + case "$agent_type" in | ||
| 606 | + claude) | ||
| 607 | + update_agent_file "$CLAUDE_FILE" "Claude Code" | ||
| 608 | + ;; | ||
| 609 | + gemini) | ||
| 610 | + update_agent_file "$GEMINI_FILE" "Gemini CLI" | ||
| 611 | + ;; | ||
| 612 | + copilot) | ||
| 613 | + update_agent_file "$COPILOT_FILE" "GitHub Copilot" | ||
| 614 | + ;; | ||
| 615 | + cursor-agent) | ||
| 616 | + update_agent_file "$CURSOR_FILE" "Cursor IDE" | ||
| 617 | + ;; | ||
| 618 | + qwen) | ||
| 619 | + update_agent_file "$QWEN_FILE" "Qwen Code" | ||
| 620 | + ;; | ||
| 621 | + opencode) | ||
| 622 | + update_agent_file "$AGENTS_FILE" "opencode" | ||
| 623 | + ;; | ||
| 624 | + codex) | ||
| 625 | + update_agent_file "$AGENTS_FILE" "Codex CLI" | ||
| 626 | + ;; | ||
| 627 | + windsurf) | ||
| 628 | + update_agent_file "$WINDSURF_FILE" "Windsurf" | ||
| 629 | + ;; | ||
| 630 | + kilocode) | ||
| 631 | + update_agent_file "$KILOCODE_FILE" "Kilo Code" | ||
| 632 | + ;; | ||
| 633 | + auggie) | ||
| 634 | + update_agent_file "$AUGGIE_FILE" "Auggie CLI" | ||
| 635 | + ;; | ||
| 636 | + roo) | ||
| 637 | + update_agent_file "$ROO_FILE" "Roo Code" | ||
| 638 | + ;; | ||
| 639 | + codebuddy) | ||
| 640 | + update_agent_file "$CODEBUDDY_FILE" "CodeBuddy CLI" | ||
| 641 | + ;; | ||
| 642 | + qoder) | ||
| 643 | + update_agent_file "$QODER_FILE" "Qoder CLI" | ||
| 644 | + ;; | ||
| 645 | + amp) | ||
| 646 | + update_agent_file "$AMP_FILE" "Amp" | ||
| 647 | + ;; | ||
| 648 | + shai) | ||
| 649 | + update_agent_file "$SHAI_FILE" "SHAI" | ||
| 650 | + ;; | ||
| 651 | + q) | ||
| 652 | + update_agent_file "$Q_FILE" "Amazon Q Developer CLI" | ||
| 653 | + ;; | ||
| 654 | + bob) | ||
| 655 | + update_agent_file "$BOB_FILE" "IBM Bob" | ||
| 656 | + ;; | ||
| 657 | + *) | ||
| 658 | + log_error "未知的 agent type:'$agent_type'" | ||
| 659 | + log_error "可选值:claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|roo|amp|shai|q|bob|qoder" | ||
| 660 | + exit 1 | ||
| 661 | + ;; | ||
| 662 | + esac | ||
| 663 | +} | ||
| 664 | + | ||
| 665 | +update_all_existing_agents() { | ||
| 666 | + local found_agent=false | ||
| 667 | + | ||
| 668 | + # 检查各类智能体文件:存在则更新 | ||
| 669 | + if [[ -f "$CLAUDE_FILE" ]]; then | ||
| 670 | + update_agent_file "$CLAUDE_FILE" "Claude Code" | ||
| 671 | + found_agent=true | ||
| 672 | + fi | ||
| 673 | + | ||
| 674 | + if [[ -f "$GEMINI_FILE" ]]; then | ||
| 675 | + update_agent_file "$GEMINI_FILE" "Gemini CLI" | ||
| 676 | + found_agent=true | ||
| 677 | + fi | ||
| 678 | + | ||
| 679 | + if [[ -f "$COPILOT_FILE" ]]; then | ||
| 680 | + update_agent_file "$COPILOT_FILE" "GitHub Copilot" | ||
| 681 | + found_agent=true | ||
| 682 | + fi | ||
| 683 | + | ||
| 684 | + if [[ -f "$CURSOR_FILE" ]]; then | ||
| 685 | + update_agent_file "$CURSOR_FILE" "Cursor IDE" | ||
| 686 | + found_agent=true | ||
| 687 | + fi | ||
| 688 | + | ||
| 689 | + if [[ -f "$QWEN_FILE" ]]; then | ||
| 690 | + update_agent_file "$QWEN_FILE" "Qwen Code" | ||
| 691 | + found_agent=true | ||
| 692 | + fi | ||
| 693 | + | ||
| 694 | + if [[ -f "$AGENTS_FILE" ]]; then | ||
| 695 | + update_agent_file "$AGENTS_FILE" "Codex/opencode" | ||
| 696 | + found_agent=true | ||
| 697 | + fi | ||
| 698 | + | ||
| 699 | + if [[ -f "$WINDSURF_FILE" ]]; then | ||
| 700 | + update_agent_file "$WINDSURF_FILE" "Windsurf" | ||
| 701 | + found_agent=true | ||
| 702 | + fi | ||
| 703 | + | ||
| 704 | + if [[ -f "$KILOCODE_FILE" ]]; then | ||
| 705 | + update_agent_file "$KILOCODE_FILE" "Kilo Code" | ||
| 706 | + found_agent=true | ||
| 707 | + fi | ||
| 708 | + | ||
| 709 | + if [[ -f "$AUGGIE_FILE" ]]; then | ||
| 710 | + update_agent_file "$AUGGIE_FILE" "Auggie CLI" | ||
| 711 | + found_agent=true | ||
| 712 | + fi | ||
| 713 | + | ||
| 714 | + if [[ -f "$ROO_FILE" ]]; then | ||
| 715 | + update_agent_file "$ROO_FILE" "Roo Code" | ||
| 716 | + found_agent=true | ||
| 717 | + fi | ||
| 718 | + | ||
| 719 | + if [[ -f "$CODEBUDDY_FILE" ]]; then | ||
| 720 | + update_agent_file "$CODEBUDDY_FILE" "CodeBuddy CLI" | ||
| 721 | + found_agent=true | ||
| 722 | + fi | ||
| 723 | + | ||
| 724 | + if [[ -f "$SHAI_FILE" ]]; then | ||
| 725 | + update_agent_file "$SHAI_FILE" "SHAI" | ||
| 726 | + found_agent=true | ||
| 727 | + fi | ||
| 728 | + | ||
| 729 | + if [[ -f "$QODER_FILE" ]]; then | ||
| 730 | + update_agent_file "$QODER_FILE" "Qoder CLI" | ||
| 731 | + found_agent=true | ||
| 732 | + fi | ||
| 733 | + | ||
| 734 | + if [[ -f "$Q_FILE" ]]; then | ||
| 735 | + update_agent_file "$Q_FILE" "Amazon Q Developer CLI" | ||
| 736 | + found_agent=true | ||
| 737 | + fi | ||
| 738 | + | ||
| 739 | + if [[ -f "$BOB_FILE" ]]; then | ||
| 740 | + update_agent_file "$BOB_FILE" "IBM Bob" | ||
| 741 | + found_agent=true | ||
| 742 | + fi | ||
| 743 | + | ||
| 744 | + # 若未发现任何智能体文件,则创建默认的 Claude 文件 | ||
| 745 | + if [[ "$found_agent" == false ]]; then | ||
| 746 | + log_info "未发现已有智能体文件,正在创建默认 Claude 文件..." | ||
| 747 | + update_agent_file "$CLAUDE_FILE" "Claude Code" | ||
| 748 | + fi | ||
| 749 | +} | ||
| 750 | +print_summary() { | ||
| 751 | + echo | ||
| 752 | + log_info "变更摘要:" | ||
| 753 | + | ||
| 754 | + if [[ -n "$NEW_LANG" ]]; then | ||
| 755 | + echo " - 新增语言:$NEW_LANG" | ||
| 756 | + fi | ||
| 757 | + | ||
| 758 | + if [[ -n "$NEW_FRAMEWORK" ]]; then | ||
| 759 | + echo " - 新增主要依赖:$NEW_FRAMEWORK" | ||
| 760 | + fi | ||
| 761 | + | ||
| 762 | + if [[ -n "$NEW_DB" ]] && [[ "$NEW_DB" != "N/A" ]]; then | ||
| 763 | + echo " - 新增存储:$NEW_DB" | ||
| 764 | + fi | ||
| 765 | + | ||
| 766 | + echo | ||
| 767 | + | ||
| 768 | + log_info "用法:$0 [claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|codebuddy|shai|q|bob|qoder]" | ||
| 769 | +} | ||
| 770 | + | ||
| 771 | +#============================================================================== | ||
| 772 | +# 主执行流程 | ||
| 773 | +#============================================================================== | ||
| 774 | + | ||
| 775 | +main() { | ||
| 776 | + # 执行前校验环境 | ||
| 777 | + validate_environment | ||
| 778 | + | ||
| 779 | + log_info "=== 正在更新功能 $CURRENT_BRANCH 的智能体上下文文件 ===" | ||
| 780 | + | ||
| 781 | + # 解析 plan 文件以提取项目信息 | ||
| 782 | + if ! parse_plan_data "$NEW_PLAN"; then | ||
| 783 | + log_error "解析 plan 数据失败" | ||
| 784 | + exit 1 | ||
| 785 | + fi | ||
| 786 | + | ||
| 787 | + # 根据传入的 agent type 决定更新范围 | ||
| 788 | + local success=true | ||
| 789 | + | ||
| 790 | + if [[ -z "$AGENT_TYPE" ]]; then | ||
| 791 | + # 未指定智能体:更新所有已存在的智能体文件 | ||
| 792 | + log_info "未指定智能体,正在更新所有已存在的智能体文件..." | ||
| 793 | + if ! update_all_existing_agents; then | ||
| 794 | + success=false | ||
| 795 | + fi | ||
| 796 | + else | ||
| 797 | + # 指定了智能体:仅更新该智能体 | ||
| 798 | + log_info "正在更新指定智能体:$AGENT_TYPE" | ||
| 799 | + if ! update_specific_agent "$AGENT_TYPE"; then | ||
| 800 | + success=false | ||
| 801 | + fi | ||
| 802 | + fi | ||
| 803 | + | ||
| 804 | + # 打印摘要 | ||
| 805 | + print_summary | ||
| 806 | + | ||
| 807 | + if [[ "$success" == true ]]; then | ||
| 808 | + log_success "智能体上下文更新完成" | ||
| 809 | + exit 0 | ||
| 810 | + else | ||
| 811 | + log_error "智能体上下文更新完成,但存在错误" | ||
| 812 | + exit 1 | ||
| 813 | + fi | ||
| 814 | +} | ||
| 815 | + | ||
| 816 | +# Execute main function if script is run directly | ||
| 817 | +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then | ||
| 818 | + main "$@" | ||
| 819 | +fi |
.specify/templates/agent-file-template.md
0 → 100644
| 1 | +# [项目名称] 开发规范 | ||
| 2 | + | ||
| 3 | +从所有功能计划自动生成。最近更新:[DATE] | ||
| 4 | + | ||
| 5 | +## 当前技术栈 | ||
| 6 | + | ||
| 7 | +[从所有 plan.md 中提取] | ||
| 8 | + | ||
| 9 | +## 项目结构 | ||
| 10 | + | ||
| 11 | +```text | ||
| 12 | +[从计划中提取的真实结构] | ||
| 13 | +``` | ||
| 14 | + | ||
| 15 | +## 常用命令 | ||
| 16 | + | ||
| 17 | +[仅保留与当前技术栈相关的命令] | ||
| 18 | + | ||
| 19 | +## 代码风格 | ||
| 20 | + | ||
| 21 | +[语言相关规范(仅保留当前使用的语言)] | ||
| 22 | + | ||
| 23 | +## 最近变更 | ||
| 24 | + | ||
| 25 | +[最近 3 个功能及其变更] | ||
| 26 | + | ||
| 27 | +<!-- 手工补充开始 --> | ||
| 28 | +<!-- 手工补充结束 --> |
.specify/templates/checklist-template.md
0 → 100644
| 1 | +# [清单类型] 检查清单:[功能名称] | ||
| 2 | + | ||
| 3 | +**用途**:[本清单覆盖内容的简要说明] | ||
| 4 | +**创建时间**:[DATE] | ||
| 5 | +**关联功能**:[链接到 spec.md 或相关文档] | ||
| 6 | + | ||
| 7 | +**说明**:本清单由 `/speckit.checklist` 命令基于功能上下文与需求自动生成。 | ||
| 8 | + | ||
| 9 | +<!-- | ||
| 10 | + ============================================================================ | ||
| 11 | + 重要:以下检查项仅为示例,用于说明格式。 | ||
| 12 | + | ||
| 13 | + /speckit.checklist 命令必须基于以下信息,将这些示例替换为真实检查项: | ||
| 14 | + - 用户提出的具体检查清单诉求 | ||
| 15 | + - spec.md 中的功能需求 | ||
| 16 | + - plan.md 中的技术上下文 | ||
| 17 | + - tasks.md 中的实现细节 | ||
| 18 | + | ||
| 19 | + 生成的 checklist 文件中不要保留这些示例项。 | ||
| 20 | + ============================================================================ | ||
| 21 | +--> | ||
| 22 | + | ||
| 23 | +## [分类 1] | ||
| 24 | + | ||
| 25 | +- [ ] CHK001 第一条检查项(动作明确) | ||
| 26 | +- [ ] CHK002 第二条检查项 | ||
| 27 | +- [ ] CHK003 第三条检查项 | ||
| 28 | + | ||
| 29 | +## [分类 2] | ||
| 30 | + | ||
| 31 | +- [ ] CHK004 另一类检查项 | ||
| 32 | +- [ ] CHK005 带具体判定标准的检查项 | ||
| 33 | +- [ ] CHK006 本分类最后一条检查项 | ||
| 34 | + | ||
| 35 | +## 备注 | ||
| 36 | + | ||
| 37 | +- 完成后勾选:`[x]` | ||
| 38 | +- 发现问题可直接在行内补充说明 | ||
| 39 | +- 可添加链接指向相关资源或文档 | ||
| 40 | +- 条目按编号顺序排列,便于引用 |
.specify/templates/plan-template.md
0 → 100644
| 1 | +# 实现计划:[FEATURE] | ||
| 2 | + | ||
| 3 | +**分支**:`[###-feature-name]` | **日期**:[DATE] | **规格说明**:[link] | ||
| 4 | +**输入**:来自 `/specs/[###-feature-name]/spec.md` 的功能规格说明 | ||
| 5 | + | ||
| 6 | +**说明**:本模板由 `/speckit.plan` 命令填充。执行流程可参考 `.specify/templates/commands/plan.md`。 | ||
| 7 | + | ||
| 8 | +## 摘要 | ||
| 9 | + | ||
| 10 | +[从功能规格中提取:核心需求 +(结合 research 的)技术方案摘要] | ||
| 11 | + | ||
| 12 | +## 技术上下文 | ||
| 13 | + | ||
| 14 | +<!-- | ||
| 15 | + 必填:将本章节内容替换为项目的技术细节。 | ||
| 16 | + 这里的结构仅用于引导迭代过程,可按需调整。 | ||
| 17 | +--> | ||
| 18 | + | ||
| 19 | +**语言/版本**: [例如 Python 3.11、Swift 5.9、Rust 1.75 或 NEEDS CLARIFICATION] | ||
| 20 | +**主要依赖**: [例如 FastAPI、UIKit、LLVM 或 NEEDS CLARIFICATION] | ||
| 21 | +**存储**: [如适用,例如 PostgreSQL、CoreData、文件 或 N/A] | ||
| 22 | +**测试**: [例如 pytest、XCTest、cargo test 或 NEEDS CLARIFICATION] | ||
| 23 | +**目标平台**: [例如 Linux 服务器、iOS 15+、WASM 或 NEEDS CLARIFICATION] | ||
| 24 | +**工程类型**: [single/web/mobile - 决定源码结构] | ||
| 25 | +**性能目标**: [领域相关,例如 1000 req/s、10k lines/sec、60 fps 或 NEEDS CLARIFICATION] | ||
| 26 | +**约束**: [领域相关,例如 <200ms p95、<100MB 内存、支持离线 或 NEEDS CLARIFICATION] | ||
| 27 | +**规模/范围**: [领域相关,例如 10k 用户、1M LOC、50 个页面 或 NEEDS CLARIFICATION] | ||
| 28 | + | ||
| 29 | +## 宪法检查 | ||
| 30 | + | ||
| 31 | +*门禁:必须在阶段 0 调研前通过;在阶段 1 设计后需要复检。* | ||
| 32 | + | ||
| 33 | +[根据 constitution 文件确定门禁项] | ||
| 34 | + | ||
| 35 | + | ||
| 36 | +## 项目结构 | ||
| 37 | + | ||
| 38 | +### 文档(本功能) | ||
| 39 | + | ||
| 40 | +```text | ||
| 41 | +specs/[###-feature]/ | ||
| 42 | +├── plan.md # 本文件(/speckit.plan 输出) | ||
| 43 | +├── research.md # 阶段 0 输出(/speckit.plan) | ||
| 44 | +├── data-model.md # 阶段 1 输出(/speckit.plan) | ||
| 45 | +├── quickstart.md # 阶段 1 输出(/speckit.plan) | ||
| 46 | +├── contracts/ # 阶段 1 输出(/speckit.plan) | ||
| 47 | +└── tasks.md # 阶段 2 输出(/speckit.tasks - 不由 /speckit.plan 创建) | ||
| 48 | +``` | ||
| 49 | + | ||
| 50 | +### 源码(仓库根目录) | ||
| 51 | +<!-- | ||
| 52 | + 必填:将下方占位树替换为本功能对应的真实目录结构。 | ||
| 53 | + 删除未使用的选项,并将所选结构扩展为真实路径(例如 apps/admin、packages/xxx)。 | ||
| 54 | + 最终交付的 plan.md 不应包含 “选项” 字样。 | ||
| 55 | +--> | ||
| 56 | + | ||
| 57 | +```text | ||
| 58 | +# [不使用则删除] 选项 1:单体工程(默认) | ||
| 59 | +src/ | ||
| 60 | +├── models/ | ||
| 61 | +├── services/ | ||
| 62 | +├── cli/ | ||
| 63 | +└── lib/ | ||
| 64 | + | ||
| 65 | +tests/ | ||
| 66 | +├── contract/ | ||
| 67 | +├── integration/ | ||
| 68 | +└── unit/ | ||
| 69 | + | ||
| 70 | +# [不使用则删除] 选项 2:Web 应用(检测到 "frontend" + "backend" 时) | ||
| 71 | +backend/ | ||
| 72 | +├── src/ | ||
| 73 | +│ ├── models/ | ||
| 74 | +│ ├── services/ | ||
| 75 | +│ └── api/ | ||
| 76 | +└── tests/ | ||
| 77 | + | ||
| 78 | +frontend/ | ||
| 79 | +├── src/ | ||
| 80 | +│ ├── components/ | ||
| 81 | +│ ├── pages/ | ||
| 82 | +│ └── services/ | ||
| 83 | +└── tests/ | ||
| 84 | + | ||
| 85 | +# [不使用则删除] 选项 3:移动端 + API(检测到 "iOS/Android" 时) | ||
| 86 | +api/ | ||
| 87 | +└── [同 backend 的结构] | ||
| 88 | + | ||
| 89 | +ios/ 或 android/ | ||
| 90 | +└── [平台相关结构:功能模块、UI 流程、平台测试] | ||
| 91 | +``` | ||
| 92 | + | ||
| 93 | +**结构选择**: [说明最终选择的结构,并引用上面列出的真实目录] | ||
| 94 | + | ||
| 95 | +## 复杂度记录 | ||
| 96 | + | ||
| 97 | +> **仅在“宪法检查”存在需要解释的违规项时填写** | ||
| 98 | + | ||
| 99 | +| 违规项 | 必要性 | 为什么不能用更简单方案 | | ||
| 100 | +|--------|--------|--------------------------| | ||
| 101 | +| [例如:第 4 个子工程] | [当前需求] | [为何 3 个工程不够] | | ||
| 102 | +| [例如:Repository 模式] | [具体问题] | [为何不能直接访问数据库] | |
.specify/templates/spec-template.md
0 → 100644
| 1 | +# 功能规格说明:[功能名称] | ||
| 2 | + | ||
| 3 | +**功能分支**:`[###-feature-name]` | ||
| 4 | +**创建时间**:[DATE] | ||
| 5 | +**状态**:草稿 | ||
| 6 | +**输入**:用户描述:"$ARGUMENTS" | ||
| 7 | + | ||
| 8 | +## 用户场景与验收 *(必填)* | ||
| 9 | + | ||
| 10 | +<!-- | ||
| 11 | + 重要:用户故事必须按重要性排序,体现为“用户旅程”优先级。 | ||
| 12 | + 每个用户故事/旅程都必须可以独立验收——意味着即使只实现其中一个, | ||
| 13 | + 也应当形成可用的 MVP(最小可用产品)并能交付价值。 | ||
| 14 | + | ||
| 15 | + 为每个故事分配优先级(P1、P2、P3...),其中 P1 最关键。 | ||
| 16 | + 将每个故事视为一段可独立交付的功能切片,能够: | ||
| 17 | + - 独立开发 | ||
| 18 | + - 独立验收 | ||
| 19 | + - 独立部署 | ||
| 20 | + - 独立向用户演示 | ||
| 21 | +--> | ||
| 22 | + | ||
| 23 | +### 用户故事 1 - [简短标题](优先级:P1) | ||
| 24 | + | ||
| 25 | +[用通俗语言描述该用户旅程] | ||
| 26 | + | ||
| 27 | +**为什么是这个优先级**:[解释价值以及为何排在该优先级] | ||
| 28 | + | ||
| 29 | +**独立验收**:[描述如何独立验收——例如:“通过 [具体动作] 即可完整验收,并交付 [具体价值]”】【示例仅供参考】 | ||
| 30 | + | ||
| 31 | +**验收场景**: | ||
| 32 | + | ||
| 33 | +1. **假设** [初始状态],**当** [动作],**则** [期望结果] | ||
| 34 | +2. **假设** [初始状态],**当** [动作],**则** [期望结果] | ||
| 35 | + | ||
| 36 | +--- | ||
| 37 | + | ||
| 38 | +### 用户故事 2 - [简短标题](优先级:P2) | ||
| 39 | + | ||
| 40 | +[用通俗语言描述该用户旅程] | ||
| 41 | + | ||
| 42 | +**为什么是这个优先级**:[解释价值以及为何排在该优先级] | ||
| 43 | + | ||
| 44 | +**独立验收**:[描述如何独立验收] | ||
| 45 | + | ||
| 46 | +**验收场景**: | ||
| 47 | + | ||
| 48 | +1. **假设** [初始状态],**当** [动作],**则** [期望结果] | ||
| 49 | + | ||
| 50 | +--- | ||
| 51 | + | ||
| 52 | +### 用户故事 3 - [简短标题](优先级:P3) | ||
| 53 | + | ||
| 54 | +[用通俗语言描述该用户旅程] | ||
| 55 | + | ||
| 56 | +**为什么是这个优先级**:[解释价值以及为何排在该优先级] | ||
| 57 | + | ||
| 58 | +**独立验收**:[描述如何独立验收] | ||
| 59 | + | ||
| 60 | +**验收场景**: | ||
| 61 | + | ||
| 62 | +1. **假设** [初始状态],**当** [动作],**则** [期望结果] | ||
| 63 | + | ||
| 64 | +--- | ||
| 65 | + | ||
| 66 | +[按需补充更多用户故事,并为每个故事分配优先级] | ||
| 67 | + | ||
| 68 | +### 边界情况 | ||
| 69 | + | ||
| 70 | +<!-- | ||
| 71 | + 需要补全:本章节内容是占位符。 | ||
| 72 | + 请补充正确的边界情况。 | ||
| 73 | +--> | ||
| 74 | + | ||
| 75 | +- 当 [边界条件] 时会发生什么? | ||
| 76 | +- 系统如何处理 [错误场景]? | ||
| 77 | + | ||
| 78 | +## 需求 *(必填)* | ||
| 79 | + | ||
| 80 | +<!-- | ||
| 81 | + 需要补全:本章节内容是占位符。 | ||
| 82 | + 请补充正确的功能需求。 | ||
| 83 | +--> | ||
| 84 | + | ||
| 85 | +### 功能需求 | ||
| 86 | + | ||
| 87 | +- **FR-001**:系统必须 [具体能力,例如“允许用户创建账号”] | ||
| 88 | +- **FR-002**:系统必须 [具体能力,例如“校验邮箱地址”] | ||
| 89 | +- **FR-003**:用户必须能够 [关键交互,例如“重置密码”] | ||
| 90 | +- **FR-004**:系统必须 [数据要求,例如“持久化用户偏好”] | ||
| 91 | +- **FR-005**:系统必须 [行为要求,例如“记录所有安全事件”] | ||
| 92 | + | ||
| 93 | +*标记不清晰需求的示例:* | ||
| 94 | + | ||
| 95 | +- **FR-006**:系统必须通过 [NEEDS CLARIFICATION:认证方式未指定——邮箱/密码、SSO、OAuth?] 对用户进行认证 | ||
| 96 | +- **FR-007**:系统必须保留用户数据 [NEEDS CLARIFICATION:保留期限未指定] | ||
| 97 | + | ||
| 98 | +### 关键实体 *(当功能涉及数据时填写)* | ||
| 99 | + | ||
| 100 | +- **[实体 1]**:[表示什么;关键属性(不写实现细节)] | ||
| 101 | +- **[实体 2]**:[表示什么;与其他实体的关系] | ||
| 102 | + | ||
| 103 | +## 成功标准 *(必填)* | ||
| 104 | + | ||
| 105 | +<!-- | ||
| 106 | + 需要补全:定义可度量的成功标准。 | ||
| 107 | + 必须与技术实现无关,并且可度量。 | ||
| 108 | +--> | ||
| 109 | + | ||
| 110 | +### 可度量结果 | ||
| 111 | + | ||
| 112 | +- **SC-001**:[可度量指标,例如“用户可在 2 分钟内完成账号创建”】【示例仅供参考】 | ||
| 113 | +- **SC-002**:[可度量指标,例如“系统在 1000 并发用户下无明显劣化”】【示例仅供参考】 | ||
| 114 | +- **SC-003**:[用户满意度指标,例如“90% 的用户首次尝试即可完成主任务”】【示例仅供参考】 | ||
| 115 | +- **SC-004**:[业务指标,例如“将与 [X] 相关的支持工单减少 50%”】【示例仅供参考】 |
.specify/templates/tasks-template.md
0 → 100644
| 1 | +--- | ||
| 2 | + | ||
| 3 | +description: "功能实现的任务清单模板" | ||
| 4 | +--- | ||
| 5 | + | ||
| 6 | +# 任务清单:[功能名称] | ||
| 7 | + | ||
| 8 | +**输入**:来自 `/specs/[###-feature-name]/` 的设计与规格文档 | ||
| 9 | +**前置条件**:plan.md(必需)、spec.md(用户故事必需)、research.md、data-model.md、contracts/ | ||
| 10 | + | ||
| 11 | +**测试**:下方示例包含测试任务。测试是可选的——仅在功能规格中明确要求时才需要包含。 | ||
| 12 | + | ||
| 13 | +**组织方式**:任务按用户故事分组,保证每个故事都能独立实现与独立验证。 | ||
| 14 | + | ||
| 15 | +## 格式:`[ID] [P?] [Story] 描述` | ||
| 16 | + | ||
| 17 | +- **[P]**:可并行执行(不同文件、无依赖) | ||
| 18 | +- **[Story]**:任务所属用户故事(例如 US1、US2、US3) | ||
| 19 | +- 描述中包含准确的文件路径 | ||
| 20 | + | ||
| 21 | +## 路径约定 | ||
| 22 | + | ||
| 23 | +- **单体工程**:仓库根目录下 `src/`、`tests/` | ||
| 24 | +- **Web 应用**:`backend/src/`、`frontend/src/` | ||
| 25 | +- **移动端**:`api/src/`、`ios/src/` 或 `android/src/` | ||
| 26 | +- 下方展示默认按“单体工程”组织;请根据 plan.md 的结构调整 | ||
| 27 | + | ||
| 28 | +<!-- | ||
| 29 | + ============================================================================ | ||
| 30 | + 重要:以下任务仅为示例,用于说明结构与写法。 | ||
| 31 | + | ||
| 32 | + /speckit.tasks 命令必须基于以下信息,将这些示例替换为真实任务: | ||
| 33 | + - spec.md 的用户故事(含优先级 P1、P2、P3...) | ||
| 34 | + - plan.md 的功能与实现要求 | ||
| 35 | + - data-model.md 的实体 | ||
| 36 | + - contracts/ 的接口定义 | ||
| 37 | + | ||
| 38 | + 任务必须按用户故事组织,使每个故事都能: | ||
| 39 | + - 独立实现 | ||
| 40 | + - 独立验证 | ||
| 41 | + - 作为 MVP 增量交付 | ||
| 42 | + | ||
| 43 | + 生成的 tasks.md 文件中不要保留这些示例任务。 | ||
| 44 | + ============================================================================ | ||
| 45 | +--> | ||
| 46 | + | ||
| 47 | +## 阶段 1:准备(共享基础设施) | ||
| 48 | + | ||
| 49 | +**目标**:项目初始化与基础结构搭建 | ||
| 50 | + | ||
| 51 | +- [ ] T001 按实现计划创建项目结构 | ||
| 52 | +- [ ] T002 初始化 [language] 工程并安装 [framework] 依赖 | ||
| 53 | +- [ ] T003 [P] 配置 lint 与格式化工具 | ||
| 54 | + | ||
| 55 | +--- | ||
| 56 | + | ||
| 57 | +## 阶段 2:基础(阻塞性前置条件) | ||
| 58 | + | ||
| 59 | +**目标**:核心基础设施(任何用户故事开始前必须完成) | ||
| 60 | + | ||
| 61 | +**⚠️ 关键**:在本阶段完成前,不允许开始任何用户故事的工作 | ||
| 62 | + | ||
| 63 | +基础任务示例(请根据项目实际情况调整): | ||
| 64 | + | ||
| 65 | +- [ ] T004 建立数据库结构与迁移机制 | ||
| 66 | +- [ ] T005 [P] 实现认证/鉴权框架 | ||
| 67 | +- [ ] T006 [P] 建立 API 路由与中间件结构 | ||
| 68 | +- [ ] T007 创建所有故事依赖的基础模型/实体 | ||
| 69 | +- [ ] T008 配置错误处理与日志基础设施 | ||
| 70 | +- [ ] T009 建立环境配置管理 | ||
| 71 | + | ||
| 72 | +**检查点**:基础设施就绪——用户故事实现可以并行推进 | ||
| 73 | + | ||
| 74 | +--- | ||
| 75 | + | ||
| 76 | +## 阶段 3:用户故事 1 - [标题](优先级:P1)🎯 MVP | ||
| 77 | + | ||
| 78 | +**目标**:[简述该故事交付内容] | ||
| 79 | + | ||
| 80 | +**独立验证**:[如何单独验证该故事可用] | ||
| 81 | + | ||
| 82 | +### 用户故事 1 的测试(可选:仅在明确要求测试时)⚠️ | ||
| 83 | + | ||
| 84 | +> **注意:先写测试,并确保在实现前测试会失败** | ||
| 85 | + | ||
| 86 | +- [ ] T010 [P] [US1] 为 [endpoint] 编写契约测试:tests/contract/test_[name].py | ||
| 87 | +- [ ] T011 [P] [US1] 为 [用户旅程] 编写集成测试:tests/integration/test_[name].py | ||
| 88 | + | ||
| 89 | +### 用户故事 1 的实现 | ||
| 90 | + | ||
| 91 | +- [ ] T012 [P] [US1] 创建 [Entity1] 模型:src/models/[entity1].py | ||
| 92 | +- [ ] T013 [P] [US1] 创建 [Entity2] 模型:src/models/[entity2].py | ||
| 93 | +- [ ] T014 [US1] 实现 [Service]:src/services/[service].py(依赖 T012、T013) | ||
| 94 | +- [ ] T015 [US1] 实现 [endpoint/feature]:src/[location]/[file].py | ||
| 95 | +- [ ] T016 [US1] 增加校验与错误处理 | ||
| 96 | +- [ ] T017 [US1] 为用户故事 1 的关键操作补充日志 | ||
| 97 | + | ||
| 98 | +**检查点**:此时用户故事 1 应完整可用,且可独立验证 | ||
| 99 | + | ||
| 100 | +--- | ||
| 101 | + | ||
| 102 | +## 阶段 4:用户故事 2 - [标题](优先级:P2) | ||
| 103 | + | ||
| 104 | +**目标**:[简述该故事交付内容] | ||
| 105 | + | ||
| 106 | +**独立验证**:[如何单独验证该故事可用] | ||
| 107 | + | ||
| 108 | +### 用户故事 2 的测试(可选:仅在明确要求测试时)⚠️ | ||
| 109 | + | ||
| 110 | +- [ ] T018 [P] [US2] 为 [endpoint] 编写契约测试:tests/contract/test_[name].py | ||
| 111 | +- [ ] T019 [P] [US2] 为 [用户旅程] 编写集成测试:tests/integration/test_[name].py | ||
| 112 | + | ||
| 113 | +### 用户故事 2 的实现 | ||
| 114 | + | ||
| 115 | +- [ ] T020 [P] [US2] 创建 [Entity] 模型:src/models/[entity].py | ||
| 116 | +- [ ] T021 [US2] 实现 [Service]:src/services/[service].py | ||
| 117 | +- [ ] T022 [US2] 实现 [endpoint/feature]:src/[location]/[file].py | ||
| 118 | +- [ ] T023 [US2] 与用户故事 1 的组件集成(如需要) | ||
| 119 | + | ||
| 120 | +**检查点**:此时用户故事 1 和 2 应都能独立工作 | ||
| 121 | + | ||
| 122 | +--- | ||
| 123 | + | ||
| 124 | +## 阶段 5:用户故事 3 - [标题](优先级:P3) | ||
| 125 | + | ||
| 126 | +**目标**:[简述该故事交付内容] | ||
| 127 | + | ||
| 128 | +**独立验证**:[如何单独验证该故事可用] | ||
| 129 | + | ||
| 130 | +### 用户故事 3 的测试(可选:仅在明确要求测试时)⚠️ | ||
| 131 | + | ||
| 132 | +- [ ] T024 [P] [US3] 为 [endpoint] 编写契约测试:tests/contract/test_[name].py | ||
| 133 | +- [ ] T025 [P] [US3] 为 [用户旅程] 编写集成测试:tests/integration/test_[name].py | ||
| 134 | + | ||
| 135 | +### 用户故事 3 的实现 | ||
| 136 | + | ||
| 137 | +- [ ] T026 [P] [US3] 创建 [Entity] 模型:src/models/[entity].py | ||
| 138 | +- [ ] T027 [US3] 实现 [Service]:src/services/[service].py | ||
| 139 | +- [ ] T028 [US3] 实现 [endpoint/feature]:src/[location]/[file].py | ||
| 140 | + | ||
| 141 | +**检查点**:此时所有用户故事都应可独立运行 | ||
| 142 | + | ||
| 143 | +--- | ||
| 144 | + | ||
| 145 | +[如需要,可按同样格式继续添加更多用户故事阶段] | ||
| 146 | + | ||
| 147 | +--- | ||
| 148 | + | ||
| 149 | +## 阶段 N:打磨与横切关注点 | ||
| 150 | + | ||
| 151 | +**目标**:影响多个用户故事的改进项 | ||
| 152 | + | ||
| 153 | +- [ ] TXXX [P] 更新 docs/ 内的文档 | ||
| 154 | +- [ ] TXXX 代码清理与重构 | ||
| 155 | +- [ ] TXXX 跨故事的性能优化 | ||
| 156 | +- [ ] TXXX [P] 在 tests/unit/ 增加单测(如需) | ||
| 157 | +- [ ] TXXX 安全加固 | ||
| 158 | +- [ ] TXXX 验证 quickstart.md | ||
| 159 | + | ||
| 160 | +--- | ||
| 161 | + | ||
| 162 | +## 依赖与执行顺序 | ||
| 163 | + | ||
| 164 | +### 阶段依赖 | ||
| 165 | + | ||
| 166 | +- **准备(阶段 1)**:无依赖,可立即开始 | ||
| 167 | +- **基础(阶段 2)**:依赖准备阶段完成——阻塞所有用户故事 | ||
| 168 | +- **用户故事(阶段 3+)**:均依赖基础阶段完成 | ||
| 169 | + - 之后可并行推进(若有人力) | ||
| 170 | + - 或按优先级串行推进(P1 → P2 → P3) | ||
| 171 | +- **打磨(最终阶段)**:依赖所有目标用户故事完成 | ||
| 172 | + | ||
| 173 | +### 用户故事依赖 | ||
| 174 | + | ||
| 175 | +- **用户故事 1(P1)**:基础阶段后可开始——不依赖其他故事 | ||
| 176 | +- **用户故事 2(P2)**:基础阶段后可开始——可能与 US1 集成,但应保持可独立验证 | ||
| 177 | +- **用户故事 3(P3)**:基础阶段后可开始——可能与 US1/US2 集成,但应保持可独立验证 | ||
| 178 | + | ||
| 179 | +### 每个用户故事内部顺序 | ||
| 180 | + | ||
| 181 | +- 测试(如包含)必须先写,且在实现前应失败 | ||
| 182 | +- 先模型,再服务 | ||
| 183 | +- 先服务,再接口/端点 | ||
| 184 | +- 先核心实现,再集成 | ||
| 185 | +- 完成当前故事后再进入下一个优先级 | ||
| 186 | + | ||
| 187 | +### 并行机会 | ||
| 188 | + | ||
| 189 | +- 阶段 1 中标记 [P] 的任务可并行 | ||
| 190 | +- 阶段 2 中标记 [P] 的任务可并行(在阶段 2 内) | ||
| 191 | +- 基础阶段完成后,所有用户故事可并行开始(若团队容量允许) | ||
| 192 | +- 用户故事内标记 [P] 的测试可并行 | ||
| 193 | +- 用户故事内标记 [P] 的模型可并行 | ||
| 194 | +- 不同用户故事可由不同成员并行推进 | ||
| 195 | + | ||
| 196 | +--- | ||
| 197 | + | ||
| 198 | +## 并行示例:用户故事 1 | ||
| 199 | + | ||
| 200 | +```bash | ||
| 201 | +# 一次性启动用户故事 1 的所有测试(如需要测试): | ||
| 202 | +任务: "为 [endpoint] 编写契约测试:tests/contract/test_[name].py" | ||
| 203 | +任务: "为 [用户旅程] 编写集成测试:tests/integration/test_[name].py" | ||
| 204 | + | ||
| 205 | +# 一次性启动用户故事 1 的所有模型任务: | ||
| 206 | +任务: "创建 [Entity1] 模型:src/models/[entity1].py" | ||
| 207 | +任务: "创建 [Entity2] 模型:src/models/[entity2].py" | ||
| 208 | +``` | ||
| 209 | + | ||
| 210 | +--- | ||
| 211 | + | ||
| 212 | +## 实施策略 | ||
| 213 | + | ||
| 214 | +### MVP 优先(仅用户故事 1) | ||
| 215 | + | ||
| 216 | +1. 完成阶段 1:准备 | ||
| 217 | +2. 完成阶段 2:基础(关键:阻塞所有故事) | ||
| 218 | +3. 完成阶段 3:用户故事 1 | ||
| 219 | +4. **停止并验证**:独立验证用户故事 1 | ||
| 220 | +5. 如已就绪则部署/演示 | ||
| 221 | + | ||
| 222 | +### 增量交付 | ||
| 223 | + | ||
| 224 | +1. 完成准备 + 基础 → 基础设施就绪 | ||
| 225 | +2. 增加用户故事 1 → 独立验证 → 部署/演示(MVP) | ||
| 226 | +3. 增加用户故事 2 → 独立验证 → 部署/演示 | ||
| 227 | +4. 增加用户故事 3 → 独立验证 → 部署/演示 | ||
| 228 | +5. 每个故事都应在不破坏既有能力的前提下增量交付价值 | ||
| 229 | + | ||
| 230 | +### 多人并行策略 | ||
| 231 | + | ||
| 232 | +多人协作时: | ||
| 233 | + | ||
| 234 | +1. 团队协作完成准备 + 基础 | ||
| 235 | +2. 基础完成后: | ||
| 236 | + - 开发 A:用户故事 1 | ||
| 237 | + - 开发 B:用户故事 2 | ||
| 238 | + - 开发 C:用户故事 3 | ||
| 239 | +3. 各故事独立完成并独立集成 | ||
| 240 | + | ||
| 241 | +--- | ||
| 242 | + | ||
| 243 | +## 备注 | ||
| 244 | + | ||
| 245 | +- [P] 任务:不同文件、无依赖,可并行 | ||
| 246 | +- [Story] 标签:将任务映射到具体用户故事,便于追溯 | ||
| 247 | +- 每个用户故事都应可独立完成与独立验证 | ||
| 248 | +- 实现前先确认测试会失败(如有测试) | ||
| 249 | +- 每完成一个任务或合理的任务组再提交 | ||
| 250 | +- 在任意检查点都可以停下来做独立验证 | ||
| 251 | +- 避免:任务描述模糊、同文件冲突、跨故事强耦合导致无法独立交付 |
-
Please register or login to post a comment