@liangjie559567/ultrapower 5.0.21 → 5.0.23
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/plugin.json +1 -1
- package/commands/ax-analyze-error.md +0 -1
- package/commands/ax-context.md +0 -1
- package/commands/ax-decompose.md +0 -1
- package/commands/ax-draft.md +0 -1
- package/commands/ax-evolution.md +0 -1
- package/commands/ax-evolve.md +0 -1
- package/commands/ax-export.md +0 -1
- package/commands/ax-implement.md +0 -1
- package/commands/ax-knowledge.md +0 -1
- package/commands/ax-reflect.md +0 -1
- package/commands/ax-review.md +0 -1
- package/commands/ax-rollback.md +0 -1
- package/commands/ax-status.md +0 -1
- package/commands/ax-suspend.md +0 -1
- package/commands/brainstorm.md +0 -1
- package/commands/execute-plan.md +0 -1
- package/commands/write-plan.md +0 -1
- package/dist/__tests__/validateMode.test.d.ts +2 -0
- package/dist/__tests__/validateMode.test.d.ts.map +1 -0
- package/dist/__tests__/validateMode.test.js +100 -0
- package/dist/__tests__/validateMode.test.js.map +1 -0
- package/dist/lib/validateMode.d.ts +49 -0
- package/dist/lib/validateMode.d.ts.map +1 -0
- package/dist/lib/validateMode.js +68 -0
- package/dist/lib/validateMode.js.map +1 -0
- package/docs/CLAUDE.md +1 -1
- package/docs/prd/ultrapower-standards-draft.md +191 -0
- package/docs/prd/ultrapower-standards-rough.md +560 -0
- package/docs/reviews/ultrapower-standards/review_critic.md +230 -0
- package/docs/reviews/ultrapower-standards/review_domain.md +243 -0
- package/docs/reviews/ultrapower-standards/review_product.md +102 -0
- package/docs/reviews/ultrapower-standards/review_tech.md +142 -0
- package/docs/reviews/ultrapower-standards/review_ux.md +110 -0
- package/docs/reviews/ultrapower-standards/summary.md +85 -0
- package/docs/standards/README.md +85 -0
- package/docs/standards/agent-lifecycle.md +445 -0
- package/docs/standards/anti-patterns.md +365 -0
- package/docs/standards/audit-report.md +388 -0
- package/docs/standards/contribution-guide.md +208 -0
- package/docs/standards/hook-execution-order.md +320 -0
- package/docs/standards/runtime-protection.md +336 -0
- package/docs/standards/state-machine.md +316 -0
- package/docs/standards/templates/agent-template.md +63 -0
- package/docs/standards/templates/hook-template.md +74 -0
- package/docs/standards/templates/skill-template.md +48 -0
- package/docs/standards/user-guide.md +290 -0
- package/docs/tasks/ultrapower-standards/manifest.md +441 -0
- package/package.json +1 -1
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
# Hook 执行顺序规范
|
|
2
|
+
|
|
3
|
+
> **ultrapower-version**: 5.0.21
|
|
4
|
+
> **优先级**: P0(必须遵守)
|
|
5
|
+
> **真理之源**: `docs/standards/audit-report.md`
|
|
6
|
+
> **覆盖范围**: T-04a(HookType 枚举与路由)+ T-04b(执行顺序与优先级)
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 目录
|
|
11
|
+
|
|
12
|
+
1. [HookType 完整枚举(T-04a)](#1-hooktype-完整枚举t-04a)
|
|
13
|
+
- 1.1 全部 15 类 HookType 分类表
|
|
14
|
+
- 1.2 每类 HookType 路由规则
|
|
15
|
+
- 1.3 HookType 完整性验证方法
|
|
16
|
+
2. [执行顺序与优先级(T-04b)](#2-执行顺序与优先级t-04b)
|
|
17
|
+
- 2.1 Stop 阶段优先级链
|
|
18
|
+
- 2.2 互斥规则
|
|
19
|
+
- 2.3 Hook 失败降级策略
|
|
20
|
+
- 2.4 Hook 超时处理
|
|
21
|
+
- 2.5 终止开关
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 1. HookType 完整枚举(T-04a)
|
|
26
|
+
|
|
27
|
+
### 1.1 全部 15 类 HookType 分类表
|
|
28
|
+
|
|
29
|
+
来源:`src/hooks/bridge.ts`
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
type HookType =
|
|
33
|
+
| "keyword-detector" // UserPromptSubmit 阶段
|
|
34
|
+
| "stop-continuation" // Stop 阶段(最低优先级)
|
|
35
|
+
| "ralph" // Stop 阶段(最高优先级)
|
|
36
|
+
| "persistent-mode" // Stop 阶段(含 ultrawork,次优先级)
|
|
37
|
+
| "session-start" // Session 生命周期
|
|
38
|
+
| "session-end" // Session 生命周期(敏感)
|
|
39
|
+
| "pre-tool-use" // 工具调用生命周期
|
|
40
|
+
| "post-tool-use" // 工具调用生命周期
|
|
41
|
+
| "autopilot" // Agent 生命周期
|
|
42
|
+
| "subagent-start" // Agent 生命周期
|
|
43
|
+
| "subagent-stop" // Agent 生命周期
|
|
44
|
+
| "pre-compact" // 系统维护
|
|
45
|
+
| "setup-init" // 系统维护(敏感)
|
|
46
|
+
| "setup-maintenance" // 系统维护(敏感)
|
|
47
|
+
| "permission-request"; // 系统维护(敏感,不可静默降级)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**六阶段完整分类表:**
|
|
51
|
+
|
|
52
|
+
| 阶段 | HookType | 触发时机 | 必需字段 | 敏感级别 |
|
|
53
|
+
|------|----------|----------|----------|----------|
|
|
54
|
+
| UserPromptSubmit | `keyword-detector` | 用户提交 prompt 时 | `[]`(默认) | 普通 |
|
|
55
|
+
| Stop(P1,最高) | `ralph` | Stop 事件,ralph 模式激活时 | `[]`(默认) | 普通 |
|
|
56
|
+
| Stop(P1.5) | `persistent-mode` | Stop 事件,ultrawork/autopilot 模式激活时 | `[]`(默认) | 普通 |
|
|
57
|
+
| Stop(P2,最低) | `stop-continuation` | Stop 事件,无高优先级 hook 处理时 | `[]`(默认) | 普通 |
|
|
58
|
+
| Session 生命周期 | `session-start` | 会话启动时 | `[]`(默认) | 普通 |
|
|
59
|
+
| Session 生命周期 | `session-end` | 会话结束时 | `["sessionId", "directory"]` | **敏感** |
|
|
60
|
+
| 工具调用生命周期 | `pre-tool-use` | 工具调用前 | `[]`(默认) | 普通 |
|
|
61
|
+
| 工具调用生命周期 | `post-tool-use` | 工具调用后 | `[]`(默认) | 普通 |
|
|
62
|
+
| Agent 生命周期 | `autopilot` | autopilot agent 启动/停止时 | `[]`(默认) | 普通 |
|
|
63
|
+
| Agent 生命周期 | `subagent-start` | subagent 启动时 | `["sessionId", "directory"]` | 普通 |
|
|
64
|
+
| Agent 生命周期 | `subagent-stop` | subagent 停止时 | `["sessionId", "directory"]` | 普通 |
|
|
65
|
+
| 系统维护 | `pre-compact` | 上下文压缩前 | `["sessionId", "directory"]` | 普通 |
|
|
66
|
+
| 系统维护 | `setup-init` | 系统初始化时 | `["sessionId", "directory"]` | **敏感** |
|
|
67
|
+
| 系统维护 | `setup-maintenance` | 系统维护时 | `["sessionId", "directory"]` | **敏感** |
|
|
68
|
+
| 系统维护 | `permission-request` | 工具权限请求时 | `["sessionId", "directory", "toolName"]` | **敏感,不可静默降级** |
|
|
69
|
+
|
|
70
|
+
> **注意(差异点 D-01)**:`setup` 已拆分为 `setup-init` 和 `setup-maintenance` 两个独立类型,敏感 hook 共 4 类(非 PRD 原描述的 3 类)。
|
|
71
|
+
|
|
72
|
+
### 1.2 每类 HookType 路由规则
|
|
73
|
+
|
|
74
|
+
**路由入口**:`src/hooks/bridge.ts`(`routeHook` 函数)
|
|
75
|
+
|
|
76
|
+
| HookType | 路由目标 | 路由条件 |
|
|
77
|
+
|----------|----------|----------|
|
|
78
|
+
| `keyword-detector` | `src/hooks/keyword-detector/` | 所有 UserPromptSubmit 事件 |
|
|
79
|
+
| `ralph` | `src/hooks/ralph/` | Stop 事件 + ralph 模式激活 |
|
|
80
|
+
| `persistent-mode` | `src/hooks/persistent-mode/` | Stop 事件 + ultrawork/autopilot 模式激活 |
|
|
81
|
+
| `stop-continuation` | `src/hooks/stop-continuation/` | Stop 事件 + 无高优先级 hook 处理 |
|
|
82
|
+
| `session-start` | `src/hooks/session-start/` | 会话启动事件 |
|
|
83
|
+
| `session-end` | `src/hooks/session-end/` | 会话结束事件 |
|
|
84
|
+
| `pre-tool-use` | `src/hooks/guards/pre-tool.ts` | 所有工具调用前 |
|
|
85
|
+
| `post-tool-use` | `src/hooks/guards/post-tool.ts` | 所有工具调用后 |
|
|
86
|
+
| `autopilot` | `src/hooks/autopilot/` | autopilot agent 生命周期事件 |
|
|
87
|
+
| `subagent-start` | `src/hooks/subagent-tracker/` | subagent 启动事件 |
|
|
88
|
+
| `subagent-stop` | `src/hooks/subagent-tracker/` | subagent 停止事件 |
|
|
89
|
+
| `pre-compact` | `src/hooks/pre-compact/` | 上下文压缩前事件 |
|
|
90
|
+
| `setup-init` | `src/hooks/setup/` | 系统初始化事件 |
|
|
91
|
+
| `setup-maintenance` | `src/hooks/setup/` | 系统维护事件 |
|
|
92
|
+
| `permission-request` | `src/hooks/persistent-mode/` | 工具权限请求事件 |
|
|
93
|
+
|
|
94
|
+
**路由前置处理**:所有 hook 输入在路由前必须经过 `bridge-normalize.ts` 白名单过滤(见 `runtime-protection.md` 第 1 节)。
|
|
95
|
+
|
|
96
|
+
### 1.3 HookType 完整性验证方法
|
|
97
|
+
|
|
98
|
+
**目的**:确保规范文档中的 HookType 列表与 `bridge.ts` 实现保持同步。
|
|
99
|
+
|
|
100
|
+
**自动化验证脚本**(CI 门禁,T-14 实现):
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
#!/bin/bash
|
|
104
|
+
# 从 bridge.ts 提取 HookType 定义
|
|
105
|
+
BRIDGE_TYPES=$(grep -oP '"[a-z-]+"(?=;?\s*//.*HookType|(?<=\|)\s*"[a-z-]+")' \
|
|
106
|
+
src/hooks/bridge.ts | sort | uniq)
|
|
107
|
+
|
|
108
|
+
# 从规范文档提取 HookType 列表
|
|
109
|
+
DOC_TYPES=$(grep -oP '`[a-z-]+`' \
|
|
110
|
+
docs/standards/hook-execution-order.md | sort | uniq)
|
|
111
|
+
|
|
112
|
+
# 对比差异
|
|
113
|
+
diff <(echo "$BRIDGE_TYPES") <(echo "$DOC_TYPES") || \
|
|
114
|
+
echo "ERROR: HookType 规范与实现不一致"
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**手动验证步骤**:
|
|
118
|
+
|
|
119
|
+
1. 打开 `src/hooks/bridge.ts`,找到 `HookType` 类型定义
|
|
120
|
+
2. 统计 `|` 分隔的类型数量,应为 **15 个**
|
|
121
|
+
3. 与本文档 1.1 节表格逐一对比
|
|
122
|
+
4. 如有差异,以 `bridge.ts` 为准,更新本文档
|
|
123
|
+
|
|
124
|
+
**当前验收状态**:规范覆盖 15 个 HookType,与 `bridge.ts` 定义一一对应 ✅
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## 2. 执行顺序与优先级(T-04b)
|
|
129
|
+
|
|
130
|
+
### 2.1 Stop 阶段优先级链
|
|
131
|
+
|
|
132
|
+
来源:`src/hooks/persistent-mode/index.ts`
|
|
133
|
+
|
|
134
|
+
**实际优先级(与 PRD 注释存在差异,见差异点 D-02):**
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
Ralph(P1,最高优先级)
|
|
138
|
+
↓ 未处理时
|
|
139
|
+
Autopilot(P1.5,次优先级)
|
|
140
|
+
↓ 未处理时
|
|
141
|
+
Ultrawork/persistent-mode(P2,最低优先级)
|
|
142
|
+
↓ 未处理时
|
|
143
|
+
stop-continuation(P3,兜底)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
> **注意(差异点 D-02)**:实际优先级链为 `Ralph > Autopilot > Ultrawork`,PRD 原描述为 `Ralph > Ultrawork > Todo-Continuation`。`Todo-Continuation` 已移除,`Autopilot` 插入 P1.5 位置。
|
|
147
|
+
|
|
148
|
+
**优先级判断逻辑**(来自 `persistent-mode/index.ts`):
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
// Stop 阶段路由(伪代码)
|
|
152
|
+
if (isRalphActive()) {
|
|
153
|
+
return handleRalph(); // P1:最高优先级
|
|
154
|
+
}
|
|
155
|
+
if (isAutopilotActive()) {
|
|
156
|
+
return handleAutopilot(); // P1.5:次优先级
|
|
157
|
+
}
|
|
158
|
+
if (isUltraworkActive()) {
|
|
159
|
+
return handleUltrawork(); // P2:最低优先级
|
|
160
|
+
}
|
|
161
|
+
return handleStopContinuation(); // P3:兜底
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**Ralph 自动扩展行为**:
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
// src/hooks/ralph/index.ts
|
|
168
|
+
const MAX_TODO_CONTINUATION_ATTEMPTS = 5;
|
|
169
|
+
// 当 ralph 达到 max_iterations 限制时,自动追加 +10 次迭代
|
|
170
|
+
// 最多追加 MAX_TODO_CONTINUATION_ATTEMPTS 次
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### 2.2 互斥规则
|
|
174
|
+
|
|
175
|
+
**Stop 阶段互斥规则**:
|
|
176
|
+
|
|
177
|
+
高优先级 hook 处理后,低优先级 hook **不得**重复处理同一 Stop 事件。
|
|
178
|
+
|
|
179
|
+
```
|
|
180
|
+
✅ 正确:Ralph 处理 → Autopilot 跳过 → Ultrawork 跳过
|
|
181
|
+
❌ 错误:Ralph 处理 → Autopilot 也处理(重复处理同一 Stop 事件)
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
**执行模式互斥规则**(来源:`src/hooks/mode-registry/index.ts`):
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
// 互斥模式(见差异点 D-04)
|
|
188
|
+
const EXCLUSIVE_MODES = ['autopilot', 'ultrapilot', 'swarm', 'pipeline'];
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
> **注意(差异点 D-04)**:互斥模式为 4 个(`autopilot`、`ultrapilot`、`swarm`、`pipeline`),PRD 原描述仅提及 `autopilot` 与 `ultrapilot` 互斥。
|
|
192
|
+
|
|
193
|
+
**互斥检测规则**:
|
|
194
|
+
|
|
195
|
+
- `EXCLUSIVE_MODES` 中的任意两个模式不得同时激活
|
|
196
|
+
- 检测到互斥冲突时,后激活的模式被拒绝,返回错误
|
|
197
|
+
- `ralph` 和 `ultrawork` 不在互斥列表中,可与其他模式组合使用
|
|
198
|
+
|
|
199
|
+
### 2.3 Hook 失败降级策略
|
|
200
|
+
|
|
201
|
+
来源:`src/hooks/bridge.ts`
|
|
202
|
+
|
|
203
|
+
**通用策略(静默降级)**:
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
// src/hooks/bridge.ts
|
|
207
|
+
try {
|
|
208
|
+
// hook 执行逻辑
|
|
209
|
+
} catch (error) {
|
|
210
|
+
return { continue: true }; // 静默降级,不阻塞用户工作流
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
**设计理由**:hook 不应阻塞用户工作流,失败时静默降级是设计选择而非疏漏。
|
|
215
|
+
|
|
216
|
+
**例外:permission-request(不可静默降级)**
|
|
217
|
+
|
|
218
|
+
`permission-request` 是安全边界,失败时**不得**静默降级。
|
|
219
|
+
|
|
220
|
+
| Hook 类型 | 失败时行为 | 规范要求 |
|
|
221
|
+
|-----------|-----------|----------|
|
|
222
|
+
| 普通 hook(11 类) | 静默降级,返回 `{ continue: true }` | 允许(设计选择) |
|
|
223
|
+
| 敏感 hook(session-end、setup-init、setup-maintenance) | 静默降级,返回 `{ continue: true }` | 允许(v1) |
|
|
224
|
+
| `permission-request` | **当前**:静默降级(差异点 D-05) | **规范要求**:失败时必须阻塞,返回 `{ continue: false }` |
|
|
225
|
+
|
|
226
|
+
**当前实现(差异点 D-05)**:
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
// src/hooks/persistent-mode/index.ts(当前实现)
|
|
230
|
+
export function createHookOutput(result: PersistentModeResult): {
|
|
231
|
+
continue: boolean; message?: string;
|
|
232
|
+
} {
|
|
233
|
+
return { continue: true, message: result.message || undefined };
|
|
234
|
+
// 注意:始终返回 { continue: true },包括 permission-request 失败时
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
**规范要求(v2 目标)**:
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
// 目标实现(v2)
|
|
242
|
+
export function createHookOutput(
|
|
243
|
+
result: PersistentModeResult,
|
|
244
|
+
hookType?: HookType
|
|
245
|
+
): { continue: boolean; message?: string } {
|
|
246
|
+
if (hookType === 'permission-request' && result.error) {
|
|
247
|
+
return { continue: false, message: result.message };
|
|
248
|
+
}
|
|
249
|
+
return { continue: true, message: result.message || undefined };
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
**实现者注意**:在 v2 修复前,不得假设 `permission-request` 失败会自动阻塞执行。
|
|
254
|
+
|
|
255
|
+
### 2.4 Hook 超时处理
|
|
256
|
+
|
|
257
|
+
> **当前状态**:代码中未见明确超时限制实现。以下为规范要求(待实现)。
|
|
258
|
+
|
|
259
|
+
**PreToolUse hook 超时规则**:
|
|
260
|
+
|
|
261
|
+
| 参数 | 值 | 说明 |
|
|
262
|
+
|------|-----|------|
|
|
263
|
+
| 默认超时 | 5 秒 | PreToolUse hook 最长执行时间 |
|
|
264
|
+
| 超时行为 | 继续执行工具调用 | 不阻塞,Claude 继续执行工具 |
|
|
265
|
+
| 超时记录 | 写入 `last-tool-error.json` | 记录超时事件供后续分析 |
|
|
266
|
+
|
|
267
|
+
**PostToolUse hook 超时规则**:
|
|
268
|
+
|
|
269
|
+
| 参数 | 值 | 说明 |
|
|
270
|
+
|------|-----|------|
|
|
271
|
+
| 默认超时 | 5 秒 | PostToolUse hook 最长执行时间 |
|
|
272
|
+
| 超时行为 | 状态写入标记为"待重试" | 不回滚已执行的工具调用 |
|
|
273
|
+
| 回滚策略 | 禁止回滚 | 工具调用已完成,不可撤销 |
|
|
274
|
+
|
|
275
|
+
**超时处理流程**:
|
|
276
|
+
|
|
277
|
+
```
|
|
278
|
+
PreToolUse 超时:
|
|
279
|
+
hook 执行 → 超时(5s)→ 记录到 last-tool-error.json → 继续执行工具调用
|
|
280
|
+
|
|
281
|
+
PostToolUse 超时:
|
|
282
|
+
工具调用完成 → hook 执行 → 超时(5s)→ 状态标记"待重试" → 不回滚工具调用
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### 2.5 终止开关
|
|
286
|
+
|
|
287
|
+
来源:`src/hooks/bridge.ts`
|
|
288
|
+
|
|
289
|
+
| 环境变量 | 作用 | 示例 |
|
|
290
|
+
|----------|------|------|
|
|
291
|
+
| `DISABLE_OMC` | 禁用所有 hooks | `DISABLE_OMC=1 claude` |
|
|
292
|
+
| `OMC_SKIP_HOOKS` | 按逗号分隔名称跳过特定 hooks | `OMC_SKIP_HOOKS=ralph,autopilot claude` |
|
|
293
|
+
|
|
294
|
+
**使用场景**:
|
|
295
|
+
|
|
296
|
+
```bash
|
|
297
|
+
# 完全禁用所有 hooks(调试用)
|
|
298
|
+
DISABLE_OMC=1 claude
|
|
299
|
+
|
|
300
|
+
# 跳过特定 hooks(测试用)
|
|
301
|
+
OMC_SKIP_HOOKS=permission-request,setup-init claude
|
|
302
|
+
|
|
303
|
+
# 跳过 Stop 阶段所有 hooks
|
|
304
|
+
OMC_SKIP_HOOKS=ralph,persistent-mode,stop-continuation claude
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
**注意**:`DISABLE_OMC` 和 `OMC_SKIP_HOOKS` 仅用于调试和测试,生产环境不得使用。
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
## 差异点说明
|
|
312
|
+
|
|
313
|
+
本规范记录以下与 PRD 的差异点(来源:`audit-report.md`):
|
|
314
|
+
|
|
315
|
+
| 差异点 | 描述 | 当前状态 | 规范要求 |
|
|
316
|
+
|--------|------|---------|---------|
|
|
317
|
+
| D-01 | 敏感 hook 数量 | 4 类(setup 拆分为 setup-init + setup-maintenance) | 以 4 类为准 |
|
|
318
|
+
| D-02 | Stop 阶段优先级链 | Ralph > Autopilot > Ultrawork(Todo-Continuation 已移除) | 以实际代码为准 |
|
|
319
|
+
| D-04 | 互斥模式范围 | 4 个互斥模式(含 swarm、pipeline) | 以 4 个为准 |
|
|
320
|
+
| D-05 | permission-request 失败处理 | 静默降级(返回 `{ continue: true }`) | v2 修复为强制阻塞 |
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
# 运行时防护规范
|
|
2
|
+
|
|
3
|
+
> **ultrapower-version**: 5.0.21
|
|
4
|
+
> **优先级**: P0(必须遵守)
|
|
5
|
+
> **真理之源**: `docs/standards/audit-report.md`
|
|
6
|
+
> **覆盖范围**: T-03a(Hook 输入防护)+ T-03b(State/Mode 防护)
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 目录
|
|
11
|
+
|
|
12
|
+
1. [Hook 输入防护(T-03a)](#1-hook-输入防护t-03a)
|
|
13
|
+
- 1.1 全部 15 类 HookType 必需字段白名单
|
|
14
|
+
- 1.2 bridge-normalize.ts 扩展要求
|
|
15
|
+
- 1.3 未知字段处理规则
|
|
16
|
+
- 1.4 permission-request 强制阻塞要求
|
|
17
|
+
2. [State/Mode 防护(T-03b)](#2-statemode-防护t-03b)
|
|
18
|
+
- 2.1 原子写入强制要求
|
|
19
|
+
- 2.2 状态文件并发保护级别对照表
|
|
20
|
+
- 2.3 Windows 平台差异说明
|
|
21
|
+
- 2.4 路径安全:mode 参数白名单
|
|
22
|
+
- 2.5 状态文件损坏恢复流程
|
|
23
|
+
- 2.6 敏感数据处理
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 1. Hook 输入防护(T-03a)
|
|
28
|
+
|
|
29
|
+
### 1.1 全部 15 类 HookType 必需字段白名单
|
|
30
|
+
|
|
31
|
+
来源:`src/hooks/bridge.ts`(`requiredKeysForHook` 函数)+ `src/hooks/bridge-normalize.ts`
|
|
32
|
+
|
|
33
|
+
所有 hook 输入必须经过 `bridge-normalize.ts` 的白名单过滤。以下为每类 HookType 的必需字段和敏感级别:
|
|
34
|
+
|
|
35
|
+
| HookType | 必需字段 | 敏感级别 | 未知字段处理 |
|
|
36
|
+
|----------|----------|----------|------------|
|
|
37
|
+
| `keyword-detector` | `[]`(默认) | 普通 | 透传(记录 debug 警告) |
|
|
38
|
+
| `ralph` | `[]`(默认) | 普通 | 透传(记录 debug 警告) |
|
|
39
|
+
| `persistent-mode` | `[]`(默认) | 普通 | 透传(记录 debug 警告) |
|
|
40
|
+
| `stop-continuation` | `[]`(默认) | 普通 | 透传(记录 debug 警告) |
|
|
41
|
+
| `session-start` | `[]`(默认) | 普通 | 透传(记录 debug 警告) |
|
|
42
|
+
| `session-end` | `["sessionId", "directory"]` | **敏感** | 严格白名单,丢弃未知字段 |
|
|
43
|
+
| `pre-tool-use` | `[]`(默认) | 普通 | 透传(记录 debug 警告) |
|
|
44
|
+
| `post-tool-use` | `[]`(默认) | 普通 | 透传(记录 debug 警告) |
|
|
45
|
+
| `autopilot` | `[]`(默认) | 普通 | 透传(记录 debug 警告) |
|
|
46
|
+
| `subagent-start` | `["sessionId", "directory"]` | 普通 | 透传(记录 debug 警告) |
|
|
47
|
+
| `subagent-stop` | `["sessionId", "directory"]` | 普通 | 透传(记录 debug 警告) |
|
|
48
|
+
| `pre-compact` | `["sessionId", "directory"]` | 普通 | 透传(记录 debug 警告) |
|
|
49
|
+
| `setup-init` | `["sessionId", "directory"]` | **敏感** | 严格白名单,丢弃未知字段 |
|
|
50
|
+
| `setup-maintenance` | `["sessionId", "directory"]` | **敏感** | 严格白名单,丢弃未知字段 |
|
|
51
|
+
| `permission-request` | `["sessionId", "directory", "toolName"]` | **敏感,不可静默降级** | 严格白名单,丢弃未知字段 |
|
|
52
|
+
|
|
53
|
+
**敏感 Hook 白名单(当前实现,来自 `bridge-normalize.ts`):**
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
const SENSITIVE_HOOKS = new Set([
|
|
57
|
+
'permission-request',
|
|
58
|
+
'setup-init',
|
|
59
|
+
'setup-maintenance',
|
|
60
|
+
'session-end'
|
|
61
|
+
]);
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
> **注意(差异点 D-01)**:当前实现为 4 类敏感 hook,PRD 原描述为 3 类(将 setup 视为一类)。`setup` 已拆分为 `setup-init` 和 `setup-maintenance` 两个独立类型。
|
|
65
|
+
|
|
66
|
+
### 1.2 bridge-normalize.ts 扩展要求
|
|
67
|
+
|
|
68
|
+
**当前状态**:白名单仅覆盖 4 类敏感 hook,非敏感 hook 的未知字段透传。
|
|
69
|
+
|
|
70
|
+
**规范要求(v2 目标)**:将白名单扩展至全部 15 类 HookType,每类明确必需字段和禁止字段。
|
|
71
|
+
|
|
72
|
+
**v1 过渡规则**(当前实现的合理性说明):
|
|
73
|
+
- 敏感 hook(4 类):严格白名单,未知字段被丢弃 ✅
|
|
74
|
+
- 非敏感 hook(11 类):未知字段透传,记录 debug 警告(设计选择,非疏漏)
|
|
75
|
+
|
|
76
|
+
### 1.3 未知字段处理规则
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
// 当前实现(bridge-normalize.ts)
|
|
80
|
+
function filterPassthrough(input, hookType) {
|
|
81
|
+
const isSensitive = hookType != null && SENSITIVE_HOOKS.has(hookType);
|
|
82
|
+
if (isSensitive) {
|
|
83
|
+
// 严格白名单:只允许 KNOWN_FIELDS,丢弃其他字段
|
|
84
|
+
return filterToKnownFields(input, hookType);
|
|
85
|
+
} else {
|
|
86
|
+
// 非敏感:未知字段透传,仅记录 debug 警告
|
|
87
|
+
logDebugWarning(input, hookType);
|
|
88
|
+
return input;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**规范要求**:
|
|
94
|
+
- 敏感 hook 的未知字段**必须**被丢弃,不得透传
|
|
95
|
+
- 非敏感 hook 的未知字段当前透传(v1 可接受),v2 需统一丢弃
|
|
96
|
+
|
|
97
|
+
### 1.4 permission-request 强制阻塞要求
|
|
98
|
+
|
|
99
|
+
**当前实现(差异点 D-05)**:
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
// src/hooks/persistent-mode/index.ts
|
|
103
|
+
export function createHookOutput(result: PersistentModeResult): {
|
|
104
|
+
continue: boolean; message?: string;
|
|
105
|
+
} {
|
|
106
|
+
return { continue: true, message: result.message || undefined };
|
|
107
|
+
// 注意:始终返回 { continue: true },包括 permission-request 失败时
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**规范要求(待实现)**:
|
|
112
|
+
|
|
113
|
+
`permission-request` 是安全边界,失败时**不得**静默降级。规范要求:
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
// 目标实现(v2)
|
|
117
|
+
export function createHookOutput(
|
|
118
|
+
result: PersistentModeResult,
|
|
119
|
+
hookType?: HookType
|
|
120
|
+
): { continue: boolean; message?: string } {
|
|
121
|
+
if (hookType === 'permission-request' && result.error) {
|
|
122
|
+
// 安全边界:permission-request 失败时必须阻塞
|
|
123
|
+
return { continue: false, message: result.message };
|
|
124
|
+
}
|
|
125
|
+
return { continue: true, message: result.message || undefined };
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**当前状态**:v1 记录此不一致性,v2 修复。实现者不得假设 `permission-request` 失败会自动阻塞。
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## 2. State/Mode 防护(T-03b)
|
|
134
|
+
|
|
135
|
+
### 2.1 原子写入强制要求
|
|
136
|
+
|
|
137
|
+
来源:`src/lib/atomic-write.ts`
|
|
138
|
+
|
|
139
|
+
**所有状态文件写入必须使用以下函数之一**:
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
// 同步原子写入(推荐用于状态文件)
|
|
143
|
+
atomicWriteJsonSync(filePath: string, data: unknown): void
|
|
144
|
+
|
|
145
|
+
// 异步原子写入
|
|
146
|
+
atomicWriteJson(filePath: string, data: unknown): Promise<void>
|
|
147
|
+
|
|
148
|
+
// 原始文件内容写入(非 JSON)
|
|
149
|
+
atomicWriteFileSync(filePath: string, content: string): void
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**禁止直接使用**:
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
// ❌ 禁止
|
|
156
|
+
fs.writeFileSync(filePath, JSON.stringify(data));
|
|
157
|
+
fs.writeFile(filePath, content, callback);
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
**原子写入完整流程**:
|
|
161
|
+
|
|
162
|
+
```
|
|
163
|
+
1. ensureDirSync(dir) — 确保目录存在
|
|
164
|
+
2. 独占创建临时文件 — wx 标志(O_CREAT|O_EXCL|O_WRONLY),权限 0o600
|
|
165
|
+
tmpPath = path.join(dir, `.${base}.tmp.${randomUUID()}`)
|
|
166
|
+
3. writeSync(fd, content, 0, 'utf8') — 写入内容
|
|
167
|
+
4. fsyncSync(fd) — 落盘
|
|
168
|
+
5. closeSync(fd) — 关闭文件描述符
|
|
169
|
+
6. renameSync(tmpPath, filePath) — 原子替换
|
|
170
|
+
7. 目录级 fsync(best-effort) — Windows 上可能失败,已捕获异常
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**文件权限**:所有原子写入文件权限为 `0o600`(仅所有者可读写)。
|
|
174
|
+
|
|
175
|
+
### 2.2 状态文件并发保护级别对照表
|
|
176
|
+
|
|
177
|
+
来源:`src/hooks/subagent-tracker/index.ts`、`src/lib/atomic-write.ts`
|
|
178
|
+
|
|
179
|
+
| 状态文件 | 并发保护机制 | 保护级别 |
|
|
180
|
+
|---------|------------|---------|
|
|
181
|
+
| `subagent-tracking.json` | debounce(100ms)+ flushInProgress Set + mergeTrackerStates + 文件锁(PID:timestamp) | **最高**(四层保护) |
|
|
182
|
+
| `team-state.json` | atomicWriteJsonSync | 中(原子写入,无 debounce) |
|
|
183
|
+
| `ralph-state.json` | atomicWriteJsonSync | 中(原子写入,无 debounce) |
|
|
184
|
+
| `autopilot-state.json` | atomicWriteJsonSync | 中(原子写入,无 debounce) |
|
|
185
|
+
| 其他 `*-state.json` | atomicWriteJsonSync | 中(原子写入,无 debounce) |
|
|
186
|
+
| subagent-tracker 内部即时写入 | `writeFileSync`(直接写入) | **低**(无原子保护,差异点 D-07) |
|
|
187
|
+
|
|
188
|
+
**subagent-tracking.json 锁机制详情**:
|
|
189
|
+
|
|
190
|
+
```
|
|
191
|
+
锁文件格式:PID:timestamp
|
|
192
|
+
stale 检测:锁持有超过 5 秒(LOCK_TIMEOUT_MS = 5000),或持有进程已死亡
|
|
193
|
+
等待机制:Atomics.wait(syncSleep)
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**mergeTrackerStates 合并策略**:
|
|
197
|
+
- 计数器(tool_calls、cost 等):取 `Math.max`
|
|
198
|
+
- 同一 agent_id 的状态:newer timestamp wins
|
|
199
|
+
|
|
200
|
+
**规范目标**:
|
|
201
|
+
- v1:明确记录此不一致性(本文档已完成)
|
|
202
|
+
- v2:统一为 debounce + atomic 双层保护(技术债务 TD-4)
|
|
203
|
+
|
|
204
|
+
### 2.3 Windows 平台差异说明
|
|
205
|
+
|
|
206
|
+
来源:`src/lib/atomic-write.ts` 代码注释
|
|
207
|
+
|
|
208
|
+
**关键差异**:
|
|
209
|
+
|
|
210
|
+
| 平台 | rename 行为 | 目标文件被占用时 |
|
|
211
|
+
|------|------------|----------------|
|
|
212
|
+
| POSIX(Linux/macOS) | 原子替换(inode 交换) | 成功(原子操作) |
|
|
213
|
+
| Windows | `MoveFileExW with MOVEFILE_REPLACE_EXISTING` | **失败**(抛出错误) |
|
|
214
|
+
|
|
215
|
+
**当前代码处理方式**:
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
// src/lib/atomic-write.ts
|
|
219
|
+
try {
|
|
220
|
+
const dirFd = fs.openSync(dir, 'r');
|
|
221
|
+
fs.fsyncSync(dirFd);
|
|
222
|
+
fs.closeSync(dirFd);
|
|
223
|
+
} catch {
|
|
224
|
+
// Some platforms don't support directory fsync - that's okay
|
|
225
|
+
// Windows 上目录级 fsync 失败时静默捕获
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
**规范要求**:
|
|
230
|
+
- 实现者**不得**假设 Windows 和 POSIX 的 rename 行为一致
|
|
231
|
+
- 在 Windows 上,如果目标文件被其他进程持有,`renameSync` 会失败
|
|
232
|
+
- 所有涉及文件操作的代码必须考虑 Windows 平台行为差异
|
|
233
|
+
|
|
234
|
+
### 2.4 路径安全:mode 参数白名单
|
|
235
|
+
|
|
236
|
+
**安全边界(不可协商)**:`mode` 参数必须通过白名单校验,禁止直接拼接到文件路径。
|
|
237
|
+
|
|
238
|
+
**合法 mode 值(8 个,来源:`src/hooks/mode-registry/index.ts`)**:
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
// src/lib/validateMode.ts(T-07 实现)
|
|
242
|
+
const VALID_MODES = [
|
|
243
|
+
'autopilot', 'ultrapilot', 'team', 'pipeline',
|
|
244
|
+
'ralph', 'ultrawork', 'ultraqa', 'swarm'
|
|
245
|
+
] as const;
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
> **注意(差异点 D-03)**:合法 mode 值为 8 个(含 `swarm`),PRD 原描述为 7 个。
|
|
249
|
+
|
|
250
|
+
**使用方式**:
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
import { validateMode, assertValidMode } from '../lib/validateMode';
|
|
254
|
+
|
|
255
|
+
// 校验(返回 boolean)
|
|
256
|
+
if (!validateMode(mode)) {
|
|
257
|
+
throw new Error(`Invalid mode: ${mode}`);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// 断言(抛出异常)
|
|
261
|
+
const validMode = assertValidMode(mode);
|
|
262
|
+
const stateFilePath = `.omc/state/${validMode}-state.json`;
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
**禁止模式**:
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
// ❌ 禁止:未校验直接拼接
|
|
269
|
+
const path = `.omc/state/${mode}-state.json`; // 路径遍历风险
|
|
270
|
+
|
|
271
|
+
// ✅ 正确:先校验再拼接
|
|
272
|
+
const validMode = assertValidMode(mode);
|
|
273
|
+
const path = `.omc/state/${validMode}-state.json`;
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### 2.5 状态文件损坏恢复流程
|
|
277
|
+
|
|
278
|
+
来源:`src/lib/atomic-write.ts`(`safeReadJson` 函数)
|
|
279
|
+
|
|
280
|
+
**检测到损坏 JSON 时的恢复流程**:
|
|
281
|
+
|
|
282
|
+
```
|
|
283
|
+
1. 尝试读取文件
|
|
284
|
+
2. JSON.parse 失败(或 ENOENT)
|
|
285
|
+
→ safeReadJson 返回 null(不崩溃)
|
|
286
|
+
3. 调用方检测到 null
|
|
287
|
+
→ 记录错误到 last-tool-error.json
|
|
288
|
+
→ 使用空状态初始化(不崩溃)
|
|
289
|
+
4. 下次写入时原子覆盖损坏文件
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
**部分写入检测**:
|
|
293
|
+
- 文件大小为 0:视为损坏
|
|
294
|
+
- JSON 不完整(parse 失败):视为损坏
|
|
295
|
+
- 两种情况均返回 null,由调用方决定恢复策略
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
// src/lib/atomic-write.ts
|
|
299
|
+
export function safeReadJson<T>(filePath: string): T | null {
|
|
300
|
+
// ENOENT 或 JSON.parse 失败时返回 null(不崩溃)
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### 2.6 敏感数据处理
|
|
305
|
+
|
|
306
|
+
**`agent-replay-*.jsonl` 文件**包含完整 agent 对话历史,可能含有代码、密钥片段。
|
|
307
|
+
|
|
308
|
+
**规范要求**:
|
|
309
|
+
|
|
310
|
+
| 要求 | 说明 |
|
|
311
|
+
|------|------|
|
|
312
|
+
| 文件权限 | `0o600`(仅所有者可读写) |
|
|
313
|
+
| 数据保留期限 | 7 天,超期自动清理 |
|
|
314
|
+
| git 提交 | 禁止提交(`.omc/` 已在 `.gitignore` 中) |
|
|
315
|
+
| CI 验证 | 必须验证 `.gitignore` 包含 `.omc/` 目录 |
|
|
316
|
+
|
|
317
|
+
**验证 `.gitignore` 配置**:
|
|
318
|
+
|
|
319
|
+
```bash
|
|
320
|
+
# CI 检查命令
|
|
321
|
+
grep -q "\.omc/" .gitignore || echo "ERROR: .omc/ not in .gitignore"
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
## 差异点说明
|
|
327
|
+
|
|
328
|
+
本规范记录以下与 PRD 的差异点(来源:`audit-report.md`):
|
|
329
|
+
|
|
330
|
+
| 差异点 | 描述 | 当前状态 | 规范要求 |
|
|
331
|
+
|--------|------|---------|---------|
|
|
332
|
+
| D-01 | 敏感 hook 数量 | 4 类(setup 拆分为 setup-init + setup-maintenance) | 以 4 类为准 |
|
|
333
|
+
| D-05 | permission-request 失败处理 | 静默降级(返回 `{ continue: true }`) | v2 修复为强制阻塞 |
|
|
334
|
+
| D-06 | 非敏感 hook 未知字段 | 透传(记录 debug 警告) | v2 统一丢弃 |
|
|
335
|
+
| D-07 | subagent-tracker 内部写入 | `writeFileSync` 直接写入 | v2 统一为 atomicWriteJsonSync |
|
|
336
|
+
| D-03 | 合法 mode 数量 | 8 个(含 swarm) | 以 8 个为准 |
|