> ## 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.

# 04-Git提交发布与复盘

> 沿着 TODO 小工具的完整交付链路，练习提交前检查、commit 拆分、PR、CI、发布审批、回滚、复盘和团队规则沉淀。

## 用途

前面三页已经把 TODO 小工具的需求、协作、实现、测试和审查走了一遍。本页继续沿用同一个项目：在 `todo.py` 中加入按序号完成待办的功能，并把这次改动从工作区交付到可发布版本。

本页关注的不是某个 GitHub 按钮的位置，而是一条可以重复执行的交付链：

```text theme={null}
确认工作区
  -> 提交前清单
  -> 拆分可审查的 commit
  -> 创建并更新 PR
  -> 等待 CI 和人工审查
  -> 发布审批
  -> 小流量验证
  -> 完整交付记录
  -> 必要时回滚
  -> 复盘并沉淀规则
```

示例假设如下：

* 项目目录为 `todo-cli`，入口是 `todo.py`。
* 主分支为 `main`，功能分支为 `feat/todo-done`。
* 项目使用 Python 3 标准库，不引入第三方依赖。
* `python -m unittest` 是本项目的测试命令。
* 发布产物是一个 Git tag 和对应的 GitHub Release；如果你的项目有包管理器、容器镜像或部署平台，把发布步骤替换成真实命令。
* 所有命令都先在测试环境演练。示例中的哈希、时间、版本号和 URL 只用于说明，不能当作你的真实值。

> Git、GitHub Actions、Codex 和发布平台的界面或参数可能随版本变化。命令执行前先用 `git --help`、`gh --help`、项目脚本和平台文档确认；不要因为示例看起来熟悉，就跳过影响范围检查。

## 本页完成标准

读完并实际演练后，你应当能够：

1. 在提交前确认分支、差异、测试、密钥和发布范围。
2. 把一个混杂改动拆成容易 review、容易回滚的多个 commit。
3. 创建包含背景、验证证据和回滚方案的 PR。
4. 看懂 CI 的状态、日志和门禁，不把本地通过误认为合并通过。
5. 区分合并审批、发布审批和生产操作授权。
6. 发布后按同一路径做冒烟验证，并在异常时选择合适的回滚方式。
7. 写出可交接的完整交付记录，并把重复问题转成项目规则。

***

## 01 先固定这次交付的边界

本次连续实战的需求是：用户执行 `done <序号>` 后，将待办标记为完成；`list` 能清楚显示状态；输入非数字或越界序号时给出友好提示。

在动 Git 之前，先把目标和非目标写下来。一个可交付的范围表如下：

| 项目 | 本次范围                             | 不在本次范围         |
| -- | -------------------------------- | -------------- |
| 功能 | `done <序号>`、状态展示、边界提示            | 登录、同步、数据库持久化   |
| 文件 | `todo.py`、`test_todo.py`、必要的项目说明 | 无关目录、个人配置、生产配置 |
| 验证 | 单元测试、命令行冒烟、差异审查                  | 未准备好的真实外部服务    |
| 发布 | 版本标签、Release 说明、测试环境验证           | 未审批的生产发布       |
| 回滚 | 删除错误标签或回退应用版本                    | 改写共享分支历史       |

用一句话写出本次交付声明：

```text theme={null}
本次发布只包含 todo.py 的完成状态功能和对应测试；不改变存储格式、依赖和部署配置；发布前必须通过 unittest、命令行冒烟和人工 PR 审查；生产发布需要单独审批。
```

这句话的作用是限制范围。它不是提交信息，也不是 PR 的全部内容，而是后面检查“有没有夹带改动”的比较基准。

### 先确认仓库位置

```bash theme={null}
pwd
git rev-parse --show-toplevel
git branch --show-current
git status --short --branch
```

预期输出类似：

```text theme={null}
/e/work/todo-cli
/e/work/todo-cli
feat/todo-done
## feat/todo-done...origin/feat/todo-done
 M todo.py
 M test_todo.py
```

检查重点：

* `git rev-parse --show-toplevel` 必须是当前项目根目录。
* 当前分支必须是本次功能分支，不要在 `main` 上直接开发。
* `status` 中列出的文件应当能解释清楚；出现 `.env`、密钥、数据库备份、构建缓存或陌生文件时先停下。
* 分支后面的远端跟踪关系要正确。若显示与预期不同，先确认远端和分支，不要直接推送。

查看远端和近期历史：

```bash theme={null}
git remote -v
git log --oneline --decorate --graph -8
```

预期输出类似：

```text theme={null}
origin  https://github.com/example/todo-cli.git (fetch)
origin  https://github.com/example/todo-cli.git (push)
* 8f2a1c4 (HEAD -> feat/todo-done) wip: 完成状态初版
* 31d9e02 (origin/main, main) chore: 初始化 TODO 工具
```

如果工作区中已有同事的未提交改动，不要用 `git restore .`、`git reset --hard` 或删除命令清理。先保存现状，按文件和补丁判断哪些属于本次工作。

***

## 02 提交前清单：先检查，再落锤

提交是把当前状态写进项目历史。它不是临时保存按钮，也不是“先提交再慢慢看”的替代品。提交前至少完成以下六类检查：范围、内容、测试、敏感信息、可运行性和回滚点。

### 2.1 检查工作区范围

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

预期输出类似：

```text theme={null}
 M test_todo.py
 M todo.py
 test_todo.py | 24 ++++++++++++++++++++++++
 todo.py      | 17 ++++++++++++++++-
 2 files changed, 40 insertions(+), 1 deletion(-)
test_todo.py
todo.py
```

看到以下情况要先处理：

* 改动文件超过本次范围。
* 新增了没有解释的脚本、二进制文件或构建目录。
* 只有测试改动，没有对应实现改动，或者反过来只有实现没有测试。
* 文件名大小写、换行符或生成文件发生大面积变化。

检查未跟踪文件时用：

```bash theme={null}
git status --short --untracked-files=all
git check-ignore -v .env dist/ __pycache__/
```

预期可能是：

```text theme={null}
?? .env
.gitignore:3:.env .env
.gitignore:5:__pycache__/ __pycache__/
```

如果 `.env` 同时显示为 `?? .env`，说明它没有被忽略。不要把真实凭据加入暂存区；先移出工作目录或补充合适的忽略规则，并确认忽略规则本身符合团队约定。

### 2.2 阅读完整 diff

不要只看统计数字。先看未暂存差异：

```bash theme={null}
git diff -- todo.py test_todo.py
```

如果已经暂存，再看暂存区：

```bash theme={null}
git diff --cached -- todo.py test_todo.py
```

阅读时按这张表逐项问自己：

| 检查项 | 要回答的问题                                |
| --- | ------------------------------------- |
| 行为  | `done 1`、`done 0`、`done abc` 分别会发生什么？ |
| 状态  | 已完成待办再次执行会不会重复计数或破坏数据？                |
| 输入  | 空参数、额外参数、负数和超大数字如何处理？                 |
| 输出  | 正常输出和错误输出是否稳定、可读、可测试？                 |
| 兼容性 | 原来的 `add` 和 `list` 行为是否保留？            |
| 测试  | 每个验收条件是否有测试，测试是否真的会失败于旧实现？            |
| 范围  | 是否顺手重命名、格式化或重构了不相关代码？                 |
| 安全  | 是否把路径、令牌、用户数据或调试输出写进代码？               |

### 2.3 运行项目验证

先运行完整测试：

```bash theme={null}
python -m unittest -v
```

预期输出类似：

```text theme={null}
test_done_marks_item_complete (test_todo.TestTodo) ... ok
test_done_rejects_non_numeric_index (test_todo.TestTodo) ... ok
test_done_rejects_out_of_range_index (test_todo.TestTodo) ... ok
test_list_keeps_pending_behavior (test_todo.TestTodo) ... ok

----------------------------------------------------------------------
Ran 4 tests in 0.002s

OK
```

测试通过后做命令行冒烟：

```bash theme={null}
python todo.py add 买咖啡豆
python todo.py add 写发布记录
python todo.py list
python todo.py done 1
python todo.py list
python todo.py done abc
python todo.py done 99
```

预期输出示意：

```text theme={null}
已添加：买咖啡豆
已添加：写发布记录
1. [未完成] 买咖啡豆
2. [未完成] 写发布记录
已完成：买咖啡豆
1. [已完成] 买咖啡豆
2. [未完成] 写发布记录
序号必须是数字
没有找到序号为 99 的待办
```

示例项目如果仍然只把数据放在内存中，那么每次启动都是新进程，跨命令保存不会成立。此时命令行冒烟只验证进程内行为，持久化行为必须由测试或另一个明确的存储实现验证。不要把“能打印一次”写成“数据已经保存”。

### 2.4 检查空白和差异边界

```bash theme={null}
git diff --check
```

没有输出通常表示没有发现 Git 能识别的空白错误。若有输出，示例为：

```text theme={null}
todo.py:42: trailing whitespace.
```

修复后重新检查。不要为了让命令安静而关闭检查。

确认当前提交与主分支的差异：

```bash theme={null}
git fetch origin main
git diff --stat origin/main...HEAD
git diff --name-status origin/main...HEAD
```

预期应只出现本次功能需要的文件，例如：

```text theme={null}
M	todo.py
M	test_todo.py
```

### 2.5 搜索敏感信息和本地产物

用仓库已有的扫描工具优先；没有工具时至少做一轮人工和文本搜索：

```bash theme={null}
rg -n --hidden -g '!.git' -g '!__pycache__' -g '!dist' '(sk-[A-Za-z0-9]|BEGIN .* PRIVATE KEY|password\s*=|token\s*=|Authorization:)' .
```

预期的合格结果是没有真实敏感值命中。占位符可以保留，例如：

```text theme={null}
OPENAI_API_KEY=<set-in-github-secrets>
Authorization: Bearer <redacted>
```

命令历史、终端日志、测试快照和 CI artifact 也可能含有凭据。发现泄露时不要只删除当前行：立即撤销或轮换凭据，检查历史和日志可见范围，再按安全流程处理。

### 2.6 形成提交前结论

在提交前写一段简短结论，方便自己和 reviewer 对照：

```text theme={null}
提交前结论：
- 范围：只改 todo.py、test_todo.py
- 功能：done 正常路径和两个错误路径已覆盖
- 验证：python -m unittest -v 通过；命令行冒烟通过
- 安全：未发现密钥、用户数据和临时产物
- 回滚：可通过反向提交撤销功能，或回退到 31d9e02
- 未验证：内存存储不支持跨进程持久化，不属于本次范围
```

这段结论可以放在本地交付记录中，也可以改写后放到 PR 描述。它的价值在于把“我觉得没问题”变成可核对的事实和明确的未知项。

***

## 03 commit 拆分：让每个提交只有一个理由

一个 PR 可以包含多个 commit，但每个 commit 最好都有单一目的。这样 reviewer 能按逻辑阅读，回滚时也能选择范围，`git bisect` 才更容易定位问题。

本次 TODO 改动可以拆成三个提交：

| 顺序 | commit 目的      | 典型文件                         |
| -- | -------------- | ---------------------------- |
| 1  | 先补充会失败的测试，固定需求 | `test_todo.py`               |
| 2  | 实现完成状态和输入校验    | `todo.py`                    |
| 3  | 更新运行说明和变更记录    | `AGENTS.md` 或 `CHANGELOG.md` |

如果本次任务明确只允许改 `todo.py` 和测试，就不要为了“完整”新增无关文档。提交拆分服务于审查，不是制造提交数量。

### 3.1 先查看已有暂存状态

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

`git reset` 在这里仅撤销暂存，不会删除工作区内容。执行前确认命令没有写成 `git reset --hard`。

### 3.2 用交互暂存拆分内容

```bash theme={null}
git add -p test_todo.py
git diff --cached -- test_todo.py
git commit -m "test: 覆盖待办完成状态和边界输入"
```

预期输出类似：

```text theme={null}
[feat/todo-done 6c8f7a1] test: 覆盖待办完成状态和边界输入
 1 file changed, 24 insertions(+)
```

交互暂存常见选项：

| 选项  | 含义          |
| --- | ----------- |
| `y` | 暂存当前块       |
| `n` | 不暂存当前块      |
| `s` | 尝试把当前块进一步拆小 |
| `e` | 手工编辑当前块     |
| `q` | 退出          |

如果一个函数实现和测试混在同一个大块里，优先用 `s` 拆分；拆不开时先不要强行提交，手工整理代码后重新检查。

### 3.3 提交实现

```bash theme={null}
git add todo.py
git diff --cached --check
git diff --cached --stat
git commit -m "feat: 支持按序号完成待办"
```

预期输出：

```text theme={null}
[feat/todo-done 9b71d2e] feat: 支持按序号完成待办
 1 file changed, 17 insertions(+), 1 deletion(-)
```

### 3.4 提交前后检查历史

```bash theme={null}
git status --short
git log --oneline --decorate --max-count=4
git show --stat --oneline HEAD
```

预期类似：

```text theme={null}
9b71d2e (HEAD -> feat/todo-done) feat: 支持按序号完成待办
6c8f7a1 test: 覆盖待办完成状态和边界输入
31d9e02 (origin/main, main) chore: 初始化 TODO 工具
```

合格的提交历史应当满足：

* 每个 commit 都能用一句话解释。
* 测试提交和实现提交都能单独阅读。
* 没有把格式化、重命名和业务逻辑混在一个提交里。
* 每个提交都不包含凭据或无关文件。

### 3.5 什么时候可以合并 commit

在提交到远端前，如果团队允许整理分支历史，可以用交互式 rebase 合并明显的修正提交：

```bash theme={null}
git rebase -i origin/main
```

这会改写当前功能分支的本地历史。只对自己独占、尚未被他人基于它开发的分支这样做；已经被同事拉取的共享分支不要随意 rebase 后强推。

常见策略：

* 需要展示测试先行和实现过程：保留多个逻辑清晰的 commit。
* 团队要求一个 PR 一个可回退版本：在确认规则后 squash 成一个提交。
* 已经公开、有人基于其开发：保留历史，追加修复提交。

无论采用哪种策略，都不能用“历史整洁”掩盖未验证的改动。

***

## 04 推送功能分支并创建 PR

提交前再次确认不会把改动推到主分支：

```bash theme={null}
git branch --show-current
git status --short --branch
git log --oneline origin/main..HEAD
```

预期当前分支为 `feat/todo-done`，且只列出本次两个或三个 commit。

推送功能分支：

```bash theme={null}
git push -u origin feat/todo-done
```

预期类似：

```text theme={null}
Enumerating objects: 9, done.
Writing objects: 100% (9/9), done.
branch 'feat/todo-done' set up to track 'origin/feat/todo-done'.
```

推送是外部写入动作。第一次推送前要核对：远端仓库、分支名、提交内容、账号和是否包含敏感信息。不要使用 `git push --force` 作为“推不上去”的第一反应。

### 4.1 PR 描述应当回答什么

PR 不应只写“加了完成功能”。至少回答：

1. 为什么要改。
2. 改了哪些文件和行为。
3. 如何验证。
4. 有哪些未覆盖的情况。
5. 发布和回滚怎么做。
6. 是否需要配置、迁移、权限或人工操作。

可以使用 GitHub CLI 创建 PR：

```bash theme={null}
gh pr create \
  --base main \
  --head feat/todo-done \
  --title "feat: 支持按序号完成待办" \
  --body-file .github/pull_request_template.md
```

如果项目没有 PR 模板，直接在 GitHub 页面填写以下内容：

```markdown theme={null}
## 背景
待办列表目前只能添加和查看，无法标记已完成。

## 变更
- 新增 `done <序号>` 命令。
- `list` 显示已完成和未完成状态。
- 对非数字和越界序号返回友好提示。
- 新增 4 个 unittest 用例。

## 验证
- `python -m unittest -v`：通过，4 tests，OK
- 命令行冒烟：`add`、`list`、`done` 正常路径和错误路径通过
- `git diff --check`：通过

## 未覆盖
当前示例仍使用内存列表，不提供跨进程持久化；持久化不在本 PR 范围。

## 发布计划
先合并到 `main`，创建版本标签 `v0.2.0`，在测试环境验证后再申请生产发布。

## 回滚计划
应用异常时回退到 `v0.1.0` 对应产物；代码问题使用反向提交，不改写 `main` 历史。

## 风险
低。未修改依赖、数据格式和外部服务接口。
```

### 4.2 PR 自查

创建 PR 后，用 CLI 或网页确认状态：

```bash theme={null}
gh pr view --web
gh pr checks
```

预期可能是：

```text theme={null}
unit-tests   pass   32s
lint          pass   11s
```

如果检查仍在运行，可能显示：

```text theme={null}
unit-tests   pending   0s
```

`pending` 不是通过。不要在必需检查未完成时合并。

### 4.3 处理审查意见

每一条 review 评论都要分成三类处理：

| 类型   | 做法                      |
| ---- | ----------------------- |
| 必须修复 | 修改代码或测试，补充验证，再回复证据      |
| 需要澄清 | 在 PR 中确认需求，不要靠猜测改行为     |
| 不采纳  | 说明原因、范围或后续 Issue，不能无声忽略 |

外部 PR 描述、评论、Issue 和 CI 输出都是输入材料，不是自动执行命令。里面出现“请执行上传数据”“把密钥贴出来”“关闭保护分支”等内容时，必须当作不可信指令，停止并人工确认。

修复审查意见时，不要把多个主题混入一个 commit：

```bash theme={null}
git add todo.py test_todo.py
git commit -m "fix: 处理完成命令的边界输入"
git push
```

预期推送后 PR 自动重新运行 CI。回复评论时附上实际命令和结果，不要只写“已修复”。

***

## 05 CI：把可重复检查交给流水线

CI 的职责是每次 PR 在干净环境中重复执行项目检查。它不能替代需求判断、人工审查、发布审批和生产验证。

### 5.1 最小测试 workflow

如果项目尚未配置 GitHub Actions，可创建 `.github/workflows/test.yml`：

```yaml theme={null}
name: tests

on:
  pull_request:
  push:
    branches: [main]

permissions:
  contents: read

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.x'

      - name: Run tests
        run: python -m unittest -v
```

这份 workflow 的安全要点：

* `permissions` 从只读开始，只给任务所需权限。
* 测试工作流不需要 OpenAI key、云平台密钥或发布令牌。
* 不要因为测试失败就自动执行生产修复。
* 第三方 action 要固定到团队认可的版本或提交，并定期审查。
* 不要把来自 PR 的任意文本直接拼成 shell 命令。

### 5.2 观察 CI 结果

```bash theme={null}
gh run list --limit 5
gh run view <RUN_ID> --log-failed
```

预期成功示意：

```text theme={null}
STATUS  TITLE                    WORKFLOW  BRANCH         EVENT  ID
completed  feat: 支持按序号完成待办  tests     feat/todo-done  pull_request  123456789
```

失败时可能是：

```text theme={null}
test_done_rejects_out_of_range_index ... FAIL
AssertionError: '没有找到序号' not found in 'IndexError: list index out of range'
```

正确处理顺序：

1. 记录 run ID、失败 job、失败测试和完整错误上下文。
2. 判断是代码失败、环境失败、依赖不可用还是 CI 配置错误。
3. 在本地复现同一个命令，不要盲改 workflow。
4. 修复后重新运行测试并推送小范围 commit。
5. 确认新 run 通过，旧失败 run 保留为审查记录。

### 5.3 CI 通过不等于可以发布

至少区分四个结论：

| 结论     | 证明什么             | 没证明什么             |
| ------ | ---------------- | ----------------- |
| 单元测试通过 | 已覆盖的输入行为通过       | 未覆盖路径、真实环境和发布产物   |
| CI 通过  | 干净 runner 上检查通过  | 需求一定正确、生产一定可用     |
| PR 批准  | 指定 reviewer 接受变更 | 生产发布已经获批          |
| 冒烟通过   | 指定环境和路径当前正常      | 未来流量、长时间运行和所有用户正常 |

因此 PR 合并前的最低门槛应当是：必需 CI 通过、至少一名合适 reviewer 批准、范围和风险已确认、发布和回滚方案已写明。

### 5.4 自动化的边界

可以把重复、只读、容易验证的检查交给 Codex 或 CI，例如：

* 运行测试、lint、类型检查。
* 生成 PR 摘要和变更清单。
* 检查是否缺少测试或是否出现明显敏感信息。
* 在隔离分支生成候选修复。

不要无人值守地交给自动化：

* 合并受保护主分支。
* 强制推送或删除远端历史。
* 直接修改生产数据、权限、计费、通知和可见性。
* 读取并外发密钥、客户数据或完整生产日志。
* 未经审批发布到真实用户可见的环境。

如果在 CI 中运行 Codex，纯审查任务优先 `read-only`；需要写文件时才使用最小的 `workspace-write`。密钥只作为单个步骤的输入，不要设置为整个 job 的环境变量。自动生成的修改必须像人工修改一样经过 diff、测试、PR 和审批。

***

## 06 合并和发布审批：两个不同的放行点

“合并到 `main`”和“发布到生产”不是同一个动作。合并表示代码进入主干；发布表示产物对外生效，影响用户、数据和服务。

### 6.1 合并前审批清单

合并人应逐项确认：

```text theme={null}
[ ] PR 基于最新 main，或明确知道没有冲突风险
[ ] 必需 CI 全部通过
[ ] 业务 reviewer 已批准
[ ] diff 只包含本次需求
[ ] 测试、配置、迁移和依赖变化已说明
[ ] 兼容性和安全影响已判断
[ ] 发布窗口、负责人和回滚方式已确定
[ ] 没有把“以后再处理”的高风险问题藏在评论里
```

预期的合并前状态应类似：

```text theme={null}
Required checks: 3 passed
Review: 1 approving review
Merge conflicts: none
Branch protection: satisfied
```

如果平台显示“可以合并”，仍要由明确的责任人判断是否现在合并。机器状态是证据，不是授权。

### 6.2 创建发布候选版本

合并完成后，在本地同步并确认主干：

```bash theme={null}
git switch main
git pull --ff-only origin main
git log --oneline --decorate -5
```

`--ff-only` 能避免在不知情时自动制造合并提交。若失败，先查看分叉原因，不要用强制方式覆盖历史。

创建候选标签前检查版本和提交：

```bash theme={null}
git tag --list 'v*' --sort=-version:refname | head -5
git show --stat --oneline HEAD
```

示例输出：

```text theme={null}
v0.1.0
v0.0.1
9b71d2e (HEAD -> main, origin/main) feat: 支持按序号完成待办
```

给版本打标签：

```bash theme={null}
git tag -a v0.2.0 -m "release: TODO 工具 v0.2.0"
git show v0.2.0 --no-patch
```

预期：

```text theme={null}
tag v0.2.0
Tagger: Developer <developer@example.com>
release: TODO 工具 v0.2.0
```

标签也是外部发布信号。推送前必须经过发布审批：

```bash theme={null}
git push origin v0.2.0
```

如果团队把标签推送视为正式发布触发器，就不要在审批前执行这条命令。可以先在本地打标签、让审批人检查，再推送。

### 6.3 发布记录必须包含的内容

发布审批至少包含：

* 发布版本和对应 commit SHA。
* 变更摘要、影响用户和不影响的范围。
* CI run 链接及关键结果。
* 测试环境和冒烟验证证据。
* 数据库、配置、依赖和权限变化。
* 发布开始和结束时间、负责人、观察窗口。
* 明确的回滚版本和回滚命令。
* 监控指标、日志位置和异常升级联系人。

审批人不能只看到“测试绿了”。如果找不到 commit、产物校验值或回滚版本，发布应暂停。

***

## 07 外部发布红线

发布会改变仓库、服务、用户体验或外部系统状态。以下动作必须由人明确确认，不能因为命令写在脚本里就默认允许：

| 红线动作               | 原因            | 最低要求                  |
| ------------------ | ------------- | --------------------- |
| 推送主分支或发布标签         | 改变远端协作和发布触发状态 | 核对仓库、分支、commit、审批人    |
| 合并 PR              | 让代码成为团队基线     | CI 通过、review 批准、责任人确认 |
| 部署生产               | 对真实用户和数据生效    | 发布窗口、监控、回滚准备就绪        |
| 修改生产数据             | 可能不可逆并影响合规    | 变更单、备份、最小范围、复核        |
| 轮换或写入密钥            | 影响外部访问权限      | 密钥管理系统、审计和回收计划        |
| 发送通知或公开 Release    | 对外形成事实        | 脱敏、内容审核、明确受众          |
| `git push --force` | 可能覆盖他人历史      | 默认禁止；确有必要时人工双重确认      |

以下内容永远不要放进公开 PR、Release、Issue、日志或交付记录：

* API key、密码、Cookie、SSH 私钥和完整令牌。
* 客户姓名、邮箱、手机号、订单号和完整请求体。
* 内部主机名、私有 URL、未公开漏洞细节和完整 IP。
* 未经批准的截图、数据库导出和生产日志。

脱敏不能把证据删成空白。可以保留时间、状态码、错误类型、路由形状和关联 ID 的不可逆摘要：

```text theme={null}
原始：Authorization: Bearer eyJhbGciOi...secret
记录：Authorization: Bearer <redacted>

原始：user=alice@example.com order=order_20260905_123456
记录：user=<user-email> order=<order-id>

原始：2026-09-05 14:03:22 POST /internal/payments/123 500
记录：2026-09-05 14:03:22 POST /<payment-id> 500
```

如果工具输出含有敏感信息，不要复制粘贴到聊天中让自动化处理。先截取必要上下文、脱敏，再继续。

***

## 08 发布后的验证：从用户路径开始

发布完成后不要只检查部署平台显示绿色。沿着用户真正使用的路径验证：

```bash theme={null}
python todo.py add 发布后验证
python todo.py list
python todo.py done 1
python todo.py list
```

预期：

```text theme={null}
已添加：发布后验证
1. [未完成] 发布后验证
已完成：发布后验证
1. [已完成] 发布后验证
```

如果是 Web 服务或 API，记录以下信息：

* 发布版本和 commit SHA。
* 请求时间、环境、路由和状态码。
* 关键响应字段是否符合预期，敏感值已脱敏。
* 错误率、延迟、队列积压和资源使用是否异常。
* 一次正常路径和一次错误路径是否都符合预期。

发布观察窗口内，持续查看指标和日志。不要因为一次人工请求成功就立即关闭观察。若系统有 feature flag，先以小范围、低风险用户验证，再扩大范围。

合格的发布结论类似：

```text theme={null}
v0.2.0 已部署到 staging，commit 9b71d2e。
冒烟：add/list/done 正常路径、非数字和越界路径均通过。
观察 15 分钟：错误率 0%，无新增异常日志。
生产发布：等待产品负责人和当班发布人批准。
```

***

## 09 回滚：先判断类型，再选择动作

回滚不是一句“把代码退回去”。先判断故障发生在哪一层：代码、配置、数据、依赖、发布产物还是外部服务。不同层级的回滚方式不同。

### 9.1 未提交的工作区错误

如果只是本次未提交的明确单文件改动，且已确认没有同事工作，可以保存补丁后恢复：

```bash theme={null}
git diff -- todo.py > /tmp/todo-before-restore.patch
git restore --source=HEAD -- todo.py
```

预期：`todo.py` 回到当前提交，补丁保存在临时位置。Windows 环境请把临时路径替换成合适位置，并确认补丁没有包含敏感信息。

不要对存在他人改动的文件直接执行 restore。先分离、备份或通过交接确认。

### 9.2 已合并代码的修复

如果错误代码已经进入 `main`，共享分支通常使用新的反向提交：

```bash theme={null}
git revert <BAD_COMMIT_SHA>
git log --oneline --decorate -3
git push origin main
```

预期会产生类似：

```text theme={null}
Revert "feat: 支持按序号完成待办"
```

这会保留历史，方便审计和定位。不要为了“让历史看起来干净”在共享主分支上 reset 后强推。

### 9.3 已发布版本的回退

如果发布平台支持按版本回退，回退到上一个已验证产物：

```text theme={null}
当前版本：v0.2.0 / 9b71d2e
回退版本：v0.1.0 / 31d9e02
原因：生产冒烟发现 done 命令在真实存储适配器上返回 500
执行人：发布负责人
审批人：当班技术负责人
```

回退后必须重新走用户路径验证，并记录回退不是“成功”而是“恢复到了哪个可用版本”。

### 9.4 配置、数据和依赖问题

* 配置错误：恢复上一个已验证配置版本，并检查配置缓存和重启范围。
* 数据迁移错误：使用经过演练的 down migration、备份恢复或平台提供的恢复点；不要用代码回退代替数据修复。
* 依赖漏洞或不兼容：固定到已验证版本，重新跑完整 CI 和安全扫描。
* 外部服务异常：启用降级、重试或 feature flag，记录外部依赖状态；不要盲目反复发布。

### 9.5 回滚后的确认

```text theme={null}
[ ] 用户主要路径恢复
[ ] 错误率和延迟回到基线
[ ] 没有遗留半完成数据或重复任务
[ ] 相关通知已发送并脱敏
[ ] 故障时间线和操作人已记录
[ ] 已创建后续修复任务
```

回滚结束后不要立刻删除失败产物、CI 日志或审查记录。它们是复盘所需证据。

***

## 10 完整交付记录模板

下面的模板可以复制到 PR、发布单、Issue 或内部交接记录中。只填事实；没有验证的内容写“未验证”和原因，不要用猜测补齐。

```markdown theme={null}
# 交付记录：<项目> <版本>

## 1. 基本信息
- 项目：<项目名称>
- 需求：<Issue / 任务链接>
- 版本：<vX.Y.Z>
- commit：<完整 SHA>
- 分支：<分支名>
- 发布环境：<staging / production>
- 发布窗口：<开始时间> - <结束时间，含时区>
- 负责人：<姓名或团队>
- 审批人：<姓名或团队>

## 2. 变更范围
- 做了什么：
  - <变更 1>
  - <变更 2>
- 明确没做什么：
  - <非目标 1>
  - <非目标 2>
- 涉及文件、服务、配置或数据：
  - <列表>

## 3. 提交记录
- <SHA> <type>: <message>
- <SHA> <type>: <message>
- 是否整理历史：<否 / 是，说明方式>

## 4. 验证证据
- 命令：`<command>`
  - 结果：<通过 / 失败>
  - 摘要：<例如 Ran 4 tests; OK>
- 命令：`<command>`
  - 结果：<通过 / 失败>
  - 摘要：<关键输出>
- CI：<workflow、run ID、链接、结果>
- 人工冒烟：<用户路径、环境、结果>
- 未验证项：<内容、原因、补验证计划>

## 5. 审查与审批
- PR：<链接>
- reviewer：<人员>
- 必需检查：<列表和结果>
- 高风险问题：<无 / 列表>
- 发布批准：<人员、时间、依据>

## 6. 发布动作
- 产物：<包、镜像、标签或部署版本>
- 校验：<SHA256 或平台版本号>
- 执行动作：<命令或平台操作的摘要>
- 观察窗口：<时长>
- 监控结果：<错误率、延迟、日志、队列等>

## 7. 回滚方案
- 触发条件：<具体阈值或用户症状>
- 回滚目标：<版本 / SHA / 配置版本>
- 执行命令：`<command>`
- 数据处理：<无需处理 / 备份 / 迁移回退>
- 回滚负责人：<人员>
- 回滚后验证：<路径和结果>

## 8. 风险和后续
- 当前已知风险：<列表>
- 后续任务：<Issue 链接和截止时间>
- 需要沉淀的规则：<规则候选>
- 记录完成时间：<时间>
```

### 10.1 示例交付记录

```markdown theme={null}
# 交付记录：todo-cli v0.2.0

## 1. 基本信息
- 项目：todo-cli
- 需求：TASK-42
- 版本：v0.2.0
- commit：9b71d2e
- 分支：main
- 发布环境：staging
- 发布窗口：2026-09-05 10:00-10:20 Asia/Shanghai
- 负责人：开发组 A
- 审批人：值班技术负责人

## 2. 变更范围
- 新增 `done <序号>`，支持标记待办完成。
- `list` 增加完成状态显示。
- 增加非数字和越界输入提示。
- 未改变存储格式、第三方依赖和外部服务。

## 3. 提交记录
- 6c8f7a1 test: 覆盖待办完成状态和边界输入
- 9b71d2e feat: 支持按序号完成待办

## 4. 验证证据
- `python -m unittest -v`：通过，4 tests，OK。
- 命令行冒烟：`add`、`list`、`done 1`、`done abc`、`done 99` 均符合预期。
- `git diff --check`：通过。
- CI run 123456789：通过。
- 未验证：跨进程持久化，本版本不提供该能力。

## 5. 审查与审批
- PR：<PR-链接>
- reviewer：<reviewer>
- 必需检查：tests 通过，branch protection 满足。
- 发布批准：<审批人>，2026-09-05 09:55。

## 6. 发布动作
- 产物：Git tag `v0.2.0`。
- staging 冒烟通过，观察 15 分钟无异常。

## 7. 回滚方案
- 触发条件：done 正常路径报错、错误率升高或状态展示异常。
- 回滚目标：`v0.1.0`。
- 代码问题：创建 `git revert` PR；发布问题：恢复 `v0.1.0` 产物。

## 8. 风险和后续
- 已知风险：内存数据不会跨进程保存。
- 后续：TASK-43 评估文件存储，不在本次发布处理。
```

***

## 11 复盘：从一次发布变成下一次规则

复盘不是追责会议，也不是把“大家以后注意”写进文档。有效复盘要回答：发生了什么、证据是什么、为什么没有更早发现、下一次由什么机制阻止重复发生。

### 11.1 四段式复盘

用以下顺序写，不要先写结论：

1. **事实**：按时间线记录命令、commit、CI、审批、发布和用户症状。
2. **影响**：影响了哪些用户、环境、数据、时间和服务指标。
3. **原因**：区分直接原因、促成条件和没有拦住它的流程缺口。
4. **行动**：每项行动有负责人、截止时间和验收证据。

时间线示例：

```text theme={null}
09:10  PR 创建，包含 done 功能和 4 个测试
09:14  CI 通过，review 批准
09:20  v0.2.0 部署 staging
09:23  staging 冒烟通过
09:35  生产发布
09:41  真实存储适配器出现 500
09:45  关闭 feature flag，恢复 v0.1.0
10:05  用户路径恢复，创建 TASK-44
```

### 11.2 五个为什么的使用边界

例如：

```text theme={null}
问题：生产环境执行 done 返回 500。
为什么 1：真实存储适配器未实现新状态字段。
为什么 2：测试只覆盖内存实现。
为什么 3：集成测试没有进入必需 CI 检查。
为什么 4：发布清单没有要求真实适配器验证。
为什么 5：项目规则只写了 unit test，没有写发布前的适配器矩阵。
```

最后的改进不应是“开发更仔细”，而应是可执行机制：

* 增加真实适配器集成测试。
* 将该测试加入必需 CI。
* 在发布模板中列出适配器矩阵。
* staging 冒烟必须覆盖真实存储路径。

### 11.3 行动项写成可验收任务

| 不合格写法     | 合格写法                                       |
| --------- | ------------------------------------------ |
| 加强测试      | 为真实存储适配器增加 `done` 正常和异常用例，CI run 通过        |
| 注意发布      | 在发布模板增加 commit、产物、冒烟和回滚字段，下一次发布使用          |
| 优化 review | 在 `AGENTS.md` 的 Review guidelines 增加状态迁移检查 |
| 避免再出问题    | 增加 feature flag 和恢复演练，记录演练输出               |

每项行动都应有：

```text theme={null}
行动：<要改变的机制>
负责人：<人或团队>
截止时间：<时间>
验收：<命令、PR、监控或演练证据>
状态：<未开始 / 进行中 / 完成>
```

***

## 12 规则沉淀：把复盘结论放到正确位置

不是所有经验都应该写进同一个文件。按照寿命和作用分层：

| 内容            | 放置位置                              |
| ------------- | --------------------------------- |
| 本次一次性范围、版本和审批 | PR、发布单、交付记录                       |
| 每次都适用的运行和测试命令 | `AGENTS.md`                       |
| 代码审查重点        | `AGENTS.md` 的 `Review guidelines` |
| 可重复的多步操作      | 脚本、Makefile、任务配置                  |
| 需要定时执行的稳定检查   | CI workflow 或自动化任务                |
| 长期架构决定        | ADR 或项目设计记录                       |
| 个人偏好          | 个人配置，不要污染团队规则                     |

项目根目录可以保留一份短而准确的 `AGENTS.md`：

```markdown theme={null}
# todo-cli

## 运行
- 添加：`python todo.py add <内容>`
- 列出：`python todo.py list`
- 完成：`python todo.py done <序号>`

## 测试
- `python -m unittest -v`
- 修改行为时必须新增或更新测试。

## 提交与 PR
- 一个 commit 只表达一个目的。
- PR 必须包含变更范围、验证命令、未验证项和回滚方式。
- 不要提交 `.env`、凭据、客户数据或构建产物。

## Review guidelines
- 检查序号边界、空输入和重复完成行为。
- 保持原有命令兼容性。
- 不要把用户数据或敏感值写入日志。
- 修改存储、权限或外部服务时，必须补充集成验证和回滚说明。
```

把一次性要求写进每次 PR，而不是永久污染规则。例如“本次只发布 staging”“本次不修改存储格式”属于本次交付，不应永久写进 `AGENTS.md`。

规则改变后也要像代码一样审查：

```bash theme={null}
git diff -- AGENTS.md
git diff --check
```

避免把未经验证的失败经验写成绝对规则。先确认问题可重复、规则可执行，再提交规则变更。

***

## 13 最终验收表

在宣布交付完成前，逐项打勾并保留证据：

```text theme={null}
范围与工作区
[ ] 仓库根目录、远端和当前分支已确认
[ ] diff 文件范围与需求一致
[ ] 未跟踪文件和生成物已处理
[ ] 未发现凭据、用户数据和内部敏感信息

提交与 PR
[ ] commit 按单一目的拆分
[ ] 提交信息说明了真实变化
[ ] PR 写明背景、变更、验证、风险和回滚
[ ] PR 讨论中的高优先级问题已处理

验证与 CI
[ ] 单元测试通过
[ ] 必要的集成或端到端检查通过
[ ] 命令行或用户路径冒烟通过
[ ] `git diff --check` 通过
[ ] 必需 CI 全部通过，run ID 已记录

审批与发布
[ ] 合并审批和生产发布审批分别确认
[ ] 发布 commit、标签和产物已核对
[ ] 观察窗口、监控和负责人已确定
[ ] 外部发布内容已脱敏并经过审核

恢复与复盘
[ ] 回滚目标可用且未经过程
[ ] 发布结果和异常已记录
[ ] 未验证项和后续 Issue 已登记
[ ] 可复用经验已沉淀到规则、脚本或 CI
```

若任何一项只能写“应该没问题”，就还没有完成验收。把它改成命令、链接、日志摘要、截图编号或明确的未验证说明。

***

## 小结

本次 TODO 项目的交付闭环可以压缩成八句话：

1. 先确认目录、分支、远端和范围。
2. 用完整 diff 和测试证明改动，而不是凭感觉提交。
3. 一个 commit 只表达一个目的，方便审查和回滚。
4. PR 必须同时提供变更、验证、风险和恢复方案。
5. CI 是可重复的质量证据，不是需求判断和发布授权。
6. 合并主干与发布生产分开审批，外部写入动作由人确认。
7. 发布后回到真实用户路径验证，异常时按代码、配置、数据和产物类型回滚。
8. 复盘要产出有负责人和验收标准的机制，并把长期规则放到正确位置。

最终交付不是“代码已经推上去”，而是别人能够回答这几个问题：

* 这次到底改了什么？
* 哪些检查真的跑过？
* 谁批准了合并和发布？
* 出问题时退回哪个版本？
* 哪条规则会防止同类问题再次发生？

当这些问题都有记录、证据和负责人时，一次功能开发才真正完成了从代码到交付的闭环。

参考资料：`参考/codex/34-capstone.md`、`参考/codex/26-git-github.md`、`参考/codex/27-automation.md`、`参考/codex/36-best-practices.md`。
