dsh-superpower 6.3.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.
Files changed (60) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +334 -0
  3. package/cordis.patch.yml +3 -0
  4. package/lib/superpowers.d.ts +44 -0
  5. package/lib/superpowers.d.ts.map +1 -0
  6. package/lib/superpowers.js +291 -0
  7. package/lib/superpowers.js.map +1 -0
  8. package/package.json +62 -0
  9. package/skills/brainstorming/SKILL.md +207 -0
  10. package/skills/brainstorming/scripts/frame-template.html +213 -0
  11. package/skills/brainstorming/scripts/helper.js +167 -0
  12. package/skills/brainstorming/scripts/server.cjs +723 -0
  13. package/skills/brainstorming/scripts/start-server.sh +209 -0
  14. package/skills/brainstorming/scripts/stop-server.sh +120 -0
  15. package/skills/brainstorming/spec-document-reviewer-prompt.md +47 -0
  16. package/skills/brainstorming/visual-companion.md +293 -0
  17. package/skills/dispatching-parallel-agents/SKILL.md +167 -0
  18. package/skills/executing-plans/SKILL.md +64 -0
  19. package/skills/finishing-a-development-branch/SKILL.md +202 -0
  20. package/skills/receiving-code-review/SKILL.md +205 -0
  21. package/skills/requesting-code-review/SKILL.md +95 -0
  22. package/skills/requesting-code-review/code-reviewer.md +169 -0
  23. package/skills/subagent-driven-development/SKILL.md +347 -0
  24. package/skills/subagent-driven-development/implementer-prompt.md +133 -0
  25. package/skills/subagent-driven-development/re-review-prompt.md +84 -0
  26. package/skills/subagent-driven-development/scripts/review-package +46 -0
  27. package/skills/subagent-driven-development/scripts/sdd-workspace +40 -0
  28. package/skills/subagent-driven-development/scripts/task-brief +41 -0
  29. package/skills/subagent-driven-development/task-reviewer-prompt.md +129 -0
  30. package/skills/systematic-debugging/CREATION-LOG.md +119 -0
  31. package/skills/systematic-debugging/SKILL.md +283 -0
  32. package/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
  33. package/skills/systematic-debugging/condition-based-waiting.md +116 -0
  34. package/skills/systematic-debugging/defense-in-depth.md +122 -0
  35. package/skills/systematic-debugging/find-polluter.sh +72 -0
  36. package/skills/systematic-debugging/root-cause-tracing.md +169 -0
  37. package/skills/systematic-debugging/test-academic.md +14 -0
  38. package/skills/systematic-debugging/test-pressure-1.md +58 -0
  39. package/skills/systematic-debugging/test-pressure-2.md +68 -0
  40. package/skills/systematic-debugging/test-pressure-3.md +69 -0
  41. package/skills/test-driven-development/SKILL.md +322 -0
  42. package/skills/test-driven-development/writing-good-tests.md +145 -0
  43. package/skills/using-git-worktrees/SKILL.md +167 -0
  44. package/skills/using-superpowers/SKILL.md +64 -0
  45. package/skills/using-superpowers/references/antigravity-tools.md +23 -0
  46. package/skills/using-superpowers/references/codex-tools.md +108 -0
  47. package/skills/using-superpowers/references/dsh-tools.md +47 -0
  48. package/skills/using-superpowers/references/gemini-tools.md +63 -0
  49. package/skills/using-superpowers/references/hermes-tools.md +56 -0
  50. package/skills/using-superpowers/references/pi-tools.md +16 -0
  51. package/skills/verification-before-completion/SKILL.md +120 -0
  52. package/skills/writing-plans/SKILL.md +160 -0
  53. package/skills/writing-plans/plan-document-reviewer-prompt.md +49 -0
  54. package/skills/writing-skills/SKILL.md +679 -0
  55. package/skills/writing-skills/anthropic-best-practices.md +1146 -0
  56. package/skills/writing-skills/examples/CLAUDE_MD_TESTING.md +188 -0
  57. package/skills/writing-skills/graphviz-conventions.dot +172 -0
  58. package/skills/writing-skills/persuasion-principles.md +187 -0
  59. package/skills/writing-skills/render-graphs.js +169 -0
  60. package/skills/writing-skills/testing-skills-with-subagents.md +384 -0
@@ -0,0 +1,283 @@
1
+ ---
2
+ name: systematic-debugging
3
+ description: "遇到缺陷、测试失败或异常行为时使用,要求先完成根因分析再提出修复,确保系统化调试"
4
+ ---
5
+
6
+ # 系统化调试
7
+
8
+ ## 概览
9
+
10
+ **核心原则:** 始终先找到根因,再尝试修复。只修表象即是失败。
11
+
12
+ **违背该流程的字面要求,即是违背调试的精神。**
13
+
14
+ ## 铁律
15
+
16
+ ```
17
+ 未完成根因调查前,禁止提出任何修复方案
18
+ ```
19
+
20
+ 若尚未完成阶段一,则不得提出修复建议。
21
+
22
+ ## 适用场景
23
+
24
+ 适用于任何技术问题:
25
+ - 测试失败
26
+ - 生产环境缺陷
27
+ - 异常行为
28
+ - 性能问题
29
+ - 构建失败
30
+ - 集成问题
31
+
32
+ **尤其应在以下情况使用:**
33
+ - 时间紧迫时(紧急情况下最容易产生侥幸猜测)
34
+ - 看似有“显而易见的快速修复”时
35
+ - 已尝试过多种修复方案时
36
+ - 此前修复未生效时
37
+ - 尚未完全理解问题时
38
+
39
+ **以下情况也不要跳过:**
40
+ - 问题看似简单时(简单缺陷同样有根因)
41
+ - 时间仓促时(仓促必定导致返工)
42
+ - 管理者要求立刻修复时(系统化比盲目试错更快)
43
+
44
+ ## 四个阶段
45
+
46
+ 必须按顺序完成每个阶段,方可进入下一阶段。
47
+
48
+ ### 阶段一:根因调查
49
+
50
+ **在尝试任何修复之前:**
51
+
52
+ 1. **仔细阅读错误信息**
53
+ - 不要跳过错误或警告
54
+ - 其中往往包含精确的解决线索
55
+ - 完整阅读堆栈跟踪
56
+ - 记录行号、文件路径、错误码
57
+
58
+ 2. **稳定复现**
59
+ - 能否稳定触发?
60
+ - 精确的复现步骤是什么?
61
+ - 是否每次都会出现?
62
+ - 若无法复现 → 先补充数据,切勿猜测
63
+
64
+ 3. **检查近期变更**
65
+ - 哪些变更可能引发该问题?
66
+ - 查看 git diff、近期提交
67
+ - 新增依赖、配置变更
68
+ - 环境差异
69
+
70
+ 4. **在多组件系统中收集证据**
71
+
72
+ **当系统包含多个组件时(CI → 构建 → 签名,API → 服务 → 数据库):**
73
+
74
+ **在提出修复前,先添加诊断探针:**
75
+ ```
76
+ 对于每个组件边界:
77
+ - 记录进入组件的数据
78
+ - 记录离开组件的数据
79
+ - 验证环境/配置是否正确传递
80
+ - 检查每一层的实际状态
81
+
82
+ 运行一次以收集“在哪一层断裂”的证据
83
+ 然后分析证据以定位故障组件
84
+ 再针对该组件深入调查
85
+ ```
86
+
87
+ **示例(多层系统):**
88
+ ```bash
89
+ # Layer 1: Workflow
90
+ echo "=== Secrets available in workflow: ==="
91
+ echo "IDENTITY: ${IDENTITY:+SET}${IDENTITY:-UNSET}"
92
+
93
+ # Layer 2: Build script
94
+ echo "=== Env vars in build script: ==="
95
+ env | grep IDENTITY || echo "IDENTITY not in environment"
96
+
97
+ # Layer 3: Signing script
98
+ echo "=== Keychain state: ==="
99
+ security list-keychains
100
+ security find-identity -v
101
+
102
+ # Layer 4: Actual signing
103
+ codesign --sign "$IDENTITY" --verbose=4 "$APP"
104
+ ```
105
+
106
+ **由此可得:** 哪一层失败(secrets → workflow ✓,workflow → build ✗)
107
+
108
+ 5. **追踪数据流**
109
+
110
+ **当错误位于调用栈深处时:**
111
+
112
+ 完整的回溯追踪技巧见本目录下的 `root-cause-tracing.md`。
113
+
114
+ **精简版:**
115
+ - 异常值从何而来?
116
+ - 是谁以错误值调用了此处?
117
+ - 持续向上回溯,直至找到源头
118
+ - 在源头修复,而非在表象处打补丁
119
+
120
+ ### 阶段二:模式分析
121
+
122
+ **先找到规律,再动手修复:**
123
+
124
+ 1. **寻找可用的参照**
125
+ - 在同一代码库中定位相似且正常工作的代码
126
+ - 有哪些与故障相似但能正常工作的用例?
127
+
128
+ 2. **对照参考实现**
129
+ - 若要套用某种模式,请完整阅读参考实现
130
+ - 不要走马观花——逐行阅读
131
+ - 在套用前彻底理解该模式
132
+
133
+ 3. **识别差异**
134
+ - 正常与异常之间有何不同?
135
+ - 列出每一个差异,哪怕再细微
136
+ - 不要想当然地认为“这点不可能有影响”
137
+
138
+ 4. **理解依赖**
139
+ - 该功能还依赖哪些组件?
140
+ - 需要哪些设置、配置、环境?
141
+ - 它做了哪些隐含假设?
142
+
143
+ ### 阶段三:假设与验证
144
+
145
+ **用科学方法推进:**
146
+
147
+ 1. **提出单一假设**
148
+ - 清晰表述:“我认为根因是 X,理由是 Y”
149
+ - 落到纸面
150
+ - 要具体,避免含糊
151
+
152
+ 2. **最小化验证**
153
+ - 为验证假设做最小幅度的改动
154
+ - 一次只改变一个变量
155
+ - 不要同时修复多个问题
156
+
157
+ 3. **验证后再继续**
158
+ - 生效了吗?是 → 进入阶段四
159
+ - 未生效?提出新的假设
160
+ - 不要在原有改动之上继续叠加修复
161
+
162
+ 4. **当你不确定时**
163
+ - 直言“我不理解 X”
164
+ - 不要不懂装懂
165
+ - 寻求帮助
166
+ - 进一步研究
167
+
168
+ ### 阶段四:实现
169
+
170
+ **修复根因,而非表象:**
171
+
172
+ 1. **创建失败用例**
173
+ - 用最简方式复现问题
174
+ - 如有测试框架则编写自动化测试
175
+ - 若无框架则编写一次性复现脚本
176
+ - 修复前必须具备该用例
177
+ - 编写规范的失败测试请使用 `superpowers:test-driven-development` 技能
178
+
179
+ 2. **实施单一修复**
180
+ - 针对已确认的根因
181
+ - 一次只做一处改动
182
+ - 不要顺手“顺便优化”
183
+ - 不要打包重构
184
+
185
+ 3. **验证修复**
186
+ - 用例现在通过了吗?
187
+ - 是否破坏了其他测试?
188
+ - 问题是否真正解决?
189
+ - 在宣称成功前,请使用 `superpowers:verification-before-completion` 技能进行校验
190
+
191
+ 4. **若修复未生效**
192
+ - 停下来
193
+ - 计数:已尝试几次修复?
194
+ - 若 < 3 次:回到阶段一,结合新信息重新分析
195
+ - **若 ≥ 3 次:停下来,质疑架构(见下方第 5 步)**
196
+ - 在未进行架构讨论前,不要尝试第 4 次修复
197
+
198
+ 5. **若 3 次以上修复均失败:质疑架构**
199
+
200
+ **指向架构问题的信号:**
201
+ - 每次修复都在不同位置暴露出新的共享状态/耦合/问题
202
+ - 修复需要“大规模重构”才能落地
203
+ - 每次修复都在别处引发新症状
204
+
205
+ **停下来,质疑根本:**
206
+ - 该模式本身是否合理?
207
+ - 是否只是“靠惯性硬撑”?
208
+ - 应该重构架构,还是继续修补表象?
209
+
210
+ **在尝试更多修复前,先与人类协作伙伴讨论**
211
+
212
+ 这不是假设失败——这是架构错误。
213
+
214
+ ## 危险信号——停下来并回归流程
215
+
216
+ 若你发现自己出现以下想法:
217
+ - “先快速修一下,之后再调查”
218
+ - “先试着改一下 X 看看是否有效”
219
+ - “多改几处,一起跑测试”
220
+ - “跳过测试,我手动验证就行”
221
+ - “大概是 X,我直接修掉”
222
+ - “还没完全搞懂,但这个改法也许能行”
223
+ - “规范要求是 X,但我换种方式适配一下”
224
+ - “主要问题有这些:[未做调查就直接列出修复清单]”
225
+ - 在追踪数据流之前就提出解决方案
226
+ - **“再试一次修复”(已尝试 2 次以上时)**
227
+ - **每次修复都在不同位置暴露新问题**
228
+
229
+ **以上全部意味着:停下来,回到阶段一。**
230
+
231
+ **若已失败 3 次以上:** 质疑架构(见阶段四第 5 步)
232
+
233
+ ## 人类协作伙伴提示你做错了的信号
234
+
235
+ **留意这些提醒:**
236
+ - “这不是没生效吗?”——你在未验证的情况下做了假设
237
+ - “它会告诉我们……吗?”——你本应补充证据收集
238
+ - “别猜了”——你在未理解问题前就提出修复
239
+ - “深入思考一下”——需要质疑根本假设,而非只修表象
240
+ - “我们是不是卡住了?”(带有挫败感)——你的方法行不通
241
+
242
+ **出现这些信号时:** 停下来,回到阶段一。
243
+
244
+ ## 常见借口
245
+
246
+ | 借口 | 实际情况 |
247
+ |--------|---------|
248
+ | “问题很简单,不需要走流程” | 简单问题同样有根因。走流程对简单缺陷反而更快。 |
249
+ | “紧急情况,没时间走流程” | 系统化调试比猜测试错更快。 |
250
+ | “先试一下,之后再调查” | 第一次修复就定下了基调。从一开始就做对。 |
251
+ | “确认修复有效后再补测试” | 未经测试的修复不可靠。先写测试才能证明有效。 |
252
+ | “一次多修几处更省时间” | 无法定位哪一处真正生效,还会引入新缺陷。 |
253
+ | “参考实现太长,我按自己的理解适配一下” | 一知半解必然导致缺陷。必须完整阅读。 |
254
+ | “我已经看到问题了,直接修就行” | 看到表象 ≠ 理解根因。 |
255
+ | “再试一次修复”(已失败 2 次以上) | 失败 3 次以上即为架构问题。应质疑模式本身,而非再次修补。 |
256
+
257
+ ## 快速参考
258
+
259
+ | 阶段 | 关键活动 | 成功标准 |
260
+ |-------|---------------|------------------|
261
+ | **1. 根因** | 阅读错误、复现、检查变更、收集证据 | 明确是什么、为什么 |
262
+ | **2. 模式** | 寻找可用参照、对照比较 | 识别差异 |
263
+ | **3. 假设** | 提出假设、最小化验证 | 假设被证实或被推翻并形成新假设 |
264
+ | **4. 实现** | 编写用例、修复、验证 | 缺陷已解决、测试通过 |
265
+
266
+ ## 当流程揭示“无根因”时
267
+
268
+ 若系统化调查表明问题确实源于环境、时序或外部因素:
269
+
270
+ 1. 流程已走完
271
+ 2. 记录已调查的内容
272
+ 3. 实施相应的容错处理(重试、超时、错误提示)
273
+ 4. 补充监控/日志以便后续调查
274
+
275
+ **但是:** 95% 所谓“无根因”的情况,都是调查不充分。
276
+
277
+ ## 辅助技巧
278
+
279
+ 本目录下提供以下系统化调试的配套技巧:
280
+
281
+ - **`root-cause-tracing.md`** - 沿调用栈回溯追踪,直至找到最初触发点
282
+ - **`defense-in-depth.md`** - 找到根因后在多层添加校验
283
+ - **`condition-based-waiting.md`** - 用条件轮询替代固定时延等待
@@ -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,116 @@
1
+ # 基于条件的等待
2
+
3
+ ## 概述
4
+
5
+ 不稳定的测试往往依赖任意延时来猜测时机。这会引发竞态条件,导致测试在本地快速机器上通过,却在高负载或 CI 环境下失败。
6
+
7
+ **核心原则:** 等待你真正关心的条件满足,而不是猜测它需要多长时间。
8
+
9
+ ## 适用场景
10
+
11
+ ```dot
12
+ digraph when_to_use {
13
+ "Test uses setTimeout/sleep?" [shape=diamond];
14
+ "Testing timing behavior?" [shape=diamond];
15
+ "Document WHY timeout needed" [shape=box];
16
+ "Use condition-based waiting" [shape=box];
17
+
18
+ "Test uses setTimeout/sleep?" -> "Testing timing behavior?" [label="yes"];
19
+ "Testing timing behavior?" -> "Document WHY timeout needed" [label="yes"];
20
+ "Testing timing behavior?" -> "Use condition-based waiting" [label="no"];
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
+
62
+ ```typescript
63
+ async function waitFor<T>(
64
+ condition: () => T | undefined | null | false,
65
+ description: string,
66
+ timeoutMs = 5000
67
+ ): Promise<T> {
68
+ const startTime = Date.now();
69
+
70
+ while (true) {
71
+ const result = condition();
72
+ if (result) return result;
73
+
74
+ if (Date.now() - startTime > timeoutMs) {
75
+ throw new Error(`Timeout waiting for ${description} after ${timeoutMs}ms`);
76
+ }
77
+
78
+ await new Promise(r => setTimeout(r, 10)); // Poll every 10ms
79
+ }
80
+ }
81
+ ```
82
+
83
+ 完整实现及领域专用辅助函数(`waitForEvent`、`waitForEventCount`、`waitForEventMatch`)请参阅本目录下的 `condition-based-waiting-example.ts`,均来自真实调试过程。
84
+
85
+ ## 常见错误
86
+
87
+ **❌ 轮询过快:** `setTimeout(check, 1)` - 浪费 CPU
88
+ **✅ 修正:** 每 10ms 轮询一次
89
+
90
+ **❌ 无超时:** 条件始终不满足时会无限循环
91
+ **✅ 修正:** 始终设置超时,并给出清晰的错误信息
92
+
93
+ **❌ 数据过期:** 在循环前缓存状态
94
+ **✅ 修正:** 在循环内调用 getter 获取最新数据
95
+
96
+ ## 何时应该使用任意延时
97
+
98
+ ```typescript
99
+ // 工具每 100ms 触发一次 — 需要 2 次触发来验证部分输出
100
+ await waitForEvent(manager, 'TOOL_STARTED'); // 第一步:等待条件满足
101
+ await new Promise(r => setTimeout(r, 200)); // 第二步:等待时序行为
102
+ // 200ms = 按 100ms 间隔计算的 2 次触发 — 已说明理由并加注注释
103
+ ```
104
+
105
+ **要求:**
106
+ 1. 先等待触发条件满足
107
+ 2. 基于已知的时序(而非猜测)
108
+ 3. 注释说明原因
109
+
110
+ ## 实际效果
111
+
112
+ 来自调试实践(2025-10-03):
113
+ - 横跨 3 个文件修复了 15 个不稳定测试
114
+ - 通过率:60% → 100%
115
+ - 执行时间:快 40%
116
+ - 彻底消除竞态条件
@@ -0,0 +1,122 @@
1
+ # 纵深防御校验
2
+
3
+ ## 概述
4
+
5
+ 当你修复由非法数据引发的缺陷时,往往觉得在一个地方加校验就够了。但单点校验很容易被不同的代码路径、重构或 Mock 绕过。
6
+
7
+ **核心原则:** 在数据经过的每一层都做校验,让缺陷在结构上不可能发生。
8
+
9
+ ## 为什么需要多层校验
10
+
11
+ 单点校验:“我们修掉了这个缺陷”
12
+ 多层校验:“我们让这类缺陷不可能发生”
13
+
14
+ 不同层级捕获的问题不同:
15
+ - 入口校验能拦截大多数缺陷
16
+ - 业务逻辑校验能兜住边界情况
17
+ - 环境防护能避免特定上下文下的危险操作
18
+ - 调试日志能在其他层级失效时提供排查线索
19
+
20
+ ## 四层模型
21
+
22
+ ### 第一层:入口校验
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
+ // ... proceed
37
+ }
38
+ ```
39
+
40
+ ### 第二层:业务逻辑校验
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
+ // ... proceed
49
+ }
50
+ ```
51
+
52
+ ### 第三层:环境防护
53
+ **目的:** 在特定上下文中阻止危险操作
54
+
55
+ ```typescript
56
+ async function gitInit(directory: string) {
57
+ // In tests, refuse git init outside temp directories
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
+ // ... proceed
69
+ }
70
+ ```
71
+
72
+ ### 第四层:调试埋点
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
+ // ... proceed
84
+ }
85
+ ```
86
+
87
+ ## 应用方法
88
+
89
+ 当发现缺陷时:
90
+
91
+ 1. **追踪数据流** - 非法值从哪里来?在哪里被使用?
92
+ 2. **梳理所有检查点** - 列出数据经过的每一个关卡
93
+ 3. **在每一层补充校验** - 入口、业务、环境、调试逐层加固
94
+ 4. **逐层验证** - 尝试绕过第一层,确认第二层能否兜住
95
+
96
+ ## 实战示例
97
+
98
+ 缺陷:空的 `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
+ - 第一层:`Project.create()` 校验非空、存在且可写
108
+ - 第二层:`WorkspaceManager` 校验 projectDir 非空
109
+ - 第三层:`WorktreeManager` 在测试环境下拒绝在 tmpdir 之外执行 git init
110
+ - 第四层:在 git init 前记录堆栈日志
111
+
112
+ **结果:** 1847 项测试全部通过,缺陷无法复现
113
+
114
+ ## 核心洞察
115
+
116
+ 四层缺一不可。在测试过程中,每一层都捕获了其他层级遗漏的问题:
117
+ - 不同的代码路径绕过了入口校验
118
+ - Mock 绕过了业务逻辑检查
119
+ - 不同平台的边界情况需要环境防护来兜底
120
+ - 调试日志帮助定位了结构性误用
121
+
122
+ **不要只在一个点做校验。** 在每一层都加上检查。