> ## Documentation Index
> Fetch the complete documentation index at: https://aicoding.cscitech.top/llms.txt
> Use this file to discover all available pages before exploring further.

# 02-探索陌生代码库

> 用 Codex 只读地从目录、入口、调用链一路追到测试映射，建立可核对的代码库理解，而不是一上来修改代码。

## 本页解决什么问题

接手一个陌生代码库时，最危险的第一步不是“不会写代码”，而是**还没有形成系统模型，就开始改代码**。你可能看到了一个报错文件，却不知道它由哪个入口触发；看到了一个函数，却不知道哪些调用方依赖它；看到了一个测试，却不知道它覆盖的是哪条业务路径。

本页只讲一件事：**如何让 Codex 在只读边界内，从目录结构走到入口，再沿调用链走到测试映射，最后输出一份可供人核对的探索报告。**

这里的“只读”是行为边界，不是让 Codex 少读几个文件。探索阶段可以广泛读取、搜索、运行不会改变项目状态的检查命令，但不修改源代码、测试、配置、依赖或生成物。等你确认了范围和事实，再决定是否切换到计划或实施工作流。

你将学会：

* 如何先确认工作区、规则文件和项目入口。
* 如何从目录结构筛出真正值得阅读的文件。
* 如何找到 Web、CLI、任务队列和脚本等不同类型的入口。
* 如何用符号搜索和证据逐段追踪调用链。
* 如何把生产代码映射到测试文件、测试命令和缺失覆盖。
* 如何编写 Codex 探索提示词，让它报告证据而不是猜测。
* 什么时候继续只读探索，什么时候切入计划模式。
* 如何识别“目录摘要很漂亮但结论不可靠”等常见错误。
* 如何用一个真实项目演练完整的目录到测试映射流程。

> 本页使用的 Codex 命令和界面可能随版本变化。运行前以本机的 `codex --help`、相关子命令的 `--help` 和官方文档为准。参考资料：`参考/codex/02-core-concepts.md`、`参考/codex/14-workflows.md`、`参考/codex/11-agents-md.md`。

## 先定边界：探索不是实现

“帮我了解这个项目”和“帮我修复登录问题”是两种不同任务。前者的产物是地图、证据和未知项；后者的产物是修改和验证。把两者混在一个会话里，Codex 很容易在你还没确认根因时提出并执行改动。

探索阶段的目标可以写成四个问题：

| 问题          | 需要得到的证据                 | 暂时不做什么           |
| ----------- | ----------------------- | ---------------- |
| 项目由什么组成？    | 目录、包、构建和运行入口            | 不重排目录，不补“更合理”的结构 |
| 请求或命令从哪里进入？ | 路由、命令注册、消费者或脚本入口        | 不修改路由，不新增日志      |
| 它经过哪些调用点？   | 符号定义、调用方、数据流和分支         | 不重命名，不提取函数       |
| 哪些测试能证明它？   | 测试文件、用例、fixture、命令和覆盖缺口 | 不为了“验证”而新建测试     |

在提示词中明确以下非目标：

```text theme={null}
这是只读探索任务。
请读取、搜索和运行不会修改工作区的检查命令，但不要编辑、创建、删除或重命名任何文件。
不要安装依赖，不要联网，不要提交或推送，不要修改配置和生成物。
所有结论都要附文件路径、符号名和必要的行号或命令证据；无法确认的内容标为“未知”。
```

### 只读不等于盲目禁止命令

可以使用的证据命令通常包括：

* `pwd`、`git rev-parse --show-toplevel`：确认当前位置和项目根。
* `git status --short --branch`：确认工作区状态，不修改文件。
* `find` 或 `rg --files`：列目录和文件，不读取文件内容到磁盘。
* `rg`：搜索路由、符号、命令、测试名和配置键。
* `git log`、`git blame`：了解变更背景和责任边界。
* 项目已有的 `lint`、`typecheck`、测试收集或构建信息命令，前提是它们不会写入工作区。

需要先确认再运行的命令包括：

* `npm install`、`pnpm install`、`pip install`、`go generate` 等可能改变依赖或生成文件的命令。
* 需要联网、访问云服务、读取生产数据或发送请求的命令。
* 会生成缓存、覆盖快照、写报告或启动长期运行服务的命令。
* `git clean`、批量删除、迁移、格式化和自动修复命令。

“只读”不是只看 Codex 的总结。你要看它实际执行的命令和输出，并用 `git status`、`git diff --stat` 证明没有意外改动。

## 第一步：确定工作区和起点

不要从聊天上下文中猜项目根。先让终端给出证据，再启动 Codex，或者在会话里明确要求它执行同样的检查。

### 终端基线检查

在目标项目目录执行：

```bash theme={null}
pwd
git rev-parse --show-toplevel
git status --short --branch
rg --files -g 'AGENTS.md' -g 'AGENTS.override.md' -g 'README*' -g 'package.json' -g 'pyproject.toml' -g 'go.mod' -g 'Cargo.toml'
```

Windows PowerShell 可以使用：

```powershell theme={null}
Get-Location
git rev-parse --show-toplevel
git status --short --branch
rg --files -g 'AGENTS.md' -g 'AGENTS.override.md' -g 'README*' -g 'package.json' -g 'pyproject.toml' -g 'go.mod' -g 'Cargo.toml'
```

记录以下基线：

* Codex 实际工作的绝对路径。
* Git 根目录，而不是编辑器当前打开的子目录。
* 当前分支和已有未提交修改。
* 项目使用的语言、包管理器和主要构建工具。
* 是否存在 `AGENTS.md`、`AGENTS.override.md` 或配置中声明的备选规则文件。

已有未提交修改属于工作区事实。探索报告应把它们列为背景，不要把它们当成你本轮的结论，也不要为了“干净”而恢复或覆盖。

### 启动只读 Codex

先查看本机支持的参数：

```bash theme={null}
codex --help
codex --version
```

具体版本支持的只读参数可能不同。若本机支持沙箱参数，可以在启动时使用只读模式：

```bash theme={null}
codex --sandbox read-only
```

也可以进入会话后查看权限菜单，根据本机界面切换到只读配置。参考资料中强调，沙箱和审批是两个维度：沙箱决定能否越过文件或网络边界，审批决定何时停下来询问。探索任务的判断标准是**实际不能写入工作区**，不能只凭界面上“自动”或“询问”的标签判断。

启动后第一句先让 Codex 报告环境：

```text theme={null}
这是一个只读探索会话。先不要修改任何内容。
请报告：
1. 当前工作目录和你识别到的项目根目录；
2. 当前 Git 分支及工作区是否已有修改；
3. 发现了哪些 AGENTS.md 或 AGENTS.override.md；
4. 你准备使用哪些只读命令，以及每条命令要证明什么。
如果无法确认某项，请明确写“无法确认”，不要猜。
```

这一步的价值在于把“它到底在哪工作”和“它准备怎么查”提前暴露出来。若工作目录不对，先退出并在正确目录启动；不要让后面的目录和调用链结论建立在错误根目录上。

## AGENTS.md 的读取顺序

探索陌生项目时，规则文件本身就是第一批上下文。不要只打开仓库根目录的 `AGENTS.md` 就认为规则读完了。

按照参考资料中的 Codex 发现机制，通常按以下顺序理解：

1. 先看 Codex 主目录中的全局规则。默认是 `~/.codex/`，如果设置了 `CODEX_HOME`，则以该环境变量指向的目录为准。
2. 在全局层，如果同时存在 `AGENTS.override.md` 和 `AGENTS.md`，优先取 `AGENTS.override.md`；这一层只取一个非空文件。
3. 确定项目根后，从项目根目录向下走到当前工作目录。
4. 每个目录依次查找 `AGENTS.override.md`、`AGENTS.md`，再查找配置中的 `project_doc_fallback_filenames` 备选文件名；每个目录最多取一个非空文件。
5. 找到的项目级规则按“根目录到当前目录”的顺序合并。越靠近当前目录的规则越晚出现，冲突时通常以更具体的规则为准。

可以把它记成：

```text theme={null}
全局层二选一
  -> Git 项目根的规则
  -> 中间目录的规则
  -> 当前目录的规则
  -> 从根到叶合并，越近越具体
```

这里有两个容易误解的地方：

* `AGENTS.override.md` 只替换同一目录的候选文件，不会清掉其他目录已经合并的规则。
* `AGENTS.md` 是指导来源，不是目录索引。它可能规定测试命令、禁止访问的目录、生成文件策略和代码库特有约定；这些内容必须进入探索计划的约束。

让 Codex 显式报告规则来源：

```text theme={null}
请只读检查本次会话可能生效的项目指导文件。
按“全局层、项目根、从根到当前目录的每一级”列出：
- 实际找到的文件路径；
- 每个文件的作用范围；
- 是否有同级 override 导致普通 AGENTS.md 被跳过；
- 与本次探索有关的测试命令、禁止操作和目录约束。
不要把未实际读取的文件当作已加载。
```

如果结论和你的预期不一致，按顺序排查：当前目录是否正确、Git 根是否正确、`CODEX_HOME` 是否改变、是否存在更近的 override、规则文件是否为空、配置中的备选文件名是否拼写正确。改动配置后需要重启 Codex，不能在原会话中假定新规则已经生效。

## 第二步：从目录建立结构地图

目录探索的目的不是把每个文件都读一遍，而是建立“边界和职责”的假设，再用入口和调用链验证假设。

### 先看一级结构

先执行低成本命令：

```bash theme={null}
rg --files -g '!node_modules' -g '!vendor' -g '!dist' -g '!build' -g '!coverage' | sort
```

若文件很多，先按目录统计或只看一级目录：

```bash theme={null}
find . -maxdepth 2 -type d \
  -not -path './.git*' \
  -not -path './node_modules*' \
  -not -path './vendor*' \
  -not -path './dist*' \
  -not -path './build*' | sort
```

Windows 环境也可以使用 `rg --files`，不要把 `.git`、依赖目录、构建产物和覆盖率目录当成业务模块。

让 Codex 输出目录地图时，要求它区分证据和推断：

```text theme={null}
请只读检查项目的目录和顶层配置，先不要深入实现细节。
输出：
1. 一级目录及其可能职责；
2. 语言、框架、包管理器和构建工具的证据；
3. 入口候选（Web、CLI、队列、定时任务、脚本）；
4. 测试目录、测试配置和测试命令候选；
5. 生成物、第三方依赖和应暂时排除的目录。
每一项都附文件路径或命令输出证据，并把“已确认”和“待确认”分开。
```

### 优先阅读的文件

不同项目的文件名会变化，但优先级通常稳定：

| 优先级 | 文件类型                                       | 要回答的问题             |
| --- | ------------------------------------------ | ------------------ |
| 1   | `AGENTS.md`、`README`                       | 项目规则、启动方式、边界是什么？   |
| 2   | `package.json`、`pyproject.toml`、`go.mod` 等 | 运行时、脚本、依赖和测试入口是什么？ |
| 3   | `docker-compose.yml`、工作流文件、Makefile        | 服务如何组合、CI 如何验证？    |
| 4   | 路由、命令注册、主函数、应用工厂                           | 用户或系统从哪里进入？        |
| 5   | 入口依赖的服务、控制器、用例和模型                          | 请求如何向下流动？          |
| 6   | 对应测试配置和测试文件                                | 哪些行为已有证据？          |

不要一开始读取所有锁文件、编译产物和大型数据文件。它们可能有用，但通常不能帮助你快速建立业务调用链。

### 目录不是架构结论

目录名只能产生候选假设。例如 `services/` 可能是领域服务，也可能只是 HTTP 客户端；`utils/` 可能承载关键权限逻辑；`tests/` 可能只包含端到端测试。报告中应使用“看起来”“候选”“待通过符号引用确认”等措辞，直到你读到定义和调用点。

错误的目录结论：

```text theme={null}
项目有 controllers、services、models，所以它是标准三层架构。
```

更可靠的结论：

```text theme={null}
`src/api/routes.ts` 注册 HTTP 路由；`src/application/order-service.ts`
被路由处理器直接调用；`src/domain/order.ts` 定义状态转换。
因此当前订单创建链路呈现“路由 -> 应用服务 -> 领域对象”的实际依赖，
不能仅凭目录名把整个项目概括为严格三层架构。
```

## 第三步：从目录找到真实入口

入口是“外部事件第一次进入业务代码的位置”。不同项目入口不同：

| 类型     | 常见入口证据                             | 搜索方向                 |
| ------ | ---------------------------------- | -------------------- |
| Web 服务 | `app`、`router`、`routes`、控制器注册      | 路由路径、HTTP 方法、应用工厂    |
| CLI    | `main`、`argparse`、`commander`、命令注册 | `bin`、`scripts`、子命令名 |
| 队列消费者  | `worker`、`consumer`、`handler`、任务注册 | 队列名、任务名、消费函数         |
| 定时任务   | cron、scheduler、job 注册              | 任务函数和调度配置            |
| 数据管道   | `pipeline`、`ingest`、`run`          | 输入文件、消息和输出写入         |
| 前端     | 路由表、页面组件、事件处理器                     | URL、组件名、按钮文本         |

先问 Codex 找候选，不要直接让它“讲完整架构”：

```text theme={null}
基于刚才的目录证据，请只读定位与“订单创建”有关的真实入口。
分别检查：
- HTTP 路由或前端事件入口；
- CLI、队列、定时任务等非 HTTP 入口；
- 每个入口对应的文件、符号和注册位置。
只列出实际找到的入口。若只有候选，请说明还缺哪条证据来确认。
不要修改代码，也不要为了验证而启动外部服务。
```

再用搜索命令核对 Codex 的候选：

```bash theme={null}
rg -n "(route|router|app\.|listen\(|main\(|command|consumer|handler|schedule|cron)" src app lib scripts test tests
rg -n "(POST|GET|PUT|DELETE|createOrder|create_order|订单)" src app lib
```

搜索词要根据项目语言调整。不要把一个大而模糊的正则表达式当作证据；每个候选都要回到文件中阅读注册语句和定义。

入口报告建议包含：

```text theme={null}
入口 A
- 触发方式：HTTP POST /orders
- 注册位置：src/api/routes.ts:42
- 处理符号：createOrderHandler
- 下一跳：src/application/order-service.ts:createOrder
- 已确认依据：路由注册的处理器引用

入口 B
- 触发方式：队列 order.created
- 注册位置：src/worker/registry.ts:18
- 处理符号：handleOrderCreated
- 状态：已发现注册，尚未追到持久化调用
```

## 第四步：沿调用链追踪

目录告诉你“可能在哪里”，入口告诉你“从哪里开始”，调用链则回答“实际经过什么”。一次有效的调用链追踪至少要完成这五件事：

1. 记录入口函数的定义和参数。
2. 找出它直接调用的本地符号。
3. 对每个关键符号继续追到实现，而不是停在导入语句。
4. 标注条件分支、异常处理、外部调用和数据转换。
5. 到达持久化、响应构造、消息发布或任务完成等终点。

### 先追一条主路径

不要同时追十条流程。先选择一个具体场景，例如“有效请求创建订单”，写清触发条件和预期终点：

```text theme={null}
请沿“HTTP POST /orders，输入有效，最终创建订单并返回 201”这条主路径只读追踪调用链。
从路由注册开始，逐步列出：
- 文件路径、符号名和行号；
- 当前函数收到和返回的数据；
- 下一跳是如何被调用的；
- 校验、权限、事务、外部 I/O 和异常分支；
- 你依据的实际代码证据。
不要跳过中间函数，也不要把函数名相似当作调用关系。
遇到动态分发、依赖注入或反射时，标出不确定点和需要继续搜索的注册表。
```

### 用符号搜索补齐链路

Codex 的叙述需要被搜索证据约束。常用命令：

```bash theme={null}
rg -n "createOrder|create_order|OrderService|orderRepository" src app lib tests
rg -n "from .*order|import .*order|require\(.*order" src app lib
rg -n "new OrderService|OrderService\(|container\.resolve|register\(" src app lib
```

对 TypeScript、Python、Go 等项目，搜索导入和调用的习惯不同，但原则相同：**定义、注册、调用三类证据要分开记录**。

| 证据 | 示例                                          | 它证明什么       |
| -- | ------------------------------------------- | ----------- |
| 定义 | `order-service.ts:18` 定义 `createOrder`      | 符号实现在哪里     |
| 注册 | `container.ts:31` 绑定 `OrderService`         | 运行时可能使用哪个实现 |
| 调用 | `routes.ts:42` 调用 `service.createOrder`     | 当前链路确实经过它   |
| 测试 | `order-service.test.ts:55` 调用 `createOrder` | 测试直接覆盖了什么   |

### 画出带证据的链路

探索输出不要只写一段散文。使用编号链路，读者可以从任一节点回到文件：

```text theme={null}
1. `src/api/routes.ts:42` 注册 `POST /orders`，处理器为 `createOrderHandler`。
2. `src/api/order-handler.ts:11` 从请求体读取 `customerId` 和 `items`，调用 `orderService.createOrder(input)`。
3. `src/application/order-service.ts:24` 校验客户状态并计算订单总额。
4. `src/domain/order.ts:17` 创建领域对象；空商品列表在此处抛出 `EmptyOrderError`。
5. `src/infra/order-repository.ts:38` 在事务中写入订单和明细。
6. `src/api/order-handler.ts:29` 将持久化对象转换为响应并返回 201。
```

每一步都要能回答“下一跳为什么是它”。如果只能说“应该会调用”，就标为推断，不要升级为事实。

### 追踪分支和副作用

只追成功主路径会漏掉最重要的行为。至少追加三条分支问题：

```text theme={null}
在刚才的订单创建链路上继续只读检查：
1. 未认证用户在哪里被拒绝？
2. 空商品、库存不足和重复请求分别在哪里处理？
3. 数据库写入成功后是否发布事件、发送通知或写审计日志？
4. 外部服务超时和异常如何映射到响应？
5. 哪些步骤在事务内，哪些步骤在事务外？
每个答案附文件、符号和证据；没有证据就列入未知项。
```

特别注意以下容易断链的结构：

* 依赖注入容器让构造函数里没有直接的实现名称。
* 装饰器、注解或元数据在运行时注册路由。
* 事件发布让调用关系从同步函数跳到消费者。
* 接口、抽象类或函数类型让实际实现由配置决定。
* 生成代码、宏或代码生成脚本隐藏了入口。
* 前端请求经过 API 客户端、状态管理和中间件后才到组件。

遇到这些结构时，先搜索注册表、配置和工厂，再下结论。不要因为静态文本中没有直接调用就断言“没有调用”。

## 第五步：映射到测试

调用链完成后，马上问“哪些测试证明了这些节点”。测试映射不是只找同名文件，而是把生产路径的行为节点与测试类型对应起来。

### 先识别测试体系

读取测试配置和项目脚本：

```bash theme={null}
rg -n "(test|pytest|jest|vitest|mocha|go test|cargo test|mvn test|gradle test)" \
  package.json pyproject.toml Makefile justfile tox.ini pytest.ini jest.config.* vitest.config.* 2>/dev/null
rg --files -g '*test*' -g '*spec*' -g 'tests/**' -g 'test/**' | sort
```

让 Codex 先归类：

```text theme={null}
请只读检查项目测试体系，不创建或修改测试。
输出：
- 单元、集成、端到端和契约测试分别在哪里；
- 每类测试使用的框架和运行命令；
- fixture、mock、数据库容器或测试环境配置；
- 订单创建调用链每个关键节点已有的直接测试和间接测试。
区分“测试文件存在”和“测试确实覆盖该行为”，附用例名、符号或断言证据。
```

### 建立映射表

推荐用下表记录，而不是只写“测试比较完整”：

| 调用链节点    | 测试文件 / 用例                               | 测试类型 | 覆盖证据                 | 缺口或限制          |
| -------- | --------------------------------------- | ---- | -------------------- | -------------- |
| 路由返回 201 | `order-route.test.ts` / `creates order` | 集成   | 发起 POST 并断言状态码       | 未断言响应字段完整性     |
| 空商品拒绝    | `order.test.ts` / `rejects empty items` | 单元   | 断言 `EmptyOrderError` | 未验证 HTTP 映射    |
| 事务写入     | `repository.int.test.ts`                | 集成   | 断言订单和明细落库            | 只覆盖成功提交        |
| 事件发布     | 无                                       | 未覆盖  | 找到生产调用但无对应测试         | 需确认是否由消费者测试覆盖  |
| 未认证拒绝    | `auth-route.test.ts`                    | 集成   | 断言 401               | 未确认订单路由是否复用中间件 |

“测试文件名看起来相关”不是覆盖证据。至少要看到调用目标、输入和断言；如果使用 fixture 或共享 helper，还要继续追 helper 最终做了什么。

### 反向从测试追生产代码

正向从入口到测试容易遗漏测试专用路径，因此再反向抽查：

```text theme={null}
请从订单相关测试反向检查生产代码映射：
- 每个测试调用了哪个公开入口或内部符号；
- 是否绕过了真实路由、中间件、事务或序列化；
- mock 替代了哪些外部依赖；
- 哪些测试名称声称覆盖的场景，实际上没有走到目标分支。
请列出“测试声称证明什么”和“它实际证明什么”的差异。
```

这一步常能发现“单元测试全绿但真实请求失败”的原因：测试直接调用服务类，绕过了鉴权；mock repository 永远成功，掩盖了事务错误；测试断言返回对象，却没有检查 HTTP 状态和序列化格式。

## Codex 探索提示词工具箱

下面的提示词都以只读为前提。使用时把项目事实、路径和业务名替换成实际内容。

### 目录地图提示词

```text theme={null}
只读探索当前项目。先读取生效的 AGENTS.md 和项目 README，再检查顶层配置。
请输出目录地图、运行时和包管理器、构建/测试命令、入口候选及排除目录。
每条结论附证据路径；把已确认、合理推断、未知项分开。
不要编辑、创建、删除、重命名文件，不要安装依赖或联网。
```

### 入口定位提示词

```text theme={null}
只读定位“[业务动作]”的所有入口。
检查 HTTP 路由、CLI、队列消费者、定时任务和前端事件。
对每个入口列出触发方式、注册文件、处理符号和下一跳。
只报告实际证据，不要用目录名或相似函数名代替调用关系。
```

### 调用链提示词

```text theme={null}
从 `[入口文件:行号]` 的 `[符号]` 开始，追踪“[具体场景]”的完整调用链。
请逐步列出定义、调用、参数变化、返回值、校验、权限、事务、外部 I/O、异常分支和终点。
每一步附路径、符号和行号；遇到动态分发标出不确定点。
先只读分析，不修改任何内容。
```

### 测试映射提示词

```text theme={null}
把 `[生产入口/调用链]` 映射到现有测试。
按单元、集成、端到端、契约四类检查测试文件、用例、fixture 和命令。
对每个生产节点标记直接覆盖、间接覆盖、未覆盖或无法确认。
说明测试是否绕过路由、中间件、事务、序列化或真实外部依赖。
不要新增测试，不要改代码。
```

### 事实核验提示词

```text theme={null}
复核你上一份探索报告，只保留有代码证据的结论。
请为每条结论补上文件路径、符号名、行号或命令输出。
把“事实、推断、未知、相互矛盾的证据”分表列出。
特别检查：入口是否真实注册、调用链是否跳过了动态分发、测试是否真的断言目标行为。
```

## 命令证据：让报告可以复查

高质量探索报告不是命令清单，而是“问题、命令、输出、结论”的对应关系。建议记录以下证据：

| 探索问题   | 命令或读取动作                         | 证据应包含         |
| ------ | ------------------------------- | ------------- |
| 项目根在哪  | `git rev-parse --show-toplevel` | 绝对路径          |
| 当前是否干净 | `git status --short --branch`   | 分支和既有修改       |
| 有哪些文件  | `rg --files`                    | 排除目录后的文件列表    |
| 用什么运行  | 读取包配置和 README                   | scripts、依赖和版本 |
| 入口是什么  | `rg` 搜注册和路由                     | 注册位置和处理符号     |
| 调用到哪里  | `rg` 定义、导入、调用                   | 三类符号证据        |
| 如何验证   | 读取测试配置并收集测试                     | 命令、用例和断言      |
| 是否意外改动 | `git status`、`git diff --check` | 本轮工作区无写入证据    |

不要把完整终端日志未经整理地塞进报告。保留足以复查的关键输出，命令失败时保留退出码、错误文本和当时的工作目录。

### 有效和无效的命令证据

无效：

```text theme={null}
运行了搜索命令，发现订单服务负责订单创建。
```

问题是没有命令、路径、匹配内容，也没有说明“负责”如何被确认。

有效：

```text theme={null}
命令：rg -n "createOrder|POST /orders" src tests
证据：`src/api/routes.ts:42` 将 `POST /orders` 绑定到 `createOrderHandler`；
`src/api/order-handler.ts:11` 调用 `orderService.createOrder(input)`。
结论：HTTP 订单创建入口和应用服务下一跳已确认。
限制：尚未确认队列入口是否复用同一应用服务。
```

## 什么时候继续探索，什么时候切计划

只读探索没有“读得越多越好”的原则。它的终点是关键事实足够支撑下一步决策。

### 继续只读探索的信号

* 还不知道真正入口，只有目录名和猜测。
* 同一个符号有多个实现，运行时选择方式未确认。
* 调用链在依赖注入、事件总线或生成代码处断开。
* 关键分支、权限检查、事务边界或外部副作用未定位。
* 测试命令会写快照或启动未确认的外部服务。
* 结论和 `AGENTS.md`、README、代码证据互相矛盾。

继续探索时缩小问题，不要再次要求“分析整个项目”：

```text theme={null}
上一轮在 `src/container.ts` 的 `OrderService` 实现选择处证据不足。
请只读检查注册表、环境配置和工厂调用，回答运行测试环境实际使用哪个实现。
不要扩展到其他业务模块，也不要修改配置。
```

### 可以切计划的信号

当下面几项都明确时，可以从只读探索切到计划：

* 目标入口和关键调用链已经有路径、符号和调用证据。
* 需求影响范围和明确非目标已经写清楚。
* 相关测试命令、已有覆盖和缺口已经知道。
* `AGENTS.md` 中的项目规则和禁止操作已经纳入约束。
* 仍然存在的未知项不会改变方案，或已经显式列为风险。

此时不要直接让 Codex 改代码。先切换任务边界：

```text theme={null}
只读探索阶段完成。请基于以下已核对事实，进入计划阶段，不要编辑文件。
目标：[目标行为]
入口：[文件和符号]
调用链：[编号链路]
现有测试：[文件、用例和命令]
测试缺口：[缺口]
约束：[AGENTS.md 规则和非目标]
请给出分步计划、每步涉及文件、验证命令、风险和回滚方式。
```

复杂任务可以显式使用计划模式或 `$plan` 技能，具体名称以本机支持为准。计划不是批准书：看完计划后仍需检查文件范围、是否把未知项伪装成事实，以及验证步骤是否真正能证明目标行为。确认计划后，再启动可写工作流。

## 错误示例与修正

### 错误一：一上来就让它修

错误提示：

```text theme={null}
登录有问题，帮我修好。
```

问题：没有入口、现象、复现、约束或只读边界。Codex 可能从一个看似相关文件开始修改，最终只消除了表面错误。

修正：

```text theme={null}
先只读探索登录流程，不要修改任何文件。
从 `POST /login` 开始，定位路由、校验、认证服务、会话写入和相关测试。
输出带路径和行号的调用链，标注你无法确认的环节。
探索完成后停下，等我确认是否进入修复计划。
```

### 错误二：只看目录就下架构结论

错误结论：

```text theme={null}
有 `services` 目录，所以所有业务逻辑都在 service 层。
```

问题：目录名不是调用证据，真正逻辑可能在路由、中间件、领域对象或 SQL 查询中。

修正：读取入口处理器，搜索具体符号的定义、导入和调用，并用编号链路记录实际依赖。

### 错误三：把相似名称当调用关系

错误结论：

```text theme={null}
`createOrder` 一定调用了 `createOrderService`。
```

问题：相似名称可能来自未使用的旧实现、测试替身或不同包。

修正：确认导入来源、实例构造或容器注册，再确认实际调用表达式；动态绑定无法确认时标记未知。

### 错误四：把测试文件名当覆盖证明

错误结论：

```text theme={null}
有 `order.test.ts`，所以订单创建已经覆盖。
```

问题：测试可能只覆盖格式化函数、使用 mock 绕过入口，或只断言不抛异常。

修正：读取测试用例、输入、调用目标和断言，标记直接、间接、未覆盖和无法确认。

### 错误五：为了探索而运行会写文件的命令

错误做法：

```bash theme={null}
npm test -- --updateSnapshot
npm run lint -- --fix
go generate ./...
```

问题：这些命令可能更新快照、格式化源文件或生成代码，已经超出只读边界。

修正：先查看脚本定义和工具帮助，使用收集测试、干运行或只检查模式；无法确认时停下请求批准。运行后检查：

```bash theme={null}
git status --short
git diff --stat
git diff --check
```

### 错误六：忽略更近的 AGENTS.override.md

现象：Codex 使用了和你看到的根规则不同的测试命令。

问题：当前子目录可能存在 `AGENTS.override.md`，或全局 `CODEX_HOME` 指向了另一套规则。

修正：按全局、项目根到当前目录逐级列出实际文件，确认同级 override 的优先关系，再重启会话验证。

## 真实项目演练：从目录到测试映射

下面以一个常见的真实项目形态演练：一个使用 TypeScript、Express、PostgreSQL 和 Jest 的订单 API。项目名和路径是演练中的脱敏示例，重点是方法；不要把示例路径当成你本地项目的事实。

### 场景和目标

目标是理解“创建订单”请求：

```text theme={null}
POST /api/orders
输入：customerId、items
成功：创建订单及明细，返回 201
失败：未认证、商品为空、库存不足、重复请求和数据库错误
```

明确本轮不做：

* 不修改 API 行为。
* 不修复发现的缺陷。
* 不新增测试。
* 不启动真实支付服务，不访问生产数据库。
* 不安装依赖、不更新快照、不提交。

### 1. 读取规则和项目基线

在项目根执行：

```bash theme={null}
pwd
git rev-parse --show-toplevel
git status --short --branch
rg --files -g 'AGENTS.md' -g 'AGENTS.override.md' -g 'README*' -g 'package.json' -g 'src/**' -g 'tests/**' | sort
```

假设得到：

```text theme={null}
/home/dev/shop-api
## feature/order-audit
 M src/config/logger.ts
AGENTS.md
README.md
package.json
src/api/routes/order-routes.ts
src/api/handlers/order-handler.ts
src/application/order-service.ts
src/domain/order.ts
src/infra/order-repository.ts
src/infra/inventory-client.ts
src/worker/order-events.ts
tests/api/order-route.test.ts
tests/application/order-service.test.ts
tests/infra/order-repository.int.test.ts
```

先记录 `src/config/logger.ts` 已经被修改。它不是本轮探索产生的结果，后续报告不要把它列入本轮变更。

读取 `AGENTS.md` 和 `README.md` 后得到项目规则：测试使用 `pnpm test`，类型检查使用 `pnpm typecheck`，禁止修改迁移历史，外部服务只能使用测试替身。这个规则会影响后续验证选择。

### 2. 看包配置和目录

读取 `package.json` 的 scripts 和依赖：

```bash theme={null}
rg -n '"(scripts|test|typecheck|build|dev)"|express|jest|pg|pnpm' package.json
find src tests -maxdepth 3 -type f | sort
```

假设证据显示：

* `src/server.ts` 创建 Express 应用并监听端口。
* `src/api/routes/order-routes.ts` 注册订单路由。
* `src/application/` 存放用例服务。
* `src/infra/` 存放数据库和外部客户端。
* `tests/api/` 测试 HTTP 行为，`tests/application/` 测试业务用例，`tests/infra/` 测试数据库适配层。

此时只能说“目录呈现这种分层”，还不能说所有请求都严格遵循它。

### 3. 定位订单入口

执行：

```bash theme={null}
rg -n "orders|POST|createOrder|orderHandler" src tests
```

假设得到：

```text theme={null}
src/api/routes/order-routes.ts:12 router.post('/orders', auth, createOrderHandler)
src/api/handlers/order-handler.ts:18 export async function createOrderHandler(req, res)
src/api/handlers/order-handler.ts:27 const order = await orderService.createOrder(input)
tests/api/order-route.test.ts:34 it('creates an order', async () => {
```

入口已经由路由注册确认：`POST /orders` 先经过 `auth`，再调用 `createOrderHandler`。但还不能推断 `auth` 的拒绝行为，继续读取它的实现和测试。

### 4. 追踪成功主路径

搜索定义、导入和调用：

```bash theme={null}
rg -n "export async function createOrder|createOrder\(|new OrderService|OrderRepository|inventory" src tests
```

假设读取到以下链路：

```text theme={null}
1. `src/api/routes/order-routes.ts:12`
   注册 `POST /orders`，中间件顺序是 `auth -> createOrderHandler`。

2. `src/api/handlers/order-handler.ts:18`
   `createOrderHandler` 从 `req.body` 读取 `customerId` 和 `items`，
   调用 `orderService.createOrder({ customerId, items, requestId })`。

3. `src/application/order-service.ts:24`
   `createOrder` 检查客户状态，调用 `inventoryClient.reserve(items)`，
   然后构造 `Order` 并交给 `orderRepository.save(order)`。

4. `src/infra/inventory-client.ts:31`
   `reserve` 调用库存服务客户端。规则文件要求本地测试使用替身，
   因此这里只记录外部副作用，不访问真实服务。

5. `src/domain/order.ts:17`
   `Order.create` 拒绝空商品列表，计算金额并生成订单状态 `pending`。

6. `src/infra/order-repository.ts:38`
   `save` 在事务中写入 `orders` 和 `order_items`。

7. `src/api/handlers/order-handler.ts:31`
   将保存结果序列化为响应，返回 HTTP 201。
```

继续检查异常和副作用：

```bash theme={null}
rg -n "EmptyOrder|OutOfStock|Duplicate|rollback|commit|publish|audit|catch|status\(" src/api src/application src/domain src/infra
```

假设发现：

* 空商品由 `Order.create` 抛出 `EmptyOrderError`。
* 库存不足由 `inventoryClient.reserve` 转换为 `OutOfStockError`。
* 请求 ID 由中间件传入，但 `DuplicateRequestError` 的处理在 `order-service.ts`，需要核对是否在写库前执行。
* 订单保存成功后 `order-events.ts` 发布 `order.created`，但事件发布发生在数据库事务提交后，消费者属于另一条异步链路。

报告里应把同步主链和异步副链分开，不要把事件消费者伪装成 HTTP 请求的直接下一跳。

### 5. 映射现有测试

先读取测试命令：

```bash theme={null}
rg -n '"test"|"typecheck"|jest|testEnvironment' package.json jest.config.*
rg -n "creates an order|EmptyOrder|OutOfStock|requestId|order.created|status\(201\)" tests
```

形成映射：

| 生产节点               | 已找到的测试证据                                                          | 结论                    |
| ------------------ | ----------------------------------------------------------------- | --------------------- |
| `POST /orders` 路由  | `tests/api/order-route.test.ts:34` 发 POST 并断言 201                 | 直接覆盖成功 HTTP 路径        |
| `auth` 中间件         | `tests/api/order-route.test.ts:72` 断言未认证返回 401                    | 直接覆盖拒绝路径              |
| 空商品校验              | `tests/application/order-service.test.ts:51` 断言 `EmptyOrderError` | 直接覆盖领域规则，但未证明 HTTP 映射 |
| 库存不足               | `tests/application/order-service.test.ts:88` 使用库存 mock 并断言错误      | 覆盖应用服务分支，未访问真实客户端     |
| 数据库事务              | `tests/infra/order-repository.int.test.ts:19` 断言两张表写入             | 覆盖保存成功，未覆盖回滚失败        |
| `order.created` 事件 | `tests/worker/order-events.test.ts:23` 直接调用消费者                    | 覆盖消费者，不证明 HTTP 请求触发发布 |
| 重复请求               | 没有找到 `requestId` 相关断言                                             | 当前未覆盖，需在计划阶段决定是否补测    |

再反向检查 `tests/api/order-route.test.ts` 是否绕过了真实依赖。假设它通过 `createTestApp()` 注入内存 repository 和库存 mock，那么应写出限制：它能证明路由、中间件和响应映射，但不能证明生产数据库事务或真实库存客户端协议。

### 6. 输出本次探索结论

合格的演练报告可以收束为：

```text theme={null}
范围
- 项目根：/home/dev/shop-api
- 目标流程：POST /orders 的成功和主要失败分支
- 本轮：只读，没有新增或修改文件
- 既有工作区修改：src/config/logger.ts

已确认调用链
1. routes/order-routes.ts:12
2. auth middleware
3. api/handlers/order-handler.ts:createOrderHandler
4. application/order-service.ts:OrderService.createOrder
5. infra/inventory-client.ts:reserve
6. domain/order.ts:Order.create
7. infra/order-repository.ts:save
8. handler 序列化并返回 201

已确认测试
- API 成功和未认证：tests/api/order-route.test.ts
- 空商品和库存不足：tests/application/order-service.test.ts
- 写库成功：tests/infra/order-repository.int.test.ts
- 事件消费者：tests/worker/order-events.test.ts

主要缺口
- 重复请求分支没有找到测试证据。
- API 测试使用测试替身，不能证明真实库存协议和数据库事务。
- 事件消费者测试不等于 HTTP 到事件发布的端到端证明。

未决问题
- 数据库提交失败时，事件发布是否一定不会发生？需继续读事务与发布的边界。
- `requestId` 去重实际使用的存储位置尚未确认。

建议下一步
- 先只读确认去重和事务提交边界。
- 若目标是补回归测试，再切计划，明确只涉及 service、API 测试和必要的 fixture。
```

最后再次证明探索没有写入工作区：

```bash theme={null}
git status --short --branch
git diff --stat
git diff --check
```

预期只有开始前已经存在的 `src/config/logger.ts` 修改，没有本轮新增文件或 diff。

## 探索输出模板

将以下模板贴给 Codex 或用于自己的笔记。它强制报告从目录到测试的证据链：

```markdown theme={null}
# [项目 / 功能] 只读探索报告

## 范围与边界
- 工作目录：
- 项目根：
- 当前分支：
- 探索目标：
- 明确非目标：
- 本轮是否写入工作区：

## 生效规则
- 全局规则来源：
- 项目根到当前目录的规则来源：
- override 和备选文件名：
- 相关测试、构建和禁止操作：

## 目录地图
| 路径 | 实际职责 | 证据 | 确定性 |
|---|---|---|---|

## 入口
| 触发方式 | 注册位置 | 处理符号 | 下一跳 | 证据 |
|---|---|---|---|---|

## 调用链
1. `path:line` `symbol`：输入、处理、下一跳。
2. `path:line` `symbol`：输入、处理、下一跳。

## 分支与副作用
- 鉴权：
- 校验：
- 事务：
- 外部 I/O：
- 事件、通知或审计：
- 异常到响应的映射：

## 测试映射
| 生产节点 | 测试文件和用例 | 测试类型 | 直接/间接/未覆盖 | 限制 |
|---|---|---|---|---|

## 命令证据
| 命令 | 用途 | 关键输出 |
|---|---|---|

## 事实与未知
### 已确认
- [填写已由路径、符号或命令输出证明的事实]
### 合理推断
- [填写有依据但尚未完全确认的推断]
### 未知或矛盾
- [填写仍需继续探索或证据互相冲突的事项]

## 下一步建议
- 继续只读确认：[填写仍需核对的边界]
- 可以进入计划的原因：[填写已具备的事实]
- 计划必须遵守的约束：[填写规则和非目标]
```

如果 Codex 的输出没有路径、符号、命令或未知项，要求它按模板重写。探索报告的可用性取决于证据密度，不取决于篇幅。

## 验收清单

完成陌生代码库探索后，逐项核对：

* [ ] 当前工作目录和 Git 根目录已由命令确认。
* [ ] 当前分支和探索前已有修改已记录。
* [ ] 全局到当前目录的 `AGENTS.md` 读取顺序已确认。
* [ ] 规则中的测试命令、禁止操作和目录约束已进入报告。
* [ ] 顶层目录只作为候选假设，没有被直接当作架构结论。
* [ ] 目标功能至少找到一个真实入口，并有注册位置证据。
* [ ] 调用链包含定义、调用、参数变化和关键终点。
* [ ] 鉴权、校验、事务、异常和外部副作用已单独检查。
* [ ] 测试映射包含用例和断言证据，而不只是文件名。
* [ ] 单元、集成、端到端或契约测试的边界已区分。
* [ ] 动态分发、事件链和生成代码造成的不确定点已标出。
* [ ] 所有无法确认的内容写入未知项，没有为了完整而猜测。
* [ ] `git status` 和 `git diff --check` 证明只读探索没有产生改动。

## 小结

陌生代码库探索可以压缩成一条可复查的路线：

```text theme={null}
确认工作区
  -> 读取 AGENTS.md 和项目说明
  -> 看目录与顶层配置
  -> 定位真实入口
  -> 用定义、注册、调用证据追踪调用链
  -> 把每个节点映射到测试和断言
  -> 标出分支、副作用和未知项
  -> 再决定是否切计划
```

Codex 最适合做的是快速收集和整理证据；你的职责是确认边界、辨别事实与推断，并决定什么时候从“了解系统”进入“改变系统”。只读探索做得好，后续修复、开发、重构和补测试才有明确范围，计划也不会建立在一个漂亮但错误的目录摘要上。
