# 项目分析报告（已归档）

> **状态：历史快照，不再是现状描述。**
>
> 这份报告写于 `0.3.0` 发布**之前**，此后 `0.3.0` 修掉的安全与性能问题**没有回写到这里** ——
> 结果是它把一批**已经修好**的问题仍然列为「当前问题」，还在同一份文档里给出了两组互相矛盾的
> 验证数字（§0 记 `smoke 23/23`、`e2e 39/0跳/0失败`，§8 表却记 `smoke 17/17`、`e2e 35通过/1跳过`；
> §1.2 标题写「90 个 `.ts`」，同节树状注释又写「42 个测试文件」）。
>
> **要读现状，请去：**
> - 一轮完整的自评（做得好的 / **当前确实存在**的问题 / 提升顺序）：[`智能体视角评估.md`](智能体视角评估.md)
> - 规模与用例数（生成的、不会漂）：[`README`](../README.md#numbers) 的 Numbers 表
> - 逐版本的改动与修复依据：[`CHANGELOG`](../CHANGELOG.md)
> - 与 `pi-web-ui` 的能力对照：[`能力与边界.md`](能力与边界.md)
> - 独立的代码走查（含实测证据与已确认的文档矛盾）：[`engineering-review.md`](engineering-review.md)
>
> **为什么保留而不是删掉：** 它的**问题清单本身**仍然有价值——下面这张对照表就是这份文档现在唯一的用途：
> 让「当时判断的问题 → 现在是什么状态」可追溯，而不是让读者以为那些问题还在。

---

## 本报告各问题项的现状对照

| 本报告当时的判断 | 现在的状态 | 依据 |
|---|---|---|
| `FileService` 把整个 `process.cwd()` 暴露为文件服务，`GET /files/read?path=.env` 可读出 `PI_API_KEY` | **已修复**。新增 `src/secret-files.ts` 的默认名单，`/files/read`、`/files/raw`、写入与**目录列表预览**全部按 basename 拦（fail-closed），符号链接按**真实目标**名再判一次 | `CHANGELOG` 0.3.0 `Security`；`src/files/service.ts` |
| `PI_API_KEY` 会被子进程继承（`exec` / MCP） | **已修复**（含本轮补漏）。`src/child-env.ts` 剔除 `PI_API_KEY*`；MCP、Windows 分支、`taskkill` 与 **Linux/macOS 的 `/bin/sh` 主执行路径**同口径 | `src/exec/runner.ts`；`src/exec/runner.test.ts` 的「子进程不继承模型密钥」 |
| `guard` 的路径校验不解析符号链接 | **已修复**。`isPathInsideCwd` 解析「最近已存在祖先」的 realpath，与 `FileService`、`exec` 同强度 | `CHANGELOG` 0.3.0 `Security` |
| 危险命令正则表有两份且**已经漂移**（审批表多 `git-destructive` / `chmod-recursive`） | **已修复**。收敛到 `src/extensions/shell-rules.ts` 单一事实源；guard 的硬拦截集合刻意保持 6 条不变，行为无变化 | `CHANGELOG` 0.3.0 `Security` |
| `buildState()` 每个快照周期重复投影 / 重复遍历会话树 | **已修复**。投影与「消息→条目 id」复用同一次结果；「消息→条目 id」与「官方标签」合并为 `contextIndex()` 一趟遍历；`overBudget` 用同一个 token 总数短路 | `src/session-hub.ts` 的 `contextIndex()` / `buildState()` |
| `session.getSessionStats()` 每周期都全量过滤会话文件 | **已缓解**。它仍是 SDK 侧的全量过滤，但脚手架侧改为**按事件失效、周期内复用**（流式增量期间不重取；任何边界事件一律作废） | `src/session-hub.ts` 的 `sessionStats` / `stats()`；`src/integration.test.ts` 的对应用例 |
| `db.query()` 用 `stmt.all()` 全量载入再截断 | **已修复**。改 `stmt.iterate()` 边取边判，只物化前 `maxRows` 行，`totalRows` 仍是真实总数 | `CHANGELOG` 0.3.0 `Fixed` |
| `/files/upload` 把二进制 `toString("utf8")` 导致内容损坏 | **已修复**。新增 `FileService.writeBinary()` | `CHANGELOG` 0.3.0 `Fixed` |
| `buildState` 残留死字段 `rev: 0` | **已修复**。契约改为 `Omit<UiState, "rev">`，revision 链由发射器独占 | `CHANGELOG` 0.3.0 `Fixed` |
| `isClientMessage` 只校验 `type` 是字符串 | **已修复**。拒绝数组、空串与纯空白 `type` | `CHANGELOG` 0.3.0 `Fixed` |
| 子代理等待队列无上限（堆起永不 resolve 的 Promise） | **已修复**。`DEFAULT_MAX_QUEUED_SUBAGENTS = 32`，超限明确回绝并给出可读原因 | `src/subagents/index.ts` |
| `Metrics` 对未知指标名抛错，能带崩一次请求 | **已修复**。收敛为 `keyOf()`，未知名字静默忽略 | `CHANGELOG` 0.3.0 `Fixed` |
| SDK 私有形状访问散落各处，每处各写一遍兜底 | **已收敛**。统一到 `src/sdk-adapter.ts`（本轮把最后一处 `navigateTree` 也收了进去） | `src/sdk-adapter.ts` |
| REST 与 WS 各自实现模型列表整形 / 能力目录构建 | **已收敛**。`src/capabilities.ts` 与 `src/models.ts` 的 `toUiModel` 为两条通道共用 | `src/capabilities.ts`；`src/models.ts` |
| `createSessionHub` 是 8 个位置参数 | **有意保留**。已标 `@deprecated` 并提供具名替代 `createSessionHubFromOptions`；删掉它是**破坏性变更**，与 `CHANGELOG` 里「既有嵌入方零改动」的承诺冲突 | `src/session-hub.ts`；`src/lib.ts` |
| 审批默认关闭（`toolApprovalEnabled: false`） | **仍如此，且是有意取舍**。默认态真正生效的是 guard 正则与 `deny` 类规则；`ask` 类规则要显式打开审批 | `src/settings.ts`；`SECURITY.md` 的「未受保护项」 |
| `bash` 命令体不受路径约束（不是沙箱） | **仍如此，且已明确降调**。README / SECURITY.md 都已写清「不是沙箱、命令体无路径约束、要隔离用容器」 | `SECURITY.md`；`README.md` 的 coding 段落 |
| 测试计数在各文档间口径不一 | **已从根上消除**。README 的数字改为脚本生成（`npm run docs:numbers:check` 在 CI 里挡漂移），其余文档改为**引用**而不是复述 | `scripts/visualization/generate_readme_numbers.mjs`；`package.json` 的 `verify` |

---

## 本报告里已经完全不成立的说法（不要再引用）

- 「`docs/` 下只有三份活文档」——实际有 7 份手写 md，另有 3 张**生成**的 SVG 与 1 个静态全景页。
- 「前端未接 HITL 反问」——`HitlDialog` 已挂进前端。
- 「42 test files · 376 cases」/「smoke 17/17」/「e2e 35 通过 / 1 跳过」——同一份文档里的三组数字互相对不上，且都不是当前值。
- 「90 个 `.ts`（56 非测试 + 34 测试）」——与同节树状注释里的 42 个测试文件也不一致。

---

*这份归档页只做一件事：把「当时的判断」标成「当时的」，并把去路指清楚。它的正文已通过 git 历史保留。*
