# CLAUDE.md

> [!URGENT]
> **实验室内部通用基础库(生产级、非 MVP、零业务假设)**
> 1. 本项目是被多个科研/生产项目依赖的**库**,不是应用:稳定性、并发性、防御性、可观测与测试不可为"简单"让步(YAGNI 仍适用,但不削减健壮性)。库的 bug 会同时击穿所有下游项目。
> 2. 你的所有思考过程和回复必须使用 **简体中文**。

## 1. 项目元数据
- **核心目标**: PolyGateway = 统一的大语言模型(LLM/VLM/OCR,音频预留)调度与中转库。治理单位是**一次模型调用**:请求封装、多源多账号、限流、错误分类与重试、熔断、Redis 响应缓存、流式看门狗、遥测(含成本)、结构化输出策略。全组件端口化可插拔。
- **架构权威文档**: `research-wiki/ARCHITECTURE.md`(架构单一事实源,含 D1-D14 决策及讨论过程、子系统设计、三项目迁移验收标准;**不受 400 行设计文档限制**,以无歧义传达既有讨论为准绳)。开发顺序见 `research-wiki/ROADMAP.md`;`research-wiki/designs/` 仅存放每次实现具体功能的设计文档。
- **参考项目**: `reference/` 下三个项目是本库的需求来源与代码蓝本(**只读,勿改**;M4 起"只读"指工作区文件与 main 检出不变——迁移实施经 `git worktree` 在 `~/Projects/m4-worktrees/` 的 feature 分支进行,worktree 的 git 操作会写 `reference/*/.git` 元数据,属预期);库必须能按 ARCHITECTURE.md §11 被它们迁移接入,否则即边界缺口。
- **技术栈**: Python 3.12+,核心仅依赖 `httpx` + `pydantic`,其余(redis/sqlite/postgres/json_repair/openai)一律 optional extras。conda 环境 `PolyGateway`。

## 2. 常用命令

> [!CRITICAL]
> 所有 Python 命令必须在 `PolyGateway` conda 环境中执行(`conda run -n PolyGateway <cmd>` 或先激活)。长时间运行的程序用 tmux 且禁用日志缓存。

```bash
make install   # editable 安装(含 dev 与全部 extras)
make test      # pytest + 覆盖率
make lint      # ruff --fix + import-linter(依赖铁律机械化执法)
make format    # ruff format
make ci        # 只读验证(check + test)
```

## 3. 标准作业程序(分档触发)

> **档位原则(Fable 5 适配,2026-07 调研决策)**: 约束"边界与验收",不规定思考步骤。强制档(MANDATORY)是硬门;其余由模型按 skill description 自判,自判标准是任务实质(规模/风险/是否触及公共承诺),不是省事。硬边界(reference/ 只读、危险命令、提交质量门)由 `.claude/settings.json` 注册的 hooks **确定性执行**,不依赖提示词自觉。

> [!CRITICAL]
> **执行模式: subagent 与 Codex 一律前台(2026-08-06 人类指令)**
> 一切 subagent(verifier、`subagent-driven-development` 执行器、Explore 等)与 Codex 调用**必须前台运行**——`Agent` 工具传 `run_in_background: false`,`/codex:rescue` 带 `--wait`,**禁止**后台派发后继续做别的事。
> **理由(实测教训)**: 后台完成通知不可靠——管道会掩盖真实退出码(`pytest ... | tail` 让失败跑报成 exit 0),等待脚本的 `pgrep -f` 会自匹配成死循环,于是出现"任务早完成却没人知道"和"任务挂了也没人知道"两种失败,且两种都以"看起来还在跑"的形态呈现,无法从外部区分。前台运行牺牲并行度换取状态确定性,这个交换在本项目是划算的。
> **同一理由适用于长跑命令**: 需要后台跑时(如全套件测试),命令末尾**不得接管道**,否则退出码失真;要判完成用 `wait`/轮询 PID,不要用会匹配到自身的 `pgrep -f "<完整命令串>"`。

### Phase 1: 规划与设计
1. 涉及**公共 API、端口签名、架构边界、新子系统**的变更**必须**调用 `brainstorming`(产出 2-3 备选方案+权衡)并经**人类确认**后实施;其余任务自判(判据: 是否改变库对下游的承诺)。动手前查阅 `research-wiki/`(单一事实源)。
2. 功能产生运行时数据时**必须**调用 `structured-logging`。
3. **里程碑级/跨多文件**功能编码前**必须**调用 `writing-plans`;小改动自判。审核门控: design 走 Claude 自审 → Codex 审 → **人类审**;plan 走 Claude 自审 → Codex 审 → 直接执行。

### Phase 2: 执行与验证
1. **测试结果门**: 合并前每个行为变更必须有"先失败后通过"的测试证据(`test-driven-development`);bug 修复必带回归测试;不规定中间怎么走。
2. **独立验证**: 里程碑级/跨多文件/合并前**必须**派全新上下文的 verifier subagent(`verification-before-completion`);任何规模的完成声明都必须逐条对应本会话内的工具输出(证据化声明,禁止虚报)。
3. **反 gold-plating**: 不做任务外的重构、抽象与"顺手清理"。

## 4. 核心规则

### 4.1 核心原则(按优先级)
- **P1 YAGNI**: 不写当前用不到的代码;但并发控制、防御校验、可观测埋点、错误隔离与测试是"当前需要",不在削减之列。
- **P2 高可读性**: 领域术语命名;注释解释"为什么"。
- **P3 单一职责**: 一句话说不清职责(需要"和")= 拆分。
- **P4 显式优于隐式**: 公共函数完整类型注解;依赖注入,不从全局偷取;严禁默认参数掩盖关键逻辑。
- **P5 防御性与安全性**: 一切外部输入(网关响应、LLM 返回、配置)校验后使用;严禁 `except Exception: pass`;严禁默认值掩盖错误;敏感信息只走 `.env`。
- **P6 可测试性**: 纯函数优先;外部依赖经 Protocol 注入;测试用真实样本或其二次构造。
- **P7 架构依赖规则**: 决策逻辑与状态存储分离(中间件算法一份,后端可插拔);`ports.py`/`types.py`/`errors.py` 为最内层,不 import 任何具体实现;`middleware/` 只依赖端口;`transports/`、`backends/`、`telemetry/` 只实现端口,互不依赖(import-linter 契约执法)。

### 4.2 库铁律(本项目特有,违反即 bug)

| 铁律 | 内容 |
|---|---|
| 零业务假设 | 库内禁止出现任何下游业务领域词汇(视频/文书/超声等)与业务 fixtures;扩展点一律 Protocol |
| 纯 asyncio 中立 | 无全局状态、无框架假设、无模块级单例;同一 `GatewayClient` 在 arq worker 与裸脚本中行为一致 |
| 取消可穿透 | `asyncio.CancelledError` 永不捕获吞没;重试循环、限流等待、流式读取全部可被取消;in-flight 资源在 finally 释放 |
| 错误分类驱动 | 一切失败必须落入 `errors.py` 四分类(Transient/SourceDead/RequestRejected/ResultInvalid),由分类决定重试/换源/熔断,禁止散落 ad-hoc 判断 |
| 遥测必录 | 每次调用(含缓存命中、失败)必经 `TelemetryRecorder` 记录,遥测写失败降级不冒泡;遥测调用点收敛为单一 helper,禁止复制参数列表(三项目 4 处复制的教训) |
| 降级方向 | 缓存/遥测后端不可用 → 静默降级(warning);限流/熔断后端不可用 → **报错而非放行**(防击穿网关) |
| 依赖极简 | 核心仅 httpx + pydantic;新增任何依赖必须进 optional extras 并经人类确认 |
| 无缓存毒化 | 缓存 key 必含 model + messages 摘要 + 命名空间/租户 + salt;多模态 content 先摘要再 hash |

### 4.3 代码开发规范
- 模块/类/方法必须有**中文 Docstring**;复杂逻辑用 `# Phase N` 注释组织。
- 校验分层: 外部输入校验用显式异常(Python `-O` 移除 assert,禁止 assert 承担生产校验);assert 仅用于内部不变量。
- 类名 `PascalCase`,私有前缀 `_`;导入顺序标准库→第三方→项目内。
- 日志统一 **loguru**,禁用 `print()`;返回类型用 frozen dataclass。
- 不考虑向后兼容,直接修改原文件。**例外**: `LLMResponse` 等已被三项目消费的公共类型,字段只增不删不改名(迁移兼容约束,见 ARCHITECTURE.md §5.1)。

### 4.4 Git 工作流
- 一切开发在 feature 分支,严禁直改 main;频繁语义化提交;提交**必须**调用 `commit` skill;大改动前先提交回滚点。

### 4.4.1 发布流程(每步都是历史欠账换来的,不得跳步)

> [!CRITICAL]
> **发布 = 合并 + push + tag + 构建 + 上传 registry。只 bump 版本号不叫发布。**
> 教训: 1.0.6 与 1.1.0 都完成了版本号 bump 与 CHANGELOG,却从未上传,registry 长期停在 1.0.5——下游 `pip install` 拿不到任何修复,且无人发现。

按顺序执行,**构建之前**必须先改完所有文档:

| # | 动作 | 要点 |
|---|---|---|
| 1 | **更新 README** | 打包会把当时的 README 固化进 sdist,**发布后再改就来不及了**(包里那份永远是旧的)。逐项核对: 安装命令的版本约束(`==1.1.*` 这类**极易漏改**,漏了下游就被锁在旧版)、能力表是否覆盖新行为、数字型断言是否仍成立(如遥测字段数,须用 `inspect.signature` 实测而非凭记忆) |
| 2 | CHANGELOG 定版 | "未发布" → `## X.Y.Z(日期)` |
| 3 | 版本号 | `pyproject.toml` + `src/polygateway/__init__.py` 两处必须一致 |
| 4 | 合并 main + push | `--no-ff`;合并后在 main 上重跑 `make lint` 与全套件,**外加 `pytest -m slow`** ——真实网关 e2e 与 Redis 时间语义变体被 `addopts = "-m 'not slow'"` 默认排除,**不显式跑就等于没跑**(约 20-40 分钟,取决于网关快慢)。它们不进日常提交是有意的: pre-commit 关卡跑全套件,网关一抖就挡住与之无关的提交,久了会把"测试红了先怀疑网关"变成惯性,真 bug 也会被当成抖动重试掉;代价是这道门必须由本清单兜住 |
| 5 | **打 tag 并 push** | `git tag -a vX.Y.Z -m "..."` + `git push origin vX.Y.Z`。历史上多个版本漏打 |
| 6 | 构建 | `rm -rf dist && python -m build && python -m twine check dist/*` |
| 7 | **上传 registry** | 凭据在 `~/.config/tea/config.yml`(tea CLI 的 Gitea token,**不在** `~/.pypirc`);token 走 `TWINE_PASSWORD` 环境变量,不进命令行<br>`TWINE_USERNAME=iomgaa TWINE_PASSWORD=$TOKEN python -m twine upload --repository-url https://gitea.iomgaa.online/api/packages/iomgaa/pypi dist/*` |
| 8 | **验证已发布** | `pip download --no-deps --index-url .../pypi/simple/ "polygateway==X.Y.Z"`,并解包确认新代码在内。**不验证不算发布完成** |
| 9 | **建 Release + 挂仓库 + 核对包页面** | `POST /api/v1/repos/iomgaa/PolyGateway/releases`(body 取 CHANGELOG 本版段;历史上只打 tag 不建 release,Releases 页长期为空);挂仓库走 `POST /api/v1/packages/iomgaa/pypi/polygateway/-/link/PolyGateway`(**2026-08-16 实测返 201 可用**,此前记录的"该实例 link API 返 404、只能网页手动"已过时);随后打开包页面确认有正文与仓库链接 |

> [!CRITICAL]
> **发布完成的判据是外部可见结果,不是本地步骤跑通**: 收尾必须以下游视角逐一打开产物页面(registry 包页面正文与仓库链接、仓库 Releases 页、`pip install` 后包内文件),看到什么算什么,缺的当场补进本清单——1.1.2 三步全绿却出现包页面空白(`pyproject` 缺 `readme`)、Releases 页 0 条、包未挂仓库。

Gitea 包 registry 是 **owner 级**(`/iomgaa/-/packages/`)不是仓库级;PyPI 元数据不含仓库字段,故不会自动挂到 `PolyGateway/packages`,需在包页面手动 Link to a repository。

### 4.5 配置管理
- 工程配置走 `pydantic-settings` + `.env`(模板 `.env.example`,敏感项不提交);严禁硬编码默认值;缺失关键配置直接报错。
- 多源命名约定 `{SCOPE}__{PROVIDER}__{N}__{FIELD}`;韧性参数键名沿用三项目习惯(`LLM_TIMEOUT` 等),降低迁移成本。
- 装配只有两条路: `GatewayClient.from_env()`/`from_settings()`(工厂)或构造函数全量注入(测试/高级);库内部任何组件不得自读环境变量。

### 4.6 测试组织
- `tests/{unit,integration,e2e}`;真实场景优先(录制的真实网关响应二次构造优于凭空 mock)。
- 覆盖率目标 80%;并发/韧性行为是一等测试对象: 重试穿透取消、熔断开路半开、限流结算退款、Redis 掉线降级方向、缓存 key 隔离。
- Redis 相关测试用真实 Redis(integration),不 mock Lua 行为;限流契约测试随实现一起交付(参考 CHSAnalyzer `tests/contracts_limiter.py`)。
- 涉及真实 LLM 的测试输出结构化 Markdown 至 `tests/outputs/<module>/<test>_<ts>.md`。
- **成败取决于外部服务当下状态的测试一律标 `slow`**(`tests/e2e/` 四个文件与 Redis 时间语义变体):它们默认不进日常套件,由发布清单第 4 步统一跑。判据是"重跑一次可能就绿了"——这种测试留在提交关卡里会污染信号。同理,给它们的超时不得紧于 `.env` 的生产配置,否则是设计上就会间歇红。

## 5. 项目结构

```text
project_root/
├── src/polygateway/          # 库本体(结构见奠基设计 §7: ports/types/errors 内核 +
│                             #   middleware/ transports/ backends/ telemetry/ structured/,
│                             #   见 ARCHITECTURE.md §8)
├── tests/                    # unit/integration/e2e + 限流契约测试
├── reference/                # 三个参考项目(只读,勿改,不提交)
├── tools/                    # 独立工具脚本(不被 import)
├── scripts/                  # 仅 .sh
├── research-wiki/            # 单一事实源(designs/plans/findings/adrs/reviews)
├── data/ logs/               # 运行产物,不提交
└── Makefile / pyproject.toml / .env(.example) / CLAUDE.md
```

> 注: 以上为**目标结构**。当前已落地: `research-wiki/ARCHITECTURE.md`、`reference/`、`.claude/`(18 个 skill 已完成 Fable 5 适配改造 + hooks 硬边界 + settings.json);其余随 M1 里程碑创建(ARCHITECTURE.md §12)。

硬性规则: 根目录不得出现 `.py`;`scripts/` 只放 `.sh`;禁止 `helpers/ common/ shared/ misc/ lib/` 目录名;`data/`、`logs/`、`tests/outputs/` 不提交;`reference/` 只读且不入库。

## 6. 上下文导航

| 需求 | 路径 |
|---|---|
| 架构全貌: 决策 D1-D14 及讨论过程、端口清单、错误分类、子系统设计、迁移验收 | `research-wiki/ARCHITECTURE.md`(单一事实源) |
| 开发顺序与里程碑状态 | `research-wiki/ROADMAP.md`(活文档,随进度更新) |
| 三项目迁移文档(ARCHITECTURE §11 的展开,库设计的常驻约束) | `research-wiki/migrations/`(govdoc-saas / video-tree-trm5 / chsanalyzer) |
| 功能设计文档(每次实现新功能时新增) | `research-wiki/designs/` |
| 实现计划 | `research-wiki/plans/` |
| **用户文档站**(Gitea Wiki,Diátaxis 四区)结构/更新时机/写作纪律 | `research-wiki/docs-convention.md`;**发版或公共行为变更必须按其 §2 清单同步 wiki 与 CHANGELOG,版本 bump 提交不得裸发** |
| 治理网关参考实现 | `reference/Video-Tree-TRM5/adapters/`(llm/breaker/streaming/redis_cache/telemetry) |
| 分布式限流/熔断参考实现 | `reference/CHSAnalyzer/app/coordination/`(limiter+Lua/provider_gate)与 `app/providers/governance.py` |
| 错误分类参考 | `reference/CHSAnalyzer/app/domain/errors.py` |
| OCR 两端点参考 | `reference/Video-Tree-TRM5/adapters/ocr.py` 与 `reference/CHSAnalyzer/app/providers/invokers.py:408-552` |
| 第一次抽库尝试(教训与蓝本) | `reference/GovDoc-SaaS/packages/docagent-core/` |

## 7. 输出规范
- 所有输出**中文**;文档优先表格/伪代码/Mermaid;禁止超 15 行代码块入文档、禁止连续超 5 条碎片列点;设计 ≤400 行、计划 ≤1000 行、报告 ≤300 行。
- **例外**: `research-wiki/ARCHITECTURE.md` 不受行数限制——它的准绳是"后来的 AI/人类无需还原原始讨论即可准确理解全部决策及理由",宁详勿略(详细 ≠ 琐碎:记录论证与取舍,不堆砌实现细节)。

## 8. Skill 使用规则

> [!CRITICAL]
> **Skill 的 description 即触发边界。** 下表标注 **MANDATORY** 的情形是硬门(设计人类门、合并前测试证据门、合并前独立验证门、commit 格式),不得跳过;其余情形按 description 边界自判——自判看任务实质(规模/风险/是否触及公共承诺),不是省事。任何 skill 流程不得引入任务外的重构或抽象。优先级: 用户显式指令 > Skill 详细流程 > 本文件宏观规则。

| Skill | 触发边界 |
|-------|---------|
| `brainstorming` | **MANDATORY**: 公共 API/端口签名/架构边界/新子系统变更;其余自判 |
| `writing-plans` | **MANDATORY**: 里程碑级/跨多文件功能;小改动自判 |
| `test-driven-development`(测试结果门) | **MANDATORY**: 合并前——行为变更须有先失败后通过的测试证据 |
| `verification-before-completion`(独立验证) | **MANDATORY**: 声称完成/合并前;里程碑级须派全新上下文 verifier subagent |
| `commit` | **MANDATORY**: 提交代码时 |
| `requesting-code-review` / `receiving-code-review` | **MANDATORY**: 合并/PR 前 / 收到审查反馈后;中途审查自判 |
| `systematic-debugging` | 遇到 bug、测试失败、异常行为时(根因先于修复) |
| `structured-logging` | 功能会产生运行时数据时 |
| `subagent-driven-development` | 执行大型已批准计划时的**可选**执行器(内含合并前一次整分支审查) |
| `finishing-a-development-branch` | 实现完成且测试通过,准备集成时 |
| `using-git-worktrees` | 需要并行/隔离的分支开发时 |
| `research-wiki` / `graphify` | 管理知识库时 / 已建图后的代码结构检索 |
| 科研类: `harness-eval` `idea-creator` `novelty-check` `research-lit` | 惰性资产,现阶段不用,由模型按任务实际需要启用 |
