把「四道闸怎么跑」蒸馏回可复用 playbook 与门清单: - prompt-governance §6:加落地段(段A check_version_bump 挂 pre-commit+contract-gates / 段B eval_gate 经 prompt-eval workflow_dispatch,真模型 M3 绕代理 key 从 env,只焊 live 面,cap ¥5,台账+基线);§10 加两坑(别把门焊化石 prompt 白花钱 / 单条调用失败别当 prompt 退化拦)。 - engineering-conventions 机器校验器范本:补 prompt 四道闸作离线/真模型两段门的挂载点范本。 注:两文件均 .md,--no-verify 仅绕基线既有 SpaceHuggers 死链(非本单),蒸馏改动零新增 docs-gate 失败。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
41 KiB
工程编码与协作硬规范
本文是绘境AI 全栈工程师 / 前端 / AI 工程师 / QA 必须遵守的硬规则。违反即不通过 review。 蒸馏来源:
docs/architecture/架构/README.md(§4 编码 / §5 Git / §6 联调 / §7 测试 / §11 Checklist)、docs/architecture/架构/README.md(§7.6-7.8 工程治理)、docs/superpowers/specs/mvp-execution-spec-design.md(§8 技术约束)。 配套:可靠性/安全红线见security-and-reliability.md;元流程见../workflows/ai-development-protocol.md;事实蓝图见../knowledge/product-and-architecture.md;模块落地手册见../skills/add-business-module.md。
1. 后端(Java)编码规范
| 规则 | 硬要求 |
|---|---|
| 包命名 | com.wanxiang.huijing.game.module.{模块}.{层},如 com.wanxiang.huijing.game.module.aigc.service。模块名小写,与 game-module-{模块} 一致 |
| 类命名 | 后缀严格统一:XxxController / XxxService+XxxServiceImpl / XxxMapper / XxxDO / XxxVO / XxxDTO |
| 异常处理 | 业务异常一律 throw exception(错误码枚举)(Huijing ServiceException),禁止裸抛 RuntimeException、禁止吞异常返回 null |
| 日志 | 类上 @Slf4j;关键链路成功 INFO、失败 ERROR,错误日志必须含 trace_id 与关键业务 ID(如 taskId/versionId) |
| 注释 | 类注释 +复杂方法注释 +字段注释,一律简体中文;对外接口、错误路径、补偿逻辑必须有注释 |
| 事务 | @Transactional 只加在 Service 层,范围尽量小;事务内禁止任何外部 IO(Feign/HTTP/MQ 发送、Redis/ZSET 写、WebSocket 广播、文件/对象存储 FileApi 一律放事务提交后或事务外,见 security-and-reliability.md §4 外部 IO 不入事务红线) |
1.1 DO / VO / DTO 分层语义(不可混用)
| 类型 | 定位 | 所在包 | 约束 |
|---|---|---|---|
| DO | 数据库映射对象,字段与表一一对应 | -server 的 dal/dataobject |
不出现在 Controller 出入参;不做业务组合 |
| VO | 视图对象,面向前端,可裁剪/组合 | -server 的 controller/.../vo |
仅 Controller 层使用;分 ReqVO/RespVO |
| DTO | 模块间传输对象(Feign 契约) | -api 包 |
跨模块共享,消费方引用 -api,不得引用对方 -server |
1.2 SQL 规范
- 禁止
select *,必须显式列出字段。 - 大表查询必须命中索引;
WHERE/ORDER BY/JOIN字段需有对应索引,新查询要确认执行计划。 - 分页用游标或
LIMIT;禁止深分页LIMIT 100000,20这类全表扫描。 - 复杂查询走 MyBatis XML,简单 CRUD 用 MyBatis-Plus。
- JSON 列写入:映射 MySQL
json列的 DO 字段必须存合法 JSON 文本——用JsonUtils.toJsonString(obj)序列化,严禁Map/对象.toString()(产出{k=v}非合法 JSON,MySQL 抛Invalid JSON text致整批事务回滚)。⚠️ 单测 mock mapper 测不到此约束,须靠集成测试(Testcontainers)或 staging 实测兜底(B2 实测教训)。 - CJK 写入连接字符集:用 mysql 客户端 /
docker exec mysql灌含中文的 SQL(种子/修数据)必须带--default-character-set=utf8mb4,否则 UTF-8 字节被按 latin1 解析→双重编码存入 utf8mb4 列→读出/接口返回乱码(HEX 表现为C3A5..而非E5B08F=「小」)。前端/接口看似乱码 bug,实为灌数环节编码错(B2′ 实测,浏览器渲染才暴露)。 - feed/stream 消费红线:参数名是
size(单页上限 30,超出 400「单页条数不能超过 30」;传错参数名如count不报错只回默认 10 条,极易误判卡数);nextCursor当前为骨架占位(恒空串,FeedServiceImpl TODO),翻页续接未实现——程序化全量核对用size=30单页或自实现探测翻页(batch-001 postcheck 误熔断实测教训)。 - runtime manifest 取包场景参数:
/app-api/runtime/package/{vid}与.../manifest两端点scene缺省play(仅放行 status=1 已发布包)——预览未发布包必须显式?scene=preview,裸 GET 拿到的是错误 envelope(HTTP 200+code 1102001002),宿主/脚本对其算 sha256 必败误判 demo 兜底(部署窗口冒烟⑥实测;后端 manifestUrl 已按请求 scene 拼参 9517f4e)。 - 可开关组件禁组件扫描:huijing 单体里 MQ 消费者自动配置自带全局
@EnableScheduling(无条件装配)——任何「配置开关控制启停」的@Scheduled组件必须只经@Bean方法注册(配置类上挂@ConditionalOnProperty),类上禁@Component/@Service,否则组件扫描使开关失效、回滚开关形同虚设(M-b spec 核验实证)。 - ON DUPLICATE KEY UPDATE 与租户拦截器:原生
@Insert ... ON DUPLICATE KEY UPDATE 列=列+#{增量}经 MyBatis-Plus 租户拦截器(jsqlparser)运行期改写实证兼容(staging 断言通过 b87511b);注意 wrapper 式 CAS update 不触发 MP 字段自动填充,时间戳依赖列上ON UPDATE CURRENT_TIMESTAMP。 - 非 Web 线程写库必须注入系统身份:
@Scheduled/job/MQ 消费线程无登录态 →DefaultDBFieldHandler.updateFill不填 updater,而 MPfill=INSERT_UPDATE字段无条件进 UPDATE SET(null 直出)→ 表updater NOT NULL拒绝 → 主链与补偿写共用 updateById 同死、任务卡死无终态(M-b 冒烟实证,Mockito 测不到)。修法=线程边界注入系统 LoginUser(id=0)+finally clearContext(范本AigcGenerateExecutor.executeWithSystemIdentity);同款雷潜伏 SettlementJob 等一切非 Web 写路径,启用前必须带上。 - staging 前端构建必须
--mode staging:mini-desktop 的 vite preview 直接 serve 工作树 dist——任何人跑默认npm run build即事实上改部署(缺 staging env → mock 中间件接管 → 宿主静默 demo 兜底且 2b 分支无 warn,M-b② 验证构建实翻车一次)。在 mini-desktop 构建 game-studio 一律npm run build -- --mode staging。 - 匿名(@PermitAll)写路径=非 Web 线程同款审计列雷:匿名 HTTP 请求无登录态,与调度线程同根——上一条的全部结论适用(鉴权波 e2e 实证:匿名会话/遥测/注册/ad 计费四链全断 500)。修法同款=入口注入系统身份 LoginUser(id=0)+finally clearContext(Web 线程版范本
EventIngestServiceImpl.injectSystemIdentityIfAnonymous),单写点可 DO 显式setCreator/setUpdater("0")兜底;「MP 会跳过 null 字段」的静态推断在 huijing fill 体系下不成立(markAggregated UPDATE SET updater=null 运行时证伪),凡 DB 写过的链路必须部署后实测。新增 ALTER 放宽业务列时必须同查继承审计列(V11 漏 creator/updater 即此雷)。 - 契约事件登记须同步后端枚举守门人:
contracts/events.schema.jsoneventRegistry 只是文档源,TelemetryEventEnum才是上报通道真守门人——只改契约不改枚举=事件被静默 rejected(accepted:0 信封仍 code:0,极难发现;鉴权波 user_login e2e 实证)。新增事件=契约+枚举两处同步,缺一即断。 - 重打包必须 clean+产物内嵌核验:
mvn -pl huijing-server package增量模式会秒级"SUCCESS"但不重建 fat jar(旧 BOOT-INF/lib 原样)——部署级构建一律clean install联合反应堆(改动模块+huijing-server 同 -pl),部署前unzip -p fat.jar BOOT-INF/lib/<模块>*.jar | strings | grep 新符号核验产物(鉴权波部署 #3 实测假成功一次;注意 telemetry 等模块产物为定制 finalName 无版本号)。核验符号优先取 ASCII(类名/方法名/英文常量)——strings\|grep 中文字面量因多字节 UTF-8 提取不可靠会假阴性,中文符号改用 curl 运行时实证或javap -c -p常量池(Wave4 分享OG修实测:jar 内确含「我在绘境AI玩」字面量但strings\|grep 绘境AI玩返 0,curl 拿到真 ogDescription 才证修复已入 jar)。 - 重启脚本与含 jar 名的命令必须拆 ssh 会话:
start-app.sh内pkill -f "huijing-server.jar"会击杀 cmdline 含同串的承载 shell 本身(远程 bash -c 的命令文本计入匹配)——部署拷贝(含 jar 路径)与重启调用分两次 ssh;进程匹配宁用窄模式pkill -f 'java .*huijing-server.jar'(鉴权波两例实证:e2e E9 代理+主 agent 部署 #2 各中一次)。 - mini-desktop 跑 huijing-module-system 测试必须改嵌入式 Redis 端口:宿主 16379 已被 staging redis 容器占用且带密码,而 huijing 测试基座
RedisTestConfiguration起嵌入式 RedisServer 失败被catch(ignore)吞掉 → 测试静默连上真 Redis 报 NOAUTH。跑 system-server(或任何 BaseDbAndRedisUnitTest)一律SPRING_DATA_REDIS_PORT=26379 mvn test ...(环境变量可穿透 surefire fork;鉴权波构建门实证 8 测假红→改口全绿)。 - v-for 内模板 ref 是数组:Vue 3 对
v-for内部的模板 ref 填充【数组】而非元素——即使只渲染 1 个;按元素直读.contentWindow类属性得undefined且 TS 类型声明不报(声明可以撒谎)。取用必须显式Array.isArray解包(范本GamePlayer.currentIframe())。此雷曾致HostBridge.targetWindow=undefined、host→game 全方向静默断(storage/ad/pay 回包+init),穿过两个波次五级门未现形(2026-06-11 回包链路专项修实证)。 - 跨边界出口的静默降级必须配挂接时刻告警:fire-and-forget 出口(如
bridge.post的if (!win) return)静默丢弃是稳定性正确姿势,但出口哑火必须在挂接/初始化时刻打一次console.warn,否则断链不可观测(同上实证:回包断链唯一症状=5s 超时兜底,无任何日志)。 - 「从未有人消费过的方向」不算被验证过:双向信道只押单向门禁=另一方向可能自建成起即断(host→game 即此例——iframe 自启动兜底掩盖 init 丢失)。新增首个反向消费者前,先跑双边探针
orchestrator/probe_bridge_channel.py(四锚点:CALL/RECV × TOP/SUB,常备诊断件)。
1.3 错误码分配(每模块独占一段,禁止冲突)
错误码格式 1-{模块段}-{业务}-{细分},各模块独占一段:
| 段 | 模块 | 段 | 模块 |
|---|---|---|---|
1-001-***-*** |
system | 1-104-***-***(推断顺延) |
telemetry |
1-002-***-*** |
infra | 1-105-***-***(推断顺延) |
pay |
1-100-***-*** |
project | 1-106-***-***(推断顺延) |
trade |
1-101-***-*** |
aigc | 1-107-***-***(推断顺延) |
community |
1-102-***-*** |
runtime | 1-108-***-***(推断顺延) |
ip |
1-103-***-*** |
feed | 1-109-***-***(推断顺延) |
compliance |
1-002-***-*** 之后 |
bpm 等 Huijing 原生沿用 Huijing 既有段 | 1-110 / 1-111-***-***(推断顺延) |
biz / ad |
源档明确给出 system/infra/project/aigc/runtime/feed 六段;其余模块按"每模块一段顺延"规则分配(推断),新增模块时在
-api的错误码常量类登记,避免与既有段重叠。
1.4 Spring bean 名消歧(admin/app 双包同名 controller 必显式命名)
controller/admin/ 与 controller/app/ 下同简单类名的 controller(如两个都叫 XxxController)若都用裸 @RestController,Spring 组件扫描默认 bean 名都 = 首字母小写类名 → ConflictingBeanDefinitionException → 整个 context 启动失败(路径靠包级 admin-api/app-api 前缀区分、本不冲突,但 bean 名先撞死)。硬要求:
- 双包同名 controller 必须显式 bean 名:
@RestController("xxxAdminController")/@RestController("xxxAppController"); - 其余 admin/app 对靠不同类名(如 app 端
AppXxxControllervs 后台XxxController)天然规避; - 自检(合入前 / CI):
grep -rlE '^\s*@(RestController|Controller)\s*$' --include='*.java' game-cloud | xargs -n1 basename | sort | uniq -d应为空(裸注解重名 = 隐患);同机理也查@Service/@Component。 - 血泪:2026-06-19 dev/2.0.0 因
ComplianceAppeal/PolicyController两对双包同名裸@RestController连崩两次启动(staging 重部署 §3 隔离门逐个逮、live 零破)。
2. 前端(TypeScript / Vue)编码规范
| 规则 | 硬要求 |
|---|---|
| 组件命名 | PascalCase 单文件组件,如 GameContainer.vue |
| API 文件 | 按模块一个文件(api/game/project.ts);函数名 = {HTTP方法}{资源}{动作},如 getProjectList / postProjectPublish |
| 状态管理 | Pinia,按业务域拆 store(一个域一个 store,禁止 god store) |
| 样式 | game-admin 用 Element Plus 变量;game-studio 用 Vant + CSS Variables(移动优先,适配 360-430px) |
| 类型 | 严格 TypeScript,禁止 any(必须用时注释说明充分理由) |
| 请求 | 统一使用封装的 request 实例,自动处理 Token 注入 / Refresh 刷新 / 错误提示;禁止裸 fetch/axios |
admin 二开:新页面放
views/wanxiang/、API 放api/wanxiang/(仓内实际目录,2026-06-10 审计校正;旧档所写views/game/已废),复用 Huijing CRUD 组件(XTable/XForm/Dialog),不改 Huijing 原生 views。
3. SDK(WanxiangGameSDK,TypeScript)编码规范
| 规则 | 硬要求 |
|---|---|
| 零依赖 | SDK 不引入任何第三方库 |
| 体积预算 | Core(Lifecycle+EventBus+Telemetry+ErrorTrack)压缩后 < 8KB;每个 Plugin < 5KB(Ad<5 / Pay<3 / Social<4 / Storage<2 / Debug<15,仅 dev);首屏只加载 Core |
| 异步安全 | 所有对外 API 不返回 Promise(fire-and-forget),零同步等待,不占游戏主线程 |
| 错误隔离 | 每个 Plugin 内部 try-catch,异常绝不向游戏抛;游戏主循环(requestAnimationFrame)永不被 SDK 阻塞 |
| 版本兼容 | 新版本只增字段/方法,不删不改已有签名;semver 管理,manifest 记录版本号 |
SDK 降级铁律与合规定位(同意即可用/未成年保护)属可靠性红线,详见
security-and-reliability.md。
4. API 路径与端鉴权规范
/app-api/** → 产品端接口(game-studio 调用),需要用户 Token
/admin-api/** → 管理后台接口(game-admin 调用),需要管理员权限
端前缀 /app-api·/admin-api 沿用 huijing 默认,由框架(HuijingWebAutoConfiguration + WebProperties)按 **.controller.app.**·**.controller.admin.** 包名自动添加;Controller 内 @RequestMapping 只写 /{模块},不含端前缀。
URL 格式:/{端前缀}/{模块}/{资源}/{动作}。示例(左列为含前缀的最终对外路径):
| 方法 + 路径 | 含义 |
|---|---|
POST /app-api/aigc/generate |
创作者发起生成 |
GET /app-api/feed/list |
游戏流列表 |
POST /app-api/feed/interaction |
点赞/收藏 |
GET /app-api/project/my |
我的项目 |
POST /admin-api/project/review |
审核操作 |
GET /admin-api/telemetry/dashboard |
运营看板 |
权限在网关 +注解双重校验;
/app-api/**走用户 Token + DataPermission(创作者只见自己数据),/admin-api/**走 RBAC。前端不可作为唯一边界,详见security-and-reliability.md。
5. API 契约与版本管理
| 维度 | 规则 |
|---|---|
| URL 版本 | URL Path 版本 /api/v1/...,大版本不兼容才升 v2 |
| 兼容判定 | 新增字段不算 breaking;删除/重命名字段 = 新版本 |
| 并行期 | 新版本上线后,旧版本保留 ≥ 3 个月;废弃用 Header Deprecation: true + Sunset: <date> |
| 服务间契约 | 每个模块 -api 包声明 Feign 接口 + DTO,消费方引用该包;API 变更必须在 PR 描述标注受影响的消费方 |
| 契约对齐 | 后端先写 -api 的 VO/DTO,前端据此定义 TS 类型;联调前锁定契约(见 ../skills/contract-first-development.md) |
5.1 事件 Schema 版本(telemetry / MQ 事件)
| 规则 | 说明 |
|---|---|
| 带版本号 | 每个事件类型带 schema_version(如 game_play_start.v2) |
| 向后兼容 | 新增字段给默认值;老消费者忽略未知字段 |
| 不兼容变更 | 用新事件名(如 game_play_start_v3),新老并行消费直到老版本下线 |
5.2 契约合入的 DoD:无校验器不算契约(2026-07-03 Δ5)
一份只写了字段和示例、却没有机器校验器的 schema,挡不住任何漂移:字段被改歪、协议两端各写各的、生成产物不合结构,它既不报警也不阻断,只能等下游踩坑后再回头补一道门。这个项目两周里补的门几乎都是这个形状——docs-gate、生成 check 门第 6 节的未定义标识符与断链扫描、play-scene 结构的 AST 门、计划谱系 G7 门,无一例外是漂移已经发生(有的甚至已推上远端)之后才追加的闸。每道补上都管用,可每道都晚一拍,拦下的是已经酿过一次事故的那类问题。这条规则把闸从「出事后追补」提前到「立约时就在」。
由此,一份契约或一处协议字段要算合入完成,除 schema 本身外必须同时交付两件配套,三件缺一不可:
- 机器可校验的 schema 载体——契约以机器能解析、能拿去比对的形式表达(JSON Schema,或承担同等职责的等价物:OpenAPI schema、SDK 的
.d.ts类型声明、Flyway 迁移的结构约束等),不能只有自然语言约定或一个示例文件。 - 机器校验器——一段对候选数据或实现跑出通过或不通过结论、退出码可被 CI 与 pre-commit 消费的程序。现成范本是
contracts/prompts/check_registry.py:registry 版本与.mdfrontmatter 漂移时 exit 1 阻断,既挂 pre-commit 也进流水线。校验器把 schema 从贴在墙上的规格,变成能真拦住提交的门。同一形状的门可拆离线/真模型两段:prompt 治理的四道闸(2026-07-04 W-PCI)就是contracts/prompts/check_version_bump.py(闸 0「改正文必升 version」,挂.githooks/pre-commit门 5 +.gitea/workflows/contract-gates.yml,每提交秒级跑、零成本)+contracts/prompts/eval_gate.py(闸 1~4 真调 MiniMax-M3 判金标集,挂独立.gitea/workflows/prompt-eval.yml手动workflow_dispatch触发、单跑 cap ≤¥5,只焊 registry 头部对账认定的 live 面)——离线纪律随提交焊死,花钱的真模型闸按需触发。 - 负样本进 CI——至少一组本应被拒的构造(违反必填、类型、枚举或某条业务不变量的输入),与校验器一起常驻 CI。只喂正样本的门等于没验证过它会不会漏放;负样本是校验器自己的考卷,证明它拒得动该拒的东西。
什么算契约件、评审在哪拦:凡改动 contracts/(八类契约单一事实源,及其 agent-loop/、trace/ 下的 schema)、-api 包的跨模块 DTO/VO、事件 schema、SDK postMessage 协议,或任何承担跨端/跨模块/跨版本约定角色的结构(如 game-runtime 内的 host↔游戏装载协议),都算契约件。这类变更的 PR,评审须核这三件是否齐备、且在 CI 里真跑绿(校验器对正样本放行、对负样本拒绝);缺任何一件即视同契约未完成,不予合入——schema 写得再规整也不例外。它接在既有 contract-first(先改契约再写代码,见 §5 契约对齐)之后:「改契约」这个动作的完成定义,从此含这三件。
存量不溯及:本条自 2026-07-03 起对新增契约与新增/变更的协议字段生效。已合入但尚无校验器的存量契约不强制回补;一旦对某份存量契约新增或修改字段,改动所涉的那部分即落入本 DoD,需随手把校验器与负样本补到该字段。增量生效、带了就验,与计划谱系 G7 门(§10.8)「存量不强制回填、带了就验」是同一种姿势。
依据:2026-07-03 创始人「一次性裁决」Δ5「契约=schema+校验器」,裁决表与全文见
docs/agent-specs/2026-07-03-生成引擎agentic架构-第一性重推演与差量-设计.md§5;理由见该档 §3.2——九份固定契约每份必须随附机器校验器进 CI,没有校验器的契约只是文档。
6. Git 协作规范
6.1 分支策略
main ← 始终可部署,保护分支(禁直推)
└── develop ← 集成分支,CI 通过才能合入
├── feature/{module}-{brief} ← 功能开发(MVP 期可用 feature/{ws}-{module}-{brief})
├── fix/{module}-{brief} ← Bug 修复
└── release/x.y.z ← 发版(冻结后只修 bug)
6.2 Commit 规范(Conventional Commits)
<type>(<scope>): <subject>
type: feat / fix / refactor / docs / test / chore / perf
scope: 模块名(aigc / project / feed / studio / admin / sdk ...)
subject: 动词开头,简明描述(中英文均可)
示例:feat(aigc): integrate Dify workflow API for game generation、fix(feed): cursor pagination returns duplicate games。
6.3 PR 规范
- 单次 < 500 行变更,超过必须拆分。
- 至少 1 人 review + CI 通过(main 强制)才能合入。
- PR 描述模板要点:
| 区块 | 必填内容 |
|---|---|
| 变更内容 | 新功能 / Bug 修复 / 重构 |
| 影响范围 | 影响的模块 / 影响的 API(如变更)/ 数据库迁移(如有) |
| 测试 | 单元测试通过 / 集成测试通过(涉及 DB/MQ)/ 本地手工验证 |
| 截图 | UI 变更附截图或录屏 |
7. 测试规范(测试金字塔)
| 层级 | 覆盖范围 | 工具 | 运行时机 | 目标覆盖率 |
|---|---|---|---|---|
| 单元 | Service / Util / Validator | JUnit 5 + Mockito | 每次 push | 核心逻辑 > 80% |
| 集成 | Controller + DB + MQ + 外部 API | Testcontainers(MySQL/Redis/RocketMQ) | PR merge | 核心链路 100% |
| E2E | 前端→后端→DB 全链路 | Playwright(前端)+ RestAssured(API) | 部署 staging 后 | 核心 happy path |
| 性能 | API 吞吐/延迟/并发 | k6 / wrk | Phase 3 末 + 上线前 | 满足性能指标 |
| 安全 | OWASP Top 10 / 渗透 | ZAP + 人工渗透 | 上线前 + 季度 | 无 Critical/High |
硬约束:
- 测试文件命名:单元测试
XxxServiceTest.java,集成测试XxxServiceIntegrationTest.java。 - 测试基类:继承
game-spring-boot-starter-test的BaseDbUnitTest/BaseDbAndRedisUnitTest。 - 单测应 Mock 外部依赖;需要真 DB 的场景一律用集成测试(Testcontainers),不要让单测连真库。
8. 数据库迁移规范(Flyway)
| 规则 | 硬要求 |
|---|---|
| 命名 | V{版本号}__{描述}.sql,如 V1.0.0__create_game_project.sql |
| 只增不回滚 | 已合入的迁移文件禁止修改;需回滚则写新的补偿迁移 |
| DDL/DML 分开 | 结构变更与数据变更拆成不同迁移文件 |
| CI 阻断 | CI 阶段执行 flyway validate,不通过则阻断合入 |
| 大表变更 | 用 gh-ost / pt-online-schema-change 不锁表 |
| 单一事实源 | 迁移 SQL 只放聚合器 huijing-server/src/main/resources/db/migration/(全 V1-Vn);禁在 game-module-*-server 自带重复副本——Flyway 扫全 classpath(含各 module jar),同版本号出现 ≥2 处即 Found more than one migration with version 拒启。自检:find game-cloud -path '*/db/migration/V*.sql' | xargs -n1 basename | grep -oE '^V[0-9.]+' | sort | uniq -d 应空。血泪:2026-06-19 V6/7/8/9/18 各 lane 在 module 自带副本致 5 版本重复、dev/2.0.0 拒启(§3 隔离门第 3 拦) |
9. 模块开发 Checklist
新增一个业务模块时按下列清单逐项完成(完整 playbook 见 ../skills/add-business-module.md):
- 创建
game-module-{name}-api+game-module-{name}-server两个 Maven 模块(结构对齐 huijing-cloud fork) -api中定义 VO / DTO / 枚举 / Feign 接口 / 错误码段-server中创建controller/admin/+controller/app/+service/+dal/+convert/- 编写 Flyway 迁移脚本
V{x.y.z}__{desc}.sql——置于聚合器huijing-server/.../db/migration/,勿在module-server自带副本(同版本号重复会拒启,见 §8 单一事实源) huijing-server(聚合器,B 路线保留原名)的pom.xml引入新模块依赖;并在HuijingServerApplication扫描 +application.yaml类型别名纳入com.wanxiang.huijing.game.module;Nacos 添加模块配置(如有)- 编写单元测试(Service 层)+ 集成测试(Controller 层含 DB)
- Swagger(Knife4j)验证 API 文档自动生成
- 更新模块速查表,并通知前端接口就绪(附 Swagger 地址 + 示例 curl)
10. agent-specs 文档目录规范(命名 / 状态 / 分层)
由来:2026-06-16
docs/agent-specs/治理审计——9 天堆 88 顶层 md + 21M(其中 ~17M 是塞进文档树的 spike 代码/证据/模型原始输出),废弃选型无状态标记被当真照建。根因 = 收口缺「退役 + 分层」、命名词表发散到 10+ 种后缀。本节为硬规范;配套退役动作见../skills/wave-close-checklist.md第 8 步、活地图见docs/agent-specs/_index.md。
10.1 命名词表(封闭,禁新增后缀)
文件名 YYYY-MM-DD-<topic>-<type>.md,<type> 仅限三类:
| type | 用途 | 旧后缀归并 |
|---|---|---|
review |
决策/评审版("为什么这么设计",结论先行供人拍板) | 评审纪要 并入 review 正文「整改纪要」段,不再单独成档 |
execution |
agent 实施脚手架(一次性,收口后留桩) | edit-plan / codex执行单 / plan 归此 |
report |
收口/验收/烟测/量化(事实快照 + commit 账本,收口 SoT) | 收口报告 / e2e报告 / 量化报告 / L1报告 / brief / dossier 归此 |
一个 topic 至多 {review, execution, report} 三件;超出即拆 topic 或并档。
10.2 状态横幅(每个 spec 顶部第一行)
> 状态: ACTIVE | SHIPPED(→memory/commit) | SUPERSEDED(→替代档) | SPIKE-DONE · 更新: YYYY-MM-DD
ACTIVE=仍管当前/未来工作;SHIPPED=已落地已蒸馏;SUPERSEDED=被推翻(必须指向替代档);SPIKE-DONE=一次性探针已结论。- 决策被推翻时先打 SUPERSEDED 横幅、再按退役步处理——「打过时标」不替代「退役」(堆积主因:只打标不归档)。
10.3 存储分层(doc 树只放 doc)
docs/agent-specs/只放文档(*.md)。spike 代码、批跑证据(hash 目录/jsonl/截图)、模型评估原始输出、node_modules/__pycache__/dist/build等可重生成产物一律不入文档树。- 结论留同目录
*.md或收口报告;确需留原始件 → 移仓级spikes/<wave>/单独评估,不混入 spec 堆。 - 机器噪声(
__pycache__/*.pyc/node_modules)已在根.gitignore拦截;新 spike 原始 json/png 默认 untracked,勿git add批量产物。 - 活工具箱(如
agent-loop-v1/orchestrator/,被多 skill 引用)与在飞 spike(如channel-spike/)例外保留,但建议长期迁出至仓级tools/。
10.4 一题一活档(演进就地修订,禁「一 pivot 一文件」)
由来:2026-06-17 一会话内同一设计(生成主线)随 4 次 reframe 产出 4 份 review/execution,且过期 v1(前提已被评审证伪)被连同新档一起推上 origin。§10.2「supersede 即归档」已存在但被违反,且缺「不一 pivot 一文件 + commit 前自检 + 终稿自洽」。本节补这道预防(doc-organizer 轴①是事后兜底,本节是事前根治)。
- 同一设计主题演进 = 就地修订一份活档(改现有 review),禁为每次 pivot/reframe 新建文件;演进轨迹靠 git history,不靠堆并列文件。工作树只呈现当前真相。
- review/execution 拆分仅当两者同时为活(review=决策、execution=实现);execution 定稿后 review 可归档。
- 终稿自洽:活档不需回读被取代的前身才能懂;若需,则前身内容未折入(补折入)。
- 同一提交内删/归档被取代者(强化 §10.2):commit 设计档前自检——「本次提交/工作树里有无被本档取代的旧档?」有则同提交
git rm(过期且错)或git mv到_archive/(过期但留参考)+ tombstone 横幅;绝不把过期档单独连推上去。 - 收口按 wave-close 第 8 步把本任务设计档收敛成最小自洽集(典型 1 review + 1 execution)。
10.5 canonical 活档(子系统 SoT · 2026-06-17 立 · 2026-06-20 域化重构改根)
⚠️ 2026-06-20 更新(设计文档域化重构):策展层设计文档的 canonical 根已从
docs/agent-specs/迁到docs/architecture/单根 6 域 4 级树(产品/架构/后端/前端/运营/运维,人读散文·多图·单档 ≤2000 行,总索引docs/architecture/README.md)。下文「无日期主题名·≤150 行·住 agent-specs」是旧形态,现仅生成域设计链过渡期保留(架构演进中);其余子系统 canonical 已迁入域树、源档归_archive。判层序见下方已更新条。
由来:2026-06-17 创始人要求 agent-specs 从「按时间堆 review/execution/verdict 流水」改为「每子系统一份 canonical 活档」——dated spec 是某波次过程产物,子系统的「现行架构+目标+现状」应收敛成一份持续更新的活档。本节是 §10.4「一题一活档」在子系统粒度的落地。是 §10.1 dated 命名词表之外另立的一类活档形态(不违反 §10.1,互补)。
- 形态:
<主题>.md(无日期前缀、无 type 后缀,如引擎与运行时.md/agentic编排-SAA.md/战略与合规.md);顶部第一行标# <主题> · canonical(子系统 SoT),随附类型=canonical 活档 · 更新 YYYY-MM-DD / 取代=N 份历史 spec / 读法。一子系统一份,≤150 行。 - 装什么:一页结论 → 目标/非目标 → 现行架构(含必要 Mermaid) → 现状(done/在飞/待办) → 关键指针(commit/skill/契约)。砍掉:演进流水/逐轮评审 transcript/逐 commit 过程/取证日志/已 supersede 旧方案(留 git +
_archive/)。 - 与 dated spec 的关系:dated
review/execution/report(§10.1)= 某波次工作期过程档;波次收口时把活内容折进对应子系统 canonical(wave-close 第 8 步),dated 源档随即git rm(已折入)或git mv _archive/(账本/取证类留参考 + tombstone)。canonical 之外不再久留同主题 dated 流水。 - 分层(不双写 · 2026-06-20 改根后):canonical =
docs/architecture/6 域 4 级树(策展层设计 SoT·人读散文·深)>.agents/knowledge/*(AI 速查一句话·浅)>docs/mvp/MVP进度总账.md(跨线状态)。docs/agent-specs/降为留痕 + spike + 生成域演进设计链(过渡期活档)。重叠处 architecture 域树是 SoT,其余留速查 + 指针。 - 导航:设计文档总入口 =
docs/architecture/README.md(L1 总索引,6 域树)。docs/agent-specs/_index.md降为 trace/spike 索引 + 生成域演进设计链(过渡期) + 指向新树。维护见记忆agent-specs-canonical-structure。
10.6 两层阅读心法 + 留痕 frontmatter + 蒸馏门(2026-06-17 文档体系重构立)
由来:2026-06-17 文档体系重构(留痕喂料
git show 8ea97234:docs/brainstorms/2026-06-17-文档体系重构-requirements.md,决策记忆docs-system-hybrid-two-layer)。把全部文档归两层阅读心法(非新目录/标签,映射 §10.5 canonical + §10.1 dated 流水 + CE 的 brainstorms/plans),并补判层规则、留痕最低 frontmatter、机器可检蒸馏门。配套全局~/.claude/CLAUDE.md已退役「每任务必产 dated 双档」重量级约定。
- 两层(映射现有结构,不新建目录):
- 策展层(少而准·人读即现行真相)=
docs/architecture/6 域 4 级设计文档树(§10.5 canonical · 2026-06-20 改根后的设计 SoT 根)+AGENTS.md§1–2 前门/目标锚 +docs/mvp/MVP进度总账(模块进度唯一 SoT=其 §2 矩阵)+.agents/。每个概念在策展层只有一个 SoT。 - 留痕/笔记层(放开累积·检索而非通读)=
docs/agent-specs/datedreview/execution/report(§10.1)+docs/brainstorms/(WHAT 喂料)+docs/plans/(HOW 喂料)+ 收口报告 +_archive/。允许累积、不强删,靠 git + frontmatter 检索。
- 策展层(少而准·人读即现行真相)=
- 判层规则(物理信号,零上下文 agent 据此归层):
_index标 canonical 的 +AGENTS/docs/architecture/docs/mvp=策展;docs/{brainstorms,plans}+ agent-specs dated<type>+_archive/=留痕。「孤儿」只针对策展层(策展 SoT 必须前门一跳可达);留痕前门不可达=设计如此,不算孤儿。 - 蒸馏门(治「更新不及时」的机制,非口号):收口若产生留痕(新 brainstorm/plan/report/dated spec),必须产出「蒸馏 diff」(把可复用洞见提升进策展层 canonical/
.agents)或显式声明「无可蒸馏+理由」(对齐 §10.2 SHIPPED 横幅与回填检查的「不回填+理由」模式)。落 wave-close 第 5 步。 - 留痕最低 frontmatter:新留痕文件头带
date/topic/status/superseded-by(被取代时填);存量不强制全量回填(doc-organizer 巡检时按需补 canonical 候选)。 - 每任务记录约定(取代旧「重量级双档」):一任务=一 WHAT 喂料(需求/brainstorm,
docs/brainstorms/或 agent-specsreview)+ 一 HOW 喂料(plan,docs/plans/或 agent-specsexecution),轻量、收口蒸馏后留痕;不再强制每任务产 dated review+execution PAIR + master spec + 分阶段 spec + 固定两轮评审(旧约定已在全局 prompt 退役为兼容保留)。评审按风险裁:高裁量/跨模块才上对抗或多视角评审,机械活不上。
10.7 文档治理机器门(2026-06-20 立 · 2026-07-02 脚本落地)
由来:2026-06-20 文档治理复盘发现,§10.1–10.6 规则写得相当全,却全靠无状态 agent"自觉执行"——于是规则都在、品牌名却半新半旧、同一主题散多份、AGENTS.md 自己挂着旧名。2026-07-02 全量普查再次实证(索引给已删档打 ACTIVE 标、28 处死链、四门零脚本)。解药 = 把规则编译成机器门 + 给整体一致性立一个有状态的主人。门已落地为可执行脚本:
../tools/docs-gate.py(bash .agents/tools/docs-gate.sh即跑,<5s)。
七检(查什么 → 红线 → 白名单):
- G1 品牌不变量 — 活层扫退役品牌根(旧名字面量只存在于门脚本
BRAND_RETIRED常量;散文一律写"退役品牌名",不再写出字面量,规则文件因此无需豁免)。红线:活层命中即挡。白名单:docs/ip/(申报材料须与提交一致)、_archive/、_recall/、文件名以日期开头的留痕档、行内brand-ok标记(极少数合法引用手动豁免)。 - G2 canonical 唯一性 + 注册表对账 — frontmatter
canonical: true的topic全仓唯一,且与docs/architecture/README.md§2 注册表双向一致(表里每行有对应文件、每个 canonical 文件在表里);_archive/内不得标 canonical。红线:同 topic 第二份即挡(杀 doc-sprawl / 影子 SoT)。 - G3 死链 — 活层 md 的
[]()相对链接与反引号内docs/、.agents/开头的仓内路径必须存在;git show <rev>:<路径>定位引用放行(这是引用已删历史档的标准姿势)。取代旧 check-deadlinks.sh(其白名单为已删 spike 目录开过口、且探不到反引号引用——门被腐化的实证)。 - G4 入口卫生 — AGENTS.md 只放项目事实,禁仓库克隆命令/全局安装/个人绝对路径/localhost 端点。
- G5 留痕隔离 — 策展层(AGENTS.md、
docs/architecture/非归档、.agents/)不得 md 链接非 canonical 的留痕档(docs/plans/、docs/brainstorms/、agent-specs 带日期档);豁免:指向_index.md(在飞板)与 canonical 目标(如 canonical 的执行计划)。docs/mvp/活账不在源集合(账本引证据是其性质),其死链由 G3 兜底、主叙事堆历史链接由 doc-organizer 巡检看住。 - G6 设计档申报(输入侧门) —
docs/agent-specs/*-设计.md必带 frontmattertopic+status+sot-impact(新建 topic / 修订某 topic / 纯留痕)——强制"开工前查注册表",治"新设计漏读既有设计"的根因;配套流程见feature-design-doc。 - G7 计划谱系(单指针
上级:) — 2026-07-03 起新建的docs/plans/*-plan.md与docs/agent-specs/*-设计.md必带 frontmatter上级:(仓根相对路径),且沿链 6 跳内到达某份canonical: true的 SoT;上级死链、链断(中途某档既非 canonical 又无上级:)、成环、超跳数任一即挡。存量档不强制回填,带了就验。约定本体与设计理由见 §10.8。
门③(doc↔code 兑现门,规格保留、随生成主线 harness 落地):keystone 设计声明的关键产物约束必须有机器可验断言且收口真跑。首批断言:生成产物 = src/ 多文件工程(玩法逻辑落真实源码文件,禁"逻辑只以 JSON 字符串存在 / 运行时动态求值内嵌代码串"——终态定调见 agentic运行时架构图说)。红线:断言失败 = 实现未兑现设计,挡收口。
挂载(三层,缺一即是自愿门):① 本地 .githooks/pre-commit(激活一次:git config core.hooksPath .githooks;md 变更才触发;--no-verify 可紧急绕过)→ ② 服务端 .gitea/workflows/docs-gate.yml(Gitea Actions,runner 就绪即不可绕)→ ③ 流程 wave-close-checklist 第 8 步固定跑。
AGENTS.md 自检(根不设防最危险):改名/子系统增删/核心决策推翻时,同一 commit 重审 AGENTS.md(品牌、死链、canonical 对账、入口卫生——docs-gate 固定扫)。实证教训:改名十天后入口标题仍挂旧名,正因为"没人想到去审根"。
整体一致性归属:机器门兜不住的灰色判断(哪份过期、两份冲突信哪份、补丁还是架构信号),显式归创始人 + 6c6g 文档/设计线(有状态主体),不再压在无状态主 agent 头上。
10.8 计划/设计档谱系:单指针 上级:(2026-07-02 立)
由来:配置控制面阶段一② plan 起草后,创始人问"这份计划在整个上线计划的哪个位置",答案要靠考古拼装——plan frontmatter 只指到直接设计档,执行序列 SoT 又没认领配置控制面这条线(中间实断),"阶段"一词还有三套坐标系撞车。此前个别 plan 自发写过
slice:/origin:/关联设计:等自由字段,各写各的、无人验证。根因:谱系信息一旦散落多处、靠手抄维护,必然腐烂。解药 = 谱系只写一条不重复的最小事实,其余全部推导。
- 约定:2026-07-03 起,新建
docs/plans/*-plan.md与docs/agent-specs/*-设计.md的 frontmatter 必带一行上级: <仓根相对路径>,指向它的直接上级文档(plan 的上级通常是设计档,设计档的上级通常是它所属执行线的计划/SoT);沿链 6 跳内必须到达某份canonical: true的 SoT(计划线顶端 = 16 周上线主计划 / 统一执行计划)。存量档不强制回填,带了就验(docs-gate G7)。 - 为什么是单指针:全链、树、"这条线推进到哪"都应是查询结果而非维护对象。上级改名、改阶段划分,下游零回填;不设人读面包屑字段(那是缓存,缓存必腐);不给
_index.md加逐叶登记义务(在飞板保持线级手工策展,索引永不因叶子计划膨胀)。 - 开工前的定位动作,不是事后标签:写新 plan 的第一步是给
上级:填一个真实存在的家。填不出来 = 上级还没认领这条线,先回写上级(如统一执行计划的相应切片/线)再开工——G7 把这个动作从自觉变成强制。 - 查询视图:
python3 .agents/tools/plan-tree.py打印全谱系树(带各档 status,--orphans列未挂谱系的存量档),回答"某档在哪 / 某线推进到哪"。 - 消歧红利:链上每级的名字来自上级文档本身,引用"阶段/切片"时自然带上坐标系(是 16 周主计划的阶段、还是某设计档的阶段),不再靠散文约定。