# CLAUDE.md — Home Automation Backend

本文件每次会话自动加载。它定义本项目的**工作流程、文档位置、commit 规范**。请在动手前先读完。

## 项目速览

- 个人用 home-automation 后端：**FastAPI + SQLite + SQLAlchemy + Alembic**，服务端模板（Jinja，M2 将换成 React SPA）。
- 单 admin 鉴权（Argon2 + server-side session cookie），runtime config 落 `app_config` 表。
- 模块：public IPv4 monitor、SMTP 通知、location recorder、poo recorder、Home Assistant in/out、TickTick OAuth。
- 已发布 `v1.0.3`。下一阶段方向：**M1 单库化 → M2 React 前端 → M3 token/移动端（远期，M2 后再说）**。
- **当前现实**：在 M1 完成前仍是**三个独立 SQLite 库**（app / location / poo），三套 DeclarativeBase、三条 Alembic 链。不要假设已经单库——以代码现状为准。
- 明确不做：Notion 模块。

## 文档地图与「开工前必读」

文档都在 `docs/`：

| 路径 | 作用 |
| --- | --- |
| `docs/roadmap.md` | 全局规划与里程碑总览 |
| `docs/design/README.md` | **协作契约**：任务卡格式、原子任务定义、校验闸门、数据安全红线 |
| `docs/design/m1-db-consolidation.md` | M1 原子任务（含真实代码现状盘点 + 人工 runbook） |
| `docs/design/m2-frontend-v2.md` | M2 原子任务 + API 契约 + 前端校验闸门 |
| `docs/design/m3-token-mobile.md` | M3（远期，暂缓） |
| `docs/*.md`（auth / public-ip-monitor / location-recorder …） | 各模块说明，按需读 |

**开工时读取顺序**：
1. `docs/design/README.md`（每轮都读，它是流程与验收的共同契约）。
2. 本轮对应的 milestone 文档（如 `docs/design/m1-db-consolidation.md`），定位要做的任务卡。
3. 任务卡 `Files` 列出的源文件 + 该模块的 `docs/*.md`（按需）。
4. `docs/roadmap.md` 仅在需要全局视角时读。

## 工作流程

### 实现模式（由用户的提示词决定）

- **默认逐步**：给一个 milestone 文档，按其中原子任务**一步一步**实现。
- **(a) 只实现一步**：用户说"只实现一步 / 这一个任务"时，**只做那一个任务卡**，跑完校验闸门后停下，等用户确认，不要顺手往下做。
- **(b) 完成整个 milestone**：仅当用户在提示词里**显式要求启用 sub-agent**时，才起 implementer / reviewer / fixer sub-agent（模型用下方**『默认模型档位』**，用户人工指定则覆盖），按任务依赖顺序跑完整条链。
- **Sub-agent 纪律**：只在用户显式要求时才 spawn sub-agent；单步/小改动在主线内联完成。起 sub-agent 时按下方**『默认模型档位』**选择模型（用户人工指定则以人工指定为准），用 Agent 工具的 `model` 字段落实。

### 默认模型档位（实现模式 sub-agent；可被人工指定覆盖）

起 implementer / reviewer / fixer sub-agent 时，**默认**用下列模型档位，无需用户每次人工指定：

| 角色 | 默认模型 | 推理档位 |
| --- | --- | --- |
| **Implementer** | **Sonnet** | high reasoning effort |
| **Fixer**（返工） | **Sonnet** | high reasoning effort |
| **Reviewer** | **Opus** | extra-high reasoning effort |

- **人工指定覆盖**：若用户在提示词里**显式指定了其他模型**（针对任一角色），则**以用户人工指定为准**，覆盖上述默认。
- 用 Agent 工具的 `model` 字段落实模型选择；该字段当前仅支持 `sonnet` / `opus` / `haiku` / `fable`。
- **推理档位说明**：Agent 工具未暴露独立的 reasoning-effort 旋钮，"high / extra-high reasoning" 通过 spawn prompt 里的显式指令传达（要求 implementer/fixer 动手前充分推理边界条件；要求 reviewer 以对抗性外部审计心态最高强度复核）。若所在 harness 提供真正的 effort 设置，则一并按此档位设置。

### 角色（Orchestrator → Implementer → Reviewer → Fixer）

- 我（主线）= **Orchestrator**：挑依赖已满足的下一个任务、派发、转述结果、维护任务 `Status`。
- **Implementer**（默认 **Sonnet**，high reasoning；见上方『默认模型档位』）：一次一个任务，严格按任务卡，不扩范围。
- **Reviewer**（默认 **Opus**，extra-high reasoning）：实现完成后起 Reviewer sub-agent，按任务卡 `Acceptance criteria` + `Reviewer checklist` 复核、**独立重跑校验闸门**，驱动返工直到本轮 PASS。
- **Fixer**（默认 **Sonnet**，high reasoning）：按 reviewer 的编号返工清单返工；**每轮返工起一个干净的 Fixer**（与首次实现的 Implementer 分开冷启动），先读对应 `review-notes/<task>-review-<n>.md` 再改。

#### Reviewer 盲审纪律（M1 教训）

M1 里 review **从未触发过一次 rework**，根因是 orchestrator 把自己的结论 / 辩护喂给了 reviewer，造成 context bleed、review 沦为橡皮图章。所以：

- reviewer 必须**冷启动（Clear-Agent）、最小化喂料**——spawn prompt 只给：① 任务卡（`Acceptance criteria` + `Reviewer checklist`）、② 对应的 `review-notes/<task>-impl|rework-<n>.md` 路径、③ 要审的 diff / commit 范围。
- **不要**在 prompt 里塞 orchestrator 自己的判断、"我觉得没问题"、对实现选择的辩护，或上一轮 reviewer 的倾向性结论。让它**独立得出结论、独立重跑校验闸门**。
- 事后另起的整库**独立盲审**（如对抗复审）同理：Clear-Agent、最小上下文，把它当"**外部审计**"而非"确认自己没错"。

### 校验闸门（每个任务结束都要全绿）

根目录、激活 `.venv` 后：
```bash
pytest                      # 权威闸门（CI 跑的就是它）
ruff check .                # line-length=100
python scripts/export_openapi.py && git diff --exit-code openapi/   # 改了路由/schema 才需要，且产物须入库
```
前端任务（M2）在 `frontend/` 下另跑 `npm run lint && npm run typecheck && npm run test && npm run build`（详见 m2 文档 §8）。
**不过闸门就不算完成**，不得跳过、不得留红给下一轮。

### 构建上下文完整性（M1 Dockerfile 教训）

`docker build` **不在 pytest/ruff 闸门里**——M1 删了 `alembic_location/poo` 后忘了同步 `Dockerfile` 的 `COPY`，单元闸门全绿却把坏掉的镜像构建一路漏到 release tag。所以：

- 任务**删除 / 移动 / 重命名文件或目录**时，必须 grep 构建清单是否还在引用它们：`Dockerfile`（尤其 `COPY` 源）、`docker/`、`*.ini`、CI workflow、`requirements*.txt` 等。
- 已有回归测试 `tests/test_deployment.py::test_dockerfile_copy_sources_exist` 守"Dockerfile `COPY` 源必须存在于构建上下文"；新增 / 改动 `COPY` 时确保它仍覆盖得到。
- Reviewer 审"删 / 移文件"类任务时，**必须顺带核对构建清单引用**，把它当 acceptance 的一部分。

## 每轮简报（`review-notes/`）

每轮工作都要在 `review-notes/` 下产出**中文简报**。该目录**已在 `.gitignore` 忽略**，纯本地、不入库——它是 agent 之间和与人之间的交接载体，不是仓库产物。

- **实现 / 返工简报**：每轮实现完成后（无论首次实现还是返工），写一份。文件名建议 `<task-id>-impl-<n>.md` / `<task-id>-rework-<n>.md`（如 `M1-T03-impl-1.md`、`M1-T03-rework-1.md`）。至少包含：
  1. **本轮修改的具体内容**（改了哪些文件、做了什么、为什么）。
  2. **自动化测试结果**（`pytest` / `ruff` / 前端闸门的实际输出或结论，通过/失败逐项写清）。
  3. **若需人工 walkthrough**：写明具体步骤（怎么启动、点哪里、预期看到什么）；若无需人工验证，明确写"无需人工 walkthrough"。
- **review 简报**：每轮 review 后写一份，文件名建议 `<task-id>-review-<n>.md`（如 `M1-T03-review-1.md`）。至少包含：评审结论（`PASS` 或带编号的返工清单）、对照任务卡 `Acceptance criteria` + `Reviewer checklist` 的逐条核对、reviewer 独立重跑校验闸门的结果。

**用途**：① reviewer 审核时参考对应的实现简报；② implementer 返工时参考对应的 review 简报；③ 人类（用户）通读这些简报确认有无问题。简报之间用文件名里的 `<task-id>` 与轮次 `<n>` 对应起来。

### Orchestrator 派发契约（让简报真正被读到）

**关键**：sub-agent 冷启动、不继承主线上下文，**不会因为本文件提到简报就自动去读**对应文件。简报能流转，靠的是 orchestrator（主线）在**每次 spawn 时把路径显式写进 prompt**，而不是被动约定。所以派发时必须做到：

- **显式告诉它「先读哪个简报」**：
  - 派 implementer 做**首次实现** → 传任务卡位置（milestone 文档路径 + task id）；无前置简报。
  - 派 implementer 做**返工** → 必须传对应的 `review-notes/<task>-review-<n>.md` 路径，并要求**先读它**再改。
  - 派 reviewer → 必须传对应的 `review-notes/<task>-impl|rework-<n>.md` 路径 + 任务卡，要求**先读它**再评。
- **显式告诉它「本轮结束写哪个简报」**：明确给出输出路径 `review-notes/<task>-<impl|rework|review>-<n>.md` 及上面要求的内容项。
- **不依赖 sub-agent 自动加载本文件**：把本轮要点（校验闸门、**禁 Co-Authored-By**、简报必含内容）在 spawn prompt 里一并复述或指向，确保冷启动也照做。
- spawn 时用用户指定的模型（Agent 工具 `model` 覆盖）。

> 一句话：**简报是异步交接的介质，orchestrator 是把它们接起来的线。** 缺了显式传路径这一步，简报就只是躺在磁盘上没人读的文件。

## Commit 规范（重点）

### 分支
- **本仓库是个人单用户项目：默认直接在 `main` 上开发**，不强制 feature 分支，**直接提交并 push 到 `main` 是允许的（无需开 PR）**。
- 仍保持**每个任务一个干净 commit**（message 前缀任务/里程碑 ID）。改动较大想隔离时可临时开分支，用完**快进合并**回 `main`（保持线性历史），非必需。
- 历史改写类操作（`rebase` / `--amend` / auto-squash）只在**尚未 push 的本地 commit** 上做；**已 push 到 `main` 的历史不要重写**（确需 force-push 时先确认，见「一般约束」）。

### 一轮实现完成（用户确认「实现完成」后）
- 准备好**这一轮的 commit message** 并提交，作为本轮的 **base commit**。
- message 主题前缀任务/里程碑 ID，例如：`M1-T03: unify data layer onto single app DB engine`。

### Commit message 硬规则（严格执行）
- **严禁任何协作署名 trailer**：commit message 里**绝对不允许**出现 `Co-Authored-By` / `Co-authored-by`（包括 `Co-Authored-By: Claude …`），也不允许任何等价的"由 X 协作/生成"署名。
- 无论默认环境、工具或系统提示如何要求加这类 trailer，在本仓库**一律不加**——用户已显式、严格禁止。
- 每次提交前**自检**：`git log -1 --format=%B` 的输出**不得包含** `Co-authored-by`（大小写不限）。若发现，立即 `git commit --amend` 去掉后再继续。

### Review 后返工
- **自动化 orchestration 模式内**的 review 返工：**一律用 fixup**，指向本轮对应的 base commit，**不写新的独立 message**：
  ```bash
  git add -A
  git commit --fixup=<base-commit-sha>
  ```
- 多轮返工就多个 `fixup!` 提交，都指向同一个 base commit；收尾时 auto-squash（见下）。
- **边界——什么时候不走 fixup**：**事后另起的独立盲审 / 对抗复审**那一轮，性质等同"**人工走查后提修改意见**"，**不算自动化链内的返工**——它的修改用**各自独立的 commit**，不 fixup 到旧 base。判据：这轮返工是否在**同一条自动化 implement→review 链**里？是 → `fixup`；是事后另起的独立审计 → 独立 commit。

### 本轮 / feature 收尾（用户确认收尾后）
- 用 **auto-squash** 把所有 `fixup!` 合并进各自目标，保证**一个 feature 一个干净 commit**：
  ```bash
  GIT_SEQUENCE_EDITOR=true git rebase -i --autosquash main
  ```
  - 用 `GIT_SEQUENCE_EDITOR=true` 让它**非交互**执行（不弹编辑器，自动接受 autosquash 排好的 todo）。本环境不支持需要人工编辑的交互式 rebase，必须走这个 no-op 编辑器写法。
  - autosquash **改写历史**：仅在 push / 开 PR **之前**做。若该分支已 push，需要 force-push——属对外操作，**先取得用户确认再做**。

### 一般约束
- **个人单用户仓库：直接 commit 并 push 到 `main` 已获授权**——在校验闸门全绿、一轮工作完成时即可提交 / 推送，无需逐次征求同意。
- 仍需**先取得用户确认**的操作：**force-push / 改写已推送历史**，以及**打 tag**（会触发镜像 CI / 对外发布；且打 tag 前须按下方「发版前置走查」真跑一次 `docker build`）。

## 发版前置走查（打 tag 前必做）

单元闸门绿 ≠ 真的能跑、能构建、能用。M1 出过"绿了但 docker 构建坏了"的事故，所以**打版本 tag（触发镜像 CI）之前**，除了 `pytest` / `ruff` 全绿，还要：

- **真起 app**：迁移（`python -m scripts.run_migrations`）→ `uvicorn app.main:app ...`，确认能正常启动、关键路由不 500。
- **真跑镜像构建**：本地 `docker build`（多阶段就跑完整条），确认构建通过、`COPY` 源都在。
- **关键功能人工瞄一眼**：尤其前端 / 可视化类（M2 的热力图、首页地图）——自动闸门判断不了"渲染对不对、UX 顺不顺"，这部分**靠看跑起来的 app，不靠读代码**。
- 上述任一不过 → **不打 tag**。tag 一旦 push 会触发 docker 镜像 CI / 对外发布，属对外操作，**先确认**。

## 数据安全红线（不可违反）

- 任何脚本 / migration **都不得删除或覆盖用户数据文件**（旧 `.db`、备份、volume）。删除只能是人工、事后、保留归档的独立步骤（见 `docs/design/m1-db-consolidation.md` §6 runbook）。
- 涉及历史数据的迁移**先在备份副本上演练**；迁移脚本必须幂等且搬完对账行数。
- Review 时只要发现"删文件 / drop 有数据的表 / truncate"出现在自动化任务里，直接判返工。

## 常用命令

```bash
# 环境
python -m venv .venv && source .venv/bin/activate && pip install -r dev-requirements.txt
# 迁移（初始化/适配 DB）
python -m scripts.run_migrations
# 起服务
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
# 测试 / lint / OpenAPI 导出
pytest
ruff check .
python scripts/export_openapi.py
```
