sdd-full 3.2.0 → 4.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/bin.js +63 -31
- package/package.json +1 -1
- package/skills/brainstorming/SKILL.md +164 -0
- package/skills/brainstorming/scripts/frame-template.html +214 -0
- package/skills/brainstorming/scripts/helper.js +88 -0
- package/skills/brainstorming/scripts/server.cjs +338 -0
- package/skills/brainstorming/scripts/start-server.sh +153 -0
- package/skills/brainstorming/scripts/stop-server.sh +55 -0
- package/skills/brainstorming/spec-document-reviewer-prompt.md +48 -0
- package/skills/brainstorming/visual-companion.md +286 -0
- package/skills/chinese-code-review/SKILL.md +277 -0
- package/skills/chinese-commit-conventions/SKILL.md +364 -0
- package/skills/chinese-documentation/SKILL.md +448 -0
- package/skills/chinese-git-workflow/SKILL.md +510 -0
- package/skills/design-planning/enterprise-spec/SKILL.md +3 -52
- package/skills/design-planning/flutter-av/SKILL.md +34 -44
- package/skills/design-planning/flutter-map/SKILL.md +31 -41
- package/skills/design-planning/ui-sdd-specialized/SKILL.md +40 -46
- package/skills/development-execution/flutter-errors/SKILL.md +34 -44
- package/skills/dispatching-parallel-agents/SKILL.md +182 -0
- package/skills/executing-plans/SKILL.md +175 -0
- package/skills/finishing-a-development-branch/SKILL.md +200 -0
- package/skills/mcp-builder/SKILL.md +255 -0
- package/skills/quality-assurance/bdd-acceptance/SKILL.md +37 -44
- package/skills/receiving-code-review/SKILL.md +213 -0
- package/skills/requesting-code-review/SKILL.md +105 -0
- package/skills/requesting-code-review/code-reviewer.md +146 -0
- package/skills/requirement-analysis/sdd-full/SKILL.md +36 -717
- package/skills/requirement-analysis/unified-flow/SKILL.md +26 -128
- package/skills/rules/skill-map.md +97 -0
- package/skills/rules/user_rules.md +69 -223
- package/skills/special-tools/env-check/SKILL.md +34 -40
- package/skills/subagent-driven-development/SKILL.md +277 -0
- package/skills/subagent-driven-development/code-quality-reviewer-prompt.md +26 -0
- package/skills/subagent-driven-development/implementer-prompt.md +113 -0
- package/skills/subagent-driven-development/spec-reviewer-prompt.md +61 -0
- package/skills/systematic-debugging/CREATION-LOG.md +119 -0
- package/skills/systematic-debugging/SKILL.md +296 -0
- package/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
- package/skills/systematic-debugging/condition-based-waiting.md +115 -0
- package/skills/systematic-debugging/defense-in-depth.md +122 -0
- package/skills/systematic-debugging/find-polluter.sh +63 -0
- package/skills/systematic-debugging/root-cause-tracing.md +169 -0
- package/skills/systematic-debugging/test-academic.md +14 -0
- package/skills/systematic-debugging/test-pressure-1.md +58 -0
- package/skills/systematic-debugging/test-pressure-2.md +68 -0
- package/skills/systematic-debugging/test-pressure-3.md +69 -0
- package/skills/test-driven-development/SKILL.md +371 -0
- package/skills/test-driven-development/testing-anti-patterns.md +299 -0
- package/skills/using-git-worktrees/SKILL.md +218 -0
- package/skills/using-superpowers/SKILL.md +134 -0
- package/skills/using-superpowers/references/codex-tools.md +25 -0
- package/skills/using-superpowers/references/gemini-tools.md +33 -0
- package/skills/verification-before-completion/SKILL.md +139 -0
- package/skills/workflow-runner/SKILL.md +172 -0
- package/skills/writing-plans/SKILL.md +152 -0
- package/skills/writing-plans/plan-document-reviewer-prompt.md +49 -0
- package/skills/writing-skills/SKILL.md +654 -0
- package/skills/writing-skills/anthropic-best-practices.md +1149 -0
- package/skills/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
- package/skills/writing-skills/graphviz-conventions.dot +172 -0
- package/skills/writing-skills/persuasion-principles.md +187 -0
- package/skills/writing-skills/render-graphs.js +168 -0
- package/skills/writing-skills/testing-skills-with-subagents.md +384 -0
- package/skills/README.md +0 -97
- package/skills/call-adaptation/SKILL.md +0 -23
- package/skills/call-adaptation/call-adaptation-guide.md +0 -136
- package/skills/call-adaptation/claude-code-call-spec.md +0 -50
- package/skills/call-adaptation/trae-call-spec.md +0 -56
- package/skills/checklist.md +0 -154
- package/skills/design-planning/ai-coding-rules/SKILL.md +0 -52
- package/skills/design-planning/design-to-code/SKILL.md +0 -53
- package/skills/design-planning/function-sdd/SKILL.md +0 -54
- package/skills/design-planning/sdd-code/SKILL.md +0 -347
- package/skills/design-planning/sdd-deploy/SKILL.md +0 -501
- package/skills/design-planning/sdd-ops/SKILL.md +0 -306
- package/skills/design-planning/sdd-test/SKILL.md +0 -383
- package/skills/design-planning/ui-sdd/SKILL.md +0 -291
- package/skills/design-planning/writing-plans/SKILL.md +0 -144
- package/skills/development-execution/sdd-add/SKILL.md +0 -540
- package/skills/development-execution/systematic-debugging/SKILL.md +0 -298
- package/skills/development-execution/test-driven-development/SKILL.md +0 -373
- package/skills/development-execution/verification-before-completion/SKILL.md +0 -141
- package/skills/knowledge-precipitation/claudeception/SKILL.md +0 -96
- package/skills/knowledge-precipitation/mempalace-auto-saver/SKILL.md +0 -302
- package/skills/quality-assurance/flutter-test/SKILL.md +0 -56
- package/skills/quality-assurance/quality-gate/SKILL.md +0 -350
- package/skills/quality-assurance/security-audit/SKILL.md +0 -386
- package/skills/release-ops/finishing-a-development-branch/SKILL.md +0 -202
- package/skills/release-ops/release-flow/SKILL.md +0 -404
- package/skills/requirement-analysis/brainstorming/SKILL.md +0 -166
- package/skills/requirement-analysis/competitive-brief/SKILL.md +0 -121
- package/skills/requirement-analysis/market-research/SKILL.md +0 -143
- package/skills/requirement-analysis/prd-write/SKILL.md +0 -111
- package/skills/requirement-analysis/requirement-completion-officer/SKILL.md +0 -124
- package/skills/requirement-analysis/sdd/SKILL.md +0 -1044
- package/skills/rules/project_rules.md +0 -167
- package/skills/sdd-framework/SKILL.md +0 -90
- package/skills/special-tools/receiving-code-review/SKILL.md +0 -215
- package/skills/special-tools/requesting-code-review/SKILL.md +0 -107
- package/skills/special-tools/using-superpowers/SKILL.md +0 -117
- package/skills/templates/API-SDD.md +0 -31
- package/skills/templates/Andrej Karpathy AI/347/274/226/347/240/201/350/247/204/345/210/231/350/220/275/345/234/260SDD.md" +0 -117
- package/skills/templates/BDD/351/243/216/346/240/274/351/252/214/346/224/266/346/240/207/345/207/206SDD.md +0 -147
- package/skills/templates/Base-SDD.md +0 -38
- package/skills/templates/Brain-SDD.md +0 -36
- package/skills/templates/Code-SDD.md +0 -41
- package/skills/templates/Competitor-SDD.md +0 -34
- package/skills/templates/Env-SDD.md +0 -37
- package/skills/templates/Flutter/345/205/250/347/261/273/345/236/213/346/265/213/350/257/225/347/255/226/347/225/245SDD.md +0 -162
- package/skills/templates/Flutter/345/234/260/345/233/276/345/257/274/350/210/252/344/270/232/345/212/241SDD.md +0 -136
- package/skills/templates/Flutter/345/270/270/350/247/201/345/274/202/345/270/270/344/270/223/351/241/271SDD.md +0 -159
- package/skills/templates/Flutter/351/237/263/350/247/206/351/242/221/345/205/250/346/240/210SDD.md +0 -121
- package/skills/templates/PRD-SDD.md +0 -45
- package/skills/templates/SKILL.md +0 -29
- package/skills/templates/Test-SDD.md +0 -34
- package/skills/templates/UI-SDD.md +0 -38
- package/skills/templates/UI-SDD/344/270/223/347/224/250/346/250/241/346/235/277.md +0 -141
- package/skills/templates/UI/350/265/204/346/272/220/346/217/220/347/244/272/350/257/215/347/224/237/346/210/220SDD.md +0 -67
- package/skills/templates//344/274/201/344/270/232/347/272/247/345/205/250/346/240/210/345/267/245/347/250/213/350/247/204/350/214/203SDD.md +0 -152
- package/skills/templates//345/212/237/350/203/275SDD/344/270/223/347/224/250/346/250/241/346/235/277.md +0 -132
- package/skills/templates//347/216/257/345/242/203/351/242/204/346/243/200/346/240/207/345/207/206/345/214/226SDD.md +0 -153
- package/skills/templates//351/253/230/344/277/235/347/234/237/350/256/276/350/256/241/350/275/254/344/273/243/347/240/201SDD.md +0 -119
- package/skills//345/256/214/346/225/264/345/274/200/345/217/221/346/265/201/347/250/213/346/211/213/345/206/214.md +0 -408
- package/skills//346/212/200/350/203/275/344/275/223/347/263/273/345/256/214/345/226/204/345/273/272/350/256/256.md +0 -305
- package/skills//346/212/200/350/203/275/344/275/277/347/224/250/346/214/207/345/215/227.md +0 -265
- package/skills//346/212/200/350/203/275/345/206/263/347/255/226/346/240/221.md +0 -294
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: systematic-debugging
|
|
3
|
+
description: 遇到任何 bug、测试失败或异常行为时使用,在提出修复方案之前执行
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 系统化调试
|
|
7
|
+
|
|
8
|
+
## 概述
|
|
9
|
+
|
|
10
|
+
随意修复既浪费时间又会引入新 bug。草率的补丁只会掩盖深层问题。
|
|
11
|
+
|
|
12
|
+
**核心原则:** 在尝试修复之前,务必先找到根本原因。只修症状就是失败。
|
|
13
|
+
|
|
14
|
+
**敷衍走流程等于违背调试的精神。**
|
|
15
|
+
|
|
16
|
+
## 铁律
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
不做根因调查,不许提修复方案
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
如果你还没完成第一阶段,就不能提出修复方案。
|
|
23
|
+
|
|
24
|
+
## 何时使用
|
|
25
|
+
|
|
26
|
+
用于任何技术问题:
|
|
27
|
+
- 测试失败
|
|
28
|
+
- 生产环境 bug
|
|
29
|
+
- 异常行为
|
|
30
|
+
- 性能问题
|
|
31
|
+
- 构建失败
|
|
32
|
+
- 集成问题
|
|
33
|
+
|
|
34
|
+
**尤其在以下情况必须使用:**
|
|
35
|
+
- 时间紧迫(紧急情况最容易让人猜测式修复)
|
|
36
|
+
- 觉得"一个小修改"就能搞定
|
|
37
|
+
- 已经尝试了多种修复
|
|
38
|
+
- 上一次修复没有生效
|
|
39
|
+
- 你没有完全理解问题
|
|
40
|
+
|
|
41
|
+
**以下情况也不要跳过:**
|
|
42
|
+
- 问题看起来很简单(简单的 bug 也有根本原因)
|
|
43
|
+
- 你很赶时间(越急越容易返工)
|
|
44
|
+
- 领导要求立刻修好(系统化调试比反复尝试更快)
|
|
45
|
+
|
|
46
|
+
## 四个阶段
|
|
47
|
+
|
|
48
|
+
你必须完成每个阶段后才能进入下一个。
|
|
49
|
+
|
|
50
|
+
### 第一阶段:根因调查
|
|
51
|
+
|
|
52
|
+
**在尝试任何修复之前:**
|
|
53
|
+
|
|
54
|
+
1. **仔细阅读错误信息**
|
|
55
|
+
- 不要跳过错误或警告
|
|
56
|
+
- 它们往往直接包含解决方案
|
|
57
|
+
- 完整阅读堆栈跟踪
|
|
58
|
+
- 记下行号、文件路径、错误码
|
|
59
|
+
|
|
60
|
+
2. **稳定复现**
|
|
61
|
+
- 你能可靠地触发它吗?
|
|
62
|
+
- 具体的复现步骤是什么?
|
|
63
|
+
- 每次都能复现吗?
|
|
64
|
+
- 如果无法复现 → 收集更多数据,不要猜测
|
|
65
|
+
|
|
66
|
+
3. **检查近期变更**
|
|
67
|
+
- 什么变更可能导致了这个问题?
|
|
68
|
+
- git diff、最近的提交
|
|
69
|
+
- 新依赖、配置变更
|
|
70
|
+
- 环境差异
|
|
71
|
+
|
|
72
|
+
4. **在多组件系统中收集证据**
|
|
73
|
+
|
|
74
|
+
**当系统有多个组件时(CI → 构建 → 签名,API → 服务 → 数据库):**
|
|
75
|
+
|
|
76
|
+
**在提出修复方案之前,先添加诊断埋点:**
|
|
77
|
+
```
|
|
78
|
+
对每个组件边界:
|
|
79
|
+
- 记录进入组件的数据
|
|
80
|
+
- 记录离开组件的数据
|
|
81
|
+
- 验证环境/配置的传递
|
|
82
|
+
- 检查每一层的状态
|
|
83
|
+
|
|
84
|
+
执行一次以收集证据,确定断裂点在哪里
|
|
85
|
+
然后分析证据,定位故障组件
|
|
86
|
+
然后针对该组件深入调查
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**示例(多层系统):**
|
|
90
|
+
```bash
|
|
91
|
+
# 第 1 层:工作流
|
|
92
|
+
echo "=== Secrets available in workflow: ==="
|
|
93
|
+
echo "IDENTITY: ${IDENTITY:+SET}${IDENTITY:-UNSET}"
|
|
94
|
+
|
|
95
|
+
# 第 2 层:构建脚本
|
|
96
|
+
echo "=== Env vars in build script: ==="
|
|
97
|
+
env | grep IDENTITY || echo "IDENTITY not in environment"
|
|
98
|
+
|
|
99
|
+
# 第 3 层:签名脚本
|
|
100
|
+
echo "=== Keychain state: ==="
|
|
101
|
+
security list-keychains
|
|
102
|
+
security find-identity -v
|
|
103
|
+
|
|
104
|
+
# 第 4 层:实际签名
|
|
105
|
+
codesign --sign "$IDENTITY" --verbose=4 "$APP"
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**由此可以看出:** 哪一层出了问题(secrets → workflow ✓, workflow → build ✗)
|
|
109
|
+
|
|
110
|
+
5. **跟踪数据流**
|
|
111
|
+
|
|
112
|
+
**当错误发生在调用栈深处时:**
|
|
113
|
+
|
|
114
|
+
参见本目录下的 `root-cause-tracing.md`,了解完整的反向追踪技术。
|
|
115
|
+
|
|
116
|
+
**简要版本:**
|
|
117
|
+
- 错误值从哪里产生的?
|
|
118
|
+
- 谁用错误值调用了这里?
|
|
119
|
+
- 持续向上追踪直到找到源头
|
|
120
|
+
- 在源头修复,而不是在症状处修复
|
|
121
|
+
|
|
122
|
+
### 第二阶段:模式分析
|
|
123
|
+
|
|
124
|
+
**先找到模式,再修复:**
|
|
125
|
+
|
|
126
|
+
1. **找到可正常工作的示例**
|
|
127
|
+
- 在同一代码库中找到类似的正常代码
|
|
128
|
+
- 有什么正常的代码与出问题的代码相似?
|
|
129
|
+
|
|
130
|
+
2. **与参考实现对比**
|
|
131
|
+
- 如果是实现某个模式,完整阅读参考实现
|
|
132
|
+
- 不要略读——逐行阅读
|
|
133
|
+
- 在应用之前彻底理解该模式
|
|
134
|
+
|
|
135
|
+
3. **识别差异**
|
|
136
|
+
- 正常代码和出问题的代码之间有什么不同?
|
|
137
|
+
- 列出每一个差异,无论多小
|
|
138
|
+
- 不要假设"那不可能有影响"
|
|
139
|
+
|
|
140
|
+
4. **理解依赖关系**
|
|
141
|
+
- 这个功能需要哪些其他组件?
|
|
142
|
+
- 需要哪些设置、配置、环境?
|
|
143
|
+
- 它有哪些隐含假设?
|
|
144
|
+
|
|
145
|
+
### 第三阶段:假设与验证
|
|
146
|
+
|
|
147
|
+
**科学方法:**
|
|
148
|
+
|
|
149
|
+
1. **提出单一假设**
|
|
150
|
+
- 清晰地陈述:"我认为 X 是根本原因,因为 Y"
|
|
151
|
+
- 写下来
|
|
152
|
+
- 要具体,不要含糊
|
|
153
|
+
|
|
154
|
+
2. **最小化测试**
|
|
155
|
+
- 做出最小的改动来验证假设
|
|
156
|
+
- 每次只改一个变量
|
|
157
|
+
- 不要同时修复多个问题
|
|
158
|
+
|
|
159
|
+
3. **继续之前先验证**
|
|
160
|
+
- 生效了?是 → 进入第四阶段
|
|
161
|
+
- 没生效?提出新假设
|
|
162
|
+
- 不要在上面叠加更多修复
|
|
163
|
+
|
|
164
|
+
4. **当你不确定时**
|
|
165
|
+
- 说"我不理解 X"
|
|
166
|
+
- 不要假装自己知道
|
|
167
|
+
- 寻求帮助
|
|
168
|
+
- 做更多调研
|
|
169
|
+
|
|
170
|
+
### 第四阶段:实施
|
|
171
|
+
|
|
172
|
+
**修复根本原因,而非症状:**
|
|
173
|
+
|
|
174
|
+
1. **创建失败的测试用例**
|
|
175
|
+
- 最简化的复现
|
|
176
|
+
- 尽可能用自动化测试
|
|
177
|
+
- 没有测试框架就写一次性测试脚本
|
|
178
|
+
- 修复前必须先有测试
|
|
179
|
+
- 使用 `superpowers:test-driven-development` 技能来编写规范的失败测试
|
|
180
|
+
|
|
181
|
+
2. **实施单一修复**
|
|
182
|
+
- 修复已定位的根本原因
|
|
183
|
+
- 每次只改一处
|
|
184
|
+
- 不做"顺便改改"的优化
|
|
185
|
+
- 不捆绑重构
|
|
186
|
+
|
|
187
|
+
3. **验证修复**
|
|
188
|
+
- 测试现在通过了吗?
|
|
189
|
+
- 其他测试没有被破坏吧?
|
|
190
|
+
- 问题真的解决了吗?
|
|
191
|
+
|
|
192
|
+
4. **如果修复不起作用**
|
|
193
|
+
- 停下来
|
|
194
|
+
- 数一数:你已经尝试了几次修复?
|
|
195
|
+
- 少于 3 次:回到第一阶段,用新信息重新分析
|
|
196
|
+
- **3 次或以上:停下来质疑架构(见下方第 5 步)**
|
|
197
|
+
- 没有经过架构讨论,不要尝试第 4 次修复
|
|
198
|
+
|
|
199
|
+
5. **如果 3 次以上修复都失败了:质疑架构**
|
|
200
|
+
|
|
201
|
+
**以下模式表明存在架构问题:**
|
|
202
|
+
- 每次修复都暴露出新的共享状态/耦合/其他位置的问题
|
|
203
|
+
- 修复需要"大规模重构"才能实现
|
|
204
|
+
- 每次修复都在其他地方产生新的症状
|
|
205
|
+
|
|
206
|
+
**停下来质疑根本性问题:**
|
|
207
|
+
- 这个模式从根本上合理吗?
|
|
208
|
+
- 我们是不是在"惯性驱动"下坚持了错误方案?
|
|
209
|
+
- 应该重构架构还是继续修补症状?
|
|
210
|
+
|
|
211
|
+
**在尝试更多修复之前,和你的搭档讨论**
|
|
212
|
+
|
|
213
|
+
这不是假设失败——这是架构有误。
|
|
214
|
+
|
|
215
|
+
## 红线——停下来,按流程走
|
|
216
|
+
|
|
217
|
+
如果你发现自己在想:
|
|
218
|
+
- "先临时修一下,以后再排查"
|
|
219
|
+
- "试着改改 X 看看行不行"
|
|
220
|
+
- "一次性改多个地方,跑测试看看"
|
|
221
|
+
- "跳过测试,我手动验证"
|
|
222
|
+
- "大概是 X 的问题,让我修一下"
|
|
223
|
+
- "我不完全理解,但这应该能行"
|
|
224
|
+
- "模式说的是 X,但我换个方式用"
|
|
225
|
+
- "主要问题有这些:[未经调查就列出修复方案]"
|
|
226
|
+
- 没有追踪数据流就提出解决方案
|
|
227
|
+
- **"再试一次修复"(已经尝试了 2 次以上)**
|
|
228
|
+
- **每次修复都暴露出不同地方的新问题**
|
|
229
|
+
|
|
230
|
+
**以上这些都意味着:停下来。回到第一阶段。**
|
|
231
|
+
|
|
232
|
+
**如果 3 次以上修复都失败了:** 质疑架构(见第四阶段第 5 步)
|
|
233
|
+
|
|
234
|
+
## 搭档发出的信号——说明你的方法不对
|
|
235
|
+
|
|
236
|
+
**留意这些提醒:**
|
|
237
|
+
- "难道不是这样吗?"——你在没有验证的情况下做了假设
|
|
238
|
+
- "它能告诉我们……吗?"——你应该先收集证据
|
|
239
|
+
- "别猜了"——你在没有理解的情况下提出修复
|
|
240
|
+
- "深入想想"——要质疑根本性问题,而不只是症状
|
|
241
|
+
- "我们卡住了?"(沮丧的语气)——你的方法没有奏效
|
|
242
|
+
|
|
243
|
+
**当你看到这些信号时:** 停下来。回到第一阶段。
|
|
244
|
+
|
|
245
|
+
## 常见借口
|
|
246
|
+
|
|
247
|
+
| 借口 | 现实 |
|
|
248
|
+
|------|------|
|
|
249
|
+
| "问题很简单,不需要走流程" | 简单问题也有根本原因。对于简单 bug,流程很快就能走完。 |
|
|
250
|
+
| "紧急情况,没时间走流程" | 系统化调试比反复猜测式修复更快。 |
|
|
251
|
+
| "先试一下,再排查" | 第一次修复就定下了基调。从一开始就做对。 |
|
|
252
|
+
| "确认修复有效后再写测试" | 没有测试的修复留不住。先写测试才能证明修复有效。 |
|
|
253
|
+
| "一次修多个问题省时间" | 无法隔离哪个生效了。还会引入新 bug。 |
|
|
254
|
+
| "参考实现太长了,我自己改改" | 一知半解必然出 bug。完整阅读。 |
|
|
255
|
+
| "我看出问题了,让我修一下" | 看到症状 ≠ 理解根因。 |
|
|
256
|
+
| "再试一次"(在 2 次以上失败后) | 3 次以上失败 = 架构问题。质疑模式,不要继续修。 |
|
|
257
|
+
|
|
258
|
+
## 速查表
|
|
259
|
+
|
|
260
|
+
| 阶段 | 关键活动 | 通过标准 |
|
|
261
|
+
|------|---------|---------|
|
|
262
|
+
| **1. 根因** | 阅读错误、复现、检查变更、收集证据 | 理解了什么出了问题以及为什么 |
|
|
263
|
+
| **2. 模式** | 找到正常示例、对比 | 识别出差异 |
|
|
264
|
+
| **3. 假设** | 提出理论、最小化验证 | 假设被验证或产生新假设 |
|
|
265
|
+
| **4. 实施** | 创建测试、修复、验证 | bug 已修复,测试通过 |
|
|
266
|
+
|
|
267
|
+
## 当流程显示"找不到根因"
|
|
268
|
+
|
|
269
|
+
如果系统化排查后发现问题确实是环境相关、时序相关或外部因素导致的:
|
|
270
|
+
|
|
271
|
+
1. 你已经完成了流程
|
|
272
|
+
2. 记录你排查了什么
|
|
273
|
+
3. 实施适当的处理措施(重试、超时、错误提示)
|
|
274
|
+
4. 添加监控/日志以便后续排查
|
|
275
|
+
|
|
276
|
+
**但是:** 95% 的"找不到根因"其实是排查不充分。
|
|
277
|
+
|
|
278
|
+
## 辅助技术
|
|
279
|
+
|
|
280
|
+
以下技术是系统化调试的组成部分,可在本目录中找到:
|
|
281
|
+
|
|
282
|
+
- **`root-cause-tracing.md`** - 沿调用栈反向追踪 bug,找到最初的触发点
|
|
283
|
+
- **`defense-in-depth.md`** - 找到根因后,在多个层级添加校验
|
|
284
|
+
- **`condition-based-waiting.md`** - 用条件轮询替代硬编码等待时间
|
|
285
|
+
|
|
286
|
+
**相关技能:**
|
|
287
|
+
- **superpowers:test-driven-development** - 用于创建失败测试用例(第四阶段,第 1 步)
|
|
288
|
+
- **superpowers:verification-before-completion** - 在宣称成功之前验证修复确实有效
|
|
289
|
+
|
|
290
|
+
## 实际效果
|
|
291
|
+
|
|
292
|
+
调试实践中的数据:
|
|
293
|
+
- 系统化方法:15-30 分钟修复
|
|
294
|
+
- 随意修复方法:2-3 小时反复折腾
|
|
295
|
+
- 一次修复成功率:95% vs 40%
|
|
296
|
+
- 引入新 bug:几乎为零 vs 经常发生
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// Complete implementation of condition-based waiting utilities
|
|
2
|
+
// From: Lace test infrastructure improvements (2025-10-03)
|
|
3
|
+
// Context: Fixed 15 flaky tests by replacing arbitrary timeouts
|
|
4
|
+
|
|
5
|
+
import type { ThreadManager } from '~/threads/thread-manager';
|
|
6
|
+
import type { LaceEvent, LaceEventType } from '~/threads/types';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Wait for a specific event type to appear in thread
|
|
10
|
+
*
|
|
11
|
+
* @param threadManager - The thread manager to query
|
|
12
|
+
* @param threadId - Thread to check for events
|
|
13
|
+
* @param eventType - Type of event to wait for
|
|
14
|
+
* @param timeoutMs - Maximum time to wait (default 5000ms)
|
|
15
|
+
* @returns Promise resolving to the first matching event
|
|
16
|
+
*
|
|
17
|
+
* Example:
|
|
18
|
+
* await waitForEvent(threadManager, agentThreadId, 'TOOL_RESULT');
|
|
19
|
+
*/
|
|
20
|
+
export function waitForEvent(
|
|
21
|
+
threadManager: ThreadManager,
|
|
22
|
+
threadId: string,
|
|
23
|
+
eventType: LaceEventType,
|
|
24
|
+
timeoutMs = 5000
|
|
25
|
+
): Promise<LaceEvent> {
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
const startTime = Date.now();
|
|
28
|
+
|
|
29
|
+
const check = () => {
|
|
30
|
+
const events = threadManager.getEvents(threadId);
|
|
31
|
+
const event = events.find((e) => e.type === eventType);
|
|
32
|
+
|
|
33
|
+
if (event) {
|
|
34
|
+
resolve(event);
|
|
35
|
+
} else if (Date.now() - startTime > timeoutMs) {
|
|
36
|
+
reject(new Error(`Timeout waiting for ${eventType} event after ${timeoutMs}ms`));
|
|
37
|
+
} else {
|
|
38
|
+
setTimeout(check, 10); // Poll every 10ms for efficiency
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
check();
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Wait for a specific number of events of a given type
|
|
48
|
+
*
|
|
49
|
+
* @param threadManager - The thread manager to query
|
|
50
|
+
* @param threadId - Thread to check for events
|
|
51
|
+
* @param eventType - Type of event to wait for
|
|
52
|
+
* @param count - Number of events to wait for
|
|
53
|
+
* @param timeoutMs - Maximum time to wait (default 5000ms)
|
|
54
|
+
* @returns Promise resolving to all matching events once count is reached
|
|
55
|
+
*
|
|
56
|
+
* Example:
|
|
57
|
+
* // Wait for 2 AGENT_MESSAGE events (initial response + continuation)
|
|
58
|
+
* await waitForEventCount(threadManager, agentThreadId, 'AGENT_MESSAGE', 2);
|
|
59
|
+
*/
|
|
60
|
+
export function waitForEventCount(
|
|
61
|
+
threadManager: ThreadManager,
|
|
62
|
+
threadId: string,
|
|
63
|
+
eventType: LaceEventType,
|
|
64
|
+
count: number,
|
|
65
|
+
timeoutMs = 5000
|
|
66
|
+
): Promise<LaceEvent[]> {
|
|
67
|
+
return new Promise((resolve, reject) => {
|
|
68
|
+
const startTime = Date.now();
|
|
69
|
+
|
|
70
|
+
const check = () => {
|
|
71
|
+
const events = threadManager.getEvents(threadId);
|
|
72
|
+
const matchingEvents = events.filter((e) => e.type === eventType);
|
|
73
|
+
|
|
74
|
+
if (matchingEvents.length >= count) {
|
|
75
|
+
resolve(matchingEvents);
|
|
76
|
+
} else if (Date.now() - startTime > timeoutMs) {
|
|
77
|
+
reject(
|
|
78
|
+
new Error(
|
|
79
|
+
`Timeout waiting for ${count} ${eventType} events after ${timeoutMs}ms (got ${matchingEvents.length})`
|
|
80
|
+
)
|
|
81
|
+
);
|
|
82
|
+
} else {
|
|
83
|
+
setTimeout(check, 10);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
check();
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Wait for an event matching a custom predicate
|
|
93
|
+
* Useful when you need to check event data, not just type
|
|
94
|
+
*
|
|
95
|
+
* @param threadManager - The thread manager to query
|
|
96
|
+
* @param threadId - Thread to check for events
|
|
97
|
+
* @param predicate - Function that returns true when event matches
|
|
98
|
+
* @param description - Human-readable description for error messages
|
|
99
|
+
* @param timeoutMs - Maximum time to wait (default 5000ms)
|
|
100
|
+
* @returns Promise resolving to the first matching event
|
|
101
|
+
*
|
|
102
|
+
* Example:
|
|
103
|
+
* // Wait for TOOL_RESULT with specific ID
|
|
104
|
+
* await waitForEventMatch(
|
|
105
|
+
* threadManager,
|
|
106
|
+
* agentThreadId,
|
|
107
|
+
* (e) => e.type === 'TOOL_RESULT' && e.data.id === 'call_123',
|
|
108
|
+
* 'TOOL_RESULT with id=call_123'
|
|
109
|
+
* );
|
|
110
|
+
*/
|
|
111
|
+
export function waitForEventMatch(
|
|
112
|
+
threadManager: ThreadManager,
|
|
113
|
+
threadId: string,
|
|
114
|
+
predicate: (event: LaceEvent) => boolean,
|
|
115
|
+
description: string,
|
|
116
|
+
timeoutMs = 5000
|
|
117
|
+
): Promise<LaceEvent> {
|
|
118
|
+
return new Promise((resolve, reject) => {
|
|
119
|
+
const startTime = Date.now();
|
|
120
|
+
|
|
121
|
+
const check = () => {
|
|
122
|
+
const events = threadManager.getEvents(threadId);
|
|
123
|
+
const event = events.find(predicate);
|
|
124
|
+
|
|
125
|
+
if (event) {
|
|
126
|
+
resolve(event);
|
|
127
|
+
} else if (Date.now() - startTime > timeoutMs) {
|
|
128
|
+
reject(new Error(`Timeout waiting for ${description} after ${timeoutMs}ms`));
|
|
129
|
+
} else {
|
|
130
|
+
setTimeout(check, 10);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
check();
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Usage example from actual debugging session:
|
|
139
|
+
//
|
|
140
|
+
// BEFORE (flaky):
|
|
141
|
+
// ---------------
|
|
142
|
+
// const messagePromise = agent.sendMessage('Execute tools');
|
|
143
|
+
// await new Promise(r => setTimeout(r, 300)); // Hope tools start in 300ms
|
|
144
|
+
// agent.abort();
|
|
145
|
+
// await messagePromise;
|
|
146
|
+
// await new Promise(r => setTimeout(r, 50)); // Hope results arrive in 50ms
|
|
147
|
+
// expect(toolResults.length).toBe(2); // Fails randomly
|
|
148
|
+
//
|
|
149
|
+
// AFTER (reliable):
|
|
150
|
+
// ----------------
|
|
151
|
+
// const messagePromise = agent.sendMessage('Execute tools');
|
|
152
|
+
// await waitForEventCount(threadManager, threadId, 'TOOL_CALL', 2); // Wait for tools to start
|
|
153
|
+
// agent.abort();
|
|
154
|
+
// await messagePromise;
|
|
155
|
+
// await waitForEventCount(threadManager, threadId, 'TOOL_RESULT', 2); // Wait for results
|
|
156
|
+
// expect(toolResults.length).toBe(2); // Always succeeds
|
|
157
|
+
//
|
|
158
|
+
// Result: 60% pass rate → 100%, 40% faster execution
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# 基于条件的等待
|
|
2
|
+
|
|
3
|
+
## 概述
|
|
4
|
+
|
|
5
|
+
不稳定的测试通常用硬编码延迟来猜测时序。这会造成竞态条件——在快速机器上通过,在高负载或 CI 环境下失败。
|
|
6
|
+
|
|
7
|
+
**核心原则:** 等待你真正关心的条件,而不是猜测它需要多长时间。
|
|
8
|
+
|
|
9
|
+
## 何时使用
|
|
10
|
+
|
|
11
|
+
```dot
|
|
12
|
+
digraph when_to_use {
|
|
13
|
+
"测试使用了 setTimeout/sleep?" [shape=diamond];
|
|
14
|
+
"是在测试时序行为吗?" [shape=diamond];
|
|
15
|
+
"记录为什么需要超时" [shape=box];
|
|
16
|
+
"使用基于条件的等待" [shape=box];
|
|
17
|
+
|
|
18
|
+
"测试使用了 setTimeout/sleep?" -> "是在测试时序行为吗?" [label="是"];
|
|
19
|
+
"是在测试时序行为吗?" -> "记录为什么需要超时" [label="是"];
|
|
20
|
+
"是在测试时序行为吗?" -> "使用基于条件的等待" [label="否"];
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**适用场景:**
|
|
25
|
+
- 测试中有硬编码延迟(`setTimeout`、`sleep`、`time.sleep()`)
|
|
26
|
+
- 测试不稳定(时而通过,高负载下失败)
|
|
27
|
+
- 并行运行时测试超时
|
|
28
|
+
- 等待异步操作完成
|
|
29
|
+
|
|
30
|
+
**不适用场景:**
|
|
31
|
+
- 测试实际的时序行为(防抖、节流间隔)
|
|
32
|
+
- 如果使用硬编码超时,务必注释说明原因
|
|
33
|
+
|
|
34
|
+
## 核心模式
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
// ❌ 之前:猜测时序
|
|
38
|
+
await new Promise(r => setTimeout(r, 50));
|
|
39
|
+
const result = getResult();
|
|
40
|
+
expect(result).toBeDefined();
|
|
41
|
+
|
|
42
|
+
// ✅ 之后:等待条件满足
|
|
43
|
+
await waitFor(() => getResult() !== undefined);
|
|
44
|
+
const result = getResult();
|
|
45
|
+
expect(result).toBeDefined();
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## 常用模式速查
|
|
49
|
+
|
|
50
|
+
| 场景 | 模式 |
|
|
51
|
+
|------|------|
|
|
52
|
+
| 等待事件 | `waitFor(() => events.find(e => e.type === 'DONE'))` |
|
|
53
|
+
| 等待状态 | `waitFor(() => machine.state === 'ready')` |
|
|
54
|
+
| 等待数量 | `waitFor(() => items.length >= 5)` |
|
|
55
|
+
| 等待文件 | `waitFor(() => fs.existsSync(path))` |
|
|
56
|
+
| 复合条件 | `waitFor(() => obj.ready && obj.value > 10)` |
|
|
57
|
+
|
|
58
|
+
## 实现方式
|
|
59
|
+
|
|
60
|
+
通用轮询函数:
|
|
61
|
+
```typescript
|
|
62
|
+
async function waitFor<T>(
|
|
63
|
+
condition: () => T | undefined | null | false,
|
|
64
|
+
description: string,
|
|
65
|
+
timeoutMs = 5000
|
|
66
|
+
): Promise<T> {
|
|
67
|
+
const startTime = Date.now();
|
|
68
|
+
|
|
69
|
+
while (true) {
|
|
70
|
+
const result = condition();
|
|
71
|
+
if (result) return result;
|
|
72
|
+
|
|
73
|
+
if (Date.now() - startTime > timeoutMs) {
|
|
74
|
+
throw new Error(`Timeout waiting for ${description} after ${timeoutMs}ms`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
await new Promise(r => setTimeout(r, 10)); // 每 10ms 轮询一次
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
参见本目录下的 `condition-based-waiting-example.ts`,其中包含完整实现和领域专用辅助函数(`waitForEvent`、`waitForEventCount`、`waitForEventMatch`),源自实际调试过程。
|
|
83
|
+
|
|
84
|
+
## 常见错误
|
|
85
|
+
|
|
86
|
+
**❌ 轮询太频繁:** `setTimeout(check, 1)` —— 浪费 CPU
|
|
87
|
+
**✅ 修正:** 每 10ms 轮询一次
|
|
88
|
+
|
|
89
|
+
**❌ 没有超时:** 条件永远不满足时无限循环
|
|
90
|
+
**✅ 修正:** 始终设置超时并提供清晰的错误信息
|
|
91
|
+
|
|
92
|
+
**❌ 数据过期:** 在循环外缓存状态
|
|
93
|
+
**✅ 修正:** 在循环内调用 getter 获取最新数据
|
|
94
|
+
|
|
95
|
+
## 何时硬编码超时是正确的
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
// 工具每 100ms tick 一次——需要 2 次 tick 来验证部分输出
|
|
99
|
+
await waitForEvent(manager, 'TOOL_STARTED'); // 首先:等待条件
|
|
100
|
+
await new Promise(r => setTimeout(r, 200)); // 然后:等待有明确时序依据的行为
|
|
101
|
+
// 200ms = 100ms 间隔的 2 次 tick——有文档说明且有充分理由
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**使用要求:**
|
|
105
|
+
1. 首先等待触发条件
|
|
106
|
+
2. 基于已知时序(而非猜测)
|
|
107
|
+
3. 注释说明原因
|
|
108
|
+
|
|
109
|
+
## 实际效果
|
|
110
|
+
|
|
111
|
+
来自调试实践(2025-10-03):
|
|
112
|
+
- 修复了 3 个文件中的 15 个不稳定测试
|
|
113
|
+
- 通过率:60% → 100%
|
|
114
|
+
- 执行时间:快了 40%
|
|
115
|
+
- 再无竞态条件
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# 纵深防御校验
|
|
2
|
+
|
|
3
|
+
## 概述
|
|
4
|
+
|
|
5
|
+
当你修复了一个由无效数据引起的 bug 时,在一个地方加校验似乎就够了。但这个单点检查可能会被不同的代码路径、重构或 mock 绕过。
|
|
6
|
+
|
|
7
|
+
**核心原则:** 在数据经过的每一层都做校验。让这个 bug 在结构上不可能发生。
|
|
8
|
+
|
|
9
|
+
## 为什么需要多层校验
|
|
10
|
+
|
|
11
|
+
单层校验:"我们修了这个 bug"
|
|
12
|
+
多层校验:"我们让这个 bug 不可能再发生"
|
|
13
|
+
|
|
14
|
+
不同层级能捕获不同问题:
|
|
15
|
+
- 入口校验捕获大多数 bug
|
|
16
|
+
- 业务逻辑校验捕获边界情况
|
|
17
|
+
- 环境守卫防止特定上下文的危险操作
|
|
18
|
+
- 调试日志在其他层级失效时提供帮助
|
|
19
|
+
|
|
20
|
+
## 四个层级
|
|
21
|
+
|
|
22
|
+
### 第 1 层:入口校验
|
|
23
|
+
**目的:** 在 API 边界拒绝明显无效的输入
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
function createProject(name: string, workingDirectory: string) {
|
|
27
|
+
if (!workingDirectory || workingDirectory.trim() === '') {
|
|
28
|
+
throw new Error('workingDirectory cannot be empty');
|
|
29
|
+
}
|
|
30
|
+
if (!existsSync(workingDirectory)) {
|
|
31
|
+
throw new Error(`workingDirectory does not exist: ${workingDirectory}`);
|
|
32
|
+
}
|
|
33
|
+
if (!statSync(workingDirectory).isDirectory()) {
|
|
34
|
+
throw new Error(`workingDirectory is not a directory: ${workingDirectory}`);
|
|
35
|
+
}
|
|
36
|
+
// ... 继续处理
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### 第 2 层:业务逻辑校验
|
|
41
|
+
**目的:** 确保数据对当前操作是合理的
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
function initializeWorkspace(projectDir: string, sessionId: string) {
|
|
45
|
+
if (!projectDir) {
|
|
46
|
+
throw new Error('projectDir required for workspace initialization');
|
|
47
|
+
}
|
|
48
|
+
// ... 继续处理
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### 第 3 层:环境守卫
|
|
53
|
+
**目的:** 防止在特定环境中执行危险操作
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
async function gitInit(directory: string) {
|
|
57
|
+
// 在测试中,拒绝在临时目录之外执行 git init
|
|
58
|
+
if (process.env.NODE_ENV === 'test') {
|
|
59
|
+
const normalized = normalize(resolve(directory));
|
|
60
|
+
const tmpDir = normalize(resolve(tmpdir()));
|
|
61
|
+
|
|
62
|
+
if (!normalized.startsWith(tmpDir)) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
`Refusing git init outside temp dir during tests: ${directory}`
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// ... 继续处理
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### 第 4 层:调试埋点
|
|
73
|
+
**目的:** 记录上下文信息以便事后分析
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
async function gitInit(directory: string) {
|
|
77
|
+
const stack = new Error().stack;
|
|
78
|
+
logger.debug('About to git init', {
|
|
79
|
+
directory,
|
|
80
|
+
cwd: process.cwd(),
|
|
81
|
+
stack,
|
|
82
|
+
});
|
|
83
|
+
// ... 继续处理
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## 应用模式
|
|
88
|
+
|
|
89
|
+
当你发现一个 bug 时:
|
|
90
|
+
|
|
91
|
+
1. **追踪数据流** —— 错误值从哪里产生的?在哪里被使用?
|
|
92
|
+
2. **标注所有检查点** —— 列出数据经过的每一个节点
|
|
93
|
+
3. **在每一层添加校验** —— 入口、业务逻辑、环境、调试
|
|
94
|
+
4. **测试每一层** —— 尝试绕过第 1 层,验证第 2 层能否捕获
|
|
95
|
+
|
|
96
|
+
## 实际案例
|
|
97
|
+
|
|
98
|
+
Bug:空的 `projectDir` 导致 `git init` 在源代码目录执行
|
|
99
|
+
|
|
100
|
+
**数据流:**
|
|
101
|
+
1. 测试准备 → 空字符串
|
|
102
|
+
2. `Project.create(name, '')`
|
|
103
|
+
3. `WorkspaceManager.createWorkspace('')`
|
|
104
|
+
4. `git init` 在 `process.cwd()` 中执行
|
|
105
|
+
|
|
106
|
+
**添加的四层防御:**
|
|
107
|
+
- 第 1 层:`Project.create()` 校验非空/存在/可写
|
|
108
|
+
- 第 2 层:`WorkspaceManager` 校验 projectDir 非空
|
|
109
|
+
- 第 3 层:`WorktreeManager` 在测试中拒绝在 tmpdir 之外执行 git init
|
|
110
|
+
- 第 4 层:git init 前记录堆栈跟踪
|
|
111
|
+
|
|
112
|
+
**结果:** 全部 1847 个测试通过,bug 不可能再复现
|
|
113
|
+
|
|
114
|
+
## 关键洞察
|
|
115
|
+
|
|
116
|
+
四个层级缺一不可。在测试过程中,每一层都捕获了其他层遗漏的 bug:
|
|
117
|
+
- 不同的代码路径绕过了入口校验
|
|
118
|
+
- mock 绕过了业务逻辑检查
|
|
119
|
+
- 不同平台的边界情况需要环境守卫
|
|
120
|
+
- 调试日志发现了结构性误用
|
|
121
|
+
|
|
122
|
+
**不要止步于一个校验点。** 在每一层都添加检查。
|