# 工程编码与协作硬规范 > 本文是绘境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`](security-and-reliability.md);元流程见 [`../workflows/ai-development-protocol.md`](../workflows/ai-development-protocol.md);事实蓝图见 [`../knowledge/product-and-architecture.md`](../knowledge/product-and-architecture.md);模块落地手册见 [`../skills/add-business-module.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`](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,而 MP `fill=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.json` eventRegistry 只是文档源,**`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 端 `AppXxxController` vs 后台 `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`](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`](security-and-reliability.md)。 --- ## 5. API 契约与版本管理 | 维度 | 规则 | |---|---| | URL 版本 | URL Path 版本 `/api/v1/...`,大版本不兼容才升 `v2` | | 兼容判定 | **新增字段不算 breaking**;删除/重命名字段 = 新版本 | | 并行期 | 新版本上线后,旧版本**保留 ≥ 3 个月**;废弃用 Header `Deprecation: true` + `Sunset: ` | | 服务间契约 | 每个模块 `-api` 包声明 **Feign 接口 + DTO**,消费方引用该包;API 变更必须在 PR 描述标注受影响的消费方 | | 契约对齐 | 后端**先写 `-api` 的 VO/DTO**,前端据此定义 TS 类型;联调前锁定契约(见 [`../skills/contract-first-development.md`](../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 本身外必须同时交付两件配套,三件缺一不可: 1. **机器可校验的 schema 载体**——契约以机器能解析、能拿去比对的形式表达(JSON Schema,或承担同等职责的等价物:OpenAPI schema、SDK 的 `.d.ts` 类型声明、Flyway 迁移的结构约束等),不能只有自然语言约定或一个示例文件。 2. **机器校验器**——一段对候选数据或实现跑出通过或不通过结论、退出码可被 CI 与 pre-commit 消费的程序。现成范本是 `contracts/prompts/check_registry.py`:registry 版本与 `.md` frontmatter 漂移时 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 面)——离线纪律随提交焊死,花钱的真模型闸按需触发。 3. **负样本进 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: 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`](../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`](../skills/wave-close-checklist.md) 第 8 步、活地图见 `docs/agent-specs/_index.md`。 ### 10.1 命名词表(封闭,禁新增后缀) 文件名 `YYYY-MM-DD--.md`,`` **仅限三类**: | 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//` 单独评估,不混入 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/` dated `review/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 `` + `_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-specs `review`)+ 一 HOW 喂料(plan,`docs/plans/` 或 agent-specs `execution`),轻量、收口蒸馏后留痕;**不再强制每任务产 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`](../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`](../../docs/architecture/README.md) §2 注册表双向一致(表里每行有对应文件、每个 canonical 文件在表里);`_archive/` 内不得标 canonical。红线:同 topic 第二份即挡(杀 doc-sprawl / 影子 SoT)。 - **G3 死链** — 活层 md 的 `[]()` 相对链接与反引号内 `docs/`、`.agents/` 开头的仓内路径必须存在;`git show :<路径>` 定位引用放行(这是引用已删历史档的标准姿势)。取代旧 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` 必带 frontmatter `topic` + `status` + `sot-impact`(新建 topic / 修订某 topic / 纯留痕)——强制"开工前查注册表",治"新设计漏读既有设计"的根因;配套流程见 [`feature-design-doc`](../skills/feature-design-doc.md)。 - **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运行时架构图说`](../../docs/architecture/架构/生成引擎/agentic运行时架构图说.md))。红线:断言失败 = 实现未兑现设计,挡收口。 **挂载(三层,缺一即是自愿门)**:① 本地 `.githooks/pre-commit`(激活一次:`git config core.hooksPath .githooks`;md 变更才触发;`--no-verify` 可紧急绕过)→ ② 服务端 `.gitea/workflows/docs-gate.yml`(Gitea Actions,runner 就绪即不可绕)→ ③ 流程 [`wave-close-checklist`](../skills/wave-close-checklist.md) 第 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 周主计划的阶段、还是某设计档的阶段),不再靠散文约定。