为什么:原 controller handler 在 HTTP 层同时承担了 4 项 usecase 职责 (多源拼装、错误降级策略、limit 硬编码、响应结构组装),违反了 "controller 只做参数解析 + 调用 + 序列化" 的边界。等后续给衍生品加 缓存 / taker volume / 数据质量字段时,HTTP 层会持续变厚。 改动: - 新增 internal/usecase/market_query.go: MarketQueryUsecase.GetDerivatives() 并发拉 current funding/OI + 串行查历史 + 错误聚合为 warnings 返回 - /derivatives handler 退化到 4 行;MarketDeps / restapi.Deps 同步收窄 - /klines 保持直查 KlineRepo(thin query,G11 例外) - 新增 G11 守卫:grep "FundingRepo|OIRepo|LSRepo|TakerRepo" internal/controller 破坏性 API 变更(/derivatives 响应): - 删除 funding.error / openInterest.error - 新增顶层 warnings: []string 顶层 symbol / funding / openInterest / longShortRatio / takerBuySellVolume 保持。 harness 同步: - AGENTS.md §6 加 G11 grep - ai/project-map.md 加 market_query.go + /derivatives 数据流分支 - ai/task-templates.md T6 加"破坏性变体"分支(本次重构暴露了原假设过严) - ai/harness-health.md W1 记一次轻量验证 + 守卫数 10→11 验收: go build / go vet / G6 grep / G11 grep 全绿。
6.7 KiB
6.7 KiB
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 不变)
- 例外:如果重构本身是为了把不该在 controller 的"错误降级 / 响应组装"搬进 usecase,
可以一并修响应契约(如 sub-object 的 error 字段聚合为顶层 warnings)。
必须在 acceptance 显式标 "破坏性",并在 PR 描述列出旧 → 新字段映射。
constraints: G6 + G11(如果动 controller)
validation:
- go build ./... && go vet ./...
- 全部 grep 边界检查(AGENTS.md §6)
- make test
acceptance:
- 默认:diff 中不含行为变化(只是搬代码 / 重命名 / 抽接口)
- 破坏性变体:列出响应字段变化,确认上游(Hermes)消费方已知或不受影响
何时升级到 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