# Prelude 设计文档

> **状态**：v1（2026-10-27），取代旧的 agent-core.md 和 pi-testmode-plan.md（已删除，可从提交 `f5d01c7` 查看）
> **一句话**：测试人员只给一个测试目标，AI 自主完成测试并交付报告；每次测完都复盘，把经验写进跨项目共享的知识库。目标是更快地发现问题，尤其是测试人员没覆盖、没注意到的地方。

---

## 1. 为什么改

旧实现（pi-testmode，约 1.1 万行）把测试方法、适用判断和经验更新都写成了代码和数据结构。真实运行暴露了三个问题：

- **慢**：一个意图被拆成多个 Mission，每次交接都要重读材料。直接对模型说一次反而更快。
- **框架在修自己的问题**：5 次真实运行修掉的问题，大多是预算口径、评分算法、作弊检测误报这类框架自身的问题。
- **核心收益没有证据**：模板图带来的成本下降，一份写得好的 Skill 很可能也能做到。

新设计的原则：**判断交给模型，代码只做模型自己做不到的事。**

## 2. 原则

| # | 原则 | 含义 |
|---|---|---|
| P1 | 一个上下文做到底 | 测试主体由同一个模型在同一会话里连续完成。只有独立复验另开上下文 |
| P2 | 代码只做"支撑" | 包括：知识持久化与版本管理、独立复验、安全边界、进度展示、无人值守运行。不替模型选方法、不替模型判断经验是否适用 |
| P3 | 经验是模型能读的文字 | 经验写成 Skill，靠 pi 原生的按需加载（平时只放描述，用到才读正文） |
| P4 | 只有证据支持的结论才能进知识库 | 自生成、未经检验的经验和工具，收益可能为负 |
| P5 | 负反馈是"用错了地方" | 经验遇到反例时补充"不适用条件"，不删除原内容 |
| P6 | 重要文件只经代码写入 | 跨项目知识库只能通过扩展工具修改，由代码调用 git 提交 |

## 3. 分工

| 谁 | 负责 |
|---|---|
| **Prelude 扩展**（pi 扩展，代码） | 初始化；`/test` 模式入口；进度与状态展示；知识库的挂载、写入工具与 git 同步；授权范围与危险操作拦截；独立复验；结束检查 |
| **模型** | 接收信息、勾勒目标轮廓、确定方向、选用或自写工具、执行测试、记录发现、写报告、复盘；在当前项目写系统笔记 |
| **用户** | 给出测试目标；回答一次性的接收问题；维护 `scope.json`；处理 git 冲突 |

## 4. 一次测试的流程

流程写在**方法论 Skill**（`test-pilot`）里，由模型执行。代码只提供工具和检查。

1. **接收**：判断目标类型（网站、接口、安卓 apk、Windows 程序、CLI 或服务等），读系统笔记和相关的类别经验，然后**一次性**列出所有缺少的信息问用户：环境地址、测试账号、允许的操作、是否授权安全测试等。之后不再打断用户。
2. **勾勒轮廓**：探索目标，记录功能面、接口、角色与权限、数据流、技术栈。
3. **确定方向**：用户指定了方向就按用户的。否则按风险和"盲区清单"排定优先级。
4. **选工具**：先用知识库里的工具和成熟引擎（Playwright、adb、ZAP、nuclei 等），缺了再自己写。
5. **执行**：每个发现都要有**可执行的复现脚本**（退出码非 0 表示问题复现），并实时用 `finding_record` 记录。
6. **复验**：所有发现批量交给独立复验（§7）。
7. **报告**：写清楚测了什么、没测什么及原因、每个问题的复现步骤、证据和复验结论。
8. **复盘**：用知识库工具写入经验和工具，在项目里更新系统笔记。

**盲区清单**（方法论 Skill 内，每次至少过一遍）：边界与异常路径、并发与重复提交、状态流转、权限越权、输入编码与国际化、错误提示与恢复、可访问性、性能退化、配置与环境差异。

## 5. 知识

### 5.1 三层

| 层 | 内容 | 位置 | 谁写 |
|---|---|---|---|
| 方法论 | 怎么测，跟系统无关 | 知识库 `skills/test-pilot/` | 随扩展发布，人工维护 |
| 类别经验 + 工具 | 一类系统的共性、常见坑、可复用工具 | 知识库 `skills/<类别>/` | 模型通过扩展工具写入 |
| 系统笔记 | 某个被测系统的特点 | 被测项目 `.prelude/notes/` | 模型直接写 |

**类别按维度组合**，不做成分类树。一个系统可以同时属于多个类别，每个维度对应若干类别 Skill：

- 平台：`web`、`android`、`windows-desktop`、`cli`
- 接口：`rest-api`、`graphql`
- 业务领域：`ecommerce`
- 技术栈：`electron`、`spring`

遇到未知目标时，模型选出最接近的几个类别来读。测完后，如果确实是新的一类，就起草一个新类别。

### 5.2 一条经验

每条经验存为一个 JSON 文件：`skills/<类别>/lessons/<id>.json`。一条一个文件，可以减少合并冲突。

```json
{
  "id": "les-7f3a",
  "title": "异步扣减的库存不能立即读取校验",
  "applies_when": "库存或余额扣减经消息队列异步完成",
  "not_applies_when": ["扣减在同一事务内同步完成"],
  "guidance": "压测后先轮询到数值稳定（≥2 次相同读数）再判定守恒",
  "status": "candidate",
  "evidence": [
    { "project": "p-1a2b", "report": "reports/2026-10-27-order.md", "finding": "f-003", "at": "2026-10-27" }
  ],
  "revisions": []
}
```

- `project` 由扩展填写（取自被测项目的 `.prelude/project.json`），模型不能自己声称。
- 模型不直接读 JSON。扩展根据 lessons 生成每个类别的 `SKILL.md`：正式经验放在正文，候选经验单列在"待验证"一节。

### 5.3 晋升与修订（代码强制）

- 新经验一律以 `candidate` 状态入库。
- 当 `evidence` 中出现 **≥2 个不同的 project**，自动升为 `confirmed`。
- **修订**：遇到反例时追加 `not_applies_when` 或改进 `guidance`，同时把修订前的文本存进 `revisions`。不能删除经验，只能标记 `retired` 并写明理由。

### 5.4 工具

- 工具放在 `skills/<类别>/scripts/<tool>/`，包含脚本、`README.md`（用法与已知的失败情况）和 `selfcheck` 命令。
- 入库流程：模型先在项目的 `.prelude/work/` 里写好工具并实际用过，再调用 `kb_tool_add`。扩展运行 selfcheck，通过后才复制进知识库。
- 复用时出错：能修就修，并在 README 里记下"在什么情况下失败"；修不好就标记为废弃。

## 6. 知识库与 git

### 6.1 配置

`~/.prelude/config.json`：

```json
{
  "kb": {
    "dir": "~/.prelude/kb",
    "remote": "git@example.com:team/prelude-kb.git",
    "mainBranch": "main",
    "branch": "kb/alice-desk01-3f9c"
  }
}
```

`branch` 在初始化时自动生成（`kb/<用户名>-<主机名>-<4 位随机码>`），用户可以修改。扩展会检查远程上是否已有同名分支。

### 6.2 流程（全部由代码调用 git）

| 时机 | 动作 |
|---|---|
| 进入 `/test` | `fetch` → 把 `origin/<main>` 合并进自己的分支。**有冲突就问用户** |
| 每次知识写入 | 在自己的分支上提交，提交信息由代码生成（来源项目、报告、改动摘要） |
| 测试结束 | 把自己的分支合并进 main → 推送 main 和自己的分支。有冲突时**不阻塞报告**：改动保留在自己的分支上，在状态栏提示，下次进入 `/test` 时再询问 |
| 网络或鉴权失败 | 用本地知识库继续，并在状态栏提示；没推送的提交下次再推 |

- **冲突询问**：对每个冲突文件给出选项：保留本地 / 采用远端 / 两者都保留（仅限经验文件，另存为新 id）/ 中止合并。
- **凭据**：git 使用用户本机已有的凭据，扩展不读取、也不保存任何凭据。
- **防止绕过**：模型只能读知识库，`write` / `edit` 和 shell 写入都会被闸门拦截。扩展在每次进入模式和每次写入前检查知识库工作区是否干净；发现不是由工具产生的改动，就还原并记入审计。
- **并发**：所有知识写入在进程内串行执行，并用知识库目录下的锁文件防止多个进程同时写入。

## 7. 独立复验

模型在一个上下文里做测试，会有自我确认的倾向。复验分两步，第一步由代码完成：

1. **复现**（代码）：对每个发现，在干净的工作目录里重跑它的复现脚本 k 次（默认 3 次），按退出码算复现率。≥ 0.6 判为复现。
2. **审查**（新上下文的模型）：开一个只读子会话（`createAgentSession`，只有 `read` / `bash`），只给它发现描述、复现脚本和证据，**不给测试过程**。由它判断脚本是否真的证明了所声称的问题，比如断言是否过弱、预期值是否抄自实际输出。结论为 confirmed / rejected。

- 只有"复现且审查通过"的发现才计入报告中的缺陷。
- UI/UX 的体验建议没有复现脚本，只做审查，在报告里列为"建议"。

## 8. 被测项目里的 `.prelude/`

```
.prelude/
├── project.json   # 项目 id（初始化时生成）
├── scope.json     # 授权范围，只能人工维护，模型写入一律被拦截
├── notes/         # 系统笔记，模型自由写
├── reports/       # 测试报告
├── work/          # 模型的临时工作区（脚本、草稿工具）
├── evidence/      # 证据文件（按 sha256 命名）
├── runs/          # 每次测试的状态：state.json、findings.jsonl
└── audit.jsonl    # 审计日志：只追加，每行带前一行的哈希
```

- 初始化时询问用户 `notes/` 和 `reports/` 是否纳入项目版本管理，并据此生成 `.prelude/.gitignore`。`work/`、`evidence/`、`runs/`、`audit.jsonl` 永远忽略。
- 项目根的 `AGENTS.md` 中由扩展追加（不存在则新建）一段说明：系统笔记写在哪里、什么时候更新。测试结束时，扩展检查本次是否更新了笔记，没有则提醒模型一次。

## 9. 安全边界

- **授权范围**：`scope.json` 写明允许的目标、测试类型（功能、负载、安全扫描、破坏性）和强度上限。缺失时只允许只读探索。
- **闸门**：在 pi 的 `tool_call` 钩子里拦截：
  - 访问范围外主机；
  - 危险命令；
  - 写入受保护路径：知识库、`scope.json`、`audit.jsonl`、扩展目录；
  - 首次安装依赖需要审批。
  
  闸门出错时一律拦截。
- **隔离级别**：Windows 上只有工具层策略，**不是安全边界**；报告中写明这一点，并建议改用官方 Docker 方案运行 pi。macOS/Linux 可以使用改编自官方 sandbox 示例的 OS 级隔离。
- **打包程序**：exe 建议放在 Windows Sandbox 或虚拟机中运行，apk 放在模拟器中运行。不在用户主机上直接安装被测程序。

## 10. 进度与状态

- **状态栏**：当前阶段（接收 / 轮廓 / 执行 / 复验 / 报告 / 复盘）和已发现问题数（按严重程度分）。
- **组件区**：最近的发现列表，以及知识库同步状态（已同步 / 待推送 / 有冲突）。
- 模型用 `test_progress` 报告阶段切换，用 `finding_record` 记录发现；两者都写入 `runs/<id>/` 下的文件。

## 11. 不做的事

模板图、统计模型、自动分诊和门禁算法、多角色调度、数据库、向量检索、为模型设定方法论之外的固定流程。如果真实使用中发现某一环确实需要结构，再在那一环加最小的结构，并记入计划的决策表。