hookehuyr

chore: stop tracking ignored directories (.claude, .cursor, .specify, .trae, .github, docs)

Showing 50 changed files with 0 additions and 5504 deletions
1 -{
2 - "permissions": {
3 - "allow": [
4 - "Bash(tree:*)",
5 - "Bash(xargs awk:*)",
6 - "Bash(find:*)",
7 - "Bash(grep:*)"
8 - ]
9 - }
10 -}
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
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 是否被清晰规定?”
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
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` 文件上操作。
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` 重新生成任务列表。
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
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%"(技术相关)
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 -- **最终阶段**:打磨与横切关注点
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 ----
2 -description: "生成/更新项目原则(constitution)"
3 -agent: agent
4 ----
5 -
6 -请根据当前仓库的技术栈与业务场景,生成或更新 .specify/memory/constitution.md:
7 -
8 -- 内容使用中文
9 -- 原则需要覆盖:工程规范、UI/样式规范、网络请求返回结构、资源链接规则、开发与发布约束
10 -- 原则要具体可执行,避免空泛口号
11 -- 如果已存在同名文件,保留现有有效条款,只补齐缺失部分并做结构化整理
1 ----
2 -description: "按任务实现并自检(implement)"
3 -agent: agent
4 -argument-hint: "feature_key(例如 001-xxx)"
5 ----
6 -
7 -你需要按 specs/<feature_key>/tasks.md 逐条完成实现,并遵循:
8 -
9 -- 严格对照 specs/<feature_key>/spec.md 的验收标准
10 -- 修改尽量小,避免无关重构
11 -- 接口返回结构固定为 { code, data, msg },错误提示使用 msg
12 -- 图片域名为 cdn.ipadbiz.cn 时追加 ?imageMogr2/thumbnail/200x/strip/quality/70
13 -- 完成后运行现有的测试/检查脚本(如果仓库提供)
14 -- 不要启动或重启任何服务;不要引导打开预览
1 ----
2 -description: "生成技术方案(plan)"
3 -agent: agent
4 -argument-hint: "feature_key(例如 001-xxx)"
5 ----
6 -
7 -基于 specs/<feature_key>/spec.md,生成 specs/<feature_key>/plan.md。
8 -
9 -约束:
10 -- 给出最小改动方案,优先复用现有组件、工具与目录结构
11 -- 明确需要修改/新增的文件清单(按路径列出)
12 -- 明确数据流、状态管理方式(如需要 pinia/route query/组件本地状态)
13 -- 明确接口调用点与返回结构 { code, data, msg } 的处理策略
14 -- 样式:优先 TailwindCSS 类名(项目已引入),Less 用于层级补充
15 -- 不要启动或重启任何服务;不要引导打开预览
16 -
17 -plan.md 输出结构:
18 -1) 总体方案
19 -2) 页面与路由变更
20 -3) 组件与复用点
21 -4) 状态管理与数据流
22 -5) 接口与错误处理
23 -6) 样式与响应式策略
24 -7) 影响范围与回归清单
1 ----
2 -description: "生成需求规格说明(spec)"
3 -agent: agent
4 -argument-hint: "feature_key 与需求描述(例如 001-xxx ...)"
5 ----
6 -
7 -你需要把用户的需求整理成可评审的规格说明,输出到 specs/<feature_key>/spec.md。
8 -
9 -约束:
10 -- 只写“做什么/为什么”,不要写实现细节
11 -- 不确定的信息必须写成 [NEEDS CLARIFICATION],并给出具体要问的问题
12 -- 规格说明使用中文,结构清晰,便于团队协作评审
13 -- feature_key 使用数字前缀与短横线命名,例如 001-course-share-poster
14 -
15 -spec.md 模板:
16 -
17 -1) 背景与目标
18 -- 背景:
19 -- 目标:
20 -- 非目标:
21 -
22 -2) 用户画像与使用场景
23 -
24 -3) 需求范围
25 -- 页面/入口:
26 -- 核心流程:
27 -- 状态与异常分支:
28 -
29 -4) 用户故事(User Stories)
30 -- 作为……我想……以便……
31 -
32 -5) 验收标准(Acceptance Criteria)
33 -- 使用可验证的条件描述
34 -
35 -6) 交互与视觉要点
36 -- 移动端优先(如涉及 PC/移动差异需要说明)
37 -
38 -7) 数据与接口
39 -- 需要的字段(仅描述数据含义,不给实现)
40 -- 返回结构固定为 { code, data, msg }
41 -
42 -8) 边界条件与风险
43 -
44 -9) 待确认事项
45 -- [NEEDS CLARIFICATION] ...
1 ----
2 -description: "从方案拆分可执行任务(tasks)"
3 -agent: agent
4 -argument-hint: "feature_key(例如 001-xxx)"
5 ----
6 -
7 -基于 specs/<feature_key>/plan.md,生成 specs/<feature_key>/tasks.md。
8 -
9 -约束:
10 -- 任务要可执行、可验收,按顺序列出
11 -- 每条任务不超过 14 个字
12 -- 标注必要的回归点
13 -- 不要包含“启动服务/打开预览”相关任务
14 -
15 -tasks.md 格式:
16 -- [ ] 任务1
17 -- [ ] 任务2
18 -
19 -回归清单:
20 -- [ ] 回归项1
21 -- [ ] 回归项2
1 -# mlaj 宪法
2 -
3 -## 核心原则
4 -
5 -### 用户需求为中心
6 -
7 -- 以用户需求为中心,优先实现最小可用闭环,避免过度设计
8 -
9 -### 修改尽量小且可回滚
10 -
11 -- 修改应尽量小且可回滚,避免大范围重构
12 -
13 -### 风格一致与复用优先
14 -
15 -- 保持现有代码风格与目录结构一致,优先复用已有工具与组件
16 -
17 -### 生产代码完毕后不要重启服务
18 -
19 -- 生产代码完毕后不要重启服务
20 -
21 -### 操作完成后不要自动打开预览
22 -
23 -- 操作完成后不要自动打开预览
24 -
25 -## 技术与工程约束
26 -
27 -- 依赖管理使用 pnpm
28 -- 构建工具使用 Vite
29 -- 前端使用 Vue3(优先 setup 语法糖)
30 -- 移动端 UI 优先使用 Vant
31 -- 样式使用 Less;如果项目已引入 TailwindCSS,布局优先用 Tailwind 类名,细节用 Less 补充
32 -- 路由使用 vue-router
33 -- 状态管理使用 pinia(如项目已接入)
34 -- 不引入不必要的新库;如必须新增依赖,需要在方案中说明原因与替代方案
35 -
36 -## 接口与数据约束
37 -
38 -- 所有网络请求返回对象结构必须为 { code, data, msg }
39 -- code=1 表示成功,其他值表示失败;失败时保持 msg 可读
40 -
41 -## 资源与链接约束
42 -
43 -- 若图片域名为 cdn.ipadbiz.cn,需要自动追加 ?imageMogr2/thumbnail/200x/strip/quality/70
44 -
45 -## 治理与管理
46 -
47 -- 本宪法优先级高于临时约定与个人习惯;出现冲突时以本宪法为准
48 -- 宪法变更应同步更新版本/日期信息,并确保对现有工程约束的兼容性
49 -
50 -**版本**: [CONSTITUTION_VERSION] | **批准**: [RATIFICATION_DATE] | **最后修订**: [LAST_AMENDED_DATE]
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
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"; }
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
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
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 -<!-- 手工补充结束 -->
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 -- 条目按编号顺序排列,便于引用
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 模式] | [具体问题] | [为何不能直接访问数据库] |
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%”】【示例仅供参考】
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 -- 避免:任务描述模糊、同文件冲突、跨故事强耦合导致无法独立交付
1 -对话处理完成一个任务后,需要更新changelog.md文件,添加一个新的版本号,版本号的格式为YYYY-MM-DD,版本号的内容为任务的描述.
1 ----
2 -name: vue-best-practices
3 -description: Vue 3 TypeScript, vue-tsc, Volar, Vite, component props, testing, composition API.
4 ----
5 -
6 -# Vue Best Practices
7 -
8 -## Capability Rules
9 -
10 -| Rule | Keywords | Description |
11 -|------|----------|-------------|
12 -| [vue-tsc-strict-templates](rules/vue-tsc-strict-templates.md) | undefined component, template error, strictTemplates | Catch undefined components in templates |
13 -| [fallthrough-attributes](rules/fallthrough-attributes.md) | fallthrough, $attrs, wrapper component | Type-check fallthrough attributes |
14 -| [strict-css-modules](rules/strict-css-modules.md) | css modules, $style, typo | Catch CSS module class typos |
15 -| [data-attributes-config](rules/data-attributes-config.md) | data-*, strictTemplates, attribute | Allow data-* attributes |
16 -| [volar-3-breaking-changes](rules/volar-3-breaking-changes.md) | volar, vue-language-server, editor | Fix Volar 3.0 upgrade issues |
17 -| [module-resolution-bundler](rules/module-resolution-bundler.md) | cannot find module, @vue/tsconfig, moduleResolution | Fix module resolution errors |
18 -| [unplugin-auto-import-conflicts](rules/unplugin-auto-import-conflicts.md) | unplugin, auto-import, types any | Fix unplugin type conflicts |
19 -| [codeactions-save-performance](rules/codeactions-save-performance.md) | slow save, vscode, performance | Fix slow save in large projects |
20 -| [duplicate-plugin-detection](rules/duplicate-plugin-detection.md) | duplicate plugin, vite, vue plugin | Detect duplicate plugins |
21 -| [define-model-update-event](rules/define-model-update-event.md) | defineModel, update event, undefined | Fix model update errors |
22 -| [with-defaults-union-types](rules/with-defaults-union-types.md) | withDefaults, union type, default | Fix union type defaults |
23 -| [deep-watch-numeric](rules/deep-watch-numeric.md) | watch, deep, array, Vue 3.5 | Efficient array watching |
24 -| [vue-directive-comments](rules/vue-directive-comments.md) | @vue-ignore, @vue-skip, template | Control template type checking |
25 -| [script-setup-jsdoc](rules/script-setup-jsdoc.md) | jsdoc, script setup, documentation | Add JSDoc to script setup |
26 -| [vue-router-typed-params](rules/vue-router-typed-params.md) | route params, typed router, unplugin | Fix route params typing |
27 -
28 -## Efficiency Rules
29 -
30 -| Rule | Keywords | Description |
31 -|------|----------|-------------|
32 -| [hmr-vue-ssr](rules/hmr-vue-ssr.md) | hmr, ssr, hot reload | Fix HMR in SSR apps |
33 -| [pinia-store-mocking](rules/pinia-store-mocking.md) | pinia, mock, vitest, store | Mock Pinia stores |
34 -
35 -## Reference
36 -
37 -- [Vue Language Tools](https://github.com/vuejs/language-tools)
38 -- [Vue 3 Documentation](https://vuejs.org/)
...\ No newline at end of file ...\ No newline at end of file
1 ----
2 -title: Fix Slow Save Times with Code Actions Setting
3 -impact: HIGH
4 -impactDescription: fixes 30-60 second save delays in large Vue projects
5 -type: capability
6 -tags: performance, save-time, vscode, code-actions, volar
7 ----
8 -
9 -# Fix Slow Save Times with Code Actions Setting
10 -
11 -**Impact: HIGH** - fixes 30-60 second save delays in large Vue projects
12 -
13 -In large Vue projects, saving files can take 30-60+ seconds due to VSCode's code actions triggering expensive TypeScript state synchronization.
14 -
15 -## Problem
16 -
17 -Symptoms:
18 -- Save operation takes 30+ seconds
19 -- Editor becomes unresponsive during save
20 -- CPU spikes when saving Vue files
21 -- Happens more in larger projects
22 -
23 -## Root Cause
24 -
25 -VSCode emits document change events multiple times during save cycles. Each event triggers Volar to synchronize with TypeScript, causing expensive re-computation.
26 -
27 -## Solution
28 -
29 -Disable code actions or limit their timeout:
30 -
31 -**Option 1: Disable code actions (fastest)**
32 -```json
33 -// .vscode/settings.json
34 -{
35 - "vue.codeActions.enabled": false
36 -}
37 -```
38 -
39 -**Option 2: Limit code action time**
40 -```json
41 -// .vscode/settings.json
42 -{
43 - "vue.codeActions.savingTimeLimit": 1000
44 -}
45 -```
46 -
47 -**Option 3: Disable specific code actions**
48 -```json
49 -// .vscode/settings.json
50 -{
51 - "vue.codeActions.enabled": true,
52 - "editor.codeActionsOnSave": {
53 - "source.organizeImports": "never"
54 - }
55 -}
56 -```
57 -
58 -## VSCode Version Requirement
59 -
60 -VSCode 1.81.0+ includes fixes that reduce save time issues. Upgrade if using an older version.
61 -
62 -## Additional Optimizations
63 -
64 -```json
65 -// .vscode/settings.json
66 -{
67 - "vue.codeActions.enabled": false,
68 - "editor.formatOnSave": true,
69 - "editor.codeActionsOnSave": {},
70 - "[vue]": {
71 - "editor.formatOnSave": true,
72 - "editor.defaultFormatter": "Vue.volar"
73 - }
74 -}
75 -```
76 -
77 -## Reference
78 -
79 -- [Vue Language Tools Discussion #2740](https://github.com/vuejs/language-tools/discussions/2740)
1 ----
2 -title: Allow Data Attributes with Strict Templates
3 -impact: MEDIUM
4 -impactDescription: fixes data-testid and data-* attribute errors in strict mode
5 -type: capability
6 -tags: dataAttributes, vueCompilerOptions, strictTemplates, data-testid, testing
7 ----
8 -
9 -# Allow Data Attributes with Strict Templates
10 -
11 -**Impact: MEDIUM** - fixes data-testid and data-* attribute errors in strict mode
12 -
13 -With `strictTemplates` enabled, `data-*` attributes on components cause type errors. Use the `dataAttributes` option to allow specific patterns.
14 -
15 -## Problem
16 -
17 -```vue
18 -<template>
19 - <!-- Error: Property 'data-testid' does not exist on type... -->
20 - <MyComponent data-testid="submit-button" />
21 -
22 - <!-- Error: Property 'data-cy' does not exist on type... -->
23 - <MyComponent data-cy="login-form" />
24 -</template>
25 -```
26 -
27 -## Solution
28 -
29 -Configure `dataAttributes` to allow specific patterns:
30 -
31 -```json
32 -// tsconfig.json or tsconfig.app.json
33 -{
34 - "vueCompilerOptions": {
35 - "strictTemplates": true,
36 - "dataAttributes": ["data-*"]
37 - }
38 -}
39 -```
40 -
41 -Now all `data-*` attributes are allowed on any component.
42 -
43 -## Specific Patterns
44 -
45 -You can be more selective:
46 -
47 -```json
48 -{
49 - "vueCompilerOptions": {
50 - "dataAttributes": [
51 - "data-testid",
52 - "data-cy",
53 - "data-test-*"
54 - ]
55 - }
56 -}
57 -```
58 -
59 -This only allows the specified patterns, not all data attributes.
60 -
61 -## Common Testing Attributes
62 -
63 -For testing libraries, allow their specific attributes:
64 -
65 -| Library | Attribute | Pattern |
66 -|---------|-----------|---------|
67 -| Testing Library | `data-testid` | `"data-testid"` |
68 -| Cypress | `data-cy` | `"data-cy"` |
69 -| Playwright | `data-testid` | `"data-testid"` |
70 -| Generic | All data attributes | `"data-*"` |
71 -
72 -## Reference
73 -
74 -- [Vue Language Tools Wiki - Vue Compiler Options](https://github.com/vuejs/language-tools/wiki/Vue-Compiler-Options)
1 ----
2 -title: Vue 3.5+ Deep Watch Numeric Depth
3 -impact: MEDIUM
4 -impactDescription: enables efficient array mutation watching with numeric deep option
5 -type: capability
6 -tags: watch, deep, vue-3.5, array, mutation, performance
7 ----
8 -
9 -# Vue 3.5+ Deep Watch Numeric Depth
10 -
11 -**Impact: MEDIUM** - enables efficient array mutation watching with numeric deep option
12 -
13 -Vue 3.5 introduced `deep: number` for watch depth control. This allows watching array mutations without the performance cost of deep traversal.
14 -
15 -## Symptoms
16 -
17 -- Array mutations not triggering watch callback
18 -- Deep watch causing performance issues on large nested objects
19 -- Unaware of new Vue 3.5 feature
20 -
21 -> **Note:** TypeScript error "Type 'number' is not assignable to type 'boolean'" no longer occurs with Vue 3.5+ and current TypeScript versions. The types now correctly support numeric `deep` values.
22 -
23 -## The Feature
24 -
25 -```typescript
26 -// Vue 3.5+ only
27 -watch(items, (newVal) => {
28 - // Triggered on array mutations (push, pop, splice, etc.)
29 -}, { deep: 1 })
30 -```
31 -
32 -| deep value | Behavior |
33 -|------------|----------|
34 -| `true` | Full recursive traversal (original behavior) |
35 -| `false` | Only reference changes |
36 -| `1` | One level deep - array mutations, not nested objects |
37 -| `2` | Two levels deep |
38 -| `n` | N levels deep |
39 -
40 -## Fix
41 -
42 -**Step 1: Ensure Vue 3.5+**
43 -```bash
44 -npm install vue@^3.5.0
45 -```
46 -
47 -**Step 2: Update @vue/runtime-core types**
48 -```bash
49 -npm install -D @vue/runtime-core@latest
50 -```
51 -
52 -**Step 3: Use numeric depth**
53 -```typescript
54 -import { watch, ref } from 'vue'
55 -
56 -const items = ref([{ id: 1, data: { nested: 'value' } }])
57 -
58 -// Watch array mutations only (push, pop, etc.)
59 -watch(items, (newItems) => {
60 - console.log('Array mutated')
61 -}, { deep: 1 })
62 -
63 -// Won't trigger on: items.value[0].data.nested = 'new'
64 -// Will trigger on: items.value.push(newItem)
65 -```
66 -
67 -## Performance Comparison
68 -
69 -```typescript
70 -const largeNestedData = ref({ /* deeply nested structure */ })
71 -
72 -// SLOW - traverses entire structure
73 -watch(largeNestedData, handler, { deep: true })
74 -
75 -// FAST - only watches top-level changes
76 -watch(largeNestedData, handler, { deep: 1 })
77 -
78 -// FASTEST - only reference changes
79 -watch(largeNestedData, handler, { deep: false })
80 -```
81 -
82 -## Alternative: watchEffect for Selective Tracking
83 -
84 -```typescript
85 -// Only tracks properties actually accessed
86 -watchEffect(() => {
87 - // Only re-runs when items.value.length or first item changes
88 - console.log(items.value.length, items.value[0]?.id)
89 -})
90 -```
91 -
92 -## TypeScript Note
93 -
94 -If TypeScript complains about numeric deep, ensure:
95 -1. Vue version is 3.5+
96 -2. `@vue/runtime-core` types are updated
97 -3. tsconfig targets correct node_modules types
98 -
99 -## Reference
100 -
101 -- [Vue Watchers Docs](https://vuejs.org/guide/essentials/watchers.html)
102 -- [Vue 3.5 Release Notes](https://blog.vuejs.org/posts/vue-3-5)
1 ----
2 -title: defineModel Fires Update Event with Undefined
3 -impact: MEDIUM
4 -impactDescription: fixes runtime errors from unexpected undefined in model updates
5 -type: capability
6 -tags: defineModel, v-model, update-event, undefined, vue-3.5
7 ----
8 -
9 -# defineModel Fires Update Event with Undefined
10 -
11 -**Impact: MEDIUM** - fixes runtime errors from unexpected undefined in model updates
12 -
13 -> **Version Note (2025):** This issue may be resolved in Vue 3.5+. Testing with Vue 3.5.26 could not reproduce the double emission with `undefined`. If you're on Vue 3.5+, verify the issue exists in your specific scenario before applying workarounds.
14 -
15 -Components using `defineModel` may fire the `@update:model-value` event with `undefined` in certain edge cases. TypeScript types don't always reflect this behavior, potentially causing runtime errors when the parent expects a non-nullable value.
16 -
17 -## Symptoms
18 -
19 -- Parent component receives `undefined` unexpectedly
20 -- Runtime error: "Cannot read property of undefined"
21 -- Type mismatch between expected `T` and received `T | undefined`
22 -- Issue appears when clearing/resetting the model value
23 -
24 -## Root Cause
25 -
26 -`defineModel` returns `Ref<T | undefined>` by default, even when `T` is non-nullable. The update event can fire with `undefined` when:
27 -- Component unmounts
28 -- Model is explicitly cleared
29 -- Internal state resets
30 -
31 -## Fix
32 -
33 -**Option 1: Use required option (Vue 3.5+)**
34 -```typescript
35 -// Returns Ref<Item> instead of Ref<Item | undefined>
36 -const model = defineModel<Item>({ required: true })
37 -```
38 -
39 -**Option 2: Type parent handler to accept undefined**
40 -```vue
41 -<template>
42 - <MyComponent
43 - v-model="item"
44 - @update:model-value="handleUpdate"
45 - />
46 -</template>
47 -
48 -<script setup lang="ts">
49 -// Handle both value and undefined
50 -const handleUpdate = (value: Item | undefined) => {
51 - if (value !== undefined) {
52 - item.value = value
53 - }
54 -}
55 -</script>
56 -```
57 -
58 -**Option 3: Use default value in defineModel**
59 -```typescript
60 -const model = defineModel<string>({ default: '' })
61 -```
62 -
63 -## Type Declaration Pattern
64 -
65 -```typescript
66 -// In child component
67 -interface Props {
68 - modelValue: Item
69 -}
70 -const model = defineModel<Item>({ required: true })
71 -
72 -// Emits will be typed as (value: Item) not (value: Item | undefined)
73 -```
74 -
75 -## Reference
76 -
77 -- [vuejs/core#12817](https://github.com/vuejs/core/issues/12817)
78 -- [vuejs/core#10103](https://github.com/vuejs/core/issues/10103)
79 -- [defineModel docs](https://vuejs.org/api/sfc-script-setup.html#definemodel)
1 ----
2 -title: Duplicate Vue Plugin Detection
3 -impact: MEDIUM
4 -impactDescription: fixes cryptic build errors from Vue plugin registered twice
5 -type: capability
6 -tags: vite, plugin, vue, duplicate, config, inline
7 ----
8 -
9 -# Duplicate Vue Plugin Detection
10 -
11 -**Impact: MEDIUM** - fixes cryptic build errors from Vue plugin registered twice
12 -
13 -When using Vite's JavaScript API, if the Vue plugin is loaded in `vite.config.js` and specified again in `inlineConfig`, it gets registered twice, causing cryptic build errors.
14 -
15 -## Symptoms
16 -
17 -- Build produces unexpected output or fails silently
18 -- "Cannot read property of undefined" during build
19 -- Different build behavior between CLI and JavaScript API
20 -- Vue components render incorrectly after build
21 -
22 -## Root Cause
23 -
24 -Vite doesn't deduplicate plugins by name when merging configs. The Vue plugin's internal state gets corrupted when registered twice.
25 -
26 -## Fix
27 -
28 -**Option 1: Use configFile: false with inline plugins**
29 -```typescript
30 -import { build } from 'vite'
31 -import vue from '@vitejs/plugin-vue'
32 -
33 -await build({
34 - configFile: false, // Don't load vite.config.js
35 - plugins: [vue()],
36 - // ... rest of config
37 -})
38 -```
39 -
40 -**Option 2: Don't specify plugins in inlineConfig**
41 -```typescript
42 -// vite.config.js already has vue plugin
43 -import { build } from 'vite'
44 -
45 -await build({
46 - // Don't add vue plugin here - it's in vite.config.js
47 - root: './src',
48 - build: { outDir: '../dist' }
49 -})
50 -```
51 -
52 -**Option 3: Filter out Vue plugin before merging**
53 -```typescript
54 -import { build, loadConfigFromFile } from 'vite'
55 -import vue from '@vitejs/plugin-vue'
56 -
57 -const { config } = await loadConfigFromFile({ command: 'build', mode: 'production' })
58 -
59 -// Remove existing Vue plugin
60 -const filteredPlugins = config.plugins?.filter(
61 - p => !p || (Array.isArray(p) ? false : p.name !== 'vite:vue')
62 -) || []
63 -
64 -await build({
65 - ...config,
66 - plugins: [...filteredPlugins, vue({ /* your options */ })]
67 -})
68 -```
69 -
70 -## Detection Script
71 -
72 -Add this to debug plugin registration:
73 -```typescript
74 -// vite.config.ts
75 -export default defineConfig({
76 - plugins: [
77 - vue(),
78 - {
79 - name: 'debug-plugins',
80 - configResolved(config) {
81 - const vuePlugins = config.plugins.filter(p => p.name?.includes('vue'))
82 - if (vuePlugins.length > 1) {
83 - console.warn('WARNING: Multiple Vue plugins detected:', vuePlugins.map(p => p.name))
84 - }
85 - }
86 - }
87 - ]
88 -})
89 -```
90 -
91 -## Common Scenarios
92 -
93 -| Scenario | Solution |
94 -|----------|----------|
95 -| Using `vite.createServer()` | Use `configFile: false` |
96 -| Build script with custom config | Don't duplicate plugins |
97 -| Monorepo with shared config | Check for plugin inheritance |
98 -
99 -## Reference
100 -
101 -- [Vite Issue #5335](https://github.com/vitejs/vite/issues/5335)
102 -- [Vite JavaScript API](https://vite.dev/guide/api-javascript.html)
1 ----
2 -title: Enable Fallthrough Attributes Type Checking
3 -impact: HIGH
4 -impactDescription: enables type-safe fallthrough attributes in component libraries
5 -type: capability
6 -tags: fallthroughAttributes, vueCompilerOptions, component-library, wrapper-components
7 ----
8 -
9 -# Enable Fallthrough Attributes Type Checking
10 -
11 -**Impact: MEDIUM** - enables type-aware attribute forwarding in component libraries
12 -
13 -When building component libraries with wrapper components, enable `fallthroughAttributes` to get IDE autocomplete for attributes that will be forwarded to child elements.
14 -
15 -## What It Does
16 -
17 -Wrapper components that pass attributes to child elements can benefit from type-aware completion:
18 -
19 -```vue
20 -<!-- MyButton.vue - wrapper around native button -->
21 -<template>
22 - <button v-bind="$attrs"><slot /></button>
23 -</template>
24 -```
25 -
26 -## Solution
27 -
28 -Enable `fallthroughAttributes` in your tsconfig:
29 -
30 -```json
31 -// tsconfig.json or tsconfig.app.json
32 -{
33 - "vueCompilerOptions": {
34 - "fallthroughAttributes": true
35 - }
36 -}
37 -```
38 -
39 -## How It Works
40 -
41 -When `fallthroughAttributes: true`:
42 -- Vue Language Server analyzes which element receives `$attrs`
43 -- IDE autocomplete suggests valid attributes for the target element
44 -- Helps developers discover available attributes
45 -
46 -> **Note:** This primarily enables IDE autocomplete for valid fallthrough attributes. It does NOT reject invalid attributes as type errors - arbitrary attributes are still allowed.
47 -
48 -## Related Options
49 -
50 -Combine with `strictTemplates` for comprehensive checking:
51 -
52 -```json
53 -{
54 - "vueCompilerOptions": {
55 - "strictTemplates": true,
56 - "fallthroughAttributes": true
57 - }
58 -}
59 -```
60 -
61 -## Reference
62 -
63 -- [Vue Language Tools Wiki - Vue Compiler Options](https://github.com/vuejs/language-tools/wiki/Vue-Compiler-Options)
1 ----
2 -title: HMR Debugging for Vue SSR
3 -impact: MEDIUM
4 -impactDescription: fixes Hot Module Replacement breaking in Vue SSR applications
5 -type: efficiency
6 -tags: vite, hmr, ssr, vue, hot-reload, server-side-rendering
7 ----
8 -
9 -# HMR Debugging for Vue SSR
10 -
11 -**Impact: MEDIUM** - fixes Hot Module Replacement breaking in Vue SSR applications
12 -
13 -Hot Module Replacement breaks when modifying Vue component `<script setup>` sections in SSR applications. Changes cause errors instead of smooth updates, requiring full page reloads.
14 -
15 -## Symptoms
16 -
17 -- HMR works for `<template>` changes but breaks for `<script setup>`
18 -- "Cannot read property of undefined" after saving
19 -- Full page reload required after script changes
20 -- HMR works in dev:client but not dev:ssr
21 -
22 -## Root Cause
23 -
24 -SSR mode has a different transformation pipeline. The Vue plugin's HMR boundary detection doesn't handle SSR modules the same way as client modules.
25 -
26 -## Fix
27 -
28 -**Step 1: Ensure correct SSR plugin configuration**
29 -```typescript
30 -// vite.config.ts
31 -import { defineConfig } from 'vite'
32 -import vue from '@vitejs/plugin-vue'
33 -
34 -export default defineConfig({
35 - plugins: [vue()],
36 - ssr: {
37 - // Don't externalize these for HMR to work
38 - noExternal: ['vue', '@vue/runtime-core', '@vue/runtime-dom']
39 - }
40 -})
41 -```
42 -
43 -**Step 2: Configure dev server for SSR HMR**
44 -```typescript
45 -// server.ts
46 -import { createServer } from 'vite'
47 -
48 -const vite = await createServer({
49 - server: { middlewareMode: true },
50 - appType: 'custom'
51 -})
52 -
53 -// Use vite.ssrLoadModule for server-side imports
54 -const { render } = await vite.ssrLoadModule('/src/entry-server.ts')
55 -
56 -// Handle HMR
57 -vite.watcher.on('change', async (file) => {
58 - if (file.endsWith('.vue')) {
59 - // Invalidate the module
60 - const mod = vite.moduleGraph.getModuleById(file)
61 - if (mod) {
62 - vite.moduleGraph.invalidateModule(mod)
63 - }
64 - }
65 -})
66 -```
67 -
68 -**Step 3: Add HMR acceptance in entry-server**
69 -```typescript
70 -// entry-server.ts
71 -import { createApp } from './main'
72 -
73 -export async function render(url: string) {
74 - const app = createApp()
75 - // ... render logic
76 -}
77 -
78 -// Accept HMR updates
79 -if (import.meta.hot) {
80 - import.meta.hot.accept()
81 -}
82 -```
83 -
84 -## Framework-Specific Solutions
85 -
86 -### Nuxt 3
87 -HMR should work out of the box. If not:
88 -```bash
89 -rm -rf .nuxt node_modules/.vite
90 -npm install
91 -npm run dev
92 -```
93 -
94 -### Vite SSR Template
95 -Ensure you're using the latest `@vitejs/plugin-vue`:
96 -```bash
97 -npm install @vitejs/plugin-vue@latest
98 -```
99 -
100 -## Debugging
101 -
102 -Enable verbose HMR logging:
103 -```typescript
104 -// vite.config.ts
105 -export default defineConfig({
106 - server: {
107 - hmr: {
108 - overlay: true
109 - }
110 - },
111 - logLevel: 'info' // Shows HMR updates
112 -})
113 -```
114 -
115 -## Known Limitations
116 -
117 -- HMR for `<script>` (not `<script setup>`) may require full reload
118 -- SSR components with external dependencies may not hot-reload
119 -- State is not preserved for SSR components (expected behavior)
120 -
121 -## Reference
122 -
123 -- [vite-plugin-vue#525](https://github.com/vitejs/vite-plugin-vue/issues/525)
124 -- [Vite SSR Guide](https://vite.dev/guide/ssr.html)
1 ----
2 -title: moduleResolution Bundler Migration Issues
3 -impact: HIGH
4 -impactDescription: fixes "Cannot find module" errors after @vue/tsconfig upgrade
5 -type: capability
6 -tags: moduleResolution, bundler, tsconfig, vue-tsconfig, node, esm
7 ----
8 -
9 -# moduleResolution Bundler Migration Issues
10 -
11 -**Impact: HIGH** - fixes "Cannot find module" errors after @vue/tsconfig upgrade
12 -
13 -Recent versions of `@vue/tsconfig` changed `moduleResolution` from `"node"` to `"bundler"`. This can break existing projects with errors like "Cannot find module 'vue'" or issues with `resolveJsonModule`.
14 -
15 -## Symptoms
16 -
17 -- `Cannot find module 'vue'` or other packages
18 -- `Option '--resolveJsonModule' cannot be specified without 'node' module resolution`
19 -- Errors appear after updating `@vue/tsconfig`
20 -- Some third-party packages no longer resolve
21 -
22 -## Root Cause
23 -
24 -`moduleResolution: "bundler"` requires:
25 -1. TypeScript 5.0+
26 -2. Packages to have proper `exports` field in package.json
27 -3. Different resolution rules than Node.js classic resolution
28 -
29 -## Fix
30 -
31 -**Option 1: Ensure TypeScript 5.0+ everywhere**
32 -```bash
33 -npm install -D typescript@^5.0.0
34 -```
35 -
36 -In monorepos, ALL packages must use TypeScript 5.0+.
37 -
38 -**Option 2: Add compatibility workaround**
39 -```json
40 -{
41 - "compilerOptions": {
42 - "module": "ESNext",
43 - "moduleResolution": "bundler",
44 - "resolvePackageJsonExports": false
45 - }
46 -}
47 -```
48 -
49 -Setting `resolvePackageJsonExports: false` restores compatibility with packages that don't have proper exports.
50 -
51 -**Option 3: Revert to Node resolution**
52 -```json
53 -{
54 - "compilerOptions": {
55 - "moduleResolution": "node"
56 - }
57 -}
58 -```
59 -
60 -## Which Packages Break?
61 -
62 -Packages break if they:
63 -- Lack `exports` field in package.json
64 -- Have incorrect `exports` configuration
65 -- Rely on Node.js-specific resolution behavior
66 -
67 -## Diagnosis
68 -
69 -```bash
70 -# Check which resolution is being used
71 -cat tsconfig.json | grep moduleResolution
72 -
73 -# Test if a specific module resolves
74 -npx tsc --traceResolution 2>&1 | grep "module-name"
75 -```
76 -
77 -## Reference
78 -
79 -- [vuejs/tsconfig#8](https://github.com/vuejs/tsconfig/issues/8)
80 -- [TypeScript moduleResolution docs](https://www.typescriptlang.org/tsconfig#moduleResolution)
81 -- [Vite discussion#14001](https://github.com/vitejs/vite/discussions/14001)
1 ----
2 -title: Mocking Pinia Stores with Vitest
3 -impact: HIGH
4 -impactDescription: properly mocks Pinia stores in component tests
5 -type: efficiency
6 -tags: pinia, vitest, testing, mock, createTestingPinia, store
7 ----
8 -
9 -# Mocking Pinia Stores with Vitest
10 -
11 -**Impact: HIGH** - properly mocks Pinia stores in component tests
12 -
13 -Developers struggle to properly mock Pinia stores: `createTestingPinia` requires explicit `createSpy` configuration, and "injection Symbol(pinia) not found" errors occur without proper setup.
14 -
15 -> **Important (@pinia/testing 1.0+):** The `createSpy` option is **REQUIRED**, not optional. Omitting it throws an error: "You must configure the `createSpy` option."
16 -
17 -## Symptoms
18 -
19 -- "injection Symbol(pinia) not found" error
20 -- "You must configure the `createSpy` option" error
21 -- Actions not properly mocked
22 -- Store state not reset between tests
23 -
24 -## Fix
25 -
26 -**Pattern 1: Basic setup with createTestingPinia**
27 -```typescript
28 -import { mount } from '@vue/test-utils'
29 -import { createTestingPinia } from '@pinia/testing'
30 -import { vi } from 'vitest'
31 -import MyComponent from './MyComponent.vue'
32 -import { useCounterStore } from '@/stores/counter'
33 -
34 -test('component uses store', async () => {
35 - const wrapper = mount(MyComponent, {
36 - global: {
37 - plugins: [
38 - createTestingPinia({
39 - createSpy: vi.fn, // REQUIRED in @pinia/testing 1.0+
40 - initialState: {
41 - counter: { count: 10 } // Set initial state
42 - }
43 - })
44 - ]
45 - }
46 - })
47 -
48 - // Get the store instance AFTER mounting
49 - const store = useCounterStore()
50 -
51 - // Actions are automatically stubbed
52 - await wrapper.find('button').trigger('click')
53 - expect(store.increment).toHaveBeenCalled()
54 -})
55 -```
56 -
57 -**Pattern 2: Customize action behavior**
58 -```typescript
59 -test('component handles async action', async () => {
60 - const wrapper = mount(MyComponent, {
61 - global: {
62 - plugins: [
63 - createTestingPinia({
64 - createSpy: vi.fn,
65 - stubActions: false // Don't stub, use real actions
66 - })
67 - ]
68 - }
69 - })
70 -
71 - const store = useCounterStore()
72 -
73 - // Override specific action
74 - store.fetchData = vi.fn().mockResolvedValue({ items: [] })
75 -
76 - await wrapper.find('.load-button').trigger('click')
77 - expect(store.fetchData).toHaveBeenCalled()
78 -})
79 -```
80 -
81 -**Pattern 3: Testing store directly**
82 -```typescript
83 -import { setActivePinia, createPinia } from 'pinia'
84 -import { useCounterStore } from '@/stores/counter'
85 -
86 -describe('Counter Store', () => {
87 - beforeEach(() => {
88 - setActivePinia(createPinia())
89 - })
90 -
91 - test('increments count', () => {
92 - const store = useCounterStore()
93 - expect(store.count).toBe(0)
94 -
95 - store.increment()
96 - expect(store.count).toBe(1)
97 - })
98 -})
99 -```
100 -
101 -## Setup Store with Vitest
102 -
103 -```typescript
104 -// stores/counter.ts - Setup store syntax
105 -export const useCounterStore = defineStore('counter', () => {
106 - const count = ref(0)
107 - const doubleCount = computed(() => count.value * 2)
108 -
109 - function increment() {
110 - count.value++
111 - }
112 -
113 - return { count, doubleCount, increment }
114 -})
115 -
116 -// Test file
117 -test('setup store works', async () => {
118 - const pinia = createTestingPinia({
119 - createSpy: vi.fn,
120 - initialState: {
121 - counter: { count: 5 }
122 - }
123 - })
124 -
125 - const wrapper = mount(MyComponent, {
126 - global: { plugins: [pinia] }
127 - })
128 -
129 - const store = useCounterStore()
130 - expect(store.count).toBe(5)
131 - expect(store.doubleCount).toBe(10)
132 -})
133 -```
134 -
135 -## Reset Between Tests
136 -
137 -```typescript
138 -describe('Store Tests', () => {
139 - let pinia: Pinia
140 -
141 - beforeEach(() => {
142 - pinia = createTestingPinia({
143 - createSpy: vi.fn
144 - })
145 - })
146 -
147 - afterEach(() => {
148 - vi.clearAllMocks()
149 - })
150 -
151 - test('test 1', () => { /* ... */ })
152 - test('test 2', () => { /* ... */ })
153 -})
154 -```
155 -
156 -## Reference
157 -
158 -- [Pinia Testing Guide](https://pinia.vuejs.org/cookbook/testing.html)
159 -- [Pinia Discussion #2092](https://github.com/vuejs/pinia/discussions/2092)
1 ----
2 -title: JSDoc Documentation for Script Setup Components
3 -impact: MEDIUM
4 -impactDescription: enables proper documentation for composition API components
5 -type: capability
6 -tags: jsdoc, script-setup, documentation, composition-api, component
7 ----
8 -
9 -# JSDoc Documentation for Script Setup Components
10 -
11 -**Impact: MEDIUM** - enables proper documentation for composition API components
12 -
13 -`<script setup>` doesn't have an obvious place to attach JSDoc comments for the component itself. Use a dual-script pattern.
14 -
15 -## Problem
16 -
17 -**Incorrect:**
18 -```vue
19 -<script setup lang="ts">
20 -/**
21 - * This comment doesn't appear in IDE hover or docs
22 - * @component
23 - */
24 -import { ref } from 'vue'
25 -
26 -const count = ref(0)
27 -</script>
28 -```
29 -
30 -JSDoc comments inside `<script setup>` don't attach to the component export because there's no explicit export statement.
31 -
32 -## Solution
33 -
34 -Use both `<script>` and `<script setup>` blocks:
35 -
36 -**Correct:**
37 -```vue
38 -<script lang="ts">
39 -/**
40 - * A counter component that displays and increments a value.
41 - *
42 - * @example
43 - * ```vue
44 - * <Counter :initial="5" @update="handleUpdate" />
45 - * ```
46 - *
47 - * @component
48 - */
49 -export default {}
50 -</script>
51 -
52 -<script setup lang="ts">
53 -import { ref } from 'vue'
54 -
55 -const props = defineProps<{
56 - /** Starting value for the counter */
57 - initial?: number
58 -}>()
59 -
60 -const emit = defineEmits<{
61 - /** Emitted when counter value changes */
62 - update: [value: number]
63 -}>()
64 -
65 -const count = ref(props.initial ?? 0)
66 -</script>
67 -```
68 -
69 -## How It Works
70 -
71 -- The regular `<script>` block's default export is merged with `<script setup>`
72 -- JSDoc on `export default {}` attaches to the component
73 -- Props and emits JSDoc in `<script setup>` still work normally
74 -
75 -## What Gets Documented
76 -
77 -| Location | Shows In |
78 -|----------|----------|
79 -| `export default {}` JSDoc | Component import hover |
80 -| `defineProps` JSDoc | Prop hover in templates |
81 -| `defineEmits` JSDoc | Event handler hover |
82 -
83 -## Reference
84 -
85 -- [Vue Language Tools Discussion #5932](https://github.com/vuejs/language-tools/discussions/5932)
1 ----
2 -title: Enable Strict CSS Modules Type Checking
3 -impact: MEDIUM
4 -impactDescription: catches typos in CSS module class names at compile time
5 -type: capability
6 -tags: strictCssModules, vueCompilerOptions, css-modules, style-module
7 ----
8 -
9 -# Enable Strict CSS Modules Type Checking
10 -
11 -**Impact: MEDIUM** - catches typos in CSS module class names at compile time
12 -
13 -When using CSS modules with `<style module>`, Vue doesn't validate class names by default. Enable `strictCssModules` to catch typos and undefined classes.
14 -
15 -## Problem
16 -
17 -CSS module class name errors go undetected:
18 -
19 -```vue
20 -<script setup lang="ts">
21 -// No error for typo in class name
22 -</script>
23 -
24 -<template>
25 - <div :class="$style.buttn">Click me</div>
26 -</template>
27 -
28 -<style module>
29 -.button {
30 - background: blue;
31 -}
32 -</style>
33 -```
34 -
35 -The typo `buttn` instead of `button` silently fails at runtime.
36 -
37 -## Solution
38 -
39 -Enable `strictCssModules` in your tsconfig:
40 -
41 -```json
42 -// tsconfig.json or tsconfig.app.json
43 -{
44 - "vueCompilerOptions": {
45 - "strictCssModules": true
46 - }
47 -}
48 -```
49 -
50 -Now `$style.buttn` will show a type error because `buttn` doesn't exist in the CSS module.
51 -
52 -## What Gets Checked
53 -
54 -| Access | With strictCssModules |
55 -|--------|----------------------|
56 -| `$style.validClass` | OK |
57 -| `$style.typo` | Error: Property 'typo' does not exist |
58 -| `$style['dynamic']` | OK (dynamic access not checked) |
59 -
60 -## Limitations
61 -
62 -- Only checks static property access (`$style.className`)
63 -- Dynamic access (`$style[variable]`) is not validated
64 -- Only works with `<style module>`, not external CSS files
65 -
66 -## Reference
67 -
68 -- [Vue Language Tools Wiki - Vue Compiler Options](https://github.com/vuejs/language-tools/wiki/Vue-Compiler-Options)
1 ----
2 -title: unplugin-vue-components and unplugin-auto-import Type Conflicts
3 -impact: HIGH
4 -impactDescription: fixes component types resolving as any when using both plugins
5 -type: capability
6 -tags: unplugin-vue-components, unplugin-auto-import, types, any, dts
7 ----
8 -
9 -# unplugin-vue-components and unplugin-auto-import Type Conflicts
10 -
11 -**Impact: HIGH** - fixes component types resolving as any when using both plugins
12 -
13 -Installing both `unplugin-vue-components` and `unplugin-auto-import` can cause component types to resolve as `any`. The generated `.d.ts` files conflict with each other.
14 -
15 -## Symptoms
16 -
17 -- Components typed as `any` instead of proper component types
18 -- No autocomplete for component props
19 -- No type errors for invalid props
20 -- Types work when using only one plugin but break with both
21 -
22 -## Root Cause
23 -
24 -Both plugins generate declaration files (`components.d.ts` and `auto-imports.d.ts`) that can have conflicting declarations. TypeScript declaration merging fails silently.
25 -
26 -## Fix
27 -
28 -**Step 1: Ensure both .d.ts files are in tsconfig include**
29 -```json
30 -{
31 - "include": [
32 - "src/**/*.ts",
33 - "src/**/*.vue",
34 - "components.d.ts",
35 - "auto-imports.d.ts"
36 - ]
37 -}
38 -```
39 -
40 -**Step 2: Set explicit, different dts paths**
41 -```typescript
42 -// vite.config.ts
43 -import Components from 'unplugin-vue-components/vite'
44 -import AutoImport from 'unplugin-auto-import/vite'
45 -
46 -export default defineConfig({
47 - plugins: [
48 - Components({
49 - dts: 'src/types/components.d.ts' // Explicit unique path
50 - }),
51 - AutoImport({
52 - dts: 'src/types/auto-imports.d.ts' // Explicit unique path
53 - })
54 - ]
55 -})
56 -```
57 -
58 -**Step 3: Regenerate type files**
59 -```bash
60 -# Delete existing .d.ts files
61 -rm components.d.ts auto-imports.d.ts
62 -
63 -# Restart dev server to regenerate
64 -npm run dev
65 -```
66 -
67 -**Step 4: Verify no duplicate declarations**
68 -
69 -Check that the same component isn't declared in both files.
70 -
71 -## Plugin Order Matters
72 -
73 -Configure Components plugin AFTER AutoImport:
74 -```typescript
75 -plugins: [
76 - AutoImport({ /* ... */ }),
77 - Components({ /* ... */ }) // Must come after AutoImport
78 -]
79 -```
80 -
81 -## Common Mistake: Duplicate Imports
82 -
83 -Don't configure the same import in both plugins:
84 -```typescript
85 -// Wrong - Vue imported in both
86 -AutoImport({
87 - imports: ['vue']
88 -})
89 -Components({
90 - resolvers: [/* includes Vue components */]
91 -})
92 -```
93 -
94 -## Reference
95 -
96 -- [unplugin-vue-components#640](https://github.com/unplugin/unplugin-vue-components/issues/640)
97 -- [unplugin-auto-import docs](https://github.com/unplugin/unplugin-auto-import)
1 ----
2 -title: Volar 3.0 Breaking Changes
3 -impact: HIGH
4 -impactDescription: fixes editor integration after Volar/vue-language-server upgrade
5 -type: capability
6 -tags: volar, vue-language-server, neovim, vscode, ide, ts_ls, vtsls
7 ----
8 -
9 -# Volar 3.0 Breaking Changes
10 -
11 -**Impact: HIGH** - fixes editor integration after Volar/vue-language-server upgrade
12 -
13 -Volar 3.0 (vue-language-server 3.x) introduced breaking changes to the language server protocol. Editors configured for Volar 2.x will break with errors like "vue_ls doesn't work with ts_ls.. it expects vtsls".
14 -
15 -## Symptoms
16 -
17 -- `vue_ls doesn't work with ts_ls`
18 -- TypeScript features stop working in Vue files
19 -- No autocomplete, type hints, or error highlighting
20 -- Editor shows "Language server initialization failed"
21 -
22 -## Fix by Editor
23 -
24 -### VSCode
25 -
26 -Update the "Vue - Official" extension to latest version. It manages the language server automatically.
27 -
28 -### NeoVim (nvim-lspconfig)
29 -
30 -**Option 1: Use vtsls instead of ts_ls**
31 -```lua
32 --- Replace ts_ls/tsserver with vtsls
33 -require('lspconfig').vtsls.setup({})
34 -require('lspconfig').volar.setup({})
35 -```
36 -
37 -**Option 2: Downgrade vue-language-server**
38 -```bash
39 -npm install -g @vue/language-server@2.1.10
40 -```
41 -
42 -### JetBrains IDEs
43 -
44 -Update to latest Vue plugin. If issues persist, disable and re-enable the Vue plugin.
45 -
46 -## What Changed in 3.0
47 -
48 -| Feature | Volar 2.x | Volar 3.0 |
49 -|---------|-----------|-----------|
50 -| Package name | volar | vue_ls |
51 -| TypeScript integration | ts_ls/tsserver | vtsls required |
52 -| Hybrid mode | Optional | Default |
53 -
54 -## Workaround: Stay on 2.x
55 -
56 -If upgrading is not possible:
57 -```bash
58 -npm install -g @vue/language-server@^2.0.0
59 -```
60 -
61 -Pin in your project's package.json to prevent accidental upgrades.
62 -
63 -## Reference
64 -
65 -- [vuejs/language-tools#5598](https://github.com/vuejs/language-tools/issues/5598)
66 -- [NeoVim Vue Setup Guide](https://dev.to/danwalsh/solved-vue-3-typescript-inlay-hint-support-in-neovim-53ej)
1 ----
2 -title: Vue Template Directive Comments
3 -impact: HIGH
4 -impactDescription: enables fine-grained control over template type checking
5 -type: capability
6 -tags: vue-directive, vue-ignore, vue-expect-error, vue-skip, template, type-checking
7 ----
8 -
9 -# Vue Template Directive Comments
10 -
11 -**Impact: HIGH** - enables fine-grained control over template type checking
12 -
13 -Vue Language Tools supports special directive comments to control type checking behavior in templates.
14 -
15 -## Available Directives
16 -
17 -### @vue-ignore
18 -
19 -Suppress type errors for the next line:
20 -
21 -```vue
22 -<template>
23 - <!-- @vue-ignore -->
24 - <Component :prop="valueWithTypeError" />
25 -</template>
26 -```
27 -
28 -### @vue-expect-error
29 -
30 -Assert that the next line should have a type error (useful for testing):
31 -
32 -```vue
33 -<template>
34 - <!-- @vue-expect-error -->
35 - <Component :invalid-prop="value" />
36 -</template>
37 -```
38 -
39 -### @vue-skip
40 -
41 -Skip type checking for an entire block:
42 -
43 -```vue
44 -<template>
45 - <!-- @vue-skip -->
46 - <div>
47 - <!-- Everything in here is not type-checked -->
48 - <LegacyComponent :any="props" :go="here" />
49 - </div>
50 -</template>
51 -```
52 -
53 -### @vue-generic
54 -
55 -Declare template-level generic types:
56 -
57 -```vue
58 -<template>
59 - <!-- @vue-generic {T extends string} -->
60 - <GenericList :items="items as T[]" />
61 -</template>
62 -```
63 -
64 -## Use Cases
65 -
66 -- Migrating legacy components with incomplete types
67 -- Working with third-party components that have incorrect type definitions
68 -- Temporarily suppressing errors during refactoring
69 -- Testing that certain patterns produce expected type errors
70 -
71 -## Reference
72 -
73 -- [Vue Language Tools Wiki - Directive Comments](https://github.com/vuejs/language-tools/wiki/Directive-Comments)
1 ----
2 -title: Vue Router useRoute Params Union Type Narrowing
3 -impact: MEDIUM
4 -impactDescription: fixes "Property does not exist" errors with typed route params
5 -type: capability
6 -tags: vue-router, useRoute, unplugin-vue-router, typed-routes, params
7 ----
8 -
9 -# Vue Router useRoute Params Union Type Narrowing
10 -
11 -**Impact: MEDIUM** - fixes "Property does not exist" errors with typed route params
12 -
13 -With `unplugin-vue-router` typed routes, `route.params` becomes a union of ALL page param types. TypeScript cannot narrow `Record<never, never> | { id: string }` properly, causing "Property 'id' does not exist" errors even on the correct page.
14 -
15 -## Symptoms
16 -
17 -- "Property 'id' does not exist on type 'RouteParams'"
18 -- `route.params.id` shows as `string | undefined` everywhere
19 -- Union type of all route params instead of specific route
20 -- Type narrowing with `if (route.name === 'users-id')` doesn't work
21 -
22 -## Root Cause
23 -
24 -`unplugin-vue-router` generates a union type of all possible route params. TypeScript's control flow analysis can't narrow this union based on route name checks.
25 -
26 -## Fix
27 -
28 -**Option 1: Pass route name to useRoute (recommended)**
29 -```typescript
30 -// pages/users/[id].vue
31 -import { useRoute } from 'vue-router/auto'
32 -
33 -// Specify the route path for proper typing
34 -const route = useRoute('/users/[id]')
35 -
36 -// Now properly typed as { id: string }
37 -console.log(route.params.id) // string, not string | undefined
38 -```
39 -
40 -**Option 2: Type assertion with specific route**
41 -```typescript
42 -import { useRoute } from 'vue-router'
43 -import type { RouteLocationNormalized } from 'vue-router/auto-routes'
44 -
45 -const route = useRoute() as RouteLocationNormalized<'/users/[id]'>
46 -route.params.id // Properly typed
47 -```
48 -
49 -**Option 3: Define route-specific param type**
50 -```typescript
51 -// In your page component
52 -interface UserRouteParams {
53 - id: string
54 -}
55 -
56 -const route = useRoute()
57 -const { id } = route.params as UserRouteParams
58 -```
59 -
60 -## Required tsconfig Setting
61 -
62 -Ensure `moduleResolution: "bundler"` for unplugin-vue-router:
63 -```json
64 -{
65 - "compilerOptions": {
66 - "moduleResolution": "bundler"
67 - }
68 -}
69 -```
70 -
71 -## Caveat: Route Name Format
72 -
73 -The route name matches the file path pattern:
74 -- `pages/users/[id].vue``/users/[id]`
75 -- `pages/posts/[slug]/comments.vue``/posts/[slug]/comments`
76 -
77 -## Reference
78 -
79 -- [unplugin-vue-router#337](https://github.com/posva/unplugin-vue-router/issues/337)
80 -- [unplugin-vue-router#176](https://github.com/posva/unplugin-vue-router/discussions/176)
81 -- [unplugin-vue-router TypeScript docs](https://uvr.esm.is/guide/typescript.html)
1 ----
2 -title: Enable Strict Template Checking
3 -impact: HIGH
4 -impactDescription: catches undefined components and props at compile time
5 -type: capability
6 -tags: vue-tsc, typescript, type-checking, templates, vueCompilerOptions
7 ----
8 -
9 -# Enable Strict Template Checking
10 -
11 -**Impact: HIGH** - catches undefined components and props at compile time
12 -
13 -By default, vue-tsc does not report errors for undefined components in templates. Enable `strictTemplates` to catch these issues during type checking.
14 -
15 -## Which tsconfig?
16 -
17 -Add `vueCompilerOptions` to the tsconfig that includes Vue source files. In projects with multiple tsconfigs (like those created with `create-vue`), this is typically `tsconfig.app.json`, not the root `tsconfig.json` or `tsconfig.node.json`.
18 -
19 -**Incorrect (missing strict checking):**
20 -
21 -```json
22 -{
23 - "compilerOptions": {
24 - "strict": true
25 - }
26 - // vueCompilerOptions not configured - undefined components won't error
27 -}
28 -```
29 -
30 -**Correct (strict template checking enabled):**
31 -
32 -```json
33 -{
34 - "compilerOptions": {
35 - "strict": true
36 - },
37 - "vueCompilerOptions": {
38 - "strictTemplates": true
39 - }
40 -}
41 -```
42 -
43 -## Available Options
44 -
45 -| Option | Default | Effect |
46 -|--------|---------|--------|
47 -| `strictTemplates` | `false` | Enables all checkUnknown* options below |
48 -| `checkUnknownComponents` | `false` | Error on undefined/unregistered components |
49 -| `checkUnknownProps` | `false` | Error on props not declared in component definition |
50 -| `checkUnknownEvents` | `false` | Error on events not declared via `defineEmits` |
51 -| `checkUnknownDirectives` | `false` | Error on unregistered custom directives |
52 -
53 -## Granular Control
54 -
55 -If `strictTemplates` is too strict, enable individual checks:
56 -
57 -```json
58 -{
59 - "vueCompilerOptions": {
60 - "checkUnknownComponents": true,
61 - "checkUnknownProps": false
62 - }
63 -}
64 -```
65 -
66 -## Reference
67 -
68 -- [Vue Compiler Options](https://github.com/vuejs/language-tools/wiki/Vue-Compiler-Options)
69 -- [Vite Vue+TS Template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-vue-ts)
1 ----
2 -title: withDefaults Incorrect Default with Union Types
3 -impact: MEDIUM
4 -impactDescription: fixes incorrect default value behavior with union type props
5 -type: capability
6 -tags: withDefaults, defineProps, union-types, defaults, vue-3.5
7 ----
8 -
9 -# withDefaults Incorrect Default with Union Types
10 -
11 -**Impact: MEDIUM** - fixes spurious "Missing required prop" warning with union type props
12 -
13 -Using `withDefaults` with union types like `false | string` may produce a Vue runtime warning "Missing required prop" even when a default is provided. The runtime value IS applied correctly, but the warning can be confusing.
14 -
15 -## Symptoms
16 -
17 -- Vue warns "Missing required prop" despite default being set
18 -- Warning appears only with union types like `false | string`
19 -- TypeScript types are correct
20 -- Runtime value IS correct (the default is applied)
21 -
22 -## Problematic Pattern
23 -
24 -```typescript
25 -// This produces a spurious warning (but works at runtime)
26 -interface Props {
27 - value: false | string // Union type
28 -}
29 -
30 -const props = withDefaults(defineProps<Props>(), {
31 - value: 'default' // Runtime value IS correct, but Vue warns about missing prop
32 -})
33 -```
34 -
35 -## Fix
36 -
37 -**Option 1: Use Reactive Props Destructure (Vue 3.5+)**
38 -```vue
39 -<script setup lang="ts">
40 -interface Props {
41 - value: false | string
42 -}
43 -
44 -// Preferred in Vue 3.5+
45 -const { value = 'default' } = defineProps<Props>()
46 -</script>
47 -```
48 -
49 -**Option 2: Use runtime declaration**
50 -```vue
51 -<script setup lang="ts">
52 -const props = defineProps({
53 - value: {
54 - type: [Boolean, String] as PropType<false | string>,
55 - default: 'default'
56 - }
57 -})
58 -</script>
59 -```
60 -
61 -**Option 3: Split into separate props**
62 -```typescript
63 -interface Props {
64 - enabled: boolean
65 - customValue?: string
66 -}
67 -
68 -const props = withDefaults(defineProps<Props>(), {
69 - enabled: false,
70 - customValue: 'default'
71 -})
72 -```
73 -
74 -## Why Reactive Props Destructure Works
75 -
76 -Vue 3.5's Reactive Props Destructure handles default values at the destructuring level, bypassing the type inference issues with `withDefaults`.
77 -
78 -```typescript
79 -// The default is applied during destructuring, not type inference
80 -const { prop = 'default' } = defineProps<{ prop?: string }>()
81 -```
82 -
83 -## Enable Reactive Props Destructure
84 -
85 -This is enabled by default in Vue 3.5+. For older versions:
86 -```javascript
87 -// vite.config.js
88 -export default {
89 - plugins: [
90 - vue({
91 - script: {
92 - propsDestructure: true
93 - }
94 - })
95 - ]
96 -}
97 -```
98 -
99 -## Reference
100 -
101 -- [vuejs/core#12897](https://github.com/vuejs/core/issues/12897)
102 -- [Reactive Props Destructure RFC](https://github.com/vuejs/rfcs/discussions/502)
1 -# 架构实现与工程配置
2 -
3 -## 入口与初始化
4 -
5 -- 应用入口:[/src/main.js](file:///Users/huyirui/program/itomix/git/mlaj/src/main.js)
6 - - 创建 App、注册全局 Icon 组件、挂载路由
7 - - 全局注入 axios 到 app.config.globalProperties.$http
8 -- 根组件:[/src/App.vue](file:///Users/huyirui/program/itomix/git/mlaj/src/App.vue)
9 - - 初始化全局认证与购物车:provideAuth / provideCart
10 - - 生产环境 + 微信环境:初始化微信 JSSDK(配置签名 URL)
11 - - 生产环境:版本更新探测(弹窗提示刷新)
12 -
13 -## 路由与权限
14 -
15 -- 路由入口:[/src/router/index.js](file:///Users/huyirui/program/itomix/git/mlaj/src/router/index.js)
16 - - Hash 路由:createWebHashHistory(import.meta.env.VITE_BASE || '/')
17 - - beforeEach:统一登录页回跳处理,并在必要时探测“是否已登录”
18 -- 鉴权策略:[/src/router/guards.js](file:///Users/huyirui/program/itomix/git/mlaj/src/router/guards.js)
19 - - 白名单 + meta.requiresAuth 双策略判断
20 - - 未登录时重定向 /login 并带 redirect
21 -- 微信授权策略:[/src/router/guards.js](file:///Users/huyirui/program/itomix/git/mlaj/src/router/guards.js)
22 - - 不在路由守卫自动触发授权,避免循环
23 - - 仅在用户触发(如点击微信图标/购买流程探测)时调用 startWxAuth
24 -
25 -## 请求与登录态注入
26 -
27 -- Axios 封装:[/src/utils/axios.js](file:///Users/huyirui/program/itomix/git/mlaj/src/utils/axios.js)
28 - - 请求拦截:动态读取本地 user_info 并注入 User-Id / User-Token
29 - - 响应拦截:code=401 时,仅当当前路由确实需要登录才跳转登录(公开页面不强制跳转)
30 -- 登录态管理:[/src/contexts/auth.js](file:///Users/huyirui/program/itomix/git/mlaj/src/contexts/auth.js)
31 - - provide/inject 维护 currentUser/loading/login/logout
32 - - localStorage 持久化 currentUser
33 - - 初始化流程中会探测授权/登录态并拉取用户信息
34 -
35 -## 购物车与结算
36 -
37 -- 购物车上下文:[/src/contexts/cart.js](file:///Users/huyirui/program/itomix/git/mlaj/src/contexts/cart.js)
38 - - 单品/多品两种模式(App.vue 默认使用单品模式)
39 - - localStorage 带时间戳的过期策略(默认一天过期)
40 - - handleCheckout 负责构建订单数据并提交订单
41 -
42 -## 上传与预览
43 -
44 -- 上传工具:[/src/utils/upload.js](file:///Users/huyirui/program/itomix/git/mlaj/src/utils/upload.js)[/src/utils/qiniuFileHash.js](file:///Users/huyirui/program/itomix/git/mlaj/src/utils/qiniuFileHash.js)
45 - - 前端计算文件 Hash,支持“秒传检测 + 直传对象存储”
46 -- 关键业务复用:[/src/composables/useCheckin.js](file:///Users/huyirui/program/itomix/git/mlaj/src/composables/useCheckin.js)
47 - - 打卡/作业提交流程:校验、上传、提交、编辑回填等
48 -- 预览组件:[/src/components/media](file:///Users/huyirui/program/itomix/git/mlaj/src/components/media)
49 - - 视频/音频播放器、PDF/Office 预览等
50 -
51 -## Vite 配置与环境变量
52 -
53 -- Vite 配置:[/vite.config.js](file:///Users/huyirui/program/itomix/git/mlaj/vite.config.js)
54 - - 自动按需引入 Vant 组件(unplugin-auto-import / unplugin-vue-components)
55 - - 别名:@ / @components / @utils / @api 等
56 - - 本地代理:createProxy(viteEnv.VITE_PROXY_PREFIX, viteEnv.VITE_PROXY_TARGET)
57 -- 环境变量示例:[.env](file:///Users/huyirui/program/itomix/git/mlaj/.env)
58 - - VITE_PORT:开发端口
59 - - VITE_PROXY_TARGET / VITE_PROXY_PREFIX:接口代理目标与前缀
60 - - VITE_OUTDIR:构建输出目录
61 - - VITE_CONSOLE:调试开关
62 -
63 -## 目录结构(详细)
64 -
65 -```
66 -mlaj/
67 -├── build/ # Vite 代理封装
68 -├── docs/ # 项目文档(本目录)
69 -├── public/ # 静态资源
70 -├── src/
71 -│ ├── api/ # 按业务域拆分的接口封装(auth/course/checkin/teacher/...)
72 -│ ├── assets/ # 图片等资源
73 -│ ├── common/ # 常量
74 -│ ├── components/ # 组件(按业务域归类)
75 -│ ├── composables/ # 组合式函数(逻辑复用,含单测)
76 -│ ├── contexts/ # 全局状态(provide/inject:auth/cart)
77 -│ ├── router/ # 路由定义与守卫
78 -│ ├── utils/ # 工具层(axios、上传、鉴权存储、版本更新等)
79 -│ └── views/ # 页面(按业务域归类)
80 -├── tailwind.config.js # Tailwind 配置
81 -└── vite.config.js # Vite 配置
82 -```
1 -# 功能更新记录(Recent Changes)
2 -
3 -说明:该章节从 README 迁移到本文件,避免 README 过长。后续新增变更建议追加在文件顶部。
4 -
5 -## 2026-01-25
6 -
7 -- 新增「暂存用户打卡信息」开发规划:[/docs/plan/暂存用户打卡信息.md](file:///Users/huyirui/program/itomix/git/mlaj/docs/plan/%E6%9A%82%E5%AD%98%E7%94%A8%E6%88%B7%E6%89%93%E5%8D%A1%E4%BF%A1%E6%81%AF.md)
8 -- 完成「暂存用户打卡信息」功能开发
9 - - 核心逻辑:[/src/composables/useCheckinDraft.js](file:///Users/huyirui/program/itomix/git/mlaj/src/composables/useCheckinDraft.js)
10 - - 页面集成:[/src/views/checkin/CheckinDetailPage.vue](file:///Users/huyirui/program/itomix/git/mlaj/src/views/checkin/CheckinDetailPage.vue)
11 - - 支持自动保存、过期清理、恢复提示
12 -- 修复打卡详情页 `countValue` 初始化顺序导致的 ReferenceError 报错
13 -- 修复附件上传成功后未保存 URL 导致草稿恢复后无法预览的问题
14 -- 优化文件 URL 获取逻辑:移除硬编码默认域名,优先使用接口返回的 URL 或 src,仅在有域名信息时拼接 URL
15 -
16 -## 打卡详情页重构(/checkin/detail)
17 -
18 -- 统一了文本、媒体上传和计数打卡的入口
19 -- 实现了基于 composables 的通用提交流程:[/src/composables/useCheckin.js](file:///Users/huyirui/program/itomix/git/mlaj/src/composables/useCheckin.js)
20 -- 页面入口:[/src/views/checkin/CheckinDetailPage.vue](file:///Users/huyirui/program/itomix/git/mlaj/src/views/checkin/CheckinDetailPage.vue)
21 -- 优化了附件预览与编辑回填逻辑(音频/视频/图片预览能力)
22 -
23 -## 教师端功能完善(/teacher)
24 -
25 -- 新增作业管理页面:/teacher/tasks(列表展示:名称、开始/截止时间)
26 -- 新增作业主页:/teacher/tasks/:id(统计 + 日历视图)
27 -- 新增学员作业记录页:/teacher/student-record(作业帖子 + 点赞/点评)
28 -
29 -## 基础体验优化
30 -
31 -- 登录逻辑调整:仅在登录页点击微信图标时触发授权(避免路由守卫自动授权导致的循环)
32 - - 关键文件:[/src/router/guards.js](file:///Users/huyirui/program/itomix/git/mlaj/src/router/guards.js)[/src/router/index.js](file:///Users/huyirui/program/itomix/git/mlaj/src/router/index.js)[/src/views/auth/LoginPage.vue](file:///Users/huyirui/program/itomix/git/mlaj/src/views/auth/LoginPage.vue)
33 -- 搜索栏优化:提升 iOS 软键盘“搜索”键触发稳定性
34 -- 课程详情页:增加动态 Open Graph 标签,优化分享体验
35 -
36 -## 课程详情页动态 Open Graph 元标签
37 -
38 -- 行为:进入课程详情页时,在 head 中插入 og:title / og:description / og:image / og:url;离开页面时移除
39 -- CDN 规则:图片域名为 cdn.ipadbiz.cn 时,追加 ?imageMogr2/thumbnail/200x/strip/quality/70
40 -- 位置:[/src/views/courses/CourseDetailPage.vue](file:///Users/huyirui/program/itomix/git/mlaj/src/views/courses/CourseDetailPage.vue)
41 -
42 -## 购买流程环境校验与微信授权探测
43 -
44 -- 行为:仅对非免费课程在详情页点击“购买”时进行校验;生产环境必须为微信内置浏览器
45 -- 微信环境内:若未完成微信授权(openid_has=false),会自动发起一次微信授权并中止本次购买,授权后再次点击进入结算
46 -- 位置:[/src/views/courses/CourseDetailPage.vue](file:///Users/huyirui/program/itomix/git/mlaj/src/views/courses/CourseDetailPage.vue)
47 -
48 -## 401 拦截策略优化(公开页面不再跳登录)
49 -
50 -- 行为:接口返回 code=401 时,仅当当前路由确实需要登录时才重定向登录
51 -- 位置:[/src/utils/axios.js](file:///Users/huyirui/program/itomix/git/mlaj/src/utils/axios.js)
52 -
53 -## 搜索栏回车搜索兼容性提升
54 -
55 -- 行为:输入框类型改为 search,并可选开启 form submit 机制,同时保留 keyup.enter
56 -- 位置:[/src/components/common/SearchBar.vue](file:///Users/huyirui/program/itomix/git/mlaj/src/components/common/SearchBar.vue)
57 -
58 -## 分享海报弹窗(可复用)
59 -
60 -- 入口:课程详情页底部操作栏“分享”按钮
61 -- 组件:[/src/components/poster/SharePoster.vue](file:///Users/huyirui/program/itomix/git/mlaj/src/components/poster/SharePoster.vue)
62 -- 能力:弹窗打开时通过 Canvas 合成海报(封面、二维码、文案),生成 dataURL 展示,用户长按保存
63 -
64 -## 打卡弹窗与列表组件(可复用)
65 -
66 -- 打卡弹窗:[/src/components/checkin/CheckInDialog.vue](file:///Users/huyirui/program/itomix/git/mlaj/src/components/checkin/CheckInDialog.vue)
67 -- 打卡列表:[/src/components/checkin/CheckInList.vue](file:///Users/huyirui/program/itomix/git/mlaj/src/components/checkin/CheckInList.vue)
1 -# /src/components 组件目录索引
2 -
3 -## 目录划分
4 -
5 -| 目录 | 代表组件 | 说明 |
6 -| --- | --- | --- |
7 -| activity/ | ActivityCard.vue、ActivityApplyHistoryPopup.vue | 活动卡片、报名/历史相关弹窗 |
8 -| calendar/ | CollapsibleCalendar.vue、TaskCalendar.vue | 日历与任务日历组件 |
9 -| checkin/ | CheckInDialog.vue、CheckInList.vue、CheckinCard.vue、UploadVideoPopup.vue | 打卡/作业相关组件(弹窗、列表、卡片、上传) |
10 -| common/ | ConfirmDialog.vue、GradientHeader.vue、SearchBar.vue、UserAgreement.vue | 通用基础组件(确认、头部、搜索、协议) |
11 -| count/ | AddTargetDialog.vue、CheckinTargetList.vue、postCountModel.vue | 计数型打卡相关组件 |
12 -| courses/ | CourseCard.vue、CourseList.vue、LiveStreamCard.vue、ReviewPopup.vue | 课程展示与列表、直播卡片、评价弹窗等 |
13 -| effects/ | FrostedGlass.vue、StarryBackground.vue | 视觉效果组件 |
14 -| homePage/ | FeaturedCoursesSection.vue、LatestActivitiesSection.vue | 首页区块组件(精选/活动/推荐等) |
15 -| infoEntry/ | formPage.vue | 信息录入相关组件 |
16 -| layout/ | AppLayout.vue、BottomNav.vue | 页面布局与底部导航 |
17 -| media/ | AudioPlayer.vue、VideoPlayer.vue、PdfPreview.vue、OfficeViewer.vue | 音视频播放器与文档预览 |
18 -| payment/ | WechatPayment.vue | 微信支付相关组件 |
19 -| poster/ | RecallPoster.vue、SharePoster.vue | 海报生成与分享相关组件 |
20 -| studyDetail/ | StudyCatalogPopup.vue、StudyCommentsSection.vue、StudyMaterialsPopup.vue | 学习详情页的弹窗与评论区 |
21 -| teacher/ | TaskFilter.vue、TaskCascaderFilter.vue | 教师端筛选与任务相关组件 |
22 -
23 -## 备注
24 -
25 -- 布局目录已归一:统一使用 [/src/components/layout](file:///Users/huyirui/program/itomix/git/mlaj/src/components/layout),已移除 /src/layouts
1 -用户反馈:
1 -# 暂存用户打卡信息
2 -
3 -## 背景
4 -
5 -用户在“提交作业/打卡”页面输入了较长文字并上传了媒体,但在未点击提交时被中断(误触返回、微信进程被系统回收、来电/切后台等),再次进入页面内容丢失,导致体验断裂。
6 -
7 -本规划目标是在不改动后端接口的前提下,在前端提供“草稿暂存(文本 + 已上传媒体信息)”能力,支持一周内自动过期清理,并在用户再次进入时提示恢复或删除。
8 -
9 -涉及页面与核心逻辑参考:
10 -
11 -- 打卡提交页:[CheckinDetailPage.vue](file:///Users/huyirui/program/itomix/git/mlaj/src/views/checkin/CheckinDetailPage.vue)
12 -- 打卡核心逻辑:[useCheckin.js](file:///Users/huyirui/program/itomix/git/mlaj/src/composables/useCheckin.js)
13 -- 环境变量约定:[.env](file:///Users/huyirui/program/itomix/git/mlaj/.env)
14 -
15 -## 需求拆解(逐条对齐)
16 -
17 -1. 缓存最多保存一周:写入时记录 saved_at,读写时执行过期清理(>7天删除)。
18 -2. 用户提交后清空缓存:提交成功(code===1)后删除对应草稿。
19 -3. 进入页面若存在未完成信息:弹框提示“继续/删除”,继续则回填,删除则清空。
20 -4. 功能开关放到 .env:新增 VITE_CHECKIN_DRAFT_CACHE(0/1),默认建议 1(也可先默认 0,灰度开启)。
21 -
22 -## 范围与不做项(第一版)
23 -
24 -- 覆盖:文字内容、已上传成功的媒体(含 url/meta_id/file_type/name)。
25 -- 不覆盖:未上传完成的 File/Blob(localStorage 无法可靠持久化;要支持需 IndexedDB 存 Blob,复杂度与风险较高)。
26 -- 编辑模式(route.query.status===edit):第一版建议默认不启用草稿恢复,避免与“编辑回显(来自后端)”冲突;若要覆盖编辑场景,采用独立 key(见“扩展”)。
27 -
28 -## 关键设计
29 -
30 -### 1) 存储介质
31 -
32 -- 使用 localStorage:实现成本低,满足“一周”与“断网/切后台后仍可恢复”。
33 -- 数据量控制:只存“已上传成功”的附件元数据;不存 File 本体。
34 -
35 -### 2) 草稿 Key 设计(避免串号)
36 -
37 -建议 key 包含用户与作业上下文,确保不同用户/不同作业互不影响:
38 -
39 -- 前缀:CHECKIN_DRAFT_V1
40 -- 维度:user_id、task_id、date、task_type、status
41 -
42 -示例:
43 -
44 -- CHECKIN_DRAFT_V1:{user_id}:{task_id}:{date}:{task_type}:{status}
45 -
46 -其中:
47 -
48 -- user_id:来自 currentUser(contexts/auth.js 本地持久化)
49 -- task_id/date/task_type/status:来自路由 query(CheckinDetailPage 已使用 route.query.task_id/date/task_type/status)
50 -
51 -### 3) 数据结构(建议)
52 -
53 -```json
54 -{
55 - "version": 1,
56 - "saved_at": 1730000000000,
57 - "expires_at": 1730000000000,
58 - "context": {
59 - "user_id": "123",
60 - "task_id": "456",
61 - "date": "2026-01-25",
62 - "task_type": "upload",
63 - "status": "create"
64 - },
65 - "payload": {
66 - "message": "...",
67 - "active_type": "image",
68 - "subtask_id": "789",
69 - "file_list": [
70 - {
71 - "meta_id": "xxx",
72 - "url": "https://...",
73 - "name": "a.jpg",
74 - "file_type": "image"
75 - }
76 - ],
77 - "count": {
78 - "gratitude_count": 1,
79 - "gratitude_form_list": []
80 - }
81 - }
82 -}
83 -```
84 -
85 -说明:
86 -
87 -- file_list:仅保存 useCheckin.afterRead 上传成功后写入的字段(item.status===done 且 meta_id 存在)。
88 -- count:来源于 CheckinDetailPage 的 selectedTargets/countValue(第一版可以先不存,或存但不影响非 count 类型)。
89 -
90 -### 4) 触发保存的时机(自动暂存)
91 -
92 -- 文本变化:watch(message) debounce 500ms 保存。
93 -- 附件变化:watch(fileList) 深度监听 debounce 500ms 保存(仅保存 done 项)。
94 -- 作业选择变化:watch(selectedTaskValue) debounce 200ms 保存。
95 -- 页面离开兜底:beforeRouteLeave 或 window.pagehide/visibilitychange 时强制保存一次(避免最后一次变更没落盘)。
96 -
97 -落盘时机要遵循开关:VITE_CHECKIN_DRAFT_CACHE === '1' 才启用。
98 -
99 -### 5) 弹框提示与回填流程
100 -
101 -进入 [CheckinDetailPage.vue](file:///Users/huyirui/program/itomix/git/mlaj/src/views/checkin/CheckinDetailPage.vue)(且非 edit 模式)时:
102 -
103 -1. 读取 key 对应草稿;若不存在或已过期,直接进入正常流程。
104 -2. 若存在草稿且 payload 有实际内容(message 有非空或 file_list 非空):弹框提示。
105 -3. 用户选择:
106 - - 继续:将草稿回填到 message / activeType / fileList / selectedTaskValue(以及 count 数据如启用),并立刻再保存一次(避免回填后又丢)。
107 - - 删除:删除草稿并保持空表单。
108 -
109 -弹框建议用 showConfirmDialog(Vant 4),取消分支需要 catch,避免控制台出现 Uncaught (in promise) cancel(Vant 文档:showConfirmDialog.then/catch)[3](https://develop365.gitlab.io/vant/zh-CN/dialog/)
110 -
111 -### 6) 清理策略(“定期清除一周前”)
112 -
113 -采用“惰性清理 + 低频全量清理”组合:
114 -
115 -- 惰性清理:每次读/写草稿时,如果 expires_at < now 则删除。
116 -- 低频全量:进入打卡页时,扫描 localStorage 中以 CHECKIN_DRAFT_V1: 开头的 key,删除所有过期项。
117 -
118 -说明:localStorage 没有内建 TTL,必须业务侧维护 expires_at。
119 -
120 -### 7) 提交成功后清空
121 -
122 -清空动作必须绑定到“真正提交成功”之后:
123 -
124 -- 建议在 useCheckin.onSubmit 中,当 add/edit API 返回 code===1 且后续逻辑准备 router.back 前,删除对应 key。
125 -
126 -这样可覆盖“不同入口页复用 onSubmit”以及“提交后立即返回上一页”的场景。
127 -
128 -## 开发步骤(可落地的实现顺序)
129 -
130 -### 第 0 步:验证手段先行(TDD)
131 -
132 -新增 Vitest 用例,先定义以下可验证点:
133 -
134 -- 写入后能读取同一 key 的草稿;过期后读取返回空且自动删除。
135 -- 仅保存 status===done 且含 meta_id 的附件。
136 -- 清理函数能删除所有过期 key,不误删其他业务 localStorage。
137 -- 提交成功时会调用清理(可通过 mock API 返回 code===1 验证)。
138 -
139 -### 第 1 步:抽离草稿存储模块
140 -
141 -位置建议:src/utils/checkinDraftCache.js(纯函数、无 UI 依赖)。
142 -
143 -对外 API(示例):
144 -
145 -- is_enabled(): boolean(读取 env + 可选 query override)
146 -- build_key(context): string
147 -- save_draft(key, draft)
148 -- read_draft(key): draft|null(含 TTL 处理)
149 -- clear_draft(key)
150 -- cleanup_expired(prefix)
151 -
152 -### 第 2 步:在 CheckinDetailPage 接入“检测 + 弹框 + 回填”
153 -
154 -- onMounted:初始化后读取草稿并弹框。
155 -- 回填时机:建议在任务详情/子任务列表加载完成后再回填 selectedTaskValue,避免 option 未加载导致显示异常。
156 -
157 -### 第 3 步:在 CheckinDetailPage 接入“自动保存”
158 -
159 -- 对 message/fileList/selectedTaskValue/countValue/selectedTargets 建立 watch + debounce。
160 -- 页面离开事件兜底(pagehide/visibilitychange)。
161 -
162 -### 第 4 步:在 useCheckin.onSubmit 接入“成功清理”
163 -
164 -- onSubmit 成功分支清除草稿。
165 -- 失败分支不清除,保留草稿以便重试。
166 -
167 -## 边界条件与遗漏点梳理(建议补齐)
168 -
169 -1. 多用户切换:key 必须含 user_id,否则会串草稿。
170 -2. 多任务并存:key 必须含 task_id/date/task_type,否则会在不同作业之间误恢复。
171 -3. 附件未上传完成:
172 - - 仅保存已上传成功的项;如果用户退出时仍有 uploading 项,恢复后无法找回该 File。
173 - - 可在保存时统计未保存数量,并在恢复弹框里追加提示“有 X 个附件上传未完成未被暂存”。
174 -4. 关闭开关后的行为:
175 - - 关闭后不再读/写;建议仍执行一次 cleanup_expired,避免历史堆积。
176 -5. 版本升级/数据结构变更:draft.version 不匹配时丢弃并清除,避免解析异常。
177 -6. localStorage 配额:图片多但只存 url/meta_id 一般不会超;仍需 try/catch JSON 与 setItem 异常。
178 -7. 编辑模式:
179 - - 要支持“编辑中断恢复”,建议 key 加 post_id 维度,并在 initEditData 回显后再弹框询问是否覆盖当前表单。
180 -
181 -## 环境变量(规划)
182 -
183 -[.env](file:///Users/huyirui/program/itomix/git/mlaj/.env) 增加:
184 -
185 -- VITE_CHECKIN_DRAFT_CACHE = 1
186 -
187 -约定:
188 -
189 -- '1' 开启,'0' 关闭
190 -- 可选增加 URL 覆盖用于灰度测试:?enable_draft=1 / ?enable_draft=0(模式同 VITE_CHECKIN_MULTI_ATTACHMENT)