superpowers-zh 1.7.3 → 1.7.5
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/README.md +4 -1
- package/README.zh-Hant.md +4 -1
- package/RELEASE-NOTES.zh.md +112 -0
- package/gemini-extension.json +1 -1
- package/package.json +1 -1
- package/skills/finishing-a-development-branch/SKILL.md +8 -9
- package/skills/subagent-driven-development/SKILL.md +209 -192
- package/skills/subagent-driven-development/implementer-prompt.md +5 -4
- package/skills/subagent-driven-development/re-review-prompt.md +100 -0
- package/skills/subagent-driven-development/scripts/review-package +11 -9
- package/skills/subagent-driven-development/scripts/sdd-workspace +26 -8
- package/skills/subagent-driven-development/scripts/task-brief +4 -3
- package/skills/subagent-driven-development/task-reviewer-prompt.md +1 -5
- package/skills/test-driven-development/SKILL.md +10 -61
- package/skills/test-driven-development/writing-good-tests.md +145 -0
- package/skills/using-superpowers/references/gemini-tools.md +55 -25
- package/skills/test-driven-development/testing-anti-patterns.md +0 -299
|
@@ -4,26 +4,28 @@
|
|
|
4
4
|
# call. Using the recorded per-task BASE (not HEAD~1) keeps multi-commit
|
|
5
5
|
# tasks intact.
|
|
6
6
|
#
|
|
7
|
-
# Usage: review-package BASE HEAD [OUTFILE]
|
|
8
|
-
# Default OUTFILE: <repo-root>/.superpowers/sdd
|
|
7
|
+
# Usage: review-package PLAN_FILE BASE HEAD [OUTFILE]
|
|
8
|
+
# Default OUTFILE: <repo-root>/.superpowers/sdd/<plan-basename>/review-<base7>..<head7>.diff
|
|
9
9
|
# (named per range, so a re-review after fixes gets a distinct fresh file).
|
|
10
10
|
set -euo pipefail
|
|
11
11
|
|
|
12
|
-
if [ $# -lt
|
|
13
|
-
echo "usage: review-package BASE HEAD [OUTFILE]" >&2
|
|
12
|
+
if [ $# -lt 3 ] || [ $# -gt 4 ]; then
|
|
13
|
+
echo "usage: review-package PLAN_FILE BASE HEAD [OUTFILE]" >&2
|
|
14
14
|
exit 2
|
|
15
15
|
fi
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
plan=$1
|
|
18
|
+
base=$2
|
|
19
|
+
head=$3
|
|
20
|
+
[ -f "$plan" ] || { echo "no such plan file: $plan" >&2; exit 2; }
|
|
19
21
|
|
|
20
22
|
git rev-parse --verify --quiet "$base" >/dev/null || { echo "bad BASE: $base" >&2; exit 2; }
|
|
21
23
|
git rev-parse --verify --quiet "$head" >/dev/null || { echo "bad HEAD: $head" >&2; exit 2; }
|
|
22
24
|
|
|
23
|
-
if [ $# -eq
|
|
24
|
-
out=$
|
|
25
|
+
if [ $# -eq 4 ]; then
|
|
26
|
+
out=$4
|
|
25
27
|
else
|
|
26
|
-
dir=$("$(cd "$(dirname "$0")" && pwd)/sdd-workspace")
|
|
28
|
+
dir=$("$(cd "$(dirname "$0")" && pwd)/sdd-workspace" "$plan")
|
|
27
29
|
out="$dir/review-$(git rev-parse --short "$base")..$(git rev-parse --short "$head").diff"
|
|
28
30
|
fi
|
|
29
31
|
|
|
@@ -1,22 +1,40 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# Resolve and ensure the working-tree directory SDD uses for
|
|
3
|
-
# artifacts: task briefs, implementer reports, review packages,
|
|
4
|
-
# progress ledger. Print the directory's absolute path.
|
|
2
|
+
# Resolve and ensure the working-tree directory SDD uses for one plan's
|
|
3
|
+
# short-lived artifacts: task briefs, implementer reports, review packages,
|
|
4
|
+
# and the progress ledger. Print the plan directory's absolute path.
|
|
5
|
+
#
|
|
6
|
+
# One directory per plan (.superpowers/sdd/<plan-basename>/) so a follow-up
|
|
7
|
+
# plan in the same working tree can never read or overwrite another plan's
|
|
8
|
+
# artifacts. A stale ledger misread as current progress makes controllers
|
|
9
|
+
# skip whole task sequences — plan-scoping removes that failure structurally.
|
|
5
10
|
#
|
|
6
11
|
# The workspace lives in the working tree (not under .git/) because Claude Code
|
|
7
12
|
# treats .git/ as a protected path and denies agent writes there — which blocks
|
|
8
13
|
# an implementer subagent from writing its report file. A self-ignoring
|
|
9
|
-
# .gitignore
|
|
10
|
-
# commits without modifying any tracked file.
|
|
14
|
+
# .gitignore at .superpowers/sdd/ keeps every plan's workspace out of
|
|
15
|
+
# `git status` and out of accidental commits without modifying any tracked file.
|
|
11
16
|
#
|
|
12
17
|
# Single source of truth for the workspace location, so task-brief and
|
|
13
18
|
# review-package cannot drift to different directories.
|
|
14
19
|
#
|
|
15
|
-
# Usage: sdd-workspace
|
|
20
|
+
# Usage: sdd-workspace PLAN_FILE
|
|
16
21
|
set -euo pipefail
|
|
17
22
|
|
|
23
|
+
if [ $# -ne 1 ]; then
|
|
24
|
+
echo "usage: sdd-workspace PLAN_FILE" >&2
|
|
25
|
+
exit 2
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
plan=$1
|
|
29
|
+
[ -f "$plan" ] || { echo "no such plan file: $plan" >&2; exit 2; }
|
|
30
|
+
|
|
31
|
+
slug=$(basename "$plan" .md)
|
|
32
|
+
[ -n "$slug" ] && [ "$slug" != "." ] && [ "$slug" != ".." ] \
|
|
33
|
+
|| { echo "cannot derive a workspace name from: $plan" >&2; exit 2; }
|
|
34
|
+
|
|
18
35
|
root=$(git rev-parse --show-toplevel)
|
|
19
|
-
|
|
36
|
+
base="$root/.superpowers/sdd"
|
|
37
|
+
dir="$base/$slug"
|
|
20
38
|
mkdir -p "$dir"
|
|
21
|
-
printf '*\n' > "$
|
|
39
|
+
printf '*\n' > "$base/.gitignore"
|
|
22
40
|
cd "$dir" && pwd
|
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
# through the controller's context.
|
|
5
5
|
#
|
|
6
6
|
# Usage: task-brief PLAN_FILE TASK_NUMBER [OUTFILE]
|
|
7
|
-
# Default OUTFILE: <repo-root>/.superpowers/sdd
|
|
8
|
-
# (per worktree; concurrent runs
|
|
7
|
+
# Default OUTFILE: <repo-root>/.superpowers/sdd/<plan-basename>/task-<N>-brief.md
|
|
8
|
+
# (per plan and per worktree; concurrent runs of the SAME plan in the same
|
|
9
|
+
# working tree share it).
|
|
9
10
|
#
|
|
10
11
|
# 中文 fork 适配:上游只识别英文任务标题 "## Task N",而 superpowers-zh
|
|
11
12
|
# 的 writing-plans 产出的是 "### 任务 N:..."。下方 awk 同时匹配
|
|
@@ -24,7 +25,7 @@ n=$2
|
|
|
24
25
|
if [ $# -eq 3 ]; then
|
|
25
26
|
out=$3
|
|
26
27
|
else
|
|
27
|
-
dir=$("$(cd "$(dirname "$0")" && pwd)/sdd-workspace")
|
|
28
|
+
dir=$("$(cd "$(dirname "$0")" && pwd)/sdd-workspace" "$plan")
|
|
28
29
|
out="$dir/task-${n}-brief.md"
|
|
29
30
|
fi
|
|
30
31
|
|
|
@@ -158,12 +158,8 @@ Subagent (general-purpose):
|
|
|
158
158
|
- `[BASE_SHA]` —— 本任务之前的提交
|
|
159
159
|
- `[HEAD_SHA]` —— 当前提交
|
|
160
160
|
- `[DIFF_FILE]` —— 必填:控制者写入审查包的那个路径
|
|
161
|
-
(`scripts/review-package BASE HEAD` 会打印它写入的唯一路径;
|
|
161
|
+
(`scripts/review-package PLAN_FILE BASE HEAD` 会打印它写入的唯一路径;
|
|
162
162
|
审查包永远不会进入控制者的上下文)
|
|
163
163
|
|
|
164
164
|
**审查者返回:** 规格合规性结论(✅/❌/⚠️)、优点、问题
|
|
165
165
|
(关键/重要/次要)、任务质量结论
|
|
166
|
-
|
|
167
|
-
一次修复分派可以同时处理规格差距和质量发现;修复后的重新审查
|
|
168
|
-
覆盖两个结论。
|
|
169
|
-
</content>
|
|
@@ -208,69 +208,25 @@ npm test path/to/test.test.ts
|
|
|
208
208
|
| **清晰** | 名称描述行为 | `test('test1')` |
|
|
209
209
|
| **展示意图** | 展示期望的 API | 掩盖了代码应该做什么 |
|
|
210
210
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
- 可能测试了错误的东西
|
|
217
|
-
- 可能测试的是实现而非行为
|
|
218
|
-
- 可能遗漏了你忘掉的边界情况
|
|
219
|
-
- 你从未看到它捕获 bug
|
|
220
|
-
|
|
221
|
-
先写测试迫使你看到测试失败,证明它确实在测试某些东西。
|
|
222
|
-
|
|
223
|
-
**"我已经手动测试了所有边界情况"**
|
|
224
|
-
|
|
225
|
-
手动测试是临时的。你以为你测试了所有情况,但是:
|
|
226
|
-
- 没有测试记录
|
|
227
|
-
- 代码变更后无法重新运行
|
|
228
|
-
- 在压力下容易遗忘
|
|
229
|
-
- "我试过了能跑" 不等于 全面测试
|
|
230
|
-
|
|
231
|
-
自动化测试是系统性的。它们每次以相同方式运行。
|
|
232
|
-
|
|
233
|
-
**"删除 X 小时的工作太浪费了"**
|
|
234
|
-
|
|
235
|
-
沉没成本谬误。时间已经花了。你现在的选择:
|
|
236
|
-
- 删除并用 TDD 重写(再花 X 小时,高信心)
|
|
237
|
-
- 保留并后补测试(30 分钟,低信心,可能有 bug)
|
|
238
|
-
|
|
239
|
-
"浪费"的是保留你无法信任的代码。没有真正测试的可运行代码就是技术债。
|
|
240
|
-
|
|
241
|
-
**"TDD 太教条了,务实意味着灵活变通"**
|
|
242
|
-
|
|
243
|
-
TDD 就是务实的:
|
|
244
|
-
- 在 commit 前发现 bug(比事后调试快)
|
|
245
|
-
- 防止回归(测试立即发现破坏)
|
|
246
|
-
- 记录行为(测试展示如何使用代码)
|
|
247
|
-
- 支持重构(放心修改,测试捕获破坏)
|
|
248
|
-
|
|
249
|
-
"务实的"捷径 = 在生产环境调试 = 更慢。
|
|
250
|
-
|
|
251
|
-
**"后补测试也能达到相同目的——重要的是精神不是仪式"**
|
|
252
|
-
|
|
253
|
-
不对。后补测试回答"这段代码做了什么?"先写测试回答"这段代码应该做什么?"
|
|
254
|
-
|
|
255
|
-
后补测试受你实现的偏见影响。你测试的是你构建的东西,而非需求要求的。你验证的是你记得的边界情况,而非发现的。
|
|
256
|
-
|
|
257
|
-
先写测试迫使你在实现前发现边界情况。后补测试验证的是你记住了所有情况(你没有)。
|
|
258
|
-
|
|
259
|
-
30 分钟的后补测试 ≠ TDD。你得到了覆盖率,但失去了测试有效的证明。
|
|
211
|
+
写任何测试、或修改任何测试时,阅读 [writing-good-tests.md](writing-good-tests.md),那里是让测试保持诚实的规则:
|
|
212
|
+
- 在动手写之前,先点名那个会让该测试失败的生产代码改动
|
|
213
|
+
- 断言真实行为,绝不断言 mock 行为
|
|
214
|
+
- 只有测试才用的代码放在测试工具里,不进生产类
|
|
215
|
+
- 在 mock 一个依赖之前,先搞清它的副作用
|
|
260
216
|
|
|
261
217
|
## 常见借口
|
|
262
218
|
|
|
263
219
|
| 借口 | 现实 |
|
|
264
220
|
|------|------|
|
|
265
221
|
| "太简单了不用测" | 简单的代码也会出 bug。测试只需 30 秒。 |
|
|
266
|
-
| "我之后补测试" |
|
|
267
|
-
| "
|
|
268
|
-
| "已经手动测试过了" |
|
|
269
|
-
| "删除 X 小时的工作太浪费" |
|
|
222
|
+
| "我之后补测试" | 后写的测试立即通过——而立即通过什么都证明不了。它可能测错了对象、测的是实现而不是行为、或者漏掉你忘了的那个边界情况。你从没看着它失败过,所以你从没证明它能抓住 bug。先写测试逼你看到那次失败。 |
|
|
223
|
+
| "后补测试也能达到相同目的(重的是精神不是仪式)" | 后补测试回答的是"这做了什么?";先写测试回答的是"这应该做什么?"后写的测试已经被你写好的代码带偏了——你验证的是你**记得**的那些情况,而不是你本该**发现**的那些。有覆盖率,没有测试有效的证明。 |
|
|
224
|
+
| "已经手动测试过了" | 手动测试是临时的:没有记录你覆盖了什么、代码一改就没法重跑、压力之下极易漏掉情况。"我试的时候是好的" ≠ 全面。自动化测试每次都以同样的方式运行。 |
|
|
225
|
+
| "删除 X 小时的工作太浪费" | 沉没成本谬误——那些时间无论怎样都已经花掉了。真正的选择是:用 TDD 重写(高置信度)vs 留着它事后补测试(低置信度、很可能有 bug)。留着你无法信任的代码才是浪费。 |
|
|
270
226
|
| "留作参考,然后先写测试" | 你会去改编它。那就是后补测试。删除就是删除。 |
|
|
271
227
|
| "需要先探索一下" | 可以。探索完了扔掉,从 TDD 开始。 |
|
|
272
228
|
| "测试难写 = 设计不清楚" | 听测试的。难以测试 = 难以使用。 |
|
|
273
|
-
| "TDD 会拖慢我" | TDD
|
|
229
|
+
| "TDD 会拖慢我" | TDD **就是**务实的那条路:在提交前抓住 bug、防止回归、让你能无所畏惧地重构。所谓"务实"的抄近道,等于在生产环境里调试——更慢,不是更快。 |
|
|
274
230
|
| "手动测试更快" | 手动测试无法证明边界情况。每次修改你都得重新测。 |
|
|
275
231
|
| "现有代码没有测试" | 你在改进它。为现有代码补测试。 |
|
|
276
232
|
|
|
@@ -359,13 +315,6 @@ PASS
|
|
|
359
315
|
|
|
360
316
|
绝不在没有测试的情况下修复 bug。
|
|
361
317
|
|
|
362
|
-
## 测试反模式
|
|
363
|
-
|
|
364
|
-
添加 mock 或测试工具时,阅读 @testing-anti-patterns.md 以避免常见陷阱:
|
|
365
|
-
- 测试 mock 行为而非真实行为
|
|
366
|
-
- 在生产类中添加仅测试用的方法
|
|
367
|
-
- 在不理解依赖的情况下使用 mock
|
|
368
|
-
|
|
369
318
|
## 最终规则
|
|
370
319
|
|
|
371
320
|
```
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# 写好测试
|
|
2
|
+
|
|
3
|
+
**在以下情况加载此参考:** 编写或修改测试、添加 mock、或为测试添加清理/辅助方法时。
|
|
4
|
+
|
|
5
|
+
## 概述
|
|
6
|
+
|
|
7
|
+
一个测试的存在是为了抓住某个**具体的**破坏。这里的一切都由两条原则统辖:
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
1. 每个测试都点名它要抓的破坏
|
|
11
|
+
2. 每个测试都跑真东西
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
严格的 TDD 会自然产出这两点:一个先写、并且在真实代码上亲眼看着它失败过的测试,已经证明了自己**能**失败;而只有当真实依赖被证明缓慢或属于外部时,mock 才配被引入。
|
|
15
|
+
|
|
16
|
+
## 原则 1:点名它要抓的破坏
|
|
17
|
+
|
|
18
|
+
在写测试体之前,先回答:**什么样的生产代码改动应该让这个测试失败——而那个改动是 bug 还是一个决定?** 一个测试靠抓住走错的分支、缺失的副作用、传错的参数、边界情况或被破坏的契约来赢得它的位置。
|
|
19
|
+
|
|
20
|
+
**独立推导期望值。** 用字面量和手工核对过的 fixture;带字面量 `want` 值的表驱动测试是首选形态。一个由**被测代码本身**(或它的辅助函数)算出来的期望值,无论那段代码干了什么都会通过:
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
// ❌ 镜像断言:同一个 builder 算出了等式两边 —— 永远为真
|
|
24
|
+
const expected = buildSearchQuery({ tag: 'urgent' });
|
|
25
|
+
expect(buildSearchQuery({ tag: 'urgent' })).toBe(expected);
|
|
26
|
+
|
|
27
|
+
// ✅ 手工推导的字面量
|
|
28
|
+
expect(buildSearchQuery({ tag: 'urgent' })).toBe('tag:"urgent"');
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**不要写变更探测器。** 如果只有**有意为之的决定**才能让一个测试失败——某个常量的取值、某句消息的精确措辞、某个私有结构——那它会在重新设计时误报、却对真 bug 一路沉睡。要测那个**依赖于该决定的行为**:不是 `expect(MAX_RETRIES).toBe(5)`,而是"一次失败的调用会被重试 5 次,且第 6 次尝试永不发生"。
|
|
32
|
+
|
|
33
|
+
**测行为,不测文本。** 断言某个脚本、skill 或配置文件"包含某一行",只能证明源文件就是源文件。要拿受控输入去**跑**脚本,然后断言它的输出、副作用或退出码。用来指挥 agent 的文档,靠消费它的 agent 的行为来测(superpowers:writing-skills);写给人看的散文根本不该有测试。
|
|
34
|
+
|
|
35
|
+
**测你的代码,不测框架。** 测你的代码在其边界上所做的契约——你注册的那条路由、你发出的那条查询、你产出的那个 payload。上游的机制是它们维护者该写的测试(经典反例:断言你的 router 会调用一个已注册的 handler——那是框架的测试,不是你的)。当上游行为**确实**让你意外时,写一个窄窄的表征测试,把那个假设点名出来。同样的边界也适用于你代码内部:构造函数、getter、常量和琐碎的转发,只有当它们做校验、归一化、给默认值、做推导、做强制或产生副作用时才配有测试——否则就去断言第一个依赖于它们、且对消费者可见的结果。
|
|
36
|
+
|
|
37
|
+
### 门控函数
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
在写测试体之前:
|
|
41
|
+
点名那个会让这个测试失败的生产代码改动。
|
|
42
|
+
|
|
43
|
+
点不出来 → 围绕一个可观察的行为重新设计
|
|
44
|
+
"源文本变了" → 去跑这个产物,断言它的效果
|
|
45
|
+
只有有意为之的决定能让它失败 → 这是变更探测器;改测那个
|
|
46
|
+
依赖于该决定的行为
|
|
47
|
+
|
|
48
|
+
确认期望值的推导过程没有用到被测代码。
|
|
49
|
+
如果它复用了被测代码的逻辑或辅助函数:
|
|
50
|
+
换成字面量或手工核对过的 fixture
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## 原则 2:跑真东西
|
|
54
|
+
|
|
55
|
+
**mock 不配拥有断言。** 一个针对 mock 的断言,在 mock 存在时通过、在 mock 缺席时失败——它对被测组件什么都没说。要断言**真实组件**的行为;如果你要检查的就是那个 mock,那就把它 unmock,或者把这条断言删掉。
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
// ✅ 真实行为
|
|
59
|
+
expect(screen.getByRole('navigation')).toBeInTheDocument();
|
|
60
|
+
|
|
61
|
+
// ❌ mock 是否存在
|
|
62
|
+
expect(screen.getByTestId('sidebar-mock')).toBeInTheDocument();
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**你的人类伙伴会这样纠正你:** "我们是在测一个 mock 的行为吗?"
|
|
66
|
+
|
|
67
|
+
**在正确的层级上 mock。** 在替换真实方法之前,先搞清它的每一个副作用;只 mock 掉慢的或外部的那一步操作,把测试真正依赖的东西保留为真实的。不确定时,先拿真实实现跑一遍测试,观察实际上必须发生什么。
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
// ❌ 这个 mock 吞掉了配置写入,而重复检测正是要读它
|
|
71
|
+
vi.mock('ToolCatalog', () => ({
|
|
72
|
+
discoverAndCacheTools: vi.fn().mockResolvedValue(undefined)
|
|
73
|
+
}));
|
|
74
|
+
|
|
75
|
+
// ✅ 只 mock 掉缓慢的服务器启动;配置写入保持真实
|
|
76
|
+
vi.mock('MCPServerManager');
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**让替身足够具体。** 当参数、调用次数或调用顺序本身就是契约的一部分时,就要断言它们——一个什么都接受的 fake 什么都没验证。给每个分支(成功、报错、格式错误)配它自己的 fixture 或 spy,这样走错的分支就无法满足期望。
|
|
80
|
+
|
|
81
|
+
**完整镜像真实数据。** 按现实中的**完整结构**来 mock——所有有文档的字段——而不是只 mock 你这个测试会读的那几个。部分 mock 会静默失败:下游代码读到一个被省略的字段时,测试通过、集成崩掉。
|
|
82
|
+
|
|
83
|
+
**生产类只承载生产方法。** 只有测试才需要的清理逻辑,放在测试工具里,绝不作为生产类上的 `destroy()`。自问:这个方法只被测试调用吗?这个类拥有这份资源的生命周期吗?答错了 → 挪进测试工具。
|
|
84
|
+
|
|
85
|
+
**宁可用真实组件,也不要复杂 mock。** 当 mock 的搭建代码超过测试逻辑本身、mock 漏掉了真实组件才有的方法、或者 mock 一改测试就崩时,改成用真实组件的集成测试。**你的人类伙伴会这样问:** "这里我们真的需要用 mock 吗?"
|
|
86
|
+
|
|
87
|
+
### 门控函数
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
在添加 mock 或测试辅助函数之前:
|
|
91
|
+
列出真实方法的副作用;测试所依赖的那些保持真实 ——
|
|
92
|
+
只 mock 它们下面那一层「慢的/外部的」。
|
|
93
|
+
|
|
94
|
+
mock 的返回值要完整镜像真实结构。
|
|
95
|
+
|
|
96
|
+
只被测试调用的方法,属于测试工具,不属于生产代码。
|
|
97
|
+
|
|
98
|
+
正要对 mock 本身下断言?
|
|
99
|
+
把它 unmock,或者删掉这条断言。
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## 测试与实现一同交付
|
|
103
|
+
|
|
104
|
+
TDD 循环——失败的测试、最小实现、重构——就是"完成"的定义。交付这个行为**需要**的测试,且只交付这些:琐碎代码和给人看的散文都不配有测试,而一个为了满足流程而写的测试会永远付出维护代价。
|
|
105
|
+
|
|
106
|
+
## 变异检查
|
|
107
|
+
|
|
108
|
+
收尾之前,在脑子里对生产代码做变异;对每一种现实的变异,都应至少有一个测试失败:
|
|
109
|
+
|
|
110
|
+
- 常量或参数写错
|
|
111
|
+
- 分支处理写错
|
|
112
|
+
- 缺失状态变更或副作用
|
|
113
|
+
- 返回空值或默认值
|
|
114
|
+
- 缺失对零值、空值、nil、未授权或格式错误输入的校验
|
|
115
|
+
|
|
116
|
+
一个没有任何测试能抓住的变异,标记出该行为无保护——或者那个测试是同义反复。
|
|
117
|
+
|
|
118
|
+
## 快速参考
|
|
119
|
+
|
|
120
|
+
| 当你…… | 就这么做 |
|
|
121
|
+
|--------|---------|
|
|
122
|
+
| 写任何测试 | 点名它要抓的破坏——是 bug,不是决定 |
|
|
123
|
+
| 构造期望值 | 手工推导;绝不用被测代码去算 |
|
|
124
|
+
| 测一个脚本或文档 | 跑它 / 压测它的消费者;绝不 grep 它的文本 |
|
|
125
|
+
| 想给依赖写测试 | 测你的边界契约,不测它们有文档的机制 |
|
|
126
|
+
| 想对一个被 mock 的元素下断言 | 改测真实组件,或者把它 unmock |
|
|
127
|
+
| 正要 mock 某个方法 | 先搞清它的副作用;在慢的/外部的那一层上 mock |
|
|
128
|
+
| 构造一个 mock 返回值 | 完整镜像真实结构 |
|
|
129
|
+
| 需要只有测试才用的清理逻辑 | 放进测试工具 |
|
|
130
|
+
| 眼看 mock 搭建代码膨胀 | 改成用真实组件的集成测试 |
|
|
131
|
+
| 写完一个测试文件 | 跑一遍变异检查 |
|
|
132
|
+
|
|
133
|
+
## 危险信号
|
|
134
|
+
|
|
135
|
+
- 搭建过程和断言共用同一个对象,等式必然成立
|
|
136
|
+
- 这个测试只可能因为 panic、崩溃或选择器缺失而失败
|
|
137
|
+
- 这个测试在每次有意改动时都失败,却从不在意外破坏时失败
|
|
138
|
+
- 期望值藏在循环、builder 或辅助函数背后
|
|
139
|
+
- 这个测试去 grep 源码文本,或者断言某个已删除的符号仍然是删除状态
|
|
140
|
+
- 就算只剩下框架,这个测试依然"成立"
|
|
141
|
+
- 这个测试是为覆盖率而存在的,不检查任何副作用或结果
|
|
142
|
+
- 某条断言检查的是 `*-mock` 这种 test ID,或者你把 mock 去掉它就失败
|
|
143
|
+
- 某个方法只被测试文件调用
|
|
144
|
+
- mock 搭建占了测试的一半以上,或者你说不出为什么需要这个 mock
|
|
145
|
+
- "为了安全起见"而 mock
|
|
@@ -1,33 +1,63 @@
|
|
|
1
1
|
# Gemini CLI 工具映射
|
|
2
2
|
|
|
3
|
-
Skills
|
|
4
|
-
|
|
5
|
-
| Skill
|
|
6
|
-
|
|
7
|
-
|
|
|
8
|
-
|
|
|
9
|
-
|
|
|
10
|
-
|
|
|
11
|
-
|
|
|
12
|
-
|
|
|
13
|
-
|
|
|
14
|
-
|
|
|
15
|
-
|
|
|
16
|
-
|
|
|
17
|
-
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
3
|
+
Skills 说的是动作("分派一个子智能体"、"建一条待办"、"读一个文件")。在 Gemini CLI 上,这些动作对应下面这些工具。
|
|
4
|
+
|
|
5
|
+
| Skill 请求的动作 | Gemini CLI 等价工具 |
|
|
6
|
+
|----------------|-------------------|
|
|
7
|
+
| 读取一个文件 | `read_file` |
|
|
8
|
+
| 一次读取多个文件 | `read_many_files` |
|
|
9
|
+
| 创建新文件 | `write_file` |
|
|
10
|
+
| 编辑文件 | `replace` |
|
|
11
|
+
| 执行 shell 命令 | `run_shell_command` |
|
|
12
|
+
| 搜索文件内容 | `grep_search` |
|
|
13
|
+
| 按名称查找文件 | `glob` |
|
|
14
|
+
| 列出文件和子目录 | `list_directory` |
|
|
15
|
+
| 抓取 URL | `web_fetch` |
|
|
16
|
+
| 搜索网页 | `google_web_search` |
|
|
17
|
+
| 调用一个 skill | `activate_skill` |
|
|
18
|
+
| 分派子智能体(`Subagent (general-purpose):` 模板) | `invoke_agent`,`agent_name: "generalist"`(也可用 `@generalist` 聊天语法调用——见[子智能体支持](#子智能体支持)) |
|
|
19
|
+
| 多个并行分派 | 同一条响应里发多个 `invoke_agent` 调用 |
|
|
20
|
+
| 任务跟踪("建一条待办"、"标记完成") | `write_todos`(状态:pending、in_progress、completed、cancelled、blocked) |
|
|
21
|
+
|
|
22
|
+
## 指令文件
|
|
23
|
+
|
|
24
|
+
当某个 skill 提到"你的指令文件"时,在 Gemini CLI 上指的是 **`GEMINI.md`**。Gemini CLI 按层级加载 `GEMINI.md`:全局的在 `~/.gemini/GEMINI.md`,项目级的在工作区目录及其各级父目录里,另外当某个工具访问子目录中的文件时,该子目录下的 `GEMINI.md` 也会被加载。
|
|
25
|
+
|
|
26
|
+
## 个人 skills 目录
|
|
27
|
+
|
|
28
|
+
用户级 skills 放在 **`~/.gemini/skills/`**,**`~/.agents/skills/`** 是跨运行时的别名目录(与 Codex、Copilot CLI 共用)。当同一层级下两个目录都存在时,`.agents/skills/` 优先。每个 skill 是一个子目录,里面有一份带 `name` 和 `description` frontmatter 的 `SKILL.md`。
|
|
29
|
+
|
|
30
|
+
## 子智能体支持
|
|
31
|
+
|
|
32
|
+
Gemini CLI 通过 `invoke_agent` 工具分派子智能体,该工具接收 `agent_name` 和 `prompt` 两个参数。同一个分派动作也有聊天语法快捷方式:输入 `@generalist <prompt>` 等价于以 `agent_name: "generalist"` 调用 `invoke_agent`。内置的 agent 名包括 `generalist`、`cli_help`、`codebase_investigator`,以及(启用浏览器工具后的)`browser_agent`。
|
|
33
|
+
|
|
34
|
+
Skills 用 `Subagent (general-purpose):` 来分派,并且要么引用一个提示词模板文件(例如 `superpowers:subagent-driven-development` 的 `./implementer-prompt.md`),要么直接给出内联提示词。在 Gemini CLI 上:
|
|
35
|
+
|
|
36
|
+
| Skill 里的分派形式 | Gemini CLI 等价做法 |
|
|
37
|
+
|------------------|-------------------|
|
|
38
|
+
| 引用某个 `*-prompt.md` 模板(implementer、task-reviewer、code-reviewer 等) | 把模板填好,然后以 `agent_name: "generalist"` 和填好的提示词调用 `invoke_agent` |
|
|
39
|
+
| 引用 `superpowers:requesting-code-review` 的 `./code-reviewer.md` | 以 `agent_name: "generalist"` 和填好的审查模板调用 `invoke_agent` |
|
|
40
|
+
| 内联提示词(没有引用模板) | 以 `agent_name: "generalist"` 和你的内联提示词调用 `invoke_agent` |
|
|
41
|
+
|
|
42
|
+
### 填写提示词
|
|
43
|
+
|
|
44
|
+
Skills 提供的提示词模板里有 `{WHAT_WAS_IMPLEMENTED}` 或 `[FULL TEXT of task]` 这类占位符。把所有占位符都填好,再把完整提示词交给 `invoke_agent`。模板本身就包含了该 agent 的角色、审查标准和期望的输出格式——子智能体会照着它执行。
|
|
45
|
+
|
|
46
|
+
### 并行分派
|
|
47
|
+
|
|
48
|
+
Gemini CLI 支持并行分派子智能体。在同一条响应里发出多个 `invoke_agent` 调用(或在一个提示词里写多个 `@generalist` 调用),即可让相互独立的子智能体工作并行跑。有依赖关系的任务保持串行,但**不要**为了让历史记录简单一点就把相互独立的子智能体任务串起来。
|
|
22
49
|
|
|
23
50
|
## Gemini CLI 额外工具
|
|
24
51
|
|
|
25
|
-
|
|
52
|
+
以下工具是 Gemini CLI 独有的:
|
|
26
53
|
|
|
27
54
|
| 工具 | 用途 |
|
|
28
55
|
|------|------|
|
|
29
|
-
| `
|
|
30
|
-
| `
|
|
31
|
-
| `ask_user` |
|
|
32
|
-
| `
|
|
33
|
-
| `
|
|
56
|
+
| `save_memory`(旧版) | 当 `experimental.memoryV2 = false` 时,跨会话持久化事实 |
|
|
57
|
+
| `get_internal_docs` | 查阅 Gemini CLI 自带的文档 |
|
|
58
|
+
| `ask_user` | 向用户提出结构化问题(文本 / 单选 / 多选) |
|
|
59
|
+
| `enter_plan_mode` / `exit_plan_mode` | 进入和退出只读的计划模式 |
|
|
60
|
+
| `update_topic` | 更新当前会话的主题 / 战略意图元数据 |
|
|
61
|
+
| `complete_task` | 表示某个 Gemini 子智能体已完成,并把结果返回给父 agent |
|
|
62
|
+
| `tracker_create_task`、`tracker_update_task`、`tracker_get_task`、`tracker_list_tasks`、`tracker_add_dependency`、`tracker_visualize` | 功能完整的任务跟踪器,支持依赖关系与可视化 |
|
|
63
|
+
| `read_mcp_resource`、`list_mcp_resources` | 访问 MCP 资源 |
|