feat: 初始化 cryptoHermes 行情网关 v1 MVP + harness 工程文档
Binance USDⓈ-M Futures 行情网关,给上游 Hermes 量化分析引擎提供单一聚合 接口 /v1/market/context(K 线 + funding + OI + 多空比 + taker volume)。 只读公共行情,不下单、不接私钥、不查账户。 ## v1 实现范围(Milestone 1-5) - Clean Architecture 4 层(controller/usecase/repo/entity),接口边界在 internal/usecase/ports.go - Binance Futures REST client(K 线 / ticker24h / funding / OI / 多空比 / taker volume 共 9 个接口),全链路 string 价格避免 float64 精度问题 - TimescaleDB 5 张 hypertable(market_klines / funding_rates / open_interest / long_short_ratio / taker_buy_sell_volume),主键含 时间维度,UpsertMany 幂等 - robfig/cron 定时采集(15m/1h/4h/1d/1w 多周期 K 线 + 衍生品 15 分钟 落库),未收线 K 线 (close_time > now) 由 mapper/repo 双重过滤 - pkg/httpclient 统一限流(默认 20 req/s, burst 40)+ 重试,避免触发 Binance 2400 weight/min IP 上限 - /v1/market/context 聚合接口:errgroup 并发拉 snapshot/funding/OI,DB K 线不足 200 根回源 Binance 异步补 - cmd/backfill CLI 支持指定 from/to 大段回填(Binance 历史 OI / 多空比 官方只保留 30 天,必须自己存) - Docker Compose + Makefile + golang-migrate,本地一键启 技术指标(support/resistance/Vegas/箱体)留待 v2,技术段返回空对象 + warning 占位。 ## Harness 工程文档 - AGENTS.md — AI agent 工作速查(10 个章节) - ai/project-map.md — 仓库结构、扩展点、控制流 - ai/risk-guardrails.md — G1-G10 守卫规则(每条带可机械验证命令) - ai/adr/0001-architecture-foundations.md — 9 条架构基础决策 - ai/task-templates.md — 6 种任务契约模板 - ai/harness-health.md — 当前 harness 健康度评估 3 个 grep 守卫已验证通过:controller / usecase 无具体实现依赖,全项目 无私钥/签名字段。
This commit is contained in:
189
ai/adr/0001-architecture-foundations.md
Normal file
189
ai/adr/0001-architecture-foundations.md
Normal file
@@ -0,0 +1,189 @@
|
||||
# ADR 0001 — 架构基础决策
|
||||
|
||||
**状态**:Accepted
|
||||
**日期**:2026-05-24
|
||||
**决策者**:项目维护者
|
||||
**适用范围**:cryptoHermes v1 全部代码
|
||||
|
||||
---
|
||||
|
||||
## 背景
|
||||
|
||||
cryptoHermes 是给上游 Hermes 量化分析引擎用的 Binance Futures 行情网关。在 MVP 启动前需要锁定一组基础技术决策,避免后续 PR 反复重新评估。
|
||||
|
||||
需求边界(来自 `docs/dev.md` 第 1 章):
|
||||
- 只读公共行情,不下单、不接私钥
|
||||
- BTCUSDT / ETHUSDT,分钟到周级 K 线 + 衍生品(funding / OI / 多空比 / taker volume)
|
||||
- 历史数据必须自存(Binance 历史 OI / 多空比官方只保留 30 天)
|
||||
- Hermes 只调用 `/v1/market/context` 一个聚合接口
|
||||
- 部署形态:单体服务 + Postgres,国内开发机通过 HTTPS_PROXY 访问 Binance
|
||||
|
||||
---
|
||||
|
||||
## 决策
|
||||
|
||||
### D1. Web 框架:Fiber v2
|
||||
|
||||
**选了 Fiber v2**(而不是 net/http 标准库、Gin、Echo、Chi)。
|
||||
|
||||
**理由**:
|
||||
- 性能足够(fasthttp 底层),单体网关不需要再调优
|
||||
- API 与 Express/Gin 相近,团队上手快
|
||||
- 内置 recover/logger/req-id 中间件
|
||||
- v2 稳定,社区活跃
|
||||
|
||||
**代价**:
|
||||
- 基于 fasthttp,不兼容 `net/http` 中间件生态
|
||||
- 错误类型用 `*fiber.Error`,需要在 controller 包一层
|
||||
|
||||
**何时重新评估**:如果需要 HTTP/2 server push、需要复用 `net/http` 生态中间件、或者 v2 长期不维护——届时考虑迁回标准库。
|
||||
|
||||
---
|
||||
|
||||
### D2. 数据库:TimescaleDB on PG 16
|
||||
|
||||
**选了 TimescaleDB**(而不是纯 Postgres、InfluxDB、ClickHouse)。
|
||||
|
||||
**理由**:
|
||||
- 时序数据天然适合 hypertable:5 张表全部按时间分片,查询近期数据走最新 chunk,旧数据自动归档
|
||||
- SQL 接口和普通 PG 完全兼容,pgx 直连,无需新驱动
|
||||
- 没有引入新的运维栈(开发机用 Docker 起 `timescale/timescaledb:latest-pg16` 即可)
|
||||
- Chunk 间隔可按表调(K 线 1 周、funding 30 天)
|
||||
|
||||
**代价**:
|
||||
- 必须跑 `SELECT create_hypertable(...)` 才能享受时序优化,新表容易忘
|
||||
- 升级 PG 大版本需要看 Timescale 兼容矩阵
|
||||
|
||||
**何时重新评估**:如果数据量进入 TB 级、需要列存压缩或者 OLAP 复杂聚合——考虑 ClickHouse。
|
||||
|
||||
---
|
||||
|
||||
### D3. Architecture:Clean Architecture(4 层 + ports.go 接口边界)
|
||||
|
||||
**4 层**:`controller / usecase / repo / entity`。
|
||||
**接口边界**:`internal/usecase/ports.go` 是唯一定义对外接口的地方。
|
||||
|
||||
**理由**:
|
||||
- 上游 Hermes 只通过 REST 调用本服务,但 Binance 这个数据源大概率会扩展到 CoinGlass / ETF / CME。把数据源抽象成 `MarketDataProvider` / `DerivativesProvider` 接口,新增数据源只需要新建 `internal/repo/webapi/<source>/`,不动业务编排。
|
||||
- 单测可以 mock 接口,不需要起真 DB / 真 Binance。
|
||||
- `internal/app/app.go` 是唯一的组合根,DI 集中可见。
|
||||
|
||||
**强制约束**(见 `ai/risk-guardrails.md` G6):
|
||||
```
|
||||
controller 不可直接 import binance/postgres
|
||||
usecase 不可直接 import binance/postgres
|
||||
```
|
||||
|
||||
**代价**:
|
||||
- 多一层 `ports.go`,每个新接口要写两次(接口 + 实现)
|
||||
- 对小型项目可能略 overkill,但本项目已经规划到 v3(多数据源),早做减少返工
|
||||
|
||||
**何时重新评估**:如果 v2 之后接口数量爆炸(> 30 个)→ 考虑按子域拆分 `ports.go` 为多个文件。
|
||||
|
||||
---
|
||||
|
||||
### D4. 传输层:REST-only(v1 不上 gRPC / 消息队列 / WebSocket)
|
||||
|
||||
**选了 REST**(而不是 gRPC、NATS、WS 推送)。
|
||||
|
||||
**理由**:
|
||||
- Hermes 是同语言 Go 服务,但跨服务调用频率是分钟级(拿一次 context),REST + JSON 完全够
|
||||
- gRPC 多一份 proto 维护负担,团队/工具收益不明显
|
||||
- WebSocket 推送是独立工程量(重连、消息丢失、订阅状态机),v1 不进入
|
||||
- Cron + REST 拉取模型对运维友好(任何 HTTP 监控就够)
|
||||
|
||||
**代价**:
|
||||
- 如果未来 Hermes 需要 sub-second 行情,REST 拉模型撑不住
|
||||
|
||||
**何时重新评估**:Hermes 接入实盘策略后,如果发现"分钟级"延迟成为瓶颈 → 写新 ADR 引入 WS。
|
||||
|
||||
---
|
||||
|
||||
### D5. 价格 / 成交量:全链路 `string`
|
||||
|
||||
**所有金额字段端到端用 `string`**:Binance DTO 是 string、entity 是 string、JSON 响应是 string。落库时由 PG 转 `NUMERIC(36,18)`。
|
||||
|
||||
**理由**:
|
||||
- Binance 原样返回字符串,避免 float64 转换丢精度
|
||||
- `108000.12` 这种数 float64 表示就会丢尾数;累积统计后偏差更大
|
||||
- 上游量化分析对价格精度敏感,宁可让 Hermes 自己决定怎么解析
|
||||
|
||||
**代价**:
|
||||
- 加减乘除需要 PG 端做或者引入 decimal 库
|
||||
- JSON 响应里 price 是字符串,前端/客户端要适配
|
||||
|
||||
**强制约束**:见 `ai/risk-guardrails.md` G2。
|
||||
|
||||
---
|
||||
|
||||
### D6. 数据源边界:只用 Binance USDⓈ-M Futures 公共 API
|
||||
|
||||
**v1 不引入 CoinGlass、Glassnode、ETF Flow、CME、链上数据**。
|
||||
|
||||
**理由**:
|
||||
- Binance 一家覆盖 80% 的需求(K 线 / 衍生品 / 多空比)
|
||||
- 引入新数据源等于引入新的鉴权、限流、错误模式、SLA——v1 优先把一条链路打通
|
||||
- 路线图 v2 / v3 才扩展数据源(README "路线图" 段)
|
||||
|
||||
**强制约束**:
|
||||
- 新增数据源前必须先写 `ai/specs/<source>.md`(见 AGENTS.md §10)
|
||||
- 不可使用 Binance 的私有签名接口(见 G1)
|
||||
|
||||
---
|
||||
|
||||
### D7. 配置层:cleanenv(yaml + env override)
|
||||
|
||||
**选了 cleanenv**(而不是 viper、koanf、env-only)。
|
||||
|
||||
**理由**:
|
||||
- 一份 yaml 默认值 + 环境变量覆盖,开发和部署都方便
|
||||
- 比 viper 轻,没有多余抽象
|
||||
- `env-default:` tag 让默认值跟字段定义放在一起,可读性好
|
||||
|
||||
**代价**:无明显缺点。如果后续需要热重载(不需要)才会考虑换。
|
||||
|
||||
---
|
||||
|
||||
### D8. 限流:`golang.org/x/time/rate` token bucket
|
||||
|
||||
挂在 `pkg/httpclient/client.go` 里,默认 RPS=20 / Burst=40,从配置读。
|
||||
|
||||
**理由**:
|
||||
- 标准库附属,零外部依赖
|
||||
- token bucket 适合突发场景
|
||||
- Binance IP 限 2400 weight/min,20 req/s × 60s = 1200 req/min,留出一半余量给重试和 backfill
|
||||
|
||||
**强制约束**:所有外部 HTTP 调用走 `pkg/httpclient`,见 G5。
|
||||
|
||||
---
|
||||
|
||||
### D9. Scheduler:robfig/cron v3
|
||||
|
||||
**选了 robfig/cron v3**(而不是自己写 ticker、go-co-op/gocron)。
|
||||
|
||||
**理由**:
|
||||
- cron 表达式所有人都看得懂,schedule 修改是配置而非代码
|
||||
- 稳定老牌,3.0 之后无破坏性变化
|
||||
- 和 Fiber 在同一进程内运行,graceful shutdown 容易做
|
||||
|
||||
**代价**:
|
||||
- 没有分布式锁——但本服务 v1 是单实例,不需要
|
||||
|
||||
---
|
||||
|
||||
## 不在本 ADR 范围
|
||||
|
||||
- **下单 / 账户 / 私钥**:永久禁止(G1)。要做必须新仓库。
|
||||
- **多交易所聚合**:v3 才考虑,届时再写 ADR。
|
||||
- **WS / 实时推送**:见 D4,需要单独写 ADR。
|
||||
- **观测性(metrics / tracing)**:MVP 阶段只用 slog 结构化日志,Prometheus / OTel 留待生产部署前评估。
|
||||
|
||||
---
|
||||
|
||||
## 后续 ADR 的触发条件(来自 AGENTS.md §10)
|
||||
|
||||
- 换 HTTP 框架 → 新 ADR
|
||||
- 引入消息队列 / WS → 新 ADR
|
||||
- 跨服务通信改 gRPC → 新 ADR
|
||||
- 数据库换型(PG → ClickHouse)→ 新 ADR
|
||||
- 引入新数据源 → `ai/specs/<source>.md`(不一定要 ADR,除非影响整体架构)
|
||||
103
ai/harness-health.md
Normal file
103
ai/harness-health.md
Normal file
@@ -0,0 +1,103 @@
|
||||
# Harness Health — cryptoHermes
|
||||
|
||||
**日期**:2026-05-24
|
||||
**版本**:v1 MVP 刚完成(Milestone 1-5)
|
||||
|
||||
按 he-maintainer 的 scoring rubric 评估当前 harness 工程状态。Rating: `strong` / `partial` / `weak`。
|
||||
|
||||
---
|
||||
|
||||
## 总览
|
||||
|
||||
| 维度 | Rating | 说明 |
|
||||
|---|---|---|
|
||||
| 项目地图清晰度 | strong | `ai/project-map.md` 完整覆盖 Clean Arch 4 层 + 数据流 + 扩展点 |
|
||||
| 验证命令可跑 | strong | Makefile 全部命令在 README + AGENTS.md 双重列出 |
|
||||
| 快速反馈环路 | strong | `go build ./...` < 2s,`go vet ./...` < 1s,单包测试支持 |
|
||||
| 任务契约质量 | partial | 模板已写(`ai/task-templates.md`),但还没有真实任务用过 |
|
||||
| 守卫规则清晰度 | strong | 10 条 G1-G10,每条都有可机械验证命令 |
|
||||
| 架构决策耐久度 | strong | ADR 0001 记录 9 条核心决策;其他 ADR 等触发 |
|
||||
| Spec / test-plan | partial | 还没有 specs/,因为 v1 没有需要 spec 的任务;v2 新数据源时必填 |
|
||||
| 失败反馈环路 | partial | AGENTS.md §10 写了升级时机,但还没有 `ai/risk-guardrails.md` 改动史可参考 |
|
||||
| 文档 vs 代码对齐 | strong | AGENTS.md / README / project-map 全部基于实际仓库结构 |
|
||||
| MEMORY / 长期上下文 | n/a | 本仓库无 `MEMORY.md`,按设计——长期知识在 `docs/dev.md` 和 `ai/` 文档中 |
|
||||
|
||||
**总体**:**strong**(MVP 完成时点适用),但有 2 个 partial:
|
||||
- task-templates 未经过真实任务校验
|
||||
- specs/ 目录还是空的(等 v2 需要时填)
|
||||
|
||||
---
|
||||
|
||||
## 已具备的工程基础
|
||||
|
||||
- **3 层 AI 文档系统**:`AGENTS.md`(速查)→ `ai/project-map.md`(结构)→ `ai/adr/`(决策)。`docs/dev.md` 作为最详细的设计源头单独存在。
|
||||
- **可机械验证的守卫**:G1-G10 全部给了 grep / make 命令,没有靠 review 的软规则。
|
||||
- **唯一的组合根**:`internal/app/app.go`,DI 一处可见。
|
||||
- **接口边界文件**:`internal/usecase/ports.go` 是唯一定义对外接口的位置,新 agent 找接口去这里。
|
||||
- **强约束 grep 在 AGENTS.md §6**:每次 PR 都可以直接跑。
|
||||
|
||||
---
|
||||
|
||||
## 已识别的薄弱点
|
||||
|
||||
### W1. task-templates 未经过实战
|
||||
**现状**:模板已写完,但没有 PR 用过这些契约。
|
||||
**影响**:模板可能太严格或太松。
|
||||
**纠正时机**:完成第一个非平凡 PR(比如 Milestone 6 的技术指标)后回看,调整模板。
|
||||
|
||||
### W2. 还没有 work-status.md
|
||||
**现状**:目前是单 agent / 单会话工作,不需要 work-status 协调。
|
||||
**影响**:暂无。
|
||||
**升级时机**:开始 Milestone 6(技术指标)或 v2(新数据源)时,如果跨多次会话,建立 `ai/work-status.md`。
|
||||
|
||||
### W3. CI 完全未搭建
|
||||
**现状**:本地有 `make test` / `go vet`,但仓库无 GitHub Actions / GitLab CI。
|
||||
**影响**:守卫规则全靠人工跑 grep。
|
||||
**升级时机**:合并第一个外部 PR 前,搭一个最小 CI(`go build` + `go vet` + grep 边界检查 + migration up/down)。
|
||||
|
||||
### W4. 单元测试覆盖率未度量
|
||||
**现状**:MVP 阶段重点是端到端可用,未补单测覆盖率。
|
||||
**影响**:refactor 时风险偏高。
|
||||
**升级时机**:Milestone 6 引入指标计算(计算正确性 critical)前,先补 mapper / repo 的 table-driven 测试。
|
||||
|
||||
### W5. 部分 docs/* 未提交到 git
|
||||
**现状**:`docs/dev.md` 是 untracked(git status 显示 `?? docs/`)。
|
||||
**纠正方式**:本次 commit 一起加进来。
|
||||
|
||||
---
|
||||
|
||||
## 后续升级触发器
|
||||
|
||||
| 触发条件 | 应采取的动作 |
|
||||
|---|---|
|
||||
| 同一类错误第二次出现 | 加进 `ai/risk-guardrails.md`,给可验证命令 |
|
||||
| 重大架构决策(换 HTTP 框架 / 引入 MQ / 跨服务通信) | 写新 ADR |
|
||||
| 新增数据源(CoinGlass / ETF / CME) | 必填 `ai/specs/<source>.md` |
|
||||
| 跨多次会话的长任务 | 写 `ai/work-status.md` |
|
||||
| 单测/集成测试出现 flake | 加 test-plan,约定 retry / quarantine 策略 |
|
||||
| 文档与代码出现漂移(grep 守卫失败但代码正常) | 立刻同步,不要"以代码为准"绕开守卫 |
|
||||
|
||||
---
|
||||
|
||||
## 复评建议
|
||||
|
||||
- **3 个月后**(约 2026-08):复评本文件。若 v2 数据源工作启动,至少需要 1 份 spec + 可能新 ADR。
|
||||
- **每次新 Milestone 完成时**:跑一次 AGENTS.md §6 的 3 个 grep,确认边界未漂移。
|
||||
- **新成员(agent 或人)首次提 PR 后**:根据他们卡住的地方反向更新 AGENTS.md / project-map("如果他们卡过这里,下一个人也会卡")。
|
||||
|
||||
---
|
||||
|
||||
## Harness 文件清单(截至本次 commit)
|
||||
|
||||
```
|
||||
AGENTS.md ← AI agent 速查
|
||||
README.md ← 用户向使用文档
|
||||
docs/dev.md ← 完整设计文档(源头)
|
||||
ai/project-map.md ← 仓库结构与扩展点
|
||||
ai/risk-guardrails.md ← G1-G10 守卫规则
|
||||
ai/adr/0001-architecture-foundations.md ← 9 条架构基础决策
|
||||
ai/task-templates.md ← 6 种任务模板
|
||||
ai/harness-health.md ← 本文件
|
||||
```
|
||||
|
||||
CLAUDE.md / GEMINI.md / .cursor/*.md 等其他 AI 工具的入口文件——**暂不需要**,因为这些工具都会读 AGENTS.md。如未来要差异化对待,再分裂。
|
||||
224
ai/project-map.md
Normal file
224
ai/project-map.md
Normal file
@@ -0,0 +1,224 @@
|
||||
# Project Map — cryptoHermes
|
||||
|
||||
代码 agent 工作的地形图。每个目录写明:**职责**、**可以依赖谁**、**不可以依赖谁**、**扩展点在哪**。
|
||||
|
||||
完整设计见 `docs/dev.md`。
|
||||
|
||||
---
|
||||
|
||||
## 顶层布局
|
||||
|
||||
```
|
||||
cryptoHermes/
|
||||
├── cmd/ 二进制入口
|
||||
├── config/ cleanenv 加载
|
||||
├── internal/ 业务代码(不可被外部 import)
|
||||
├── migrations/ SQL(含 hypertable)
|
||||
├── pkg/ 项目内可复用基础设施
|
||||
├── docs/ 设计文档(dev.md 是源头)
|
||||
├── ai/ harness 工程文档(本目录)
|
||||
├── docker-compose.yml Timescale + 服务
|
||||
├── Dockerfile multi-stage golang:1.23-alpine → alpine:3.20
|
||||
├── Makefile 所有验证命令
|
||||
└── go.mod Go 1.23
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `cmd/` — 二进制入口
|
||||
|
||||
| 路径 | 职责 |
|
||||
|---|---|
|
||||
| `cmd/app/main.go` | 主服务。读 config → 调 `internal/app.Run(cfg)` |
|
||||
| `cmd/backfill/main.go` | 历史 K 线回填 CLI。直接装配 binance client + kline repo,不走 fiber |
|
||||
|
||||
**约束**:`cmd/` 里只允许装配 + 调用,不允许写业务逻辑。
|
||||
|
||||
---
|
||||
|
||||
## `config/` — 配置
|
||||
|
||||
- `config.go`:cleanenv 加载,`yaml + env` 双源。环境变量优先(见 `env:` tag)。
|
||||
- `config.yml`:默认配置。**不要在这里放敏感信息**(没有敏感信息可放,因为本服务无私钥)。
|
||||
|
||||
**扩展点**:新增配置项 → `config.go` 加字段(同时加 `yaml:` 和 `env:` tag)→ `config.yml` 加默认值 → 在 README "配置" 段补一行。
|
||||
|
||||
---
|
||||
|
||||
## `internal/` — 业务核心
|
||||
|
||||
Clean Architecture 四层。**依赖方向单向:`controller → usecase → entity ← repo`**。
|
||||
|
||||
```
|
||||
┌──────────────┐
|
||||
│ controller │ HTTP 层:参数校验、调 usecase、序列化
|
||||
└──────┬───────┘
|
||||
│ depends on
|
||||
▼
|
||||
┌──────────────┐
|
||||
│ usecase │ 业务编排。只依赖 ports.go 中的接口
|
||||
└──────┬───────┘
|
||||
│ depends on (interface only)
|
||||
▼
|
||||
┌──────────────┐ ┌──────────────┐
|
||||
│ entity │ ◄────── │ repo │ 外部依赖实现
|
||||
└──────────────┘ └──────────────┘
|
||||
(binance / postgres)
|
||||
```
|
||||
|
||||
### `internal/entity/` — 纯业务实体
|
||||
|
||||
- `kline.go`, `ticker.go`, `funding.go`, `open_interest.go`, `long_short_ratio.go`, `taker_volume.go`, `market_context.go`
|
||||
- **价格/成交量字段全部 `string`**,避免 float64 精度问题。落库时 PG 转 `NUMERIC(36,18)`。
|
||||
- `Kline` 有 `IsClosed bool` 字段——未收线 K 线 (`close_time > now()`) 由 mapper 自动判定。
|
||||
- `LongShortRatio` 有常量 `RatioTypeGlobalAccount` / `RatioTypeTopTraderPosition` / `RatioTypeTopTraderAccount`。
|
||||
|
||||
**不依赖**:fiber、pgx、binance、任何外部库。可以从这里 import 到任何其他层。
|
||||
|
||||
### `internal/usecase/` — 业务接口与编排
|
||||
|
||||
- `ports.go`:**全部对外接口在这里定义**。这是 Clean Arch 的边界文件。
|
||||
- `MarketDataProvider`(K线、ticker24h)
|
||||
- `DerivativesProvider`(funding/OI/多空比/taker volume 共 7 个方法)
|
||||
- `KlineRepository` / `FundingRepository` / `OpenInterestRepository` / `LongShortRatioRepository` / `TakerVolumeRepository`
|
||||
- `market_context.go`:聚合 `/v1/market/context`。errgroup 并发拉 snapshot/funding/OI,DB K 线不足 200 根回源 Binance 补,异步写回。
|
||||
|
||||
**约束**(grep 可验证):
|
||||
```bash
|
||||
grep -r "repo/webapi/binance\|repo/persistent" internal/usecase # 必须无输出
|
||||
```
|
||||
usecase 不可直接 import 任何具体实现。新增业务流程 → 在 `ports.go` 加接口 → 在 usecase 编排 → 由 `internal/app/app.go` 注入实现。
|
||||
|
||||
### `internal/controller/` — HTTP 层
|
||||
|
||||
- `restapi/router.go` + `middleware.go`:fiber app、recover、logger、request-id
|
||||
- `restapi/v1/health_routes.go`:`/v1/health`
|
||||
- `restapi/v1/market_routes.go`:`/v1/market/{context,klines,snapshot,derivatives}`
|
||||
|
||||
**约束**:
|
||||
```bash
|
||||
grep -r "repo/webapi/binance\|repo/persistent" internal/controller # 必须无输出
|
||||
```
|
||||
controller 只能依赖 usecase 和 entity,不能直接调 binance client 或 postgres repo。
|
||||
|
||||
**扩展点**:新增路由 → 加 handler → 通过 `MarketDeps` 注入依赖(不要直接 import repo 包)。
|
||||
|
||||
### `internal/repo/` — 外部依赖实现
|
||||
|
||||
| 子目录 | 实现哪些接口 |
|
||||
|---|---|
|
||||
| `webapi/binance/` | `MarketDataProvider`、`DerivativesProvider` |
|
||||
| `persistent/postgres/` | 5 个 `*Repository` |
|
||||
|
||||
**Binance client**(`webapi/binance/`):
|
||||
- `client.go`:包 `pkg/httpclient`,统一 base URL `https://fapi.binance.com`,错误用 `ExternalAPIError{Source, Path, StatusCode, Message}` 包装。
|
||||
- `dto.go`:贴近 Binance 原始响应(K 线是数组)。
|
||||
- `mapper.go`:DTO → entity。**K 线按下标解析**(`row[0]=openTime ... row[10]=takerBuyQuoteVolume`),自动设置 `IsClosed = close_time < now()`。
|
||||
- `market.go`:`GetKlines` / `GetKlinesRange` / `GetTicker24h`
|
||||
- `derivatives.go`:funding × 2 + OI × 2 + 多空比 × 2 + taker volume
|
||||
|
||||
**Postgres repos**(`persistent/postgres/`):
|
||||
- 5 个 repo,每个有 `UpsertMany`(`ON CONFLICT DO UPDATE`)和 `FindRecent`(按时间倒序拉,返回前反转成正序)。
|
||||
- `kline_repo.go` 的 `UpsertMany` 会过滤掉 `IsClosed=false` 的 K 线。
|
||||
- 使用 pgxpool + transaction。
|
||||
|
||||
**扩展点**:
|
||||
- 新增数据源(CoinGlass / ETF)→ 新建 `webapi/coinglass/`,实现新的 Provider 接口(先在 `ports.go` 定义)。
|
||||
- 新增表 → 新 migration + 新 repo + 在 `ports.go` 加新接口。
|
||||
|
||||
### `internal/worker/` — Collector
|
||||
|
||||
- `collector.go`:被 cron 调度的采集逻辑。每个 `Collect*` 方法遍历 `symbols × intervals/periods`,拉数据 → upsert。
|
||||
- **不直接被 controller 调用**,只通过 `internal/app/scheduler.go` 触发。
|
||||
|
||||
### `internal/app/` — 装配与生命周期
|
||||
|
||||
- `app.go`:DI 装配的唯一位置。
|
||||
- 顺序:pool → binanceClient → repos → contextUC → collector → scheduler → fiber app
|
||||
- graceful shutdown:监听 SIGINT/SIGTERM,10s 超时,先停 cron、再停 fiber、最后关 pool。
|
||||
- `scheduler.go`:cron spec 在这里。
|
||||
|
||||
**这里是 Clean Arch 的"组合根"**——所有具体类型在这里被实例化并注入抽象接口。
|
||||
|
||||
---
|
||||
|
||||
## `pkg/` — 项目内可复用基础设施
|
||||
|
||||
| 包 | 职责 |
|
||||
|---|---|
|
||||
| `pkg/httpclient/` | 通用 HTTP client + 重试(429/5xx 最多 2 次)+ `x/time/rate` 限流 |
|
||||
| `pkg/logger/` | slog 包装(按 env 选 JSON / Text) |
|
||||
| `pkg/postgres/` | pgxpool 初始化 + Ping |
|
||||
|
||||
**约束**:`pkg/` 不依赖 `internal/`,反向依赖。可以被外部项目 import(虽然目前没有)。
|
||||
|
||||
---
|
||||
|
||||
## `migrations/` — SQL
|
||||
|
||||
10 个文件(5 张表 × up/down)。
|
||||
|
||||
每个 `.up.sql` 末尾必须有:
|
||||
```sql
|
||||
SELECT create_hypertable('<table>', '<time_col>',
|
||||
chunk_time_interval => <ms>,
|
||||
if_not_exists => TRUE,
|
||||
migrate_data => TRUE);
|
||||
```
|
||||
|
||||
chunk 间隔:
|
||||
- K 线 / OI / LS / taker → 1 周 = 604800000 ms
|
||||
- funding → 30 天 = 2592000000 ms
|
||||
|
||||
**改 migration 前必读 `ai/risk-guardrails.md` 中"DB migration"段**。
|
||||
|
||||
---
|
||||
|
||||
## 数据流(控制流)
|
||||
|
||||
### 写路径(采集)
|
||||
|
||||
```
|
||||
cron tick
|
||||
└→ internal/app/scheduler.go
|
||||
└→ internal/worker/collector.go (Collect*)
|
||||
├→ binance.GetXxx (HTTP → DTO → entity)
|
||||
└→ repo.UpsertMany (entity → SQL upsert)
|
||||
```
|
||||
|
||||
### 读路径(API)
|
||||
|
||||
```
|
||||
HTTP request
|
||||
└→ internal/controller/restapi/v1/market_routes.go
|
||||
└→ usecase.MarketContext.Build
|
||||
├→ marketData.GetTicker24h ┐
|
||||
├→ derivatives.GetCurrent… ├ errgroup 并发
|
||||
├→ derivatives.GetCurrent… ┘
|
||||
├→ klineRepo.FindRecent × 5 周期
|
||||
│ └ DB 不足 → 回源 binance.GetKlines(异步写回)
|
||||
└→ fundingRepo / oiRepo / lsRepo.FindRecent
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 不可逾越的边界(自动验证见 AGENTS.md §6)
|
||||
|
||||
1. **controller ↛ binance/postgres**:必须经 usecase。
|
||||
2. **usecase ↛ binance/postgres 包**:只依赖 `ports.go` 接口。
|
||||
3. **entity ↛ 任何外部库**:可被所有层 import。
|
||||
4. **pkg ↛ internal**:基础设施不知道业务。
|
||||
5. **任何代码 ↛ 私钥/签名**:grep `apikey|hmac|x-mbx-apikey|secret_key|api_secret` 必须无 Go 代码命中。
|
||||
|
||||
---
|
||||
|
||||
## 关键扩展点速查
|
||||
|
||||
| 场景 | 改哪 |
|
||||
|---|---|
|
||||
| 新增 REST 路由 | `internal/controller/restapi/v1/`,依赖通过 `MarketDeps` 注入 |
|
||||
| 新增业务编排 | `internal/usecase/ports.go` 加接口 → 新 usecase 文件 → `internal/app/app.go` 装配 |
|
||||
| 新增数据表 | 新 migration → 新 entity(可选)→ 新 repo → `ports.go` 接口 → 装配 |
|
||||
| 新增数据源 | `ai/specs/<source>.md` → 新 `internal/repo/webapi/<source>/` → 新 Provider 接口 |
|
||||
| 新增定时任务 | `internal/worker/collector.go` 加方法 → `internal/app/scheduler.go` 注册 cron |
|
||||
| 改配置项 | `config/config.go` 加字段 → `config/config.yml` 加默认 → README 记录 |
|
||||
184
ai/risk-guardrails.md
Normal file
184
ai/risk-guardrails.md
Normal file
@@ -0,0 +1,184 @@
|
||||
# Risk Guardrails — cryptoHermes
|
||||
|
||||
可机械验证 / 不可逾越的规则。每条规则给出**为什么**、**怎么验证**。
|
||||
|
||||
任何 PR 都应该满足这里全部规则。
|
||||
|
||||
---
|
||||
|
||||
## G1. 绝不接入交易能力(Hard Stop)
|
||||
|
||||
**规则**:本服务只读公共行情。**禁止**任何下单、撤单、查询账户/持仓/资金、签名请求、托管、转账。
|
||||
|
||||
**为什么**:项目定位是"上游 Hermes 量化分析引擎的行情网关"。一旦引入交易能力,安全边界、合规风险、密钥管理责任全部上升一个数量级。`docs/dev.md` 第 1 章原文:"本服务只提供市场数据与分析上下文,不做交易、不下单、不托管资金"。
|
||||
|
||||
**怎么验证**:
|
||||
```bash
|
||||
# Go 代码不可出现私钥/签名相关字段
|
||||
grep -ri --include="*.go" "apikey\|x-mbx-apikey\|hmac\|secret_key\|api_secret" .
|
||||
# 期望:无输出(注释/字符串/文档中说明"不接入"不算)
|
||||
|
||||
# Binance 私有接口前缀不可出现
|
||||
grep -r --include="*.go" "fapi/v1/order\|fapi/v1/account\|fapi/v2/account\|fapi/v2/positionRisk" .
|
||||
# 期望:无输出
|
||||
```
|
||||
|
||||
**例外**:无。如果上游需求变化要做交易,**必须先开新仓库**或新模块(独立鉴权 / 独立部署),不能在本仓库内直接放开。
|
||||
|
||||
---
|
||||
|
||||
## G2. 价格 / 成交量绝不用 float64(Hard Stop)
|
||||
|
||||
**规则**:所有金额、价格、成交量字段**全链路用 `string`**。DTO、entity、JSON 响应都是 `string`。落库时由 PG 转 `NUMERIC(36,18)`。
|
||||
|
||||
**为什么**:`108000.12` 这种价格 float64 表示就会丢精度。Binance 原样返回字符串,本服务保留字符串到 PG。这一点在所有 5 个 entity 文件里都已经做到了,新代码必须遵守。
|
||||
|
||||
**怎么验证**:
|
||||
```bash
|
||||
# 任何新增 entity 字段如果叫 *Price/*Volume/*Quantity/*Amount,类型必须是 string
|
||||
grep -rn --include="*.go" "Price\s*float\|Volume\s*float\|Quantity\s*float\|Amount\s*float" internal/entity
|
||||
# 期望:无输出
|
||||
```
|
||||
|
||||
**例外**:纯统计/分析(如指标计算的中间过程)可以用 float64,但**结果落库或返回 API 时必须转回 string**。
|
||||
|
||||
---
|
||||
|
||||
## G3. 未收线 K 线(IsClosed=false)不入库
|
||||
|
||||
**规则**:从 Binance 拉到的最新一根 K 线如果 `close_time > now()`,**不能写入 `market_klines`**。
|
||||
|
||||
**为什么**:未收线 K 线的 close/high/low/volume 还会变。如果入库,后续不更新就是脏数据,更新又会触发 upsert 风暴。`internal/repo/persistent/postgres/kline_repo.go` 的 `UpsertMany` 已经过滤 `IsClosed=false`,`internal/repo/webapi/binance/mapper.go` 已经自动设置 `IsClosed = closeTime < now()`。新代码(含 backfill)必须遵守。
|
||||
|
||||
**怎么验证**:手动跑一次 collector,立刻 `SELECT count(*) FROM market_klines WHERE close_time > extract(epoch from now()) * 1000` 应为 0。
|
||||
|
||||
---
|
||||
|
||||
## G4. UpsertMany 必须幂等
|
||||
|
||||
**规则**:所有 repo 的 `UpsertMany` 用 `INSERT ... ON CONFLICT (<PK>) DO UPDATE SET ... updated_at = now()`。**禁止** `INSERT` 不带 ON CONFLICT 或用 `INSERT ... ON CONFLICT DO NOTHING`(后者错过了更新场景)。
|
||||
|
||||
**为什么**:cron 会重复跑、backfill 会和 cron 重叠、重启会重拉。重复执行必须不能产生重复行也不能丢更新(例如 OI 后修正、close 值微调)。
|
||||
|
||||
**怎么验证**:
|
||||
```bash
|
||||
make backfill ARGS="--symbol BTCUSDT --interval 1h --limit 100"
|
||||
psql $DATABASE_URL -c "SELECT count(*) FROM market_klines WHERE symbol='BTCUSDT' AND interval='1h'"
|
||||
make backfill ARGS="--symbol BTCUSDT --interval 1h --limit 100"
|
||||
psql $DATABASE_URL -c "SELECT count(*) FROM market_klines WHERE symbol='BTCUSDT' AND interval='1h'"
|
||||
# 期望:两次 count 一致
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## G5. Rate Limit 不可绕开
|
||||
|
||||
**规则**:所有外部 HTTP 调用走 `pkg/httpclient`,该 client 已挂 `golang.org/x/time/rate`(默认 20 req/s,burst 40)。**禁止** 在业务代码里直接用 `net/http` 调 Binance。
|
||||
|
||||
**为什么**:Binance USDⓈ-M Futures IP 级限额 2400 weight/min。一旦超限会被封 IP 几分钟到几小时。本地代理在国内是稀缺资源,封了影响整个开发环境。
|
||||
|
||||
**预算**:新增 collector 任务前估算 weight:
|
||||
- `GET /fapi/v1/klines`:1(limit ≤ 100)→ 10(limit ≤ 1000)
|
||||
- `GET /fapi/v1/ticker/24hr`:1(单 symbol)/ 40(全量)
|
||||
- `GET /fapi/v1/openInterest`:1
|
||||
- `GET /futures/data/*`:1
|
||||
|
||||
**怎么验证**:
|
||||
```bash
|
||||
grep -rn --include="*.go" "net/http\"" internal/repo/webapi
|
||||
# 期望:只在 pkg/httpclient 里,repo/webapi 不应直接 import net/http
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## G6. Clean Architecture 边界
|
||||
|
||||
**规则**:见 `ai/project-map.md` "不可逾越的边界"段。3 个 grep 必须无输出。
|
||||
|
||||
**为什么**:一旦 controller / usecase 直接 import 具体实现,测试就需要起 DB 起 Binance;后续替换数据源(CoinGlass)也变成大手术。
|
||||
|
||||
**怎么验证**:
|
||||
```bash
|
||||
grep -r "repo/webapi/binance\|repo/persistent" internal/controller # 必须无输出
|
||||
grep -r "repo/webapi/binance\|repo/persistent" internal/usecase # 必须无输出
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## G7. Migration 必须 down/up 互逆
|
||||
|
||||
**规则**:任何 SQL migration 改动后,本地必须跑:
|
||||
```bash
|
||||
make migrate-down && make migrate-up
|
||||
```
|
||||
两步都成功(不报错、不丢表)才能提交。
|
||||
|
||||
**为什么**:生产回滚是兜底手段,down 脚本错了就回不去。
|
||||
|
||||
**额外规则**:
|
||||
- 新 migration **不可改已发布 migration 的内容**——只能加新文件(编号递增)。
|
||||
- 改表结构如果会丢数据(drop column / change type),必须在 PR 里显式说明。
|
||||
- 改 hypertable chunk 间隔需要先看 `docs/dev.md` 第 10 章。
|
||||
|
||||
---
|
||||
|
||||
## G8. K 线数组下标解析不可破
|
||||
|
||||
**规则**:`internal/repo/webapi/binance/mapper.go` 中 `mapKline` 的下标解析对应 Binance 官方约定:
|
||||
|
||||
```
|
||||
[0] openTime ms
|
||||
[1] open string
|
||||
[2] high string
|
||||
[3] low string
|
||||
[4] close string
|
||||
[5] volume string
|
||||
[6] closeTime ms
|
||||
[7] quoteAssetVolume string
|
||||
[8] numberOfTrades int
|
||||
[9] takerBuyBaseVolume string
|
||||
[10] takerBuyQuoteVolume string
|
||||
[11] ignore
|
||||
```
|
||||
|
||||
**禁止**:调整顺序、跳过位置、改用对象解析(Binance 这个接口就是数组)。
|
||||
|
||||
**为什么**:错一位整张表数据就废了,回滚成本极高。`docs/dev.md` 第 9.2 节有完整说明。
|
||||
|
||||
**怎么验证**:动了 `mapper.go` 之后跑一次回填,对比 Binance 网页 K 线图同一根的 OHLCV 是否一致。
|
||||
|
||||
---
|
||||
|
||||
## G9. 配置项不引入秘钥
|
||||
|
||||
**规则**:`config/config.yml` 和环境变量列表里不允许出现 `api_key`、`secret`、`token`、`password`(DB 密码除外,且必须从环境变量读、不能 hardcode)。
|
||||
|
||||
**为什么**:本服务不需要任何 Binance 私有鉴权,也不向外部传秘密。一旦引入会破坏 G1。
|
||||
|
||||
**怎么验证**:
|
||||
```bash
|
||||
grep -i "api_key\|api_secret\|access_token" config/
|
||||
# 期望:无输出
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## G10. WebSocket / 实时推送不在 v1 范围
|
||||
|
||||
**规则**:v1 只用 REST 拉取。不引入 gorilla/websocket、不订阅 `wss://fstream.binance.com`。
|
||||
|
||||
**为什么**:WS 长连接的可观测性、重连、消息丢失处理是独立工程量。v1 用 REST + cron 已经能满足 Hermes 的"分钟级上下文"需求。要做 WS 必须先写 ADR。
|
||||
|
||||
**怎么验证**:
|
||||
```bash
|
||||
grep -rn --include="*.go" "websocket\|fstream.binance" .
|
||||
# 期望:无输出
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 升级守护规则的时机
|
||||
|
||||
同一类错误第二次出现 → 写一条新规则进来。修改本文件 = 改动了项目契约,需要在 PR 描述里说明原因并 @ 维护者。
|
||||
|
||||
新增 Hard Stop 规则时,**必须给出可机械验证的命令**——只能靠 review 来执行的规则会被绕过。
|
||||
205
ai/task-templates.md
Normal file
205
ai/task-templates.md
Normal file
@@ -0,0 +1,205 @@
|
||||
# Task Templates — cryptoHermes
|
||||
|
||||
任何非平凡修改(>1 个文件、改边界、引入新依赖、改 migration、新增 collector),先写一份微型任务契约。短即可——长度像 commit message,但目的是让你自己(和后续 agent)能在 30 秒内对齐范围。
|
||||
|
||||
---
|
||||
|
||||
## 通用契约模板
|
||||
|
||||
```
|
||||
objective: 一句话说要做的事
|
||||
scope: 会改的文件 / 目录(具体路径,不要"相关文件")
|
||||
out-of-scope: 显式不改的范围(防止 scope creep)
|
||||
constraints: 引用 ai/risk-guardrails.md 的哪几条(G1-G10)
|
||||
validation: 要跑哪些命令证明通过(必须可机械执行)
|
||||
acceptance: 完成判据(行为可观测的描述)
|
||||
```
|
||||
|
||||
**示例**:
|
||||
|
||||
```
|
||||
objective: 给 /v1/market/context 加一个 ?intervals=1h,4h 参数,允许 Hermes 只拉部分周期,减少响应体积
|
||||
scope:
|
||||
- internal/controller/restapi/v1/market_routes.go
|
||||
- internal/usecase/market_context.go
|
||||
- README.md(API 段补 query 参数说明)
|
||||
out-of-scope:
|
||||
- 不改 entity(响应结构不变,只是 klines map 的 key 集合可能少几个)
|
||||
- 不改 collector(采集策略不变)
|
||||
constraints:
|
||||
- G6:不能在 controller 里直接调 repo,必须通过 usecase 接收 intervals 参数
|
||||
validation:
|
||||
- go build ./... && go vet ./...
|
||||
- curl 'localhost:8080/v1/market/context?symbol=BTCUSDT&intervals=1h,4h' | jq '.klines | keys'
|
||||
- 期望:只返回 ["1h","4h"]
|
||||
acceptance:
|
||||
- 不传 intervals 时行为完全不变(默认全部 5 个周期)
|
||||
- 传未知 interval 返回 400
|
||||
- 至少一个 happy-path 单测覆盖
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 任务类型 → 模板
|
||||
|
||||
### T1. 新增 API 路由
|
||||
|
||||
```
|
||||
objective: <做什么>
|
||||
scope:
|
||||
- internal/controller/restapi/v1/<file>.go(新 handler)
|
||||
- internal/usecase/<file>.go(新业务方法或扩展现有)
|
||||
- internal/usecase/ports.go(如果需要新 repo 方法)
|
||||
out-of-scope:
|
||||
- 不改 entity 结构
|
||||
- 不改 collector
|
||||
constraints: G6
|
||||
validation:
|
||||
- go build ./... && go vet ./...
|
||||
- curl 测 happy-path + 错误参数
|
||||
acceptance:
|
||||
- 路由响应符合 JSON schema
|
||||
- 参数校验完整(缺失/非法都返回 400)
|
||||
- README 更新 API 段
|
||||
```
|
||||
|
||||
### T2. 新增数据表 / migration
|
||||
|
||||
```
|
||||
objective: <为什么要存这个>
|
||||
scope:
|
||||
- migrations/0000N_<name>.up.sql
|
||||
- migrations/0000N_<name>.down.sql
|
||||
- internal/entity/<name>.go
|
||||
- internal/repo/persistent/postgres/<name>_repo.go
|
||||
- internal/usecase/ports.go(新 Repository 接口)
|
||||
out-of-scope:
|
||||
- 不改已发布 migration(编号≤当前 max)
|
||||
constraints:
|
||||
- G2(金额必须 string / NUMERIC(36,18))
|
||||
- G4(UpsertMany 必须幂等)
|
||||
- G7(down/up 互逆)
|
||||
validation:
|
||||
- make migrate-up
|
||||
- make migrate-down
|
||||
- make migrate-up # 再次成功
|
||||
- go build ./...
|
||||
acceptance:
|
||||
- 表是 hypertable:psql 跑 SELECT * FROM timescaledb_information.hypertables 看到
|
||||
- upsert 跑两次行数不变
|
||||
- 至少 1 个 repo 单测(连本地 Timescale)
|
||||
```
|
||||
|
||||
### T3. 新增数据源(CoinGlass / ETF / ...)
|
||||
|
||||
**必须先写 `ai/specs/<source>.md`**(见 AGENTS.md §8)。
|
||||
|
||||
```
|
||||
objective: 接入 <source> 提供 <什么数据>
|
||||
spec: ai/specs/<source>.md
|
||||
scope:
|
||||
- internal/repo/webapi/<source>/{client,dto,mapper,...}.go
|
||||
- internal/usecase/ports.go(新 Provider 接口)
|
||||
- internal/app/app.go(DI 装配)
|
||||
- config/config.go + config.yml(新数据源的 base_url / rps / timeout)
|
||||
out-of-scope:
|
||||
- 不改 Binance client
|
||||
constraints:
|
||||
- G1(绝不接私钥)
|
||||
- G2(金额 string)
|
||||
- G5(必须走 pkg/httpclient 限流)
|
||||
validation:
|
||||
- go build ./... && go vet ./...
|
||||
- 临时挂一个 debug 路由验证能拉到真数据
|
||||
acceptance:
|
||||
- 新 Provider 实现接口;usecase 不直接 import <source> 包
|
||||
- 速率预算文档化(在 spec 里)
|
||||
```
|
||||
|
||||
### T4. 改 collector / scheduler
|
||||
|
||||
```
|
||||
objective: <调整哪个采集,为什么>
|
||||
scope:
|
||||
- internal/worker/collector.go
|
||||
- internal/app/scheduler.go
|
||||
out-of-scope:
|
||||
- 不改 binance client(如果要新方法,先开 T3 / T1)
|
||||
- 不改表结构(如果要,开 T2)
|
||||
constraints:
|
||||
- G3(未收线丢弃)
|
||||
- G4(upsert 幂等)
|
||||
- G5(rate limit 不绕开)
|
||||
validation:
|
||||
- go build ./... && go vet ./...
|
||||
- 本地起服务跑 1 个 cron 周期,psql 看新行写入
|
||||
acceptance:
|
||||
- 新 cron 表达式合理(不要 0 0 * * * 这种容易和别人撞的高峰)
|
||||
- 重启服务后 scheduler 能正常注册
|
||||
- weight 预算在 PR 描述里说明(Binance 2400/min IP 上限)
|
||||
```
|
||||
|
||||
### T5. Bug fix
|
||||
|
||||
```
|
||||
objective: 修 <bug 简述>(issue 链接或复现命令)
|
||||
scope: <最小改动范围>
|
||||
constraints: <相关 G 条>
|
||||
validation:
|
||||
- 写一个能复现 bug 的最小测试(先红后绿)
|
||||
- go build ./... && go vet ./...
|
||||
acceptance:
|
||||
- 测试通过
|
||||
- 不引入新的 lint warning
|
||||
```
|
||||
|
||||
### T6. 重构 / Clean Architecture 修正
|
||||
|
||||
```
|
||||
objective: <消除哪个边界违反 / 抽取哪段重复>
|
||||
scope: <文件列表>
|
||||
out-of-scope:
|
||||
- 不改行为(API 响应字节级一致 / DB schema 不变)
|
||||
constraints: G6
|
||||
validation:
|
||||
- go build ./... && go vet ./...
|
||||
- 全部 grep 边界检查(AGENTS.md §6)
|
||||
- make test
|
||||
acceptance:
|
||||
- diff 中不含行为变化(只是搬代码 / 重命名 / 抽接口)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 何时升级到 spec
|
||||
|
||||
micro contract 写不下、或者需要跨多个 agent / 多次会话才能完成 → 升级到 `ai/specs/<task>.md`:
|
||||
|
||||
- 多 milestone 跨越
|
||||
- 引入新数据源
|
||||
- 改变响应 schema(影响上游 Hermes)
|
||||
- 引入新部署组件(消息队列、新 service)
|
||||
|
||||
spec 内容:objective / non-goals / config 形状 / API 形状 / 错误模式 / 兼容性 / 验收 / 验证计划。
|
||||
|
||||
---
|
||||
|
||||
## 何时升级到 ADR
|
||||
|
||||
contract 涉及"以后类似场景应该都这么做" → 写 ADR(`ai/adr/000N-<decision>.md`):
|
||||
|
||||
- 换核心库
|
||||
- 改架构层数 / 改 Clean Arch 边界
|
||||
- 引入新通信协议
|
||||
- 改部署拓扑
|
||||
|
||||
ADR 结构参考 `ai/adr/0001-architecture-foundations.md`。
|
||||
|
||||
---
|
||||
|
||||
## 反模式
|
||||
|
||||
- ❌ "顺便把这里也改一下"——顺便的部分单独开一个 contract,不要混
|
||||
- ❌ "测试很难写,先合了"——测试难写说明设计有问题,先调设计
|
||||
- ❌ "之前 review 过类似的,应该没问题"——每个 PR 独立 validate
|
||||
- ❌ commit message 写"WIP"——本仓库不允许 WIP commit 进 main
|
||||
Reference in New Issue
Block a user