docs: 增加测试及 auto mode 文档
This commit is contained in:
parent
006ad97fbb
commit
1086f68381
189
docs/safety/auto-mode.mdx
Normal file
189
docs/safety/auto-mode.mdx
Normal file
@ -0,0 +1,189 @@
|
||||
---
|
||||
title: "Auto Mode - AI 分类器驱动的自主执行模式"
|
||||
description: "详解 Claude Code 的 auto mode:基于 transcript classifier 的自动权限决策、两阶段分类流水线、危险权限剥离机制、模式切换状态管理、以及与 plan mode 的协作方式。"
|
||||
keywords: ["auto mode", "yoloClassifier", "transcript classifier", "权限分类", "自动执行", "两阶段分类"]
|
||||
---
|
||||
|
||||
## 概述
|
||||
|
||||
Auto mode 是 Claude Code 的一种权限模式,让 AI 进入**连续自主执行**状态。与传统模式(每个敏感操作都弹出权限对话框等待用户审批)不同,auto mode 使用 AI 分类器(transcript classifier)自动判断每个工具调用是否安全,从而实现无中断的执行体验。
|
||||
|
||||
```
|
||||
权限模式层级:
|
||||
|
||||
default → auto → bypassPermissions
|
||||
(逐项确认) (AI 分类器审批) (全部放行)
|
||||
```
|
||||
|
||||
## 核心架构
|
||||
|
||||
### 1. AI 分类器(yoloClassifier)
|
||||
|
||||
分类器是 auto mode 的核心,位于 `src/utils/permissions/yoloClassifier.ts`。
|
||||
|
||||
每个工具调用经过分类器评估,返回三种裁决之一:
|
||||
|
||||
| 裁决 | 含义 | 处理方式 |
|
||||
|------|------|---------|
|
||||
| **allow** | 操作安全 | 直接执行,用户无感知 |
|
||||
| **deny** | 操作危险 | 拒绝执行,向 AI 返回拒绝消息 |
|
||||
| **ask** | 无法确定 | 回退到传统权限对话框 |
|
||||
|
||||
分类器的判断基于完整的对话上下文(transcript),而不仅仅看单条命令——它能理解操作的意图和上下文。
|
||||
|
||||
### 2. 两阶段分类流水线
|
||||
|
||||
分类器支持两阶段 XML 分类(`classifyYoloActionXml`):
|
||||
|
||||
```
|
||||
Stage 1 — "fast"(快速判断)
|
||||
├── max_tokens=64,stop_sequences 触发即时 yes/no
|
||||
├── 如果 allow → 直接放行(最快路径)
|
||||
└── 如果 block → 进入 Stage 2
|
||||
|
||||
Stage 2 — "thinking"(深度思考)
|
||||
├── chain-of-thought 推理
|
||||
├── 减少误报(false positives)
|
||||
└── 最终决定 allow / deny / ask
|
||||
```
|
||||
|
||||
两个阶段共享相同的 system prompt 和 user content,利用 API 的 prompt caching(1 小时 TTL)优化性能。
|
||||
|
||||
可通过配置选择模式:
|
||||
- `'both'`(默认)— 两阶段都跑
|
||||
- `'fast'` — 只跑 Stage 1
|
||||
- `'thinking'` — 只跑 Stage 2
|
||||
|
||||
### 3. 分类器结果类型
|
||||
|
||||
```typescript
|
||||
// src/types/permissions.ts
|
||||
type YoloClassifierResult = {
|
||||
thinking?: string // 分类器的推理过程
|
||||
shouldBlock: boolean // 是否阻止
|
||||
reason: string // 决策原因
|
||||
unavailable?: boolean // 分类器是否不可用
|
||||
transcriptTooLong?: boolean // 对话是否超出上下文窗口
|
||||
model: string // 使用的分类器模型
|
||||
stage?: 'fast' | 'thinking' // 哪个阶段做出的决定
|
||||
// ... token 使用量、耗时等监控字段
|
||||
}
|
||||
```
|
||||
|
||||
## 安全机制
|
||||
|
||||
### 危险权限剥离
|
||||
|
||||
进入 auto mode 时,系统调用 `stripDangerousPermissionsForAutoMode()`(`permissionSetup.ts:510`),移除所有可能绕过分类器的 allow 规则。
|
||||
|
||||
被剥离的规则类型(`dangerousPatterns.ts`):
|
||||
|
||||
| 规则类型 | 示例 | 剥离原因 |
|
||||
|---------|------|---------|
|
||||
| **Bash 代码执行** | `Bash(python:*)`, `Bash(node:*)` | 解释器可执行任意代码,绕过分类器审查 |
|
||||
| **Shell 入口** | `Bash(bash:*)`, `Bash(sh:*)` | 直接 shell 访问等同无限制 |
|
||||
| **Agent 规则** | `Agent(*)` | 任何 Agent allow 规则会绕过分类器审批子代理 |
|
||||
| **PowerShell 代码执行** | `PowerShell(node:*)` | 同 Bash 逻辑 |
|
||||
| **权限提升** | `Bash(sudo:*)`, `Bash(eval:*)` | 可执行任意命令 |
|
||||
|
||||
剥离的规则被暂存在 `strippedDangerousRules` 中,退出 auto mode 时通过 `restoreDangerousPermissions()` 恢复。
|
||||
|
||||
### 模型支持检测
|
||||
|
||||
不是所有模型都支持 auto mode。`modelSupportsAutoMode()`(`src/utils/betas.ts`)检查当前模型是否具备安全分类能力。不支持的模型无法进入 auto mode。
|
||||
|
||||
### Circuit Breaker 机制
|
||||
|
||||
`autoModeState.ts` 维护一个 circuit breaker 标志:
|
||||
|
||||
```typescript
|
||||
let autoModeCircuitBroken = false // 由远程配置控制
|
||||
```
|
||||
|
||||
当远程配置(GrowthBook `tengu_auto_mode_config.enabled`)设为 `'disabled'` 时,circuit breaker 触发,阻止 auto mode 的进入和继续使用。这为 Anthropic 提供了远程紧急关停能力。
|
||||
|
||||
## 模式切换状态管理
|
||||
|
||||
### 进入 Auto Mode
|
||||
|
||||
`transitionPermissionMode()`(`permissionSetup.ts:597`)处理所有模式切换:
|
||||
|
||||
```
|
||||
1. 检查 auto mode gate 是否开启(isAutoModeGateEnabled)
|
||||
2. 设置 autoModeActive = true
|
||||
3. 调用 stripDangerousPermissionsForAutoMode() 剥离危险规则
|
||||
4. 向对话注入 Auto Mode 系统提示
|
||||
```
|
||||
|
||||
### 退出 Auto Mode
|
||||
|
||||
```
|
||||
1. 设置 autoModeActive = false
|
||||
2. 设置 needsAutoModeExitAttachment = true(触发退出通知)
|
||||
3. 调用 restoreDangerousPermissions() 恢复被剥离的规则
|
||||
4. 向对话注入 "Exited Auto Mode" 提示
|
||||
```
|
||||
|
||||
### 触发路径
|
||||
|
||||
Auto mode 可通过以下方式激活:
|
||||
- CLI 参数 `--enable-auto-mode`
|
||||
- settings.json 中的 `autoMode` 配置
|
||||
- Plan mode 默认使用 auto mode 语义(`useAutoModeDuringPlan`,默认 true)
|
||||
- SDK 控制消息
|
||||
- REPL 中 Shift+Tab 切换
|
||||
|
||||
## 系统提示词
|
||||
|
||||
### 进入时(Full Instructions)
|
||||
|
||||
注入到对话中的指令(`messages.ts:3464`):
|
||||
|
||||
> Auto mode is active. The user chose continuous, autonomous execution. You should:
|
||||
>
|
||||
> 1. **Execute immediately** — 直接实现,做合理假设
|
||||
> 2. **Minimize interruptions** — 常规决策自行判断,减少提问
|
||||
> 3. **Prefer action over planning** — 默认直接编码,不进 plan mode
|
||||
> 4. **Expect course corrections** — 用户可随时纠正
|
||||
> 5. **Do not take overly destructive actions** — 删除数据/修改生产系统仍需确认
|
||||
> 6. **Avoid data exfiltration** — 不主动分享密钥/内部文档
|
||||
|
||||
### 持续运行时(Sparse Instructions)
|
||||
|
||||
后续轮次注入简短提醒:
|
||||
|
||||
> Auto mode still active. Execute autonomously, minimize interruptions, prefer action over planning.
|
||||
|
||||
### 退出时(Exit Instructions)
|
||||
|
||||
> You have exited auto mode. Ask clarifying questions when the approach is ambiguous rather than making assumptions.
|
||||
|
||||
## 与 Plan Mode 的协作
|
||||
|
||||
Plan mode 默认使用 auto mode 语义(`getUseAutoModeDuringPlan()`,默认 true)。这意味着:
|
||||
|
||||
- Plan mode 进入时,如果 auto mode 可用,也会激活分类器
|
||||
- `isAutoModeActive()` 是权威信号(`prePlanMode`/`strippedDangerousRules` 不可靠)
|
||||
- 退出 plan mode 时会同时退出 auto mode
|
||||
|
||||
## 分类器不可用的降级策略
|
||||
|
||||
当分类器 API 不可用时(`unavailable: true` 或 `transcriptTooLong: true`):
|
||||
|
||||
- 不会直接 allow — 回退到传统的权限对话框(ask)
|
||||
- 向 AI 发送消息:"{model} is temporarily unavailable, so auto mode cannot determine the safety of {toolName} right now."
|
||||
- 确定性错误(如对话过长)不重试,直接降级
|
||||
|
||||
## 相关源码索引
|
||||
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| `src/utils/permissions/yoloClassifier.ts` | 分类器核心实现 |
|
||||
| `src/utils/permissions/autoModeState.ts` | Auto mode 状态管理 |
|
||||
| `src/utils/permissions/permissionSetup.ts` | 模式切换、危险权限剥离 |
|
||||
| `src/utils/permissions/dangerousPatterns.ts` | 危险命令模式列表 |
|
||||
| `src/utils/permissions/classifierDecision.ts` | 分类器决策处理 |
|
||||
| `src/utils/permissions/classifierShared.ts` | 分类器共享逻辑 |
|
||||
| `src/utils/messages.ts` | Auto mode 系统提示词 |
|
||||
| `src/types/permissions.ts` | 权限类型定义 |
|
||||
| `src/utils/betas.ts` | 模型 auto mode 支持检测 |
|
||||
188
docs/test-plans/phase-16-zero-dep-pure-functions.md
Normal file
188
docs/test-plans/phase-16-zero-dep-pure-functions.md
Normal file
@ -0,0 +1,188 @@
|
||||
# Phase 16 — 零依赖纯函数测试
|
||||
|
||||
> 创建日期:2026-04-02
|
||||
> 预计:+120 tests / 8 files
|
||||
> 目标:覆盖所有零外部依赖的纯函数/类模块
|
||||
|
||||
所有模块均为纯函数或零外部依赖类,mock 成本为零,ROI 最高。
|
||||
|
||||
---
|
||||
|
||||
## 16.1 `src/utils/__tests__/stream.test.ts`(~15 tests)
|
||||
|
||||
**目标模块**: `src/utils/stream.ts`(76 行)
|
||||
**导出**: `Stream<T>` class — 手动异步队列,实现 `AsyncIterator<T>`
|
||||
|
||||
| 测试用例 | 验证点 |
|
||||
|---------|--------|
|
||||
| enqueue then read | 单条消息正确传递 |
|
||||
| enqueue multiple then drain | 多条消息顺序消费 |
|
||||
| done resolves pending readers | `done()` 后迭代结束 |
|
||||
| done with no pending readers | 无等待时安全关闭 |
|
||||
| error rejects pending readers | `error(e)` 传播异常 |
|
||||
| error after done | 后续操作安全处理 |
|
||||
| single-iteration guard | `return()` 后不可再迭代 |
|
||||
| empty stream done immediately | 无数据时 done 返回 `{ done: true }` |
|
||||
| concurrent enqueue | 多次 enqueue 不丢失 |
|
||||
| backpressure | reader 慢于 writer 时不丢数据 |
|
||||
|
||||
---
|
||||
|
||||
## 16.2 `src/utils/__tests__/abortController.test.ts`(~12 tests)
|
||||
|
||||
**目标模块**: `src/utils/abortController.ts`(99 行)
|
||||
**导出**: `createAbortController()`, `createChildAbortController()`
|
||||
|
||||
| 测试用例 | 验证点 |
|
||||
|---------|--------|
|
||||
| parent abort propagates to child | `parent.abort()` → child aborted |
|
||||
| child abort does NOT propagate to parent | `child.abort()` → parent still active |
|
||||
| already-aborted parent → child immediately aborted | 创建时即继承 abort 状态 |
|
||||
| child listener cleanup after parent abort | WeakRef 回收后无泄漏 |
|
||||
| multiple children of same parent | 独立 abort 传播 |
|
||||
| child abort then parent abort | 顺序无关 |
|
||||
| signal.maxListeners raised | MaxListenersExceededWarning 不触发 |
|
||||
|
||||
---
|
||||
|
||||
## 16.3 `src/utils/__tests__/bufferedWriter.test.ts`(~14 tests)
|
||||
|
||||
**目标模块**: `src/utils/bufferedWriter.ts`(100 行)
|
||||
**导出**: `createBufferedWriter()`
|
||||
|
||||
| 测试用例 | 验证点 |
|
||||
|---------|--------|
|
||||
| single write buffered | write → buffer 累积 |
|
||||
| flush on size threshold | 超过 maxSize 时自动 flush |
|
||||
| flush on timer | 定时器触发 flush |
|
||||
| immediate mode | `{ immediate: true }` 跳过缓冲 |
|
||||
| overflow coalescing | overflow 内容合并到下次 flush |
|
||||
| empty buffer flush | 无数据时 flush 无副作用 |
|
||||
| close flushes remaining | close 触发最终 flush |
|
||||
| multiple writes before flush | 批量写入合并 |
|
||||
| flush callback receives concatenated data | writeFn 参数正确 |
|
||||
|
||||
**Mock**: 注入 `writeFn` 回调,可选 fake timers
|
||||
|
||||
---
|
||||
|
||||
## 16.4 `src/utils/__tests__/gitDiff.test.ts`(~20 tests)
|
||||
|
||||
**目标模块**: `src/utils/gitDiff.ts`(532 行)
|
||||
**可测函数**: `parseGitNumstat()`, `parseGitDiff()`, `parseShortstat()`
|
||||
|
||||
| 测试用例 | 验证点 |
|
||||
|---------|--------|
|
||||
| parseGitNumstat — single file | `1\t2\tpath` → { added: 1, deleted: 2, file: "path" } |
|
||||
| parseGitNumstat — binary file | `-\t-\timage.png` → binary flag |
|
||||
| parseGitNumstat — rename | `{ old => new }` 格式解析 |
|
||||
| parseGitNumstat — empty diff | 空字符串 → [] |
|
||||
| parseGitNumstat — multiple files | 多行正确分割 |
|
||||
| parseGitDiff — added lines | `+` 开头行计数 |
|
||||
| parseGitDiff — deleted lines | `-` 开头行计数 |
|
||||
| parseGitDiff — hunk header | `@@ -a,b +c,d @@` 解析 |
|
||||
| parseGitDiff — new file mode | `new file mode 100644` 检测 |
|
||||
| parseGitDiff — deleted file mode | `deleted file mode` 检测 |
|
||||
| parseGitDiff — binary diff | Binary files differ 处理 |
|
||||
| parseShortstat — all components | `1 file changed, 5 insertions(+), 3 deletions(-)` |
|
||||
| parseShortstat — insertions only | 无 deletions |
|
||||
| parseShortstat — deletions only | 无 insertions |
|
||||
| parseShortstat — files only | 仅 file changed |
|
||||
| parseShortstat — empty | 空字符串 → 默认值 |
|
||||
| parseShortstat — rename | `1 file changed, ...` 重命名 |
|
||||
|
||||
**Mock**: 无需 mock — 全部是纯字符串解析
|
||||
|
||||
---
|
||||
|
||||
## 16.5 `src/__tests__/history.test.ts`(~18 tests)
|
||||
|
||||
**目标模块**: `src/history.ts`(464 行)
|
||||
**可测函数**: `parseReferences()`, `expandPastedTextRefs()`, `formatPastedTextRef()`, `formatImageRef()`, `getPastedTextRefNumLines()`
|
||||
|
||||
| 测试用例 | 验证点 |
|
||||
|---------|--------|
|
||||
| parseReferences — text ref | `#1` → [{ type: "text", ref: 1 }] |
|
||||
| parseReferences — image ref | `@1` → [{ type: "image", ref: 1 }] |
|
||||
| parseReferences — multiple refs | `#1 #2 @3` → 3 refs |
|
||||
| parseReferences — no refs | `"hello"` → [] |
|
||||
| parseReferences — duplicate refs | `#1 #1` → 去重或保留 |
|
||||
| parseReferences — zero ref | `#0` → 边界 |
|
||||
| parseReferences — large ref | `#999` → 正常 |
|
||||
| formatPastedTextRef — basic | 输出格式验证 |
|
||||
| formatPastedTextRef — multiline | 多行内容格式 |
|
||||
| getPastedTextRefNumLines — 1 line | 返回 1 |
|
||||
| getPastedTextRefNumLines — multiple lines | 换行计数 |
|
||||
| expandPastedTextRefs — single ref | 替换单个引用 |
|
||||
| expandPastedTextRefs — multiple refs | 替换多个引用 |
|
||||
| expandPastedTextRefs — no refs | 原样返回 |
|
||||
| expandPastedTextRefs — mixed content | 文本 + 引用混合 |
|
||||
| formatImageRef — basic | 输出格式 |
|
||||
|
||||
**Mock**: `mock.module("src/bootstrap/state.ts", ...)` 解锁模块
|
||||
|
||||
---
|
||||
|
||||
## 16.6 `src/utils/__tests__/sliceAnsi.test.ts`(~16 tests)
|
||||
|
||||
**目标模块**: `src/utils/sliceAnsi.ts`(91 行)
|
||||
**导出**: `sliceAnsi()` — ANSI 感知的字符串切片
|
||||
|
||||
| 测试用例 | 验证点 |
|
||||
|---------|--------|
|
||||
| plain text slice | `"hello".slice(1,3)` 等价 |
|
||||
| preserve ANSI codes | `\x1b[31mhello\x1b[0m` 切片后保留颜色 |
|
||||
| close opened styles | 切片点在 ANSI 样式中间时正确关闭 |
|
||||
| hyperlink handling | OSC 8 超链接不被切断 |
|
||||
| combining marks (diacritics) | `é` = `e\u0301` 不被切开 |
|
||||
| Devanagari matras | 零宽字符不被切断 |
|
||||
| full-width characters | CJK 字符宽度 = 2 |
|
||||
| empty slice | 返回空字符串 |
|
||||
| full slice | 返回完整字符串 |
|
||||
| boundary at ANSI code | 边界恰好在 escape 序列上 |
|
||||
| nested ANSI styles | 多层嵌套时正确处理 |
|
||||
| slice start > end | 空结果 |
|
||||
|
||||
**Mock**: `mock.module("@alcalzone/ansi-tokenize", ...)`, `mock.module("ink/stringWidth", ...)`
|
||||
|
||||
---
|
||||
|
||||
## 16.7 `src/utils/__tests__/treeify.test.ts`(~15 tests)
|
||||
|
||||
**目标模块**: `src/utils/treeify.ts`(170 行)
|
||||
**导出**: `treeify()` — 递归树渲染
|
||||
|
||||
| 测试用例 | 验证点 |
|
||||
|---------|--------|
|
||||
| simple flat tree | `{ a: {}, b: {} }` → 2 行 |
|
||||
| nested tree | `{ a: { b: { c: {} } } }` → 3 行缩进 |
|
||||
| array values | `[1, 2, 3]` 渲染为列表 |
|
||||
| circular reference | 不无限递归 |
|
||||
| empty object | `{}` 处理 |
|
||||
| single key | 布局适配 |
|
||||
| branch vs last-branch character | ├─ vs └─ |
|
||||
| custom prefix | options 前缀传递 |
|
||||
| deep nesting | 5+ 层缩进正确 |
|
||||
| mixed object/array | 混合结构 |
|
||||
|
||||
**Mock**: `mock.module("figures", ...)`, color 模块 mock
|
||||
|
||||
---
|
||||
|
||||
## 16.8 `src/utils/__tests__/words.test.ts`(~10 tests)
|
||||
|
||||
**目标模块**: `src/utils/words.ts`(800 行,大部分是词表数据)
|
||||
**导出**: `generateWordSlug()`, `generateShortWordSlug()`
|
||||
|
||||
| 测试用例 | 验证点 |
|
||||
|---------|--------|
|
||||
| generateWordSlug format | `adjective-verb-noun` 三段式 |
|
||||
| generateShortWordSlug format | `adjective-noun` 两段式 |
|
||||
| all parts non-empty | 无空段 |
|
||||
| hyphen separator | `-` 分隔 |
|
||||
| all parts from word lists | 成分来自预定义词表 |
|
||||
| multiple calls uniqueness | 连续调用不总是相同 |
|
||||
| no consecutive hyphens | 无 `--` |
|
||||
| lowercase only | 全小写 |
|
||||
|
||||
**Mock**: `mock.module("crypto", ...)` 控制 `randomBytes` 实现确定性测试
|
||||
203
docs/test-plans/phase-17-tool-submodules.md
Normal file
203
docs/test-plans/phase-17-tool-submodules.md
Normal file
@ -0,0 +1,203 @@
|
||||
# Phase 17 — Tool 子模块纯逻辑测试
|
||||
|
||||
> 创建日期:2026-04-02
|
||||
> 预计:+150 tests / 11 files
|
||||
> 目标:覆盖 Tool 目录下有丰富纯逻辑但零测试的子模块
|
||||
|
||||
---
|
||||
|
||||
## 17.1 `src/tools/PowerShellTool/__tests__/powershellSecurity.test.ts`(~25 tests)
|
||||
|
||||
**目标模块**: `src/tools/PowerShellTool/powershellSecurity.ts`(1091 行)
|
||||
|
||||
**安全关键** — 检测 ~20 种攻击向量。
|
||||
|
||||
| 测试分组 | 测试数 | 验证点 |
|
||||
|---------|-------|--------|
|
||||
| Invoke-Expression 检测 | 3 | `IEX`, `Invoke-Expression`, 变形 |
|
||||
| Download cradle 检测 | 3 | `Net.WebClient`, `Invoke-WebRequest`, pipe |
|
||||
| Privilege escalation | 3 | `Start-Process -Verb RunAs`, `runas.exe` |
|
||||
| COM object | 2 | `New-Object -ComObject`, WScript.Shell |
|
||||
| Scheduled tasks | 2 | `schtasks`, `Register-ScheduledTask` |
|
||||
| WMI | 2 | `Invoke-WmiMethod`, `Get-WmiObject` |
|
||||
| Module loading | 2 | `Import-Module` 从网络路径 |
|
||||
| 安全命令通过 | 3 | `Get-Process`, `Get-ChildItem`, `Write-Host` |
|
||||
| 混淆绕过尝试 | 3 | base64, 字符串拼接, 空格变形 |
|
||||
| 组合命令 | 2 | `;` 分隔的多命令 |
|
||||
|
||||
**Mock**: 构造 `ParsedPowerShellCommand` 对象(不需要真实 AST)
|
||||
|
||||
---
|
||||
|
||||
## 17.2 `src/tools/PowerShellTool/__tests__/commandSemantics.test.ts`(~10 tests)
|
||||
|
||||
**目标模块**: `src/tools/PowerShellTool/commandSemantics.ts`(143 行)
|
||||
|
||||
| 测试用例 | 验证点 |
|
||||
|---------|--------|
|
||||
| grep exit 0/1/2 | 语义映射 |
|
||||
| robocopy exit codes | Windows 特殊退出码 |
|
||||
| findstr exit codes | Windows find 工具 |
|
||||
| unknown command | 默认语义 |
|
||||
| extractBaseCommand — basic | `grep "pattern" file` → `grep` |
|
||||
| extractBaseCommand — path | `C:\tools\rg.exe` → `rg` |
|
||||
| heuristicallyExtractBaseCommand | 模糊匹配 |
|
||||
|
||||
---
|
||||
|
||||
## 17.3 `src/tools/PowerShellTool/__tests__/destructiveCommandWarning.test.ts`(~15 tests)
|
||||
|
||||
**目标模块**: `src/tools/PowerShellTool/destructiveCommandWarning.ts`(110 行)
|
||||
|
||||
| 测试用例 | 验证点 |
|
||||
|---------|--------|
|
||||
| Remove-Item -Recurse -Force | 危险 |
|
||||
| Format-Volume | 危险 |
|
||||
| git reset --hard | 危险 |
|
||||
| DROP TABLE | 危险 |
|
||||
| Remove-Item (no -Force) | 安全 |
|
||||
| Get-ChildItem | 安全 |
|
||||
| 管道组合 | `rm -rf` + pipe |
|
||||
| 大小写混合 | `ReMoVe-ItEm` |
|
||||
|
||||
---
|
||||
|
||||
## 17.4 `src/tools/PowerShellTool/__tests__/gitSafety.test.ts`(~12 tests)
|
||||
|
||||
**目标模块**: `src/tools/PowerShellTool/gitSafety.ts`(177 行)
|
||||
|
||||
| 测试用例 | 验证点 |
|
||||
|---------|--------|
|
||||
| normalizeGitPathArg — forward slash | 规范化 |
|
||||
| normalizeGitPathArg — backslash | Windows 路径规范化 |
|
||||
| normalizeGitPathArg — NTFS short name | `GITFI~1` → `.git` |
|
||||
| isGitInternalPathPS — .git/config | true |
|
||||
| isGitInternalPathPS — normal file | false |
|
||||
| isDotGitPathPS — hidden git dir | true |
|
||||
| isDotGitPathPS — .gitignore | false |
|
||||
| bare repo attack | `.git` 路径遍历 |
|
||||
|
||||
---
|
||||
|
||||
## 17.5 `src/tools/LSPTool/__tests__/formatters.test.ts`(~20 tests)
|
||||
|
||||
**目标模块**: `src/tools/LSPTool/formatters.ts`(593 行)
|
||||
|
||||
| 测试用例 | 验证点 |
|
||||
|---------|--------|
|
||||
| formatGoToDefinitionResult — single | 单个定义 |
|
||||
| formatGoToDefinitionResult — multiple | 多个定义(分组) |
|
||||
| formatFindReferencesResult | 引用列表 |
|
||||
| formatHoverResult — markdown | markdown 内容 |
|
||||
| formatHoverResult — plaintext | 纯文本 |
|
||||
| formatDocumentSymbolResult — classes | 类符号 |
|
||||
| formatDocumentSymbolResult — functions | 函数符号 |
|
||||
| formatDocumentSymbolResult — nested | 嵌套符号 |
|
||||
| formatWorkspaceSymbolResult | 工作区符号 |
|
||||
| formatPrepareCallHierarchyResult | 调用层次 |
|
||||
| formatIncomingCallsResult | 入调用 |
|
||||
| formatOutgoingCallsResult | 出调用 |
|
||||
| empty results | 各函数空结果 |
|
||||
| groupByFile helper | 文件分组逻辑 |
|
||||
|
||||
---
|
||||
|
||||
## 17.6 `src/tools/GrepTool/__tests__/utils.test.ts`(~10 tests)
|
||||
|
||||
**目标模块**: `src/tools/GrepTool/GrepTool.ts`(577 行)
|
||||
|
||||
| 测试用例 | 验证点 |
|
||||
|---------|--------|
|
||||
| applyHeadLimit — within limit | 不截断 |
|
||||
| applyHeadLimit — exceeds limit | 正确截断 |
|
||||
| applyHeadLimit — offset + limit | 分页逻辑 |
|
||||
| applyHeadLimit — zero limit | 边界 |
|
||||
| formatLimitInfo — basic | 格式化输出 |
|
||||
|
||||
**Mock**: `mock.module("src/utils/log.ts", ...)` 解锁导入
|
||||
|
||||
---
|
||||
|
||||
## 17.7 `src/tools/WebFetchTool/__tests__/utils.test.ts`(~15 tests)
|
||||
|
||||
**目标模块**: `src/tools/WebFetchTool/utils.ts`(531 行)
|
||||
|
||||
| 测试用例 | 验证点 |
|
||||
|---------|--------|
|
||||
| validateURL — valid http | 通过 |
|
||||
| validateURL — valid https | 通过 |
|
||||
| validateURL — ftp | 拒绝 |
|
||||
| validateURL — no protocol | 拒绝 |
|
||||
| validateURL — localhost | 处理 |
|
||||
| isPermittedRedirect — same host | 允许 |
|
||||
| isPermittedRedirect — different host | 拒绝 |
|
||||
| isPermittedRedirect — subdomain | 处理 |
|
||||
| isRedirectInfo — valid object | true |
|
||||
| isRedirectInfo — invalid | false |
|
||||
|
||||
---
|
||||
|
||||
## 17.8 `src/tools/WebFetchTool/__tests__/preapproved.test.ts`(~10 tests)
|
||||
|
||||
**目标模块**: `src/tools/WebFetchTool/preapproved.ts`(167 行)
|
||||
|
||||
| 测试用例 | 验证点 |
|
||||
|---------|--------|
|
||||
| exact hostname match | 通过 |
|
||||
| subdomain match | 处理 |
|
||||
| path prefix match | `/docs/api` 匹配 |
|
||||
| path non-match | `/internal` 不匹配 |
|
||||
| unknown hostname | false |
|
||||
| empty pathname | 边界 |
|
||||
|
||||
---
|
||||
|
||||
## 17.9 `src/tools/FileReadTool/__tests__/utils.test.ts`(~15 tests)
|
||||
|
||||
**目标模块**: `src/tools/FileReadTool/FileReadTool.ts`(1184 行)
|
||||
|
||||
| 测试用例 | 验证点 |
|
||||
|---------|--------|
|
||||
| isBlockedDevicePath — /dev/sda | true |
|
||||
| isBlockedDevicePath — /dev/null | 处理 |
|
||||
| isBlockedDevicePath — normal file | false |
|
||||
| detectSessionFileType — .jsonl | 会话文件类型 |
|
||||
| detectSessionFileType — unknown | 未知类型 |
|
||||
| formatFileLines — basic | 行号格式 |
|
||||
| formatFileLines — empty | 空文件 |
|
||||
|
||||
---
|
||||
|
||||
## 17.10 `src/tools/AgentTool/__tests__/agentToolUtils.test.ts`(~18 tests)
|
||||
|
||||
**目标模块**: `src/tools/AgentTool/agentToolUtils.ts`(688 行)
|
||||
|
||||
| 测试用例 | 验证点 |
|
||||
|---------|--------|
|
||||
| filterToolsForAgent — builtin only | 只返回内置工具 |
|
||||
| filterToolsForAgent — exclude async | 排除异步工具 |
|
||||
| filterToolsForAgent — permission mode | 权限过滤 |
|
||||
| resolveAgentTools — wildcard | 通配符展开 |
|
||||
| resolveAgentTools — explicit list | 显式列表 |
|
||||
| countToolUses — multiple | 消息中工具调用计数 |
|
||||
| countToolUses — zero | 无工具调用 |
|
||||
| extractPartialResult — text only | 提取文本 |
|
||||
| extractPartialResult — mixed | 混合内容 |
|
||||
| getLastToolUseName — basic | 最后工具名 |
|
||||
| getLastToolUseName — no tool use | 无工具调用 |
|
||||
|
||||
**Mock**: `mock.module("src/bootstrap/state.ts", ...)`, `mock.module("src/utils/log.ts", ...)`
|
||||
|
||||
---
|
||||
|
||||
## 17.11 `src/tools/LSPTool/__tests__/schemas.test.ts`(~5 tests)
|
||||
|
||||
**目标模块**: `src/tools/LSPTool/schemas.ts`(216 行)
|
||||
|
||||
| 测试用例 | 验证点 |
|
||||
|---------|--------|
|
||||
| isValidLSPOperation — goToDefinition | true |
|
||||
| isValidLSPOperation — findReferences | true |
|
||||
| isValidLSPOperation — hover | true |
|
||||
| isValidLSPOperation — invalid | false |
|
||||
| isValidLSPOperation — empty string | false |
|
||||
110
docs/test-plans/phase-18-weak-fixes.md
Normal file
110
docs/test-plans/phase-18-weak-fixes.md
Normal file
@ -0,0 +1,110 @@
|
||||
# Phase 18 — WEAK 修复 + ACCEPTABLE 加固
|
||||
|
||||
> 创建日期:2026-04-02
|
||||
> 预计:+30 tests / 4 files (修改现有)
|
||||
> 目标:修复所有 WEAK 评分测试文件,消除系统性问题
|
||||
|
||||
---
|
||||
|
||||
## 18.1 `src/utils/__tests__/format.test.ts` — 断言精确化(+5 tests)
|
||||
|
||||
**问题**: `formatNumber`/`formatTokens`/`formatRelativeTime` 使用 `toContain`
|
||||
**修复**: 改为 `toBe` 精确匹配
|
||||
|
||||
```diff
|
||||
- expect(formatNumber(1500000)).toContain("1.5")
|
||||
+ expect(formatNumber(1500000)).toBe("1.5m")
|
||||
```
|
||||
|
||||
新增测试:
|
||||
|
||||
| 测试用例 | 验证点 |
|
||||
|---------|--------|
|
||||
| formatNumber — 0 | `"0"` |
|
||||
| formatNumber — billions | `"1.5b"` |
|
||||
| formatTokens — thousands | 精确匹配 |
|
||||
| formatRelativeTime — hours ago | 精确匹配 |
|
||||
| formatRelativeTime — days ago | 精确匹配 |
|
||||
|
||||
---
|
||||
|
||||
## 18.2 `src/utils/__tests__/envValidation.test.ts` — Bug 确认(+3 tests)
|
||||
|
||||
**问题**: `value=1, lowerBound=100` 返回 `status: "valid"` — 函数名暗示有下界检查
|
||||
**计划**: 先读取源码确认 `defaultValue` 和 `lowerBound` 的语义关系,然后:
|
||||
- 如果是源码 bug → 在测试中注释标记,不修改源码
|
||||
- 如果是设计意图 → 更新测试描述明确语义
|
||||
|
||||
新增测试:
|
||||
|
||||
| 测试用例 | 验证点 |
|
||||
|---------|--------|
|
||||
| parseFloat truncation | `"50.9"` → 50 |
|
||||
| whitespace handling | `" 500 "` → 500 |
|
||||
| very large number | overflow 处理 |
|
||||
|
||||
---
|
||||
|
||||
## 18.3 `src/utils/permissions/__tests__/PermissionMode.test.ts` — false 路径(+8 tests)
|
||||
|
||||
**问题**: `isExternalPermissionMode` false 路径从未执行
|
||||
**修复**: 覆盖所有 5 种 mode 的 true/false 期望
|
||||
|
||||
| 测试用例 | 验证点 |
|
||||
|---------|--------|
|
||||
| isExternalPermissionMode — plan | false |
|
||||
| isExternalPermissionMode — auto | false |
|
||||
| isExternalPermissionMode — default | false |
|
||||
| permissionModeFromString — all modes | 5 种 mode 全覆盖 |
|
||||
| permissionModeFromString — invalid | 默认值 |
|
||||
| permissionModeFromString — case insensitive | 大小写 |
|
||||
| isPermissionMode — valid strings | true |
|
||||
| isPermissionMode — invalid strings | false |
|
||||
|
||||
---
|
||||
|
||||
## 18.4 `src/tools/shared/__tests__/gitOperationTracking.test.ts` — mock analytics(+4 tests)
|
||||
|
||||
**问题**: 未 mock analytics 依赖,测试产生副作用
|
||||
**修复**: 添加 `mock.module("src/services/analytics/...", ...)`
|
||||
|
||||
新增测试:
|
||||
|
||||
| 测试用例 | 验证点 |
|
||||
|---------|--------|
|
||||
| parseGitCommitId — all GH PR actions | 补齐 6 个 action |
|
||||
| detectGitOperation — no analytics call | mock 验证 |
|
||||
| detectGitCommitId — various formats | SHA/短 SHA/HEAD |
|
||||
| git operation tracking — edge cases | 空输入、畸形输入 |
|
||||
|
||||
---
|
||||
|
||||
## 排除清单
|
||||
|
||||
以下模块 **不纳入测试**,原因合理:
|
||||
|
||||
| 模块 | 行数 | 排除原因 |
|
||||
|------|------|---------|
|
||||
| `query.ts` | 1732 | 核心循环,40+ 依赖,需完整集成环境 |
|
||||
| `QueryEngine.ts` | 1320 | 编排器,30+ 依赖 |
|
||||
| `utils/hooks.ts` | 5121 | 51 exports,spawn 子进程 |
|
||||
| `utils/config.ts` | 1817 | 文件系统 + lockfile + 全局状态 |
|
||||
| `utils/auth.ts` | 2002 | 多 provider 认证,平台特定 |
|
||||
| `utils/fileHistory.ts` | 1115 | 重 I/O 文件备份 |
|
||||
| `utils/sessionRestore.ts` | 551 | 恢复状态涉及多个子系统 |
|
||||
| `utils/ripgrep.ts` | 679 | spawn 子进程 |
|
||||
| `utils/yaml.ts` | 15 | 两行 wrapper |
|
||||
| `utils/lockfile.ts` | 43 | trivial wrapper |
|
||||
| `screens/` / `components/` | — | Ink 渲染测试环境 |
|
||||
| `bridge/` / `remote/` / `ssh/` | — | 网络层 |
|
||||
| `daemon/` / `server/` | — | 进程管理 |
|
||||
|
||||
---
|
||||
|
||||
## 预期成果
|
||||
|
||||
| 指标 | Phase 16 后 | Phase 17 后 | Phase 18 后 |
|
||||
|------|-----------|-----------|-----------|
|
||||
| 测试数 | ~1417 | ~1567 | ~1597 |
|
||||
| 文件数 | 76 | 87 | 91 |
|
||||
| WEAK 文件 | 6 | 4 | **0** |
|
||||
Loading…
x
Reference in New Issue
Block a user