@dsh-fly/dsh-plugin-fly-smart 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +122 -2
- package/THIRD_PARTY_NOTICES.md +73 -0
- package/assets/smart-debug/SKILL.md +319 -0
- package/assets/smart-diagnose/SKILL.md +170 -0
- package/assets/smart-parallel/SKILL.md +190 -0
- package/assets/smart-plan/SKILL.md +184 -0
- package/assets/smart-review-receive/SKILL.md +230 -0
- package/assets/smart-skills/SKILL.md +96 -0
- package/assets/smart-tdd/SKILL.md +333 -0
- package/assets/smart-verify-claim/SKILL.md +129 -0
- package/assets/smart-worktree/SKILL.md +182 -0
- package/assets/smart-write-skill/SKILL.md +277 -0
- package/cordis.patch.yml +12 -0
- package/index.js +847 -0
- package/lib/client.js +1569 -0
- package/lib/state.mjs +2389 -0
- package/package.json +67 -4
- package/panel/reader.js +481 -0
- package/panel/routes.mjs +393 -0
- package/tools/index.js +1505 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: smart-review-receive
|
|
3
|
+
description: Use when 收到代码评审意见、评审建议或 review 反馈,在动手改之前加载:先核实再实现,不许表演式同意、不许盲改。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- Ported from obra/superpowers skills/receiving-code-review/ — MIT License, Copyright (c) 2025 Jesse Vincent. Adapted for DSH. -->
|
|
7
|
+
|
|
8
|
+
# 接收代码评审
|
|
9
|
+
|
|
10
|
+
## 概述
|
|
11
|
+
|
|
12
|
+
代码评审要求的是技术评估,不是情绪表演。
|
|
13
|
+
|
|
14
|
+
**核心原则:Verify before implementing. Ask before assuming. Technical correctness over social comfort.**(先核实再实现。先问清再假设。技术正确性高于社交舒适。)
|
|
15
|
+
|
|
16
|
+
## The Response Pattern(响应模式)
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
WHEN receiving code review feedback:
|
|
20
|
+
|
|
21
|
+
1. READ: Complete feedback without reacting
|
|
22
|
+
2. UNDERSTAND: Restate requirement in own words (or ask)
|
|
23
|
+
3. VERIFY: Check against codebase reality
|
|
24
|
+
4. EVALUATE: Technically sound for THIS codebase?
|
|
25
|
+
5. RESPOND: Technical acknowledgment or reasoned pushback
|
|
26
|
+
6. IMPLEMENT: One item at a time, test each
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
六步展开:
|
|
30
|
+
1. **READ(读完)**:完整读完反馈再反应,不许读到一半就动手。
|
|
31
|
+
2. **UNDERSTAND(复述)**:用自己的话复述要求;复述不出来就是没懂,去问。
|
|
32
|
+
3. **VERIFY(核实)**:对照代码现实 —— 用 `read` / `grep` / `glob` 去看**真实的代码**,不是凭印象、不是凭评审者的措辞。
|
|
33
|
+
4. **EVALUATE(评估)**:对**这个**代码库是否成立?(不是对教科书成立。)
|
|
34
|
+
5. **RESPOND(回应)**:技术性认可,或者有理有据地推翻。
|
|
35
|
+
6. **IMPLEMENT(实现)**:一次一条、逐条测试。
|
|
36
|
+
|
|
37
|
+
## Forbidden Responses(明令禁止的回答)
|
|
38
|
+
|
|
39
|
+
**NEVER(核实之前):**
|
|
40
|
+
- `"You're absolutely right!"` —— *explicit instruction-file violation*
|
|
41
|
+
- `"Great point!"` / `"Excellent feedback!"` —— *performative*
|
|
42
|
+
- `"Let me implement that now"`(在核实之前)—— *performative*
|
|
43
|
+
|
|
44
|
+
以上是禁语清单,逐字对照;改一个词、换个语言说同一件事,同样违规。
|
|
45
|
+
|
|
46
|
+
**INSTEAD(该做的):**
|
|
47
|
+
- Restate the technical requirement —— 复述技术要求
|
|
48
|
+
- Ask clarifying questions —— 提出澄清问题
|
|
49
|
+
- Push back with technical reasoning if wrong —— 错了就用技术理由推翻
|
|
50
|
+
- Just start working (actions > words) —— 直接动手,行动大于表态
|
|
51
|
+
|
|
52
|
+
## Handling Unclear Feedback(处理不清晰的反馈)
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
IF any item is unclear:
|
|
56
|
+
STOP - do not implement anything yet
|
|
57
|
+
ASK for clarification on unclear items
|
|
58
|
+
|
|
59
|
+
WHY: Items may be related. Partial understanding = wrong implementation.
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**例子:**
|
|
63
|
+
```
|
|
64
|
+
用户:"把 1-6 都修了"
|
|
65
|
+
你懂 1、2、3、6,对 4、5 没把握。
|
|
66
|
+
❌ WRONG: 先把 1,2,3,6 改了,4,5 稍后再问
|
|
67
|
+
✅ RIGHT: "1、2、3、6 我理解了。动手前需要先澄清 4 和 5。"
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Handling Wrong Feedback(处理错误的反馈)
|
|
71
|
+
|
|
72
|
+
### 来自你的用户
|
|
73
|
+
|
|
74
|
+
- **可信** —— 理解之后就实现
|
|
75
|
+
- 但**范围不清仍然要问**
|
|
76
|
+
- **不许表演式同意**
|
|
77
|
+
- 直接跳到行动,或者给一句技术性回应
|
|
78
|
+
|
|
79
|
+
### 来自外部评审(评审工具、其他智能体、代码审查评论)
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
BEFORE implementing:
|
|
83
|
+
1. Check: Technically correct for THIS codebase?
|
|
84
|
+
2. Check: Breaks existing functionality?
|
|
85
|
+
3. Check: Reason for current implementation?
|
|
86
|
+
4. Check: Works on all platforms/versions?
|
|
87
|
+
5. Check: Does reviewer understand full context?
|
|
88
|
+
|
|
89
|
+
IF suggestion seems wrong:
|
|
90
|
+
Push back with technical reasoning
|
|
91
|
+
|
|
92
|
+
IF can't easily verify:
|
|
93
|
+
Say so: "I can't verify this without [X]. Should I [investigate/ask/proceed]?"
|
|
94
|
+
|
|
95
|
+
IF conflicts with your user's prior decisions:
|
|
96
|
+
Stop and discuss with your user first
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**用户的规则:**"外部反馈 —— 保持怀疑,但认真核实。"
|
|
100
|
+
|
|
101
|
+
### YAGNI Check(对"专业一点"的功能建议)
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
IF reviewer suggests "implementing properly":
|
|
105
|
+
grep codebase for actual usage # 用 grep / glob 查真实调用
|
|
106
|
+
|
|
107
|
+
IF unused: "This endpoint isn't called. Remove it (YAGNI)?"
|
|
108
|
+
IF used: Then implement properly
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**用户的规则:**"你和评审者都向我汇报。如果我们不需要这个功能,就不要加。"
|
|
112
|
+
|
|
113
|
+
## When To Push Back(何时推翻)
|
|
114
|
+
|
|
115
|
+
在这些情况下推翻:
|
|
116
|
+
- 建议会破坏既有功能
|
|
117
|
+
- 评审者没有掌握完整上下文
|
|
118
|
+
- 违反 YAGNI(没人用的功能)
|
|
119
|
+
- 对这个技术栈来说技术上不对
|
|
120
|
+
- 存在遗留 / 兼容性原因
|
|
121
|
+
- 与用户已定的架构决策冲突
|
|
122
|
+
|
|
123
|
+
**怎么推翻:**
|
|
124
|
+
- 用技术理由,不用防御姿态
|
|
125
|
+
- 提具体问题
|
|
126
|
+
- 引用能跑的测试 / 现有代码
|
|
127
|
+
- 架构级的问题,拉用户进来
|
|
128
|
+
|
|
129
|
+
**如果你不好意思当面推翻:**把这份张力说出来,然后把看到的问题告诉用户。他们会感谢你的诚实。
|
|
130
|
+
|
|
131
|
+
## Handling Good Feedback(处理正确的反馈)
|
|
132
|
+
|
|
133
|
+
反馈确实正确时:
|
|
134
|
+
```
|
|
135
|
+
✅ "Fixed. [简述改了什么]"
|
|
136
|
+
✅ "Good catch - [具体问题]. Fixed in [位置]."
|
|
137
|
+
✅ [直接改掉,让代码自己说话]
|
|
138
|
+
|
|
139
|
+
❌ "You're absolutely right!"
|
|
140
|
+
❌ "Great point!"
|
|
141
|
+
❌ "Thanks for catching that!"
|
|
142
|
+
❌ "Thanks for [anything]"
|
|
143
|
+
❌ ANY gratitude expression(任何形式的感谢)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**为什么不许道谢:**行动说话。改掉就行。代码本身证明你听到了反馈。
|
|
147
|
+
|
|
148
|
+
**如果你发现自己正要打出 "Thanks":**删掉它,改成陈述修复本身。
|
|
149
|
+
|
|
150
|
+
## Gracefully Correcting Your Pushback(推翻错了就体面地纠正)
|
|
151
|
+
|
|
152
|
+
如果你推翻之后发现自己错了:
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
✅ "You were right - I checked [X] and it does [Y]. Implementing now."
|
|
156
|
+
✅ "Verified this and you're correct. My initial understanding was wrong because [reason]. Fixing."
|
|
157
|
+
|
|
158
|
+
❌ 长篇道歉
|
|
159
|
+
❌ 为自己为什么推翻而辩护
|
|
160
|
+
❌ 过度解释
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
陈述纠正的事实,然后继续干活。
|
|
164
|
+
|
|
165
|
+
## Implementation Order(实现顺序)
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
FOR multi-item feedback:
|
|
169
|
+
1. Clarify anything unclear FIRST
|
|
170
|
+
2. Then implement in this order:
|
|
171
|
+
- Blocking issues (breaks, security) # 阻塞性问题
|
|
172
|
+
- Simple fixes (typos, imports) # 简单修复
|
|
173
|
+
- Complex fixes (refactoring, logic) # 复杂修复
|
|
174
|
+
3. Test each fix individually # 逐条测(bash 跑测试)
|
|
175
|
+
4. Verify no regressions # 确认没有回归
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
实现动作用 `edit` / `write`,改之前先 `read` 目标文件,改之后用 `grep` 核对改动确实落盘。
|
|
179
|
+
|
|
180
|
+
## Common Mistakes(常见错误对照)
|
|
181
|
+
|
|
182
|
+
| Mistake(错误) | Fix(正确做法) |
|
|
183
|
+
|---------|-----|
|
|
184
|
+
| Performative agreement | State requirement or just act |
|
|
185
|
+
| Blind implementation | Verify against codebase first(先用 `read`/`grep` 核实) |
|
|
186
|
+
| Batch without testing | One at a time, test each |
|
|
187
|
+
| Assuming reviewer is right | Check if breaks things |
|
|
188
|
+
| Avoiding pushback | Technical correctness > comfort |
|
|
189
|
+
| Partial implementation | Clarify all items first |
|
|
190
|
+
| Can't verify, proceed anyway | State limitation, ask for direction |
|
|
191
|
+
|
|
192
|
+
## Real Examples(真实例子)
|
|
193
|
+
|
|
194
|
+
**表演式同意(坏):**
|
|
195
|
+
|
|
196
|
+
```
|
|
197
|
+
评审:"Remove legacy code"
|
|
198
|
+
❌ "You're absolutely right! Let me remove that..."
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
**技术性核实(好):**
|
|
202
|
+
|
|
203
|
+
```
|
|
204
|
+
评审:"Remove legacy code"
|
|
205
|
+
✅ "Checking... build target is 10.15+, this API needs 13+. Need legacy for backward compat.
|
|
206
|
+
Current impl has wrong bundle ID - fix it or drop pre-13 support?"
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
**YAGNI(好):**
|
|
210
|
+
|
|
211
|
+
```
|
|
212
|
+
评审:"Implement proper metrics tracking with database, date filters, CSV export"
|
|
213
|
+
✅ "Grepped codebase - nothing calls this endpoint. Remove it (YAGNI)? Or is there usage I'm missing?"
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
**不清晰的条目(好):**
|
|
217
|
+
|
|
218
|
+
```
|
|
219
|
+
用户:"把 1-6 都修了"
|
|
220
|
+
你懂 1、2、3、6,对 4、5 没把握。
|
|
221
|
+
✅ "Understand 1,2,3,6. Need clarification on 4 and 5 before implementing."
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## 评审线程里的回复
|
|
225
|
+
|
|
226
|
+
在 PR / 评审线程里回复行内评论时,回到**那条评论线程**里回复(`gh api repos/{owner}/{repo}/pulls/{pr}/comments/{id}/replies`),不要发一条顶层评论。
|
|
227
|
+
|
|
228
|
+
## 与 smart 工作流的关系
|
|
229
|
+
|
|
230
|
+
smart 工作流里 `smart_consult` 的评审建议、`smart_verify` 的证据结论、`smart_brief` 讨论中用户的修改要求 —— 都按本技能处理。**推翻一条评审要有技术论据,采纳一条要复述你改了什么**;不许用「你说得对」糊过去。
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: smart-skills
|
|
3
|
+
description: Use when starting any task in this profile — establishes how to find and use the smart skills, requiring a skill check BEFORE any response including clarifying questions. 任何回应(含反问、看代码、查文件)之前先查技能。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- Ported from obra/superpowers skills/using-superpowers/ — MIT License, Copyright (c) 2025 Jesse Vincent. Adapted for DSH. -->
|
|
7
|
+
|
|
8
|
+
# Smart 技能:先查,再动
|
|
9
|
+
|
|
10
|
+
<EXTREMELY-IMPORTANT>
|
|
11
|
+
如果你觉得某个技能**哪怕只有 1% 的可能**适用于手上的事,你就**必须**调用它。
|
|
12
|
+
|
|
13
|
+
技能适用时,你没有选择权。必须用它。
|
|
14
|
+
|
|
15
|
+
这不可谈判。你无法给自己找理由绕过去。
|
|
16
|
+
</EXTREMELY-IMPORTANT>
|
|
17
|
+
|
|
18
|
+
## 规则
|
|
19
|
+
|
|
20
|
+
**任何回应或动作之前先调用相关或被要求的技能** —— 包括反问、探索代码库、查看文件。
|
|
21
|
+
如果调用后发现不适用,你不必照它做。
|
|
22
|
+
|
|
23
|
+
**进入计划之前**:如果还没做过脑暴,先调用 `smart-brainstorm`。
|
|
24
|
+
|
|
25
|
+
然后声明「Using [技能名] to [目的]」,并逐条照技能执行。技能里有清单的,每条建一个 todo。
|
|
26
|
+
|
|
27
|
+
## 技能优先级
|
|
28
|
+
|
|
29
|
+
多个技能都适用时,**流程技能在前** —— 它们定路子,实现类技能再去执行。
|
|
30
|
+
|
|
31
|
+
- **「建个东西」**(加功能、做组件、改行为)→ 先 `smart-brainstorm`,再进工作流
|
|
32
|
+
- **「修个 bug」/ 行为不对** → 先 `smart-debug`,再决定要不要开任务
|
|
33
|
+
- **要写多步计划** → `smart-plan`
|
|
34
|
+
- **写任何生产代码之前** → `smart-tdd`
|
|
35
|
+
- **即将声称完成 / 测试通过** → `smart-verify-claim`
|
|
36
|
+
- **收到评审意见** → `smart-review-receive`
|
|
37
|
+
- **开始特性工作** → `smart-worktree`
|
|
38
|
+
- **2+ 个互不相干的任务** → `smart-parallel`
|
|
39
|
+
- **创建/修改技能** → `smart-write-skill`
|
|
40
|
+
- **某个 smart 会话出了问题**(卡住 / 门不开 / 重复派发 / 花太多)→ `smart-diagnose`
|
|
41
|
+
|
|
42
|
+
## 什么时候才开 smart 任务
|
|
43
|
+
|
|
44
|
+
技能是**独立的**,多数日常事不需要开任务:
|
|
45
|
+
|
|
46
|
+
| 你在做的事 | 该走 |
|
|
47
|
+
| --- | --- |
|
|
48
|
+
| 排查一个 bug、定位一个失败 | `smart-debug`(**不开任务**) |
|
|
49
|
+
| 写一小段代码 | `smart-tdd`(**不开任务**) |
|
|
50
|
+
| 看看这个 diff / 这段改动 | `smart_begin({ shape: 'review' })` |
|
|
51
|
+
| 修一处、要留证据 | `smart_begin({ shape: 'fix' })` |
|
|
52
|
+
| 调查个问题、出个结论 | `smart_begin({ shape: 'investigation' })` |
|
|
53
|
+
| 小改动 | `smart_begin({ size: 'light' })` |
|
|
54
|
+
| 完整交付一个功能 | `smart_begin({ shape: 'feature', size: 'full' })` |
|
|
55
|
+
| 写个技能 | `smart-write-skill`(**不开任务**) |
|
|
56
|
+
| 会话出了问题、想查为什么 | `smart-diagnose`(**不开任务**) |
|
|
57
|
+
|
|
58
|
+
**判断依据**:只有当这件事需要**别人的把关**(用户门 G1–G5)或**留档交付**时,才值得开任务。
|
|
59
|
+
排查、写测试、验证声明 —— 这些用技能就够。
|
|
60
|
+
|
|
61
|
+
## 你正在找借口
|
|
62
|
+
|
|
63
|
+
这些念头出现时**停**,你正在合理化:
|
|
64
|
+
|
|
65
|
+
| 想法 | 现实 |
|
|
66
|
+
| --- | --- |
|
|
67
|
+
| 「这只是个小问题」 | 问题是任务。查技能。 |
|
|
68
|
+
| 「我得先弄点上下文」 | 查技能在反问**之前**。 |
|
|
69
|
+
| 「让我先看看代码」 | 技能告诉你**怎么看**。先查。 |
|
|
70
|
+
| 「我可以快速看一眼 git/文件」 | 文件没有对话上下文。查技能。 |
|
|
71
|
+
| 「我先收集信息」 | 技能告诉你**怎么收集**。 |
|
|
72
|
+
| 「这个不需要正式技能」 | 有技能就用。 |
|
|
73
|
+
| 「我记得这个技能」 | 技能会演化。读当前版本。 |
|
|
74
|
+
| 「这不算任务」 | 动作 = 任务。查技能。 |
|
|
75
|
+
| 「用技能太重了」 | 简单事会变复杂。用它。 |
|
|
76
|
+
| 「我就先干这一件事」 | 干任何事**之前**先查。 |
|
|
77
|
+
| 「这感觉挺高效的」 | 没纪律的动作浪费时间。技能防这个。 |
|
|
78
|
+
| 「我知道那是什么意思」 | 懂概念 ≠ 用了技能。调用它。 |
|
|
79
|
+
|
|
80
|
+
## 模型选择
|
|
81
|
+
|
|
82
|
+
**技能本身跑在会话模型上**,改不了 —— 想让某个技能便宜,就在便宜模型的会话里跑它。
|
|
83
|
+
|
|
84
|
+
**技能派出去的子代理**按 route 选:
|
|
85
|
+
|
|
86
|
+
- 机械活(探索 / 取证 / 并行调查)→ `lite` 路由
|
|
87
|
+
- 多步推理、跨多文件理解 → `hand` 路由
|
|
88
|
+
- **评审永远走 `review` 路由**,不用 `lite`
|
|
89
|
+
|
|
90
|
+
记住 *"turn count beats token price"*:便宜模型在多步工作上常花 2–3 倍轮次,
|
|
91
|
+
总成本反而更高。纯机械、一步到位的才用 `lite`。
|
|
92
|
+
|
|
93
|
+
## 用户指令
|
|
94
|
+
|
|
95
|
+
用户的直接要求(AGENTS.md、会话里的明确指示)**优先于技能**,技能又优先于默认行为。
|
|
96
|
+
只有人类明确告诉你时,才可以跳过技能流程。
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: smart-tdd
|
|
3
|
+
description: Use when 实现任何功能或修复 bug、在写实现代码之前加载:先写失败的测试,看着它失败,再写最小实现让它通过。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- Ported from obra/superpowers skills/test-driven-development/ — MIT License, Copyright (c) 2025 Jesse Vincent. Adapted for DSH. -->
|
|
7
|
+
|
|
8
|
+
# 测试驱动开发(TDD)
|
|
9
|
+
|
|
10
|
+
## 概述
|
|
11
|
+
|
|
12
|
+
先写测试。看着它失败。写最小实现让它通过。
|
|
13
|
+
|
|
14
|
+
**核心原则:如果你没看到测试失败,你就不知道它测的是对的东西。**
|
|
15
|
+
(If you didn't watch the test fail, you don't know if it tests the right thing.)
|
|
16
|
+
|
|
17
|
+
**Violating the letter of these rules is violating the spirit.**
|
|
18
|
+
|
|
19
|
+
DSH 工具对应:用 `bash` 跑测试命令,用 `read` / `grep` 看代码与测试输出,用 `write` / `edit` 写测试与实现。
|
|
20
|
+
|
|
21
|
+
## 什么时候用
|
|
22
|
+
|
|
23
|
+
**Always:** 新功能、修 bug、重构、行为变化。
|
|
24
|
+
|
|
25
|
+
**例外(先问用户):**
|
|
26
|
+
- 一次性原型(throwaway prototypes)
|
|
27
|
+
- 生成的代码(generated code)
|
|
28
|
+
- 配置文件(configuration files)
|
|
29
|
+
|
|
30
|
+
心想「就这一次跳过 TDD」?停。That's rationalization.
|
|
31
|
+
|
|
32
|
+
## 铁律(The Iron Law)
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
先写了实现、后补测试?**删掉重来。**
|
|
39
|
+
|
|
40
|
+
**没有例外:**
|
|
41
|
+
- 不许留着当「参考」("reference")
|
|
42
|
+
- 不许写测试时顺手「改造」它(adapt)
|
|
43
|
+
- 不许看它
|
|
44
|
+
- 删了就是删了(Delete means delete)
|
|
45
|
+
|
|
46
|
+
从测试全新实现。Period.
|
|
47
|
+
|
|
48
|
+
## RED-GREEN-REFACTOR 循环
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
RED(写一个失败的测试)
|
|
52
|
+
└→ 验证 RED:必须失败,且因「功能缺失」而失败
|
|
53
|
+
├─ 失败原因不对 → 回 RED
|
|
54
|
+
GREEN(最小实现)
|
|
55
|
+
└→ 验证 GREEN:这条过 + 全套测试绿
|
|
56
|
+
├─ 没过 → 回 GREEN(改代码,不是改测试)
|
|
57
|
+
REFACTOR(清理,保持全绿 → 回验证 GREEN)
|
|
58
|
+
└→ 下一个 RED
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### RED — 写一个失败的测试
|
|
62
|
+
|
|
63
|
+
写一个最小测试,说明应该发生什么。
|
|
64
|
+
|
|
65
|
+
<Good>
|
|
66
|
+
```typescript
|
|
67
|
+
test('retries failed operations 3 times', async () => {
|
|
68
|
+
let attempts = 0;
|
|
69
|
+
const operation = () => {
|
|
70
|
+
attempts++;
|
|
71
|
+
if (attempts < 3) throw new Error('fail');
|
|
72
|
+
return 'success';
|
|
73
|
+
};
|
|
74
|
+
const result = await retryOperation(operation);
|
|
75
|
+
expect(result).toBe('success');
|
|
76
|
+
expect(attempts).toBe(3);
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
名字清楚、测真实行为、只测一件事
|
|
80
|
+
</Good>
|
|
81
|
+
|
|
82
|
+
<Bad>
|
|
83
|
+
```typescript
|
|
84
|
+
test('retry works', async () => {
|
|
85
|
+
const mock = jest.fn().mockRejectedValueOnce(new Error())
|
|
86
|
+
.mockRejectedValueOnce(new Error()).mockResolvedValueOnce('success');
|
|
87
|
+
await retryOperation(mock);
|
|
88
|
+
expect(mock).toHaveBeenCalledTimes(3);
|
|
89
|
+
});
|
|
90
|
+
```
|
|
91
|
+
名字含糊、测的是 mock 不是代码
|
|
92
|
+
</Bad>
|
|
93
|
+
|
|
94
|
+
**要求:** 一个行为;名字说清行为;真代码(非万不得已不用 mock)。
|
|
95
|
+
|
|
96
|
+
### 验证 RED — 看着它失败
|
|
97
|
+
|
|
98
|
+
**MANDATORY. Never skip.** 用 `bash` 跑测试命令,例如 `npm test path/to/test.test.ts`。
|
|
99
|
+
|
|
100
|
+
确认:
|
|
101
|
+
- 测试**失败**(不是报错 error)
|
|
102
|
+
- 失败信息符合预期
|
|
103
|
+
- 因为**功能缺失**而失败(不是拼写错误之类的低级原因)
|
|
104
|
+
|
|
105
|
+
**测试直接过了?** 你测的是既有行为。修测试。
|
|
106
|
+
|
|
107
|
+
**测试报错?** 先修错误,重跑直到它「正确地失败」。
|
|
108
|
+
|
|
109
|
+
### GREEN — 最小实现
|
|
110
|
+
|
|
111
|
+
写能通过测试的最简单代码。
|
|
112
|
+
|
|
113
|
+
<Good>
|
|
114
|
+
```typescript
|
|
115
|
+
async function retryOperation<T>(fn: () => Promise<T>): Promise<T> {
|
|
116
|
+
for (let i = 0; i < 3; i++) {
|
|
117
|
+
try { return await fn(); }
|
|
118
|
+
catch (e) { if (i === 2) throw e; }
|
|
119
|
+
}
|
|
120
|
+
throw new Error('unreachable');
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
刚好够通过
|
|
124
|
+
</Good>
|
|
125
|
+
|
|
126
|
+
<Bad>
|
|
127
|
+
```typescript
|
|
128
|
+
async function retryOperation<T>(fn: () => Promise<T>, options?: {
|
|
129
|
+
maxRetries?: number; backoff?: 'linear' | 'exponential';
|
|
130
|
+
onRetry?: (attempt: number) => void;
|
|
131
|
+
}): Promise<T> {
|
|
132
|
+
// YAGNI
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
过度设计
|
|
136
|
+
</Bad>
|
|
137
|
+
|
|
138
|
+
别加功能、别顺手重构别的代码、别超出测试去「改进」。
|
|
139
|
+
|
|
140
|
+
### 验证 GREEN — 看着它通过
|
|
141
|
+
|
|
142
|
+
**MANDATORY.** 再跑一次测试命令(同验证 RED)。
|
|
143
|
+
|
|
144
|
+
确认:
|
|
145
|
+
- 测试通过
|
|
146
|
+
- 其它测试也还过
|
|
147
|
+
- 输出干净(无 error、无 warning)
|
|
148
|
+
|
|
149
|
+
**测试失败?** 改代码,不是改测试。
|
|
150
|
+
|
|
151
|
+
**别的测试红了?** 立刻修。
|
|
152
|
+
|
|
153
|
+
**「其它测试」指整个项目的测试套件,不只是你那个文件。** 你写的那一条绿不等于全套绿。交付前要跑项目的测试命令(裸 `pytest`、`npm test`、`cargo test`——仓库用什么跑什么),哪怕你的任务只提到一个测试文件。任务里的范围声明限定的是交付物,不是你的验证。那一次跑出来的任何失败——包括不是你造成的——都要点名写进报告;一条你看着滚过去却没提的红测试,就是被省略证词伪造的报告。
|
|
154
|
+
|
|
155
|
+
### REFACTOR — 清理并重复
|
|
156
|
+
|
|
157
|
+
只在全绿之后:去重复、改好名字、抽 helper。保持测试全绿,不加行为。然后为下一个功能写下一条失败的测试(回到 RED)。
|
|
158
|
+
|
|
159
|
+
## 好测试长什么样
|
|
160
|
+
|
|
161
|
+
| 质量 | 好 | 坏 |
|
|
162
|
+
|------|-----|-----|
|
|
163
|
+
| **最小** | 一件事。名字里有「和」?拆开。 | `test('validates email and domain and whitespace')` |
|
|
164
|
+
| **清楚** | 名字描述行为 | `test('test1')` |
|
|
165
|
+
| **表达意图** | 展示想要的 API | 把代码该做什么藏起来 |
|
|
166
|
+
|
|
167
|
+
写或改任何测试、加 mock、加测试用的清理/helper 方法时,遵守下面让测试保持诚实的规则(并入自 writing-good-tests.md)。一个测试存在的意义是抓住某一种特定的破坏,两条原则管住一切:
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
1. Every test names the break it catches
|
|
171
|
+
2. Every test exercises the real thing
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
严格的 TDD 自然产出这两条:先写、并看着它对真代码失败的测试,已经证明了它会失败;只有当真依赖确实慢或外部时,它才挣得一个 mock。
|
|
175
|
+
|
|
176
|
+
### 原则一:说清它抓哪种破坏
|
|
177
|
+
|
|
178
|
+
写测试体之前先回答:**什么样的生产代码改动会让这条测试失败——那是 bug 还是一个有意的决定?** 一条测试靠抓住写错的分支、丢掉的副作用、传错的参数、边界情况或被破坏的契约来挣得位置。
|
|
179
|
+
|
|
180
|
+
- **期望值独立推导。** 用字面量和手工核对的 fixture;表驱动 + 字面 `want` 值是首选形状。用被测代码(或它的 helper)算出来的期望值,无论那段代码做什么都会通过:
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
// ❌ 镜像断言:同一个 builder 算两边 —— 恒真
|
|
184
|
+
const expected = buildSearchQuery({ tag: 'urgent' });
|
|
185
|
+
expect(buildSearchQuery({ tag: 'urgent' })).toBe(expected);
|
|
186
|
+
// ✅ 手推字面量
|
|
187
|
+
expect(buildSearchQuery({ tag: 'urgent' })).toBe('tag:"urgent"');
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
- **不要变更探测器(change detector)。** 如果只有有意的决定才能让测试失败(常量的值、报错文案、私有结构),它会在重新设计时乱叫、在真 bug 面前睡觉。测依赖该决定的行为:不是 `expect(MAX_RETRIES).toBe(5)`,而是「失败的调用被重试 5 次,第 6 次不再发生」。
|
|
191
|
+
- **行为,不是文本。** 断言脚本、技能或配置里含有某一精确行,只证明「源码就是源码」。拿受控输入去跑脚本,断言输出、副作用或退出码。指导智能体的文档由消费它的智能体的行为来测试;给人看的文字不配拥有测试。
|
|
192
|
+
- **你的代码,不是框架。** 测你的代码在边界上立下的契约——你注册的路由、你发出的查询、你产出的 payload。上游机制是上游维护者该写的测试(经典反例:断言你的路由器调用了注册的 handler——那是框架的测试,不是你的)。构造器、getter、常量、平凡转发,只有在做校验、归一、默认、派生、强制或产生副作用时才配拥有测试。
|
|
193
|
+
|
|
194
|
+
**Gate Function(写测试体之前):**
|
|
195
|
+
|
|
196
|
+
```
|
|
197
|
+
说出哪一处生产代码改动会让这条测试失败。
|
|
198
|
+
说不出来 → 围绕可观察的行为重新设计;「源码文本变了」→ 跑产物、断言它的效果
|
|
199
|
+
只有有意的决定会失败 → 变更探测器,改测依赖该决定的行为
|
|
200
|
+
期望值若借助被测代码的逻辑/helper 推导 → 换成字面量或手工核对的 fixture
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### 原则二:动真格的(Exercise the Real Thing)
|
|
204
|
+
|
|
205
|
+
- **mock 不配拥有断言。** mock 在场就通过、移除就失败的断言,对组件一无所言。断言真组件的行为;要查的就是 mock 本身,就拆掉 mock 或删掉断言。人类伙伴常问的一句:「我们在测一个 mock 的行为吗?」
|
|
206
|
+
- **在正确的层次 mock。** 替换真方法之前先弄清它的每一个副作用;只 mock 慢或外部的那层,测试依赖的部分保持真实。拿不准时先对真实现跑一遍测试,观察到底需要发生什么(反例:mock 掉 `ToolCatalog.discoverAndCacheTools` 会吞掉去重检测要读的那次配置写入)。
|
|
207
|
+
- **假件要具体。** 参数、调用次数、顺序是契约的一部分就断言它们;什么都接受的假件什么都没验证。每个分支(成功、出错、畸形)给各自的 fixture 或 spy,让错误的分支满足不了期望。
|
|
208
|
+
- **完整复刻真实数据。** mock 要复刻现实中的完整结构——所有文档化字段——不只是你测试读到的那几个。残缺的 mock 在下游读到漏掉的字段时静默失败:测试通过,集成坏了。
|
|
209
|
+
- **生产类只带生产方法。** 只有测试需要的清理代码放测试工具里,绝不给生产类加 `destroy()`。问自己:这个方法只有测试调用吗?这个类拥有这个资源的生命周期吗?答错了 → 测试工具。
|
|
210
|
+
- **真组件优于复杂 mock。** mock 搭建超过测试逻辑本身、mock 漏掉真组件有的方法、mock 一变测试就碎时,换成带真组件的集成测试。人类伙伴常问的一句:「这里我们需要用 mock 吗?」
|
|
211
|
+
|
|
212
|
+
**Gate Function(加 mock 或测试 helper 之前):**
|
|
213
|
+
|
|
214
|
+
```
|
|
215
|
+
列出真方法的副作用;测试依赖的保持真实,只 mock 它下面的慢/外部层。
|
|
216
|
+
mock 响应复刻完整的真实结构;只有测试调用的方法放测试工具,不进生产代码。
|
|
217
|
+
正要对 mock 本身下断言?→ 拆掉 mock,或删掉这条断言。
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### 测试与实现一起交付
|
|
221
|
+
|
|
222
|
+
TDD 循环——失败的测试、最小实现、重构——就是「完成」的定义。只交付这条行为需要的测试:琐碎代码和给人看的文字不配拥有测试,而为流程而写的测试要被维护一辈子。
|
|
223
|
+
|
|
224
|
+
### 变异检查(The Mutation Check)
|
|
225
|
+
|
|
226
|
+
收尾前在脑子里变异生产代码:每一种现实的变异都至少要有一条测试变红——常量或参数写错、分支处理器写错、缺少状态变更或副作用、返回空值或默认值、缺少对零值/空/nil/未授权/畸形输入的校验。
|
|
227
|
+
|
|
228
|
+
没有任何变异被抓到,说明这段行为没被保护——或者那条测试是同义反复。
|
|
229
|
+
|
|
230
|
+
## 常见借口(Common Rationalizations)
|
|
231
|
+
|
|
232
|
+
| 借口 | 现实 |
|
|
233
|
+
|------|------|
|
|
234
|
+
| 「太简单不用测」 | 简单代码也会坏。测试只要 30 秒。 |
|
|
235
|
+
| 「我之后再补测试」 | 事后补的测试一写就过——这什么也证明不了。它们可能测错了东西、测实现而不是行为,或者漏掉你忘掉的边界用例。你从没看它失败,就从没证明过它抓得住 bug。先写测试强制这个失败。 |
|
|
236
|
+
| 「事后测试目标一样(重精神不重仪式)」 | 事后测试回答「这段代码做了什么?」;先写测试回答「这段代码该做什么?」事后写的测试被你已写的代码带偏——你验证的是想起来的用例,不是本会发现的用例。有覆盖率,没有「测试能工作」的证明。 |
|
|
237
|
+
| 「已经手动测过了」 | 手动测试是即兴的:没有覆盖记录,代码一变没法重跑,压力下容易忘用例。「我试的时候是好的」不等于全面。自动化测试每次跑得一模一样。 |
|
|
238
|
+
| 「删掉 X 小时的代码太浪费」 | 沉没成本谬误——那些时间无论如何已经花掉了。真正的选择是:用 TDD 重写(高信心)vs 留着再补测试(低信心、大概率带 bug)。留着一段不敢信的代码才是浪费。 |
|
|
239
|
+
| 「留着当参考,测试先写」 | 你会顺手改它。那就是事后测试。删了就是删了。 |
|
|
240
|
+
| 「我得先探索一下」 | 可以。把探索产物扔掉,从 TDD 开始。 |
|
|
241
|
+
| 「测试难写 = 设计不清」 | 听测试的。难测 = 难用。 |
|
|
242
|
+
| 「TDD 会拖慢我」 | TDD 就是务实之路:提交前抓 bug、防回归、让你敢重构。「务实」的捷径意味着在生产环境里调试——更慢,不是更快。 |
|
|
243
|
+
| 「手动测更快」 | 手动证明不了边界用例。每次改动你都得重测一遍。 |
|
|
244
|
+
| 「既有代码没有测试」 | 你正在改进它。给既有代码补测试。 |
|
|
245
|
+
|
|
246
|
+
## 红旗(Red Flags)——停下,推倒重来
|
|
247
|
+
|
|
248
|
+
- 先写代码后写测试
|
|
249
|
+
- 实现写完才写测试
|
|
250
|
+
- 测试一写就过
|
|
251
|
+
- 说不清测试为什么失败
|
|
252
|
+
- 测试「以后再补」
|
|
253
|
+
- 说服自己「就这一次」
|
|
254
|
+
- 「我已经手动测过了」
|
|
255
|
+
- 「事后测试目的一样」
|
|
256
|
+
- 「重精神不重仪式」
|
|
257
|
+
- 「留着当参考」或「改造既有代码」
|
|
258
|
+
- 「已经花了 X 小时,删了浪费」
|
|
259
|
+
- 「TDD 太教条,我这是务实」
|
|
260
|
+
- 「这次情况不一样,因为……」
|
|
261
|
+
|
|
262
|
+
**以上任何一条都意味着:删掉代码,用 TDD 重新开始。**
|
|
263
|
+
|
|
264
|
+
## 示例:修一个 bug
|
|
265
|
+
|
|
266
|
+
**Bug:** 空邮箱被接受。
|
|
267
|
+
|
|
268
|
+
**RED**
|
|
269
|
+
```typescript
|
|
270
|
+
test('rejects empty email', async () => {
|
|
271
|
+
const result = await submitForm({ email: '' });
|
|
272
|
+
expect(result.error).toBe('Email required');
|
|
273
|
+
});
|
|
274
|
+
```
|
|
275
|
+
**验证 RED**(用 `bash` 跑 `npm test`):
|
|
276
|
+
```bash
|
|
277
|
+
FAIL: expected 'Email required', got undefined
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
**GREEN**
|
|
281
|
+
```typescript
|
|
282
|
+
function submitForm(data: FormData) {
|
|
283
|
+
if (!data.email?.trim()) return { error: 'Email required' };
|
|
284
|
+
// ...
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
**验证 GREEN**(同一条命令):
|
|
288
|
+
```bash
|
|
289
|
+
PASS
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
**REFACTOR**:需要的话把校验抽出来支持多个字段。
|
|
293
|
+
|
|
294
|
+
## 完成前检查清单
|
|
295
|
+
|
|
296
|
+
- [ ] 每个新函数/方法都有测试
|
|
297
|
+
- [ ] 每条测试都在实现之前看着它失败
|
|
298
|
+
- [ ] 每条测试都因预期的原因失败(功能缺失,不是拼写错误)
|
|
299
|
+
- [ ] 为每条测试写了最小实现
|
|
300
|
+
- [ ] 全部测试通过
|
|
301
|
+
- [ ] 输出干净(无 error、无 warning)
|
|
302
|
+
- [ ] 测试用真代码(mock 仅限非万不得已)
|
|
303
|
+
- [ ] 边界情况和错误路径已覆盖
|
|
304
|
+
|
|
305
|
+
勾不满?你跳过了 TDD。重新开始。
|
|
306
|
+
|
|
307
|
+
## 卡住时
|
|
308
|
+
|
|
309
|
+
| 问题 | 解法 |
|
|
310
|
+
|------|------|
|
|
311
|
+
| 不知道怎么测 | 写出想要的 API。先写断言。问用户。 |
|
|
312
|
+
| 测试太复杂 | 设计太复杂。简化接口。 |
|
|
313
|
+
| 必须 mock 一切 | 代码耦合太紧。用依赖注入。 |
|
|
314
|
+
| 测试准备代码巨大 | 抽 helper。还是复杂?简化设计。 |
|
|
315
|
+
|
|
316
|
+
## 调试
|
|
317
|
+
|
|
318
|
+
发现 bug?先写一个复现它的失败测试,走完 TDD 循环。这个测试既证明修复有效,也防止回归。
|
|
319
|
+
|
|
320
|
+
**Never fix bugs without a test.**
|
|
321
|
+
|
|
322
|
+
## 最终规则
|
|
323
|
+
|
|
324
|
+
```
|
|
325
|
+
Production code → test exists and failed first
|
|
326
|
+
Otherwise → not TDD
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
没有用户的许可,就没有例外。
|
|
330
|
+
|
|
331
|
+
## 进阶
|
|
332
|
+
|
|
333
|
+
需要把 TDD 落成多步计划时,用 `smart_begin({ size: 'light' })` 或 `smart-plan` 技能;单点修复可直接 `smart_begin({ shape: 'fix' })`。
|