18 KiB
AGENTS.md — Home Automation Backend
本文件是本仓库 coding agent 指引的 single source of truth;CLAUDE.md 通过符号链接指向本文件。它定义本项目的工作流程、文档位置、commit 规范。支持对应项目指引的 agent 在动手前应完整读取本文件。
项目速览
- 个人用 home-automation 应用:FastAPI + React SPA + SQLite + SQLAlchemy + Alembic,前后端同源托管。
- 单 admin 鉴权(Argon2 + server-side session cookie),runtime config 落
app_config表。 - 模块:public IPv4 monitor、SMTP 通知、location / poo recorder、Home Assistant in/out、TickTick OAuth、Modbus / DSMR 能耗采集、MQTT / HA Discovery、动态电价与电费计算。
- 已发布
v1.5.1。M1、M2、M4-M7 已完成;M3 token / 移动端仍为远期方向。 - 当前现实:已收敛为单一
app.db、一套 DeclarativeBase 和一条 Alembic 链;只有历史数据迁移 runbook 会读取旧 location / poo 数据库。 - 明确不做: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/design/m4-login-hardening.md |
M4 登录加固(已完成) |
docs/design/m5-iot-energy.md |
M5 IoT / 能耗采集(已完成) |
docs/design/m6-tibber-dynamic-energy.md |
M6 动态电价、DSMR 与电费计算(已完成) |
docs/design/m7-meter-epochs-archival.md |
M7 电表生命周期 / 换表归档(已完成) |
docs/*.md(auth / public-ip-monitor / location-recorder …) |
各模块说明,按需读 |
开工时读取顺序:
docs/design/README.md(每轮都读,它是流程与验收的共同契约)。- 本轮对应的 milestone 文档(如
docs/design/m1-db-consolidation.md),定位要做的任务卡。 - 任务卡
Files列出的源文件 + 该模块的docs/*.md(按需)。 docs/roadmap.md仅在需要全局视角时读。
工作流程
实现模式(由用户的提示词决定)
- 默认逐步:给一个 milestone 文档,按其中原子任务一步一步实现。
- (a) 只实现一步:用户说"只实现一步 / 这一个任务"时,只做那一个任务卡,跑完校验闸门后停下,等用户确认,不要顺手往下做。
- (b) 完成整个 milestone:仅当用户在提示词里显式要求启用 sub-agent时,才起 implementer / reviewer / fixer sub-agent(按下方**『默认能力档位』**选择模型,用户人工指定则覆盖),按任务依赖顺序跑完整条链。
- Sub-agent 纪律:只在用户显式要求时才 spawn sub-agent;单步/小改动在主线内联完成。当前 harness 支持独立 sub-agent 时,使用其原生机制按下方**『默认能力档位』**派发;不支持时不得假装已创建 sub-agent,应明确说明限制,并仅在用户允许 fallback 时由主 agent 继续。
默认能力档位(实现模式 sub-agent;可被人工指定覆盖)
起 implementer / reviewer / fixer sub-agent 时,默认按下列能力档位选择当前 harness 支持的模型,无需用户每次人工指定:
| 角色 | 通用模型要求 | 推理档位 | Harness 示例(非强制) |
|---|---|---|---|
| Implementer | 平衡型代码实现模型 | medium 或等效档位 |
Claude Code:Sonnet;Codex/OpenAI:GPT-5.6 Terra (gpt-5.6-terra) |
| Fixer(返工) | 平衡型代码实现模型 | medium 或等效档位 |
Claude Code:Sonnet;Codex/OpenAI:GPT-5.6 Terra (gpt-5.6-terra) |
| Reviewer | 当前 harness 支持的最强通用推理 / 代码模型 | extra-high / xhigh 或等效档位 |
Claude Code:Opus;Codex/OpenAI:GPT-5.6 Sol (gpt-5.6-sol) |
- 示例非强制:示例模型只表示当前推荐映射,不构成跨 harness 的硬性模型 ID;当前 harness 不支持时,选择最符合「通用模型要求」的可用模型。
- 选择优先级:用户显式指定 > 当前 harness 的原生角色配置 > 上表的 harness 示例 > 按通用模型要求自动选择。
- 推理档位说明:若 harness 提供独立的 reasoning-effort 设置,按上表设置;若不提供,在 spawn prompt 中明确 implementer/fixer 按平衡深度思考,reviewer 按对抗性外部审计强度复核。
角色(Orchestrator → Implementer → Reviewer → Fixer)
- 我(主线)= Orchestrator:挑依赖已满足的下一个任务、派发、转述结果、维护任务
Status。 - Implementer(平衡型代码实现模型,medium 或等效档位):一次一个任务,严格按任务卡,不扩范围。
- Reviewer(最强通用推理 / 代码模型,extra-high / xhigh 或等效档位):实现完成后起 Reviewer sub-agent,按任务卡
Acceptance criteria+Reviewer checklist复核、独立重跑校验闸门,驱动返工直到本轮 PASS。 - Fixer(平衡型代码实现模型,medium 或等效档位):按 reviewer 的编号返工清单返工;每轮返工起一个干净的 Fixer(与首次实现的 Implementer 分开冷启动),先读对应
review-notes/<task>-review-<n>.md再改。
Reviewer 盲审纪律(M1 教训)
M1 里 review 从未触发过一次 rework,根因是 orchestrator 把自己的结论 / 辩护喂给了 reviewer,造成 context bleed、review 沦为橡皮图章。所以:
- reviewer 必须使用全新、独立的 sub-agent / thread 冷启动,并最小化喂料——spawn prompt 只给:① 任务卡(
Acceptance criteria+Reviewer checklist)、② 对应的review-notes/<task>-impl|rework-<n>.md路径、③ 要审的 diff / commit 范围。 - 不要在 prompt 里塞 orchestrator 自己的判断、"我觉得没问题"、对实现选择的辩护,或上一轮 reviewer 的倾向性结论。让它独立得出结论、独立重跑校验闸门。
- 事后另起的整库独立盲审(如对抗复审)同理:使用全新独立的 agent / thread、最小上下文,把它当"外部审计"而非"确认自己没错"。
校验闸门(每个任务结束都要全绿)
根目录、激活 .venv 后:
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)。
不过闸门就不算完成,不得跳过、不得留红给下一轮。
Repo-meta 例外:纯文档、agent 指引、符号链接等不影响可执行代码、构建与 API 契约的变更,可在用户明确同意时跳过代码闸门。仍须完成针对性校验(如链接目标、文件类型、diff 与 Git 状态),并在结果中明确记录未运行哪些闸门。
API 契约同步:openapi/ 与 schema.d.ts 是两步(v1.4.0 后教训)
只跑 export_openapi.py 不够。 前端的 frontend/src/api/schema.d.ts 是由 openapi/openapi.json 二次生成的,CI(.github/workflows/frontend.yml 的 Check codegen is in sync)会重跑 codegen 并 git diff --exit-code src/api/schema.d.ts。漏了第二步 → 本地闸门全绿、远端 CI 红。真出过:d07a083 改了 /api/energy/prices 的 docstring 并同步了 openapi.json,但没重跑 codegen,只差一行注释就把 CI 挂了。
所以只要动了路由 / Pydantic schema / 路由 docstring(docstring 也会进 OpenAPI description!),两步都要跑,两个产物都要入库:
# 1) 后端契约
python scripts/export_openapi.py && git diff --exit-code openapi/
# 2) 前端类型(在 frontend/ 下)
npm run codegen && git diff --exit-code src/api/schema.d.ts
- 判据:
git diff --exit-code openapi/有输出 → 必然还要跑一次npm run codegen。 - 反过来也成立:
schema.d.ts不要手改,它是生成物。 - Reviewer 审"动了路由 / schema / 路由 docstring"类任务时,把这两个产物是否都已重新生成并入库当作 acceptance 的一部分。
构建上下文完整性(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守"DockerfileCOPY源必须存在于构建上下文";新增 / 改动COPY时确保它仍覆盖得到。 - Reviewer 审"删 / 移文件"类任务时,必须顺带核对构建清单引用,把它当 acceptance 的一部分。
每轮简报(review-notes/)
由 milestone 任务卡驱动的每轮实现、返工或 review,都要在 review-notes/ 下产出中文简报。该目录已在 .gitignore 忽略,纯本地、不入库——它是 agent 之间和与人之间的交接载体,不是仓库产物。纯讨论、只读分析与不进入正式任务链的 repo-meta 变更无需产出简报,除非用户明确要求。
- 实现 / 返工简报:每轮实现完成后(无论首次实现还是返工),写一份。文件名建议
<task-id>-impl-<n>.md/<task-id>-rework-<n>.md(如M1-T03-impl-1.md、M1-T03-rework-1.md)。至少包含:- 本轮修改的具体内容(改了哪些文件、做了什么、为什么)。
- 自动化测试结果(
pytest/ruff/ 前端闸门的实际输出或结论,通过/失败逐项写清)。 - 若需人工 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 时按「用户显式指定 > harness 原生角色配置 > 默认能力档位」选择模型与 reasoning effort,并使用当前 harness 支持的原生配置方式落实。
一句话:简报是异步交接的介质,orchestrator 是把它们接起来的线。 缺了显式传路径这一步,简报就只是躺在磁盘上没人读的文件。
Commit 规范(重点)
分支
- 本仓库是个人单用户项目:默认直接在
main上开发,不强制 feature 分支,无需开 PR。是否 push 按下方「一般约束」执行。 - 仍保持每个任务一个干净 commit(message 前缀任务/里程碑 ID)。改动较大想隔离时可临时开分支,用完快进合并回
main(保持线性历史),非必需。 - 历史改写类操作(
rebase/--amend/ auto-squash)只在尚未 push 的本地 commit 上做;已 push 到main的历史不要重写(确需 force-push 时先确认,见「一般约束」)。
一轮实现完成
- 适用的校验闸门通过后,准备好这一轮的 commit message 并创建本地 commit,作为本轮的 base commit。默认不 push;只有用户明确授权自动 push 时才推送到远端,授权范围按用户原话执行。
- 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:
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:# 在以 main 为基线的 feature branch 上 GIT_SEQUENCE_EDITOR=true git rebase -i --autosquash main # 直接在 main 上整理尚未 push 的本地提交 GIT_SEQUENCE_EDITOR=true git rebase -i --autosquash origin/main- 执行前确认选定的基线位于 base commit 之前,以便 base commit 与对应
fixup!都进入 rebase 范围。用GIT_SEQUENCE_EDITOR=true让它非交互执行(不弹编辑器,自动接受 autosquash 排好的 todo)。 - autosquash 改写历史:仅在 push / 开 PR 之前做。若该分支已 push,需要 force-push——属对外操作,先取得用户确认再做。
- 执行前确认选定的基线位于 base commit 之前,以便 base commit 与对应
一般约束
- 个人单用户仓库:默认直接在
main上开发并创建本地 commit。默认不 push;只有用户明确授权自动 push 时才推送到远端。 - 始终需要单独、明确授权的操作: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"出现在自动化任务里,直接判返工。
常用命令
# 环境
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