Files
jiu/CLAUDE.md
T
wangjia 9b37568fdb
Design Source Checks / design-source (push) Successful in 16s
ci: 设计真相源检查挂 CI(PR + main push 自动跑四道闸)
- 新增 tools/check-l1-sync.mjs:L1 同源四道——tokens 快照逐字节、icons
  两端同集同内容、官网 token 值对齐原型主题 A、web 硬编码 hex 扫描
  (白名单 #fff/#1677ff,ds-allow 可豁免);注入测试验证红绿行为
- 新增 .gitea/workflows/checks.yml:pull_request + push(main) 触发,
  串跑 check-ds 12 道 / check-l1-sync / check_ds_code / codegen 新鲜度
  (regen→dart format→零 diff,消除重跑假 diff 问题)
- 修复 web/checkout.njk 对公转账图标底色硬编码 #8A94A6 → var(--faint)
- CLAUDE.md「设计真相源分层」登记 CI 闸与本地等价命令

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JJ1g8XV1YhhmHRzhwWEW7o
2026-07-04 07:14:49 +08:00

18 KiB
Raw Blame History

酒库管理系统 — 项目规则与 Orchestrator

必读

开始任何任务前,先读 docs/context/project.md 了解项目全貌。

文档地图(入口 docs/index.html):

  • 用户使用手册:docs/manual/user-manual.html(官网帮助页精简版源=web/content/docs.md,两者同步改)
  • 开发手册(架构/后端/前端/数据库/API/运维/发版):docs/manual/dev-manual.html
  • 数据库列级文档:docs/db-schema.htmlschema 变更时同步)
  • 设计契约与像素闸:design/CONTRACT.md + tools/screens.mjs

Orchestrator:如何自动选择 Agent

根据用户请求的关键词和意图,按以下规则判断调用哪些 Agent:

新功能开发(完整流水线)

触发词:「新增」「开发」「实现」「做一个」+ 功能描述

串行:requirements-analyst → architect
并行:api-designer + db-designer(同时进行,都依赖架构但互不依赖)

→ ui-designer 判断复杂度:
    简单变更:直接输出规范 → 继续下一步
    复杂设计:输出 Figma 需求文档 → 暂停等用户确认设计稿

并行:backend-coder + flutter-coder(都依赖 API 设计,互不依赖)
串行:test-engineer → linter → code-reviewer → security-auditor

仅后端修改

触发词:「修复后端」「后端接口」「API」+ 具体描述

backend-coder → test-engineer → linter → code-reviewer

仅前端修改

触发词:「UI」「界面」「Flutter」「页面」

flutter-coder → linter

Bug 修复

触发词:「bug」「报错」「修复」「问题」

1. 先判断 bug 来源(后端/前端/数据库)
2. 调用对应的 coder agent 修复
3. test-engineer 验证修复

安全/代码质量检查

触发词:「安全」「漏洞」「review」「审查」「代码质量」

并行:security-auditor + code-reviewer + linter

线上问题

触发词:「线上」「故障」「宕机」「慢」「报错」

sre(优先级最高,立即响应)
→ 根据 sre 诊断结果,再决定是否调用 backend-coder 修复

文档需求

触发词:「文档」「说明」「手册」「README」

doc-writer

Agent 边界规则(强制执行)

Agent 可以写 不可以写
requirements-analyst docs/requirements/ 任何代码
architect docs/architecture/ 任何代码
api-designer docs/api/ 任何代码
db-designer backend/schema/ 业务代码、测试代码
backend-coder backend/internal/, backend/main.go *_test.go, schema/
ui-designer docs/design/ 任何代码
flutter-coder client/ backend/
test-engineer _test.go, docs/review/-bugs.md 业务代码
linter 格式自动修复(gofmt/dart format),docs/review/lint-report.md 逻辑代码
code-reviewer docs/review/*-review.md 任何代码
security-auditor docs/security/ 任何代码
devops deploy/, .gitea/workflows/, scripts/ci/ 业务代码
sre docs/runbooks/ 任何代码
doc-writer docs/(除 context/ 外) 任何代码

Agent 间通信协议

文件传递(主要方式):

  • 每个 Agent 完成后将结果写入约定文件
  • 下游 Agent 开始前读取上游产出文件
  • 文件格式:混合模式(中文叙述 + YAML/代码块)

短链式调用(反馈循环):

  • test-engineer 发现 bug → 直接将错误信息和 docs/review/{功能}-bugs.md 路径传给 backend-coder
  • security-auditor 发现 Critical 问题 → 直接告知 backend-coder 修复,不等下次迭代

并行 Agent 调度规则

当多个 Agent 可以并行时,在同一条消息中发起多个 Agent 调用:

# 可以并行的情况(互不依赖):
- api-designer + db-designer
- backend-coder + flutter-coder
- security-auditor + code-reviewer + linter

# 必须串行的情况(有依赖):
- requirements-analyst 完成后 → architect 才能开始
- architect 完成后 → api-designer 和 db-designer 才能并行开始
- backend-coder 完成后 → test-engineer 才能开始

Git 提交规范

每个 Agent 完成工作后提交,commit message 格式:

{类型}({模块}): {简短说明}

类型:feat | fix | test | docs | chore | security | refactor
模块:backend | client | db | deploy | docs

示例:
feat(backend): 新增财务报表接口
test(backend): 财务报表模块测试用例
docs(api): 财务报表 API 接口文档
security: 修复入库单多租户隔离漏洞

开发完成标准(Definition of Done

任何功能或修复,必须满足以下全部条件才算完成:

后端变更

# 1. 编译通过
export PATH="/opt/homebrew/bin:$PATH"
cd backend && go build ./...

# 2. 所有测试通过(不得有 FAIL)
cd backend && go test ./...

# 3. 无编译警告(vet 检查)
cd backend && go vet ./...

前端变更

# 1. 静态分析无 errorwarning/info 允许存在)
cd client && flutter analyze --no-fatal-infos --no-fatal-warnings

# 2. 所有测试通过
cd client && flutter test

通用要求

  • 代码已按 Git 提交规范提交(feat/fix/test/...
  • 涉及新接口:已在对应测试文件中覆盖核心路径
  • 涉及数据库变更:schema.sql 和 model 同步更新
  • 多租户隔离未被破坏(所有查询含 shop_id 条件)

测试未通过前,禁止提交代码、禁止打 tag 发版。


代码质量门禁

以下情况不得提交

  • go build ./... 失败
  • go test ./... 有失败用例
  • 存在 security-auditor 标记的 Critical 级别问题
  • 存在 code-reviewer 标记的 blocking 级别问题

项目强制规则

多租户隔离

  • shop_id 永远从 JWT token 中提取,使用 middleware.GetShopID(c)
  • 绝不从请求参数、URL 或请求体中读取 shop_id
  • 所有数据库查询必须带 WHERE shop_id = ? 条件

数据模型:product = 特有产品/序列号(核心铁律)

  • products 每行 = 一个特有产品/序列号code=商品编号,同店唯一 uk_shop_code),不是 SKU;语义:name=品牌、series=型号、spec=版本。product 是商品信息的单一来源(序列号/品牌型号版本/生产日期 production_date/批次 batch_no/进价/图片/public_id)。
  • 入库每录一行 = 新建一个独立 productcreateIndependentProduct + nextProductCode max+1),绝不按名称/系列/规格复用(findOrCreate 复用已废弃)。同理任何"导入/建库存"逻辑:有商品编号时按编号匹配/建 product,禁止按名称合并(历史 bug 根因)。
  • 基础数据字典(品牌/型号/版本 + 产地/保质期/储存/介绍)在「基础数据」页管理,入库选文本后据此建 product。
  • 库存(inventories)/出入库明细 = 指向 product 的引用 + 快照列product_code/name/series/spec/...,导入/审核时拷贝,作历史保真+显示兜底)。显示优先 product、回退快照(COALESCE(p.*, 快照) / 前端 lineOrProduct)。快照列保留,勿擅自删

定价字段口径(2026-07 消歧后,唯一口径)

  • 明细两侧价格分列:cost_price(成本/进价单价)+ cost_amount(成本小计);出库另有 sale_price(售价)+ sale_amount(售价小计,待定价=0)。旧列 unit_price/total_price/total_amount 已弃用(值已回填新列,观察一版后 DROP),新代码禁止读写旧列。
  • 单据合计:入库 cost_total(应付=Σ总进价);出库 sale_total(应收=Σ售价小计)+ profit_total(总利润=Σ(售价>0?(售价-成本)×数量:0),建单落库,确认售价/确认进价联动重算,重算函数 recalcStockOutProfit)。
  • 成本与利润仅管理员可见:出库 List/Get 对 operator/readonly 服务端抹零(stripStockOutCost),前端同时隐藏对应列——新增出库相关展示时两侧都要遵守。
  • 财务口径不变:应收/应付、退单冲账均按上述合计列;出库退单展示与冲账同为售价口径。

库存变更

  • 入库/出库审核通过时,必须在同一事务中同时完成:
    1. 更新 inventories 表数量
    2. 写入 inventory_logs 流水记录
  • 出库前必须校验库存充足,不足时返回错误并回滚
  • 审核中(pending)单据支持撤回(withdraw)回 draft:管理员/超管任意单、操作员限本人单(OperatorID==本人handler 内判权,非 AdminOnly 中间件)

Schema 管理

  • 表结构变更:修改 backend/schema/schema.sql + backend/internal/model/ 对应 model
  • 启动时自动 AutoMigrate,无单独迁移文件
  • 禁止为每个新业务字段修改表结构,使用 custom_fields JSON 动态扩展

种子数据

  • 测试数据以 SQL 文件形式维护:backend/seeds/<shop_code>.sql
  • 通过 sh scripts/dev.sh seed <shop_code> 执行(内部用 docker exec 注入 MySQL 容器)
  • 每个门店一个文件,SQL 文件顶部包含 TRUNCATE,每次执行完整重建

Go 命令

  • 所有 Go 命令前须确保 PATHexport PATH="/opt/homebrew/bin:$PATH"
  • 或直接使用 sh scripts/dev.sh 脚本(已内置 PATH

前端 URL 配置

  • 所有后端 URL 必须通过 AppConfig 读取(client/lib/core/config/app_config.dart
  • 禁止在任何 provider / repository / widget 中硬编码 localhost:8080 或任何 IP/域名
  • 使用:AppConfig.apiBaseUrlAppConfig.healthUrlAppConfig.versionUrl

前端平台判断

  • 使用 dart:ioPlatform 类前,必须先检查 kIsWebimport 'package:flutter/foundation.dart'
  • Web 平台不支持 dart:io,直接使用会在 Web 构建时崩溃

设计真相源分层(原型漂移治理,2026-07-04 拍板)

原型(.superpowers/prototype/)与真实页面不追求全量永久同步,按层分治:

  • L1 设计系统层tokens.css / atoms.css / mobile-atoms.css / icons.js + index.html 登记):原型是单一真源。新增颜色/组件/图标必须先登记原型再同步代码与官网(如 web/_includes/icons.njk)。闸:pre-commit check-ds.mjs 12 道(本地钩子,新机器先跑 sh scripts/hooks/install.sh+ 代码侧 client/tool/check_ds_code.mjs
  • L2 屏级三态(台账 = design/CONTRACT.md 块 4「真相源」列):
    • 同步:注册于 tools/screens.mjs,fidelity 像素闸生效。整屏改版须原型先行;小迭代改后跑 fidelity 确认阈内,超阈则同步原型或降级快照。
    • 快照:原型退役为历史参考,golden + CONTRACT 文字规格为准;小迭代只更 golden不回填原型
    • 代码先行:无原型屏,golden 为唯一像素基准。
  • L3 新屏/整屏改版design-first 不变(原型 → CONTRACT → 实现 → fidelity 验收),建成后入 同步 态。
  • 漂移体检:不逐提交强制同步;大版本/整屏改版前跑 node tools/fidelity.mjs,按报告逐屏决定「重新对齐原型」或「降级快照」,结果回填 CONTRACT。
  • CI 闸.gitea/workflows/checks.ymlPR + main push 自动跑):① check-ds.mjs 原型 12 道;② tools/check-l1-sync.mjs L1 同源四道(tokens 快照逐字节 / icons 两端同集同内容 / 官网 token 值对齐原型主题 A / web 硬编码 hex,白名单 #fff、#1677ffds-allow 可豁免);③ client/tool/check_ds_code.mjs Flutter 颜色单源;④ codegen 新鲜度(regen→dart format→与入库产物零 diff)。本地可直接跑同名命令。

表格与列头筛选(ds 真相源)

  • 列表屏统一 DsTablewidgets/ds/ds_table.darttoolbar+表格+pager 连成一卡),列定义 DsColumn,可筛选列走其列头漏斗(filtered 态高亮),可隐藏列走列设置菜单
  • 不要在 toolbar 放独立筛选按钮,筛选入口内嵌列头或工具栏 DsChip
  • FilterableColumnHeader/multi_select_dropdown.dart 仅存量引用,勿在新屏使用

响应式 / 移动端适配

  • 断点判定统一用 context.isMobileclient/lib/core/responsive/responsive.dart,宽度 < 600 为窄屏/手机),禁止在各处散落 MediaQuery.width < 600 这类魔法数
  • 列表屏:统一用 DsTablewidgets/ds/ds_table.dart,真相源表格:toolbar+列+pager);窄屏卡片流复用 MobileListCard / MobileCardFieldwidgets/mobile_list_card.dart)。旧 DataTableCard 已删除(2026-07-03),勿再引用
  • 弹窗固定宽度:一律用 context.dialogWidth(X)(≤ 屏宽 92%),禁止裸写 width: <固定值> 导致窄屏溢出
  • 导航:窄屏(手机)用 Drawer 抽屉(app_shell.dart),宽屏保持侧边栏;新增页面无需单独处理,挂在 shell 下即可
  • 平台判断仍遵守上面「前端平台判断」规则(kIsWeb 先于 dart:io

权限中间件(已全局挂载)

  • middleware.ReadOnly():已挂载到主 api 路由组,只读用户(role=readonly)所有写操作自动返回 403
  • middleware.AdminOnly():挂载在 /users 路由组,仅管理员可管理用户
  • 新增写操作路由时,默认受 ReadOnly() 保护;若仅管理员可用,需加入 AdminOnly() 子组

并发安全(数据库操作)

  • 在同一事务内读后写的操作,必须用 FOR UPDATE 锁行:
    tx.Set("gorm:query_option", "FOR UPDATE").Where(...).First(&model)
    
  • 适用场景:单号生成(number_rules)、库存扣减(inventories)、任何 check-then-act 模式

发版流程

发版拆成三条互不影响的流水线,各有 tag 前缀、独立版本序列、独立 CHANGELOG:

part 范围 tag 前缀 CHANGELOG workflow
client client/ Flutter 全平台(Web→/app、macOS、Windows、Android、iOS+ 应用自更新清单 version.yaml client-v* CHANGELOG-client.md deploy-client.yml
site web/ Eleventy 营销宣传站(→/opt/jiu/marketingnginx /)。不含 Web 版 app site-v* CHANGELOG-site.md deploy-site.yml
server backend/ Go 服务(jiu-server+ 共享基建 nginx-jiu.conf / jiu.service server-v* CHANGELOG-server.md deploy-server.yml

使用 /release <part> [version] slash command

/release client 1.0.55      # 指定版本
/release server             # 省略则自增 server-v* 最新 tag 的 patch

执行顺序:本地 build → test(按 part)→ 更新对应 CHANGELOG → git commit → tag <part>-v<ver> → push main+tag。 CI/CDForgejo)按 tag 前缀触发对应 workflow,自动:编译 → 测试 → 创建 Release → 部署 alijiu.51yanmei.com443)→ Telegram 通知。

归属与解耦(关键):

  • version.yamlclient(写 version/build_number/release_notes/下载链接/changelog);nginx/systemd 归 server
  • 后端 /version/api/v1/public/release 每请求实时读 version.yaml,无缓存——client 部署 version.yaml 后立即生效,不重启后端、不触发 server 流水线
  • 官网下载页运行时 fetch('/api/v1/public/release'):版本徽章、各平台下载链接、更新日志时间线全部动态刷新——client 发版无需重建官网。接口不可达时回退到构建时静态内容(web/_data/changelog.jsCHANGELOG-client.md)。

CI 脚本scripts/ci/):lib-forgejo.sh(公共函数)+ compile-{client-web,site,backend,macos,android,ios,windows}.sh + release-{client,site,server}.sh + deploy-{client,site,server}.shcompile-{macos,android,ios,windows}.shclient-v 前缀取版本。

client 多平台构建矩阵mac runner(容量 1)串行链 build-client-web → build-macos → build-android → build-ioswindows runner 并行 build-windowsInno Setup);release-deploy-client 收齐后发布。Android 需 ANDROID_*、iOS 走 TestFlight 需 IOS_*/APPSTORE_* secrets,未配置时该 job 优雅跳过(exit 0,不阻塞)。

手动回滚manual.yml 输入带前缀 tag(如 client-v1.0.54),按前缀路由到对应 deploy-<part>.sh,从 Forgejo Release 下载产物。

CHANGELOG 格式Keep a Changelog,三个文件同构):

## [1.0.2] - YYYY-MM-DD

### 新功能
- ...

### 改进
- ...

### 修复
- ...

只保留有内容的分类。/release 会检查对应 CHANGELOG 是否已有该版本节,没有则从该 part 上一个 tag 的 git log 自动生成。

异常上报

客户端异常已在两处统一捕获,无需在每个业务层重复处理:

  • main.dartFlutterError.onError + runZonedGuarded 捕获所有未处理异常
  • api_client.dart:Dio 拦截器自动上报所有 HTTP 5xx 错误

需要手动调用 reportError(e, st) 的场景:

  • 技术性异常(JSON 解析失败、第三方 SDK 崩溃等)
  • 不需要上报:AppException 及其子类(已知业务错误)
} catch (e, st) {
  reportError(e, st);  // 一行,fire-and-forget
  rethrow;
}

项目 TODO 管理

统一使用全局 todo 工具~/.claude/skills/todo/,遵循全局 ~/.claude/CLAUDE.md 的规则)。本项目不再维护自己的 /todo slash command(已删除),一律调用全局 todo skill。

  • 数据落地:全局 skill 把待办存到当前项目todo/ 目录(todo.json + 看板 todo/todo.html,首次使用自动创建);命令须在项目根目录运行,例如 node ~/.claude/skills/todo/todo.mjs list
  • 禁止使用 TaskCreate / TodoWrite 等任何内置 todo 工具,一律走全局 todo skill。
  • 改动等级把控:接口/schema/跨 2+ 模块的「大改」先进 plan 模式经用户批准,完成后同步更新设计文档;小 bugfix 直接做。

搜索实现(拼音)

商品名搜索同时支持汉字、全拼、首字母:

  • products 表有 name_pinyin(全拼)和 name_initials(首字母)两列
  • Create / Update / FindOrCreate 写入时自动调用 util.ToPinyin() 生成,无需手动维护
  • 启动时对 name_pinyin = '' 的存量数据自动回填(backfillPinyin
  • 库存搜索 SQL 同时 LIKE 匹配 name、code、name_pinyin、name_initials
  • 新增其他需要拼音搜索的实体时,复用 backend/internal/util/pinyin.goToPinyin()