- git mv 全目录(历史保留);.gitignore 收敛为整个 .superpowers/ 忽略 - 全仓 16 处引用同步(CI checks.yml / l1-sync / screens.mjs / hooks / CLAUDE.md / CONTRACT / SSR 模板注释 / docs / web 注释) - 修 pre-commit 路径正则残留;5180 评审服务已切新路径 - 验证:check-ds 12 道 / l1-sync 6 道 / fidelity 抽查全过 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JJ1g8XV1YhhmHRzhwWEW7o
20 KiB
酒库管理系统 — 项目规则与 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.html(schema 变更时同步) - 设计契约与像素闸:
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. 静态分析无 error(warning/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)。- 入库每录一行 = 新建一个独立 product(
createIndependentProduct+nextProductCodemax+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),前端同时隐藏对应列——新增出库相关展示时两侧都要遵守。库存同口径(2026-07-06):库存 List 抹空 unit_price、Summary 抹零 stock_value/last_month_value(stripInventoryCost),前端隐藏成本价/总价列、卡片单价、货值 KPI(isAdminProvider)、商品抽屉成本行。 - 财务口径不变:应收/应付、退单冲账均按上述合计列;出库退单展示与冲账同为售价口径。
库存变更
- 入库/出库审核通过时,必须在同一事务中同时完成:
- 更新
inventories表数量 - 写入
inventory_logs流水记录
- 更新
- 出库前必须校验库存充足,不足时返回错误并回滚
- 库存三态(2026-07-07 落库
inventories.status):stock库存(默认,不公开)⇄on_sale在售(公开酒单展示,抽屉 switch 手动挂售,接口PUT /inventory/:id/status仅收这两值)→sold已售(出库审核扣光自动置入,留痕不再软删;退单恢复置回 stock)。列表默认排除已售(显式status=sold才返回);Summary 的 SKU/数量/货值排除已售、另出sold_count;公开店铺页只列 on_sale。盘亏归零仍软删(灭失≠售出)。预警/缺货退出状态维度,由数量 vs 安全库存染色承担 - 审核中(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 命令前须确保 PATH:
export 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.apiBaseUrl、AppConfig.healthUrl、AppConfig.versionUrl
前端平台判断
- 使用
dart:io的Platform类前,必须先检查kIsWeb(import 'package:flutter/foundation.dart') - Web 平台不支持
dart:io,直接使用会在 Web 构建时崩溃
设计真相源分层(原型漂移治理,2026-07-04 拍板)
原型(design/prototype/)与真实页面不追求全量永久同步,按层分治:
- L1 设计系统层(
tokens.css/atoms.css/mobile-atoms.css/icons.js+index.html登记):原型是单一真源。新增颜色/组件/图标必须先登记原型再同步代码与官网(如web/_includes/icons.njk)。闸:pre-commitcheck-ds.mjs12 道(本地钩子,新机器先跑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.yml,PR + main push 自动跑):①check-ds.mjs原型 12 道;②tools/check-l1-sync.mjsL1 同源四道(tokens 快照逐字节 / icons 两端同集同内容 / 官网 token 值对齐原型主题 A / web 硬编码 hex,白名单 #fff、#1677ff,ds-allow可豁免);③client/tool/check_ds_code.mjsFlutter 颜色单源;④ codegen 新鲜度(regen→dart format→与入库产物零 diff)。本地可直接跑同名命令。
表格与筛选/排序(ds 真相源)
- 列表屏统一
DsTable(widgets/ds/ds_table.dart:toolbar+表格+pager 连成一卡),列定义DsColumn,可隐藏列走列设置菜单 - 筛选统一放工具栏
DsChip(2026-07-07 用户口径,形式参照出库管理:单选/多选 chip + 选中值/N 项摘要 + × 单清);列头只放排序DsSortHeader(点击循环 无→升→降→无,服务端全局排序)。列头漏斗DsFilterHeader已退役删除 - 旧
FilterableColumnHeader/multi_select_dropdown.dart仅存量引用,勿在新屏使用
响应式 / 移动端适配
- 断点判定统一用
context.isMobile(client/lib/core/responsive/responsive.dart,宽度 < 600 为窄屏/手机),禁止在各处散落MediaQuery.width < 600这类魔法数 - 列表屏:统一用
DsTable(widgets/ds/ds_table.dart,真相源表格:toolbar+列+pager);窄屏卡片流用MCard(widgets/ds/m_card.dart,镜像原型 mobile-atoms)。旧MobileListCard为 legacy 仅存量引用(迁移在 todo),旧DataTableCard已删除(2026-07-03),勿再引用 - 弹窗固定宽度:一律用
context.dialogWidth(X)(≤ 屏宽 92%),禁止裸写width: <固定值>导致窄屏溢出 - 导航:窄屏(手机)= 底部 5 tab(库存/入库/出库/财务/我的)+「我的」hub 聚合二级屏(
app_shell.dart+m_tab_bar.dart,2026-07-04 起,Drawer 已退役);宽屏保持侧边栏;一切弹层窄屏统一走showMSheet底部 sheet - 平台判断仍遵守上面「前端平台判断」规则(
kIsWeb先于dart:io)
权限中间件(已全局挂载)
middleware.ReadOnly():已挂载到主api路由组,只读用户(role=readonly)所有写操作自动返回 403middleware.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/marketing,nginx /)。不含 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/CD(Forgejo)按 tag 前缀触发对应 workflow,自动:编译 → 测试 → 创建 Release → 部署 ali(jiu.51yanmei.com,443)→ Telegram 通知。
归属与解耦(关键):
version.yaml归 client(写 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.js读CHANGELOG-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}.sh。compile-{macos,android,ios,windows}.sh 去 client-v 前缀取版本。
client 多平台构建矩阵:mac runner(容量 1)串行链 build-client-web → build-macos → build-android → build-ios;windows runner 并行 build-windows(Inno 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.dart:FlutterError.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.go的ToPinyin()