Files
PolyGateway/CLAUDE.md
T
iomgaa c2e9f5396c docs: write down the release procedure that keeps getting skipped
Bumping the version is not releasing. 1.0.6 and 1.1.0 both got a version
bump and a changelog entry but were never uploaded, so the registry sat at
1.0.5 and downstream could not install any of those fixes.

The ordering matters in one non-obvious way: README has to be correct
before the build, because sdist freezes whatever is there at that moment.
That is exactly how 1.1.1 shipped with a stale README. The install pin is
called out by name since it is the easiest line to forget and the most
damaging to leave wrong.

Also records where the Gitea token actually lives — tea's config, not
.pypirc — after that misreading led to a wrong "no credentials" claim.
2026-08-06 12:44:06 -04:00

16 KiB

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.11+,核心仅依赖 httpx + pydantic,其余(redis/sqlite/postgres/json_repair/openai)一律 optional extras。conda 环境 PolyGateway

2. 常用命令

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

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 与全套件
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 环境变量,不进命令行
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",并解包确认新代码在内。不验证不算发布完成

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

5. 项目结构

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.mdreference/.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.pyreference/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 惰性资产,现阶段不用,由模型按任务实际需要启用