specops 0.2.2 → 0.2.4

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 (62) hide show
  1. package/.opencode/agent/demand-analyst.md +69 -18
  2. package/.opencode/agent/verifier.md +231 -0
  3. package/.opencode/skills/brainstorming/SKILL.md +105 -0
  4. package/.opencode/skills/demand-analysis/SKILL.md +261 -47
  5. package/.opencode/skills/dispatching-parallel-agents/SKILL.md +180 -0
  6. package/.opencode/skills/executing-plans/SKILL.md +90 -0
  7. package/.opencode/skills/finishing-a-development-branch/SKILL.md +222 -0
  8. package/.opencode/skills/receiving-code-review/SKILL.md +213 -0
  9. package/.opencode/skills/repo-clone-analyze/SKILL.md +371 -0
  10. package/.opencode/skills/requesting-code-review/SKILL.md +105 -0
  11. package/.opencode/skills/requesting-code-review/code-reviewer.md +146 -0
  12. package/.opencode/skills/subagent-driven-development/SKILL.md +242 -0
  13. package/.opencode/skills/subagent-driven-development/code-quality-reviewer-prompt.md +20 -0
  14. package/.opencode/skills/subagent-driven-development/implementer-prompt.md +78 -0
  15. package/.opencode/skills/subagent-driven-development/spec-reviewer-prompt.md +61 -0
  16. package/.opencode/skills/systematic-debugging/SKILL.md +296 -0
  17. package/.opencode/skills/systematic-debugging/condition-based-waiting.md +115 -0
  18. package/.opencode/skills/systematic-debugging/defense-in-depth.md +122 -0
  19. package/.opencode/skills/systematic-debugging/root-cause-tracing.md +169 -0
  20. package/.opencode/skills/test-driven-development/SKILL.md +399 -0
  21. package/.opencode/skills/test-driven-development/testing-anti-patterns.md +299 -0
  22. package/.opencode/skills/using-git-worktrees/SKILL.md +218 -0
  23. package/.opencode/skills/using-superpowers/SKILL.md +99 -0
  24. package/.opencode/skills/verification-before-completion/SKILL.md +150 -0
  25. package/.opencode/skills/writing-plans/SKILL.md +123 -0
  26. package/.opencode/skills/writing-skills/SKILL.md +654 -0
  27. package/dist/__e2e__/01-state-engine.e2e.test.js +1 -1
  28. package/dist/acceptance/lazyDetector.js +1 -1
  29. package/dist/acceptance/lazyDetector.test.js +1 -1
  30. package/dist/acceptance/reporter.js +1 -1
  31. package/dist/acceptance/reporter.test.js +1 -1
  32. package/dist/acceptance/runner.js +1 -1
  33. package/dist/acceptance/runner.test.js +1 -1
  34. package/dist/cli.js +1 -1
  35. package/dist/context/index.js +1 -1
  36. package/dist/context/promptTemplate.js +1 -1
  37. package/dist/context/promptTemplate.test.js +1 -1
  38. package/dist/context/techContextLoader.js +1 -1
  39. package/dist/context/techContextLoader.test.js +1 -1
  40. package/dist/engine.js +1 -1
  41. package/dist/evolution/distiller.js +1 -1
  42. package/dist/evolution/index.js +1 -1
  43. package/dist/evolution/memoryGraph.js +1 -1
  44. package/dist/evolution/selector.js +1 -1
  45. package/dist/evolution/signals.js +1 -1
  46. package/dist/evolution/solidify.js +1 -1
  47. package/dist/evolution/store.js +1 -1
  48. package/dist/evolution/types.js +1 -1
  49. package/dist/init.js +1 -1
  50. package/dist/machines/agentMachine.js +1 -1
  51. package/dist/machines/agentMachine.test.js +1 -1
  52. package/dist/machines/supervisorMachine.js +1 -1
  53. package/dist/machines/supervisorMachine.test.js +1 -1
  54. package/dist/persistence/schema.js +1 -1
  55. package/dist/persistence/stateFile.js +1 -1
  56. package/dist/persistence/stateFile.test.js +1 -1
  57. package/dist/plugin-engine.js +1 -1
  58. package/dist/plugin.js +1 -1
  59. package/dist/types/index.js +1 -1
  60. package/dist/utils/id.js +1 -1
  61. package/package.json +8 -2
  62. package/scripts/postinstall.mjs +37 -7
@@ -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(`等待 ${description} 超时,已等待 ${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 不能为空');
29
+ }
30
+ if (!existsSync(workingDirectory)) {
31
+ throw new Error(`workingDirectory 不存在: ${workingDirectory}`);
32
+ }
33
+ if (!statSync(workingDirectory).isDirectory()) {
34
+ throw new Error(`workingDirectory 不是目录: ${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');
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
+ `测试期间拒绝在临时目录之外执行 git init: ${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('即将执行 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
+ **不要在一个验证点就停下来。** 在每一层都添加检查。
@@ -0,0 +1,169 @@
1
+ # 根因追踪
2
+
3
+ ## 概述
4
+
5
+ Bug 通常在调用栈深处表现出来(git init 在错误目录、文件创建在错误位置、数据库用了错误路径)。你的本能是在错误出现的地方修复,但那是在治标。
6
+
7
+ **核心原则:** 沿着调用链反向追踪,直到找到原始触发点,然后在源头修复。
8
+
9
+ ## 何时使用
10
+
11
+ ```dot
12
+ digraph when_to_use {
13
+ "Bug 出现在栈深处?" [shape=diamond];
14
+ "能反向追踪?" [shape=diamond];
15
+ "在症状处修复" [shape=box];
16
+ "追踪到原始触发点" [shape=box];
17
+ "更好:同时添加纵深防御" [shape=box];
18
+
19
+ "Bug 出现在栈深处?" -> "能反向追踪?" [label="是"];
20
+ "能反向追踪?" -> "追踪到原始触发点" [label="是"];
21
+ "能反向追踪?" -> "在症状处修复" [label="否 - 死胡同"];
22
+ "追踪到原始触发点" -> "更好:同时添加纵深防御";
23
+ }
24
+ ```
25
+
26
+ **使用场景:**
27
+ - 错误发生在执行深处(不是入口点)
28
+ - 堆栈跟踪显示长调用链
29
+ - 不清楚无效数据从哪里来
30
+ - 需要找到哪个测试/代码触发了问题
31
+
32
+ ## 追踪流程
33
+
34
+ ### 1. 观察症状
35
+ ```
36
+ Error: git init failed in /Users/jesse/project/packages/core
37
+ ```
38
+
39
+ ### 2. 找到直接原因
40
+ **什么代码直接导致了这个?**
41
+ ```typescript
42
+ await execFileAsync('git', ['init'], { cwd: projectDir });
43
+ ```
44
+
45
+ ### 3. 问:谁调用了这里?
46
+ ```typescript
47
+ WorktreeManager.createSessionWorktree(projectDir, sessionId)
48
+ → 被 Session.initializeWorkspace() 调用
49
+ → 被 Session.create() 调用
50
+ → 被 Project.create() 的测试调用
51
+ ```
52
+
53
+ ### 4. 继续向上追踪
54
+ **传了什么值?**
55
+ - `projectDir = ''`(空字符串!)
56
+ - 空字符串作为 `cwd` 会解析为 `process.cwd()`
57
+ - 那就是源代码目录!
58
+
59
+ ### 5. 找到原始触发点
60
+ **空字符串从哪来?**
61
+ ```typescript
62
+ const context = setupCoreTest(); // 返回 { tempDir: '' }
63
+ Project.create('name', context.tempDir); // 在 beforeEach 之前就访问了!
64
+ ```
65
+
66
+ ## 添加堆栈跟踪
67
+
68
+ 当你无法手动追踪时,添加埋点:
69
+
70
+ ```typescript
71
+ // 在有问题的操作之前
72
+ async function gitInit(directory: string) {
73
+ const stack = new Error().stack;
74
+ console.error('DEBUG git init:', {
75
+ directory,
76
+ cwd: process.cwd(),
77
+ nodeEnv: process.env.NODE_ENV,
78
+ stack,
79
+ });
80
+
81
+ await execFileAsync('git', ['init'], { cwd: directory });
82
+ }
83
+ ```
84
+
85
+ **关键:** 在测试中使用 `console.error()`(不是 logger,可能不会显示)
86
+
87
+ **运行并捕获:**
88
+ ```bash
89
+ npm test 2>&1 | grep 'DEBUG git init'
90
+ ```
91
+
92
+ **分析堆栈跟踪:**
93
+ - 查找测试文件名
94
+ - 找到触发调用的行号
95
+ - 识别模式(同一个测试?同一个参数?)
96
+
97
+ ## 找到哪个测试造成了污染
98
+
99
+ 如果某些东西在测试期间出现但你不知道是哪个测试:
100
+
101
+ 使用本目录中的二分脚本 `find-polluter.sh`:
102
+
103
+ ```bash
104
+ ./find-polluter.sh '.git' 'src/**/*.test.ts'
105
+ ```
106
+
107
+ 逐个运行测试,在第一个污染者处停止。用法见脚本。
108
+
109
+ ## 真实示例:空 projectDir
110
+
111
+ **症状:** `.git` 被创建在 `packages/core/`(源代码目录)
112
+
113
+ **追踪链:**
114
+ 1. `git init` 在 `process.cwd()` 中运行 ← 空的 cwd 参数
115
+ 2. WorktreeManager 被传入空的 projectDir
116
+ 3. Session.create() 传了空字符串
117
+ 4. 测试在 beforeEach 之前访问了 `context.tempDir`
118
+ 5. setupCoreTest() 初始时返回 `{ tempDir: '' }`
119
+
120
+ **根因:** 顶层变量初始化访问了空值
121
+
122
+ **修复:** 把 tempDir 改成 getter,在 beforeEach 之前访问会抛异常
123
+
124
+ **同时添加了纵深防御:**
125
+ - 第 1 层:Project.create() 验证目录
126
+ - 第 2 层:WorkspaceManager 验证非空
127
+ - 第 3 层:NODE_ENV 守卫拒绝在 tmpdir 之外执行 git init
128
+ - 第 4 层:git init 之前的堆栈跟踪日志
129
+
130
+ ## 核心原则
131
+
132
+ ```dot
133
+ digraph principle {
134
+ "找到直接原因" [shape=ellipse];
135
+ "能向上追踪一层?" [shape=diamond];
136
+ "反向追踪" [shape=box];
137
+ "这是源头吗?" [shape=diamond];
138
+ "在源头修复" [shape=box];
139
+ "在每一层添加验证" [shape=box];
140
+ "Bug 不可能再发生" [shape=doublecircle];
141
+ "绝不只修复症状" [shape=octagon, style=filled, fillcolor=red, fontcolor=white];
142
+
143
+ "找到直接原因" -> "能向上追踪一层?";
144
+ "能向上追踪一层?" -> "反向追踪" [label="是"];
145
+ "能向上追踪一层?" -> "绝不只修复症状" [label="否"];
146
+ "反向追踪" -> "这是源头吗?";
147
+ "这是源头吗?" -> "反向追踪" [label="否 - 继续"];
148
+ "这是源头吗?" -> "在源头修复" [label="是"];
149
+ "在源头修复" -> "在每一层添加验证";
150
+ "在每一层添加验证" -> "Bug 不可能再发生";
151
+ }
152
+ ```
153
+
154
+ **绝不只在错误出现的地方修复。** 反向追踪找到原始触发点。
155
+
156
+ ## 堆栈跟踪技巧
157
+
158
+ **在测试中:** 使用 `console.error()` 而不是 logger,logger 可能被抑制
159
+ **在操作之前:** 在危险操作之前记录日志,不是在它失败之后
160
+ **包含上下文:** 目录、cwd、环境变量、时间戳
161
+ **捕获堆栈:** `new Error().stack` 显示完整调用链
162
+
163
+ ## 真实世界的影响
164
+
165
+ 来自调试实践(2025-10-03):
166
+ - 通过 5 层追踪找到根因
167
+ - 在源头修复(getter 验证)
168
+ - 添加了 4 层防御
169
+ - 1847 个测试通过,零污染