合并 master 到 Pingora 网关分支
保留 Pingora 网关发布与巡检能力 合入外部生成 worker 与 controller 运维链路 对齐 SpacetimeDB 2.5.0 依赖锁定 更新项目共享记忆与生产运维文档
This commit is contained in:
@@ -21,6 +21,8 @@
|
||||
|
||||
微信小程序虚拟支付接入、`wechat_mp_virtual` 渠道、`wx.requestVirtualPayment` 承接页和后端签名配置见 [【技术方案】微信虚拟支付接入-2026-05-26.md](./%E3%80%90%E6%8A%80%E6%9C%AF%E6%96%B9%E6%A1%88%E3%80%91%E5%BE%AE%E4%BF%A1%E8%99%9A%E6%8B%9F%E6%94%AF%E4%BB%98%E6%8E%A5%E5%85%A5-2026-05-26.md)。
|
||||
|
||||
本地通过 SSH alias 管理多台服务器、查看硬件 / systemd / HTTP 健康状态并执行受控服务启停的 egui 桌面工具见 [【开发运维】本地SSH服务器管理面板技术方案-2026-06-11.md](./technical/【开发运维】本地SSH服务器管理面板技术方案-2026-06-11.md)。
|
||||
|
||||
生产部署切换到 systemd + Nginx + SpacetimeDB 自托管的总方案见 [PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md](./technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md),该文档也是当前生产 Jenkinsfile 的唯一入口。Pingora 只作为独立二进制影子网关试点时,边界、路由口径与替换前验收见 [【开发运维】Pingora独立网关试点-2026-06-11.md](./technical/【开发运维】Pingora独立网关试点-2026-06-11.md)。SpacetimeDB 表结构变更、自动迁移边界和保留旧数据的分阶段迁移流程见 [SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md](./technical/SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md);private 表迁移 JSON 导入导出、HTTP 413 分片导入和旧数据库迁移流水线经验见 [SPACETIMEDB_JSON_STRING_MIGRATION_PROCEDURE_2026-04-27.md](./technical/SPACETIMEDB_JSON_STRING_MIGRATION_PROCEDURE_2026-04-27.md) 与 [JENKINS_SPACETIMEDB_DATABASE_MIGRATION_PIPELINES_2026-04-29.md](./technical/JENKINS_SPACETIMEDB_DATABASE_MIGRATION_PIPELINES_2026-04-29.md);后台管理独立前端工程技术方案见 [ADMIN_WEB_CONSOLE_TECHNICAL_SOLUTION_2026-04-30.md](./technical/ADMIN_WEB_CONSOLE_TECHNICAL_SOLUTION_2026-04-30.md)。
|
||||
|
||||
SpacetimeDB 表结构变更、自动迁移边界和保留旧数据的分阶段迁移流程见 [SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md](./technical/SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md)。
|
||||
@@ -83,6 +85,8 @@ RPG Agent 结果页发布门禁展示和预览来源 label 收口到 `src/compon
|
||||
|
||||
平台入口错误 / 完成弹窗的文案归一、来源格式、候选择一、dismiss key 与任务完成文案收口到 `src/components/platform-entry/platformDialogStateModel.ts`,规则见 [【前端架构】PlatformDialogStateModel收口计划-2026-06-03.md](./technical/【前端架构】PlatformDialogStateModel收口计划-2026-06-03.md)。
|
||||
|
||||
平台 UI Kit 的提示 / 确认弹窗收口到 `src/components/common/UnifiedConfirmDialog.tsx`,复制反馈收口到 `src/components/common/useCopyFeedback.ts`、`src/components/common/CopyFeedbackButton.tsx`、`src/components/common/CopyCodeButton.tsx` 与 `src/components/common/CopyFeedbackMessage.tsx`,基础状态提示收口到 `src/components/common/PlatformStatusMessage.tsx`,运行态短错误 / 成功 / 反馈 toast 收口到 `src/components/common/PlatformRuntimeStatusToast.tsx`,平台空态 / 轻量加载态收口到 `src/components/common/PlatformEmptyState.tsx`,平台动作按钮收口到 `src/components/common/PlatformActionButton.tsx`,平台白底子面板 / 小型列表卡片收口到 `src/components/common/PlatformSubpanel.tsx`,平台输入框 / 文本域收口到 `src/components/common/PlatformTextField.tsx`,平台字段标题收口到 `src/components/common/PlatformFieldLabel.tsx`,平台媒体预览框收口到 `src/components/common/PlatformMediaFrame.tsx`,平台胶囊状态标签收口到 `src/components/common/PlatformPillBadge.tsx`,平台图片全屏预览收口到 `src/components/common/PlatformImagePreviewModal.tsx`,平台 / 个人中心弹窗关闭按钮收口到 `src/components/common/PlatformModalCloseButton.tsx`,底层继续复用 `UnifiedModal`;普通提示、确认 / 取消、危险确认、复制状态机、短代码复制 chip、复制按钮表现、白底 / 个人中心 / 认证入口 token 状态条、运行态状态 toast、无操作空态、主动作按钮、白底子面板、白底交互列表卡片、普通输入字段、字段标题、图片源 / fallback / 固定比例媒体预览、全屏黑底图片查看、单个状态 / 标签 chip 和圆形关闭按钮优先使用公共 Module,规则见 [【前端架构】PlatformUiKit弹窗组件收口计划-2026-06-08.md](./technical/【前端架构】PlatformUiKit弹窗组件收口计划-2026-06-08.md)。
|
||||
|
||||
平台入口受保护数据失效后的 stage 去留判定,以及缺失草稿 / 作品 / run 时的阶段回退,收口到 `src/components/platform-entry/platformSelectionStageModel.ts`,壳层只执行缓存清空、布尔事实汇总和必要跳转,规则见 [【前端架构】PlatformSelectionStageModel收口计划-2026-06-04.md](./technical/【前端架构】PlatformSelectionStageModel收口计划-2026-06-04.md)。
|
||||
|
||||
小游戏 runtime client 的路径编码、JSON 请求、runtime guest auth 与 retry 选项收口到 `src/services/runtimeRequest.ts`,Match3D、SquareHole、Big Fish、Bark Battle、Puzzle 公开 / 推荐运行态请求、Jump Hop / Wooden Fish 正式 run 请求和 Visual Novel 局部 JSON runtime 请求已先迁移,规则见 [【前端架构】RuntimeClientFamily收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91RuntimeClientFamily%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。
|
||||
|
||||
@@ -9,5 +9,5 @@
|
||||
## 维护规则
|
||||
|
||||
- 计划文档只记录可执行阶段、负责人切分、验收门禁和当前状态。
|
||||
- 已经稳定为长期约定的内容,应同步沉淀到 `docs/【玩法创作】平台入口与玩法链路-2026-05-15.md` 或 `.hermes/shared-memory/`。
|
||||
- 已经稳定为长期约定的内容,应同步沉淀到 `docs/【玩法创作】平台入口与玩法链路-2026-05-15.md` 或 `docs/project-memory/shared-memory/`。
|
||||
- 若代码事实与计划冲突,以代码和当前融合文档为准,并回写更新本目录。
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
| 阶段 | 状态 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| Phase 0 总计划与门禁 | 已完成 | 本文档、`docs/planning/README.md`、`docs/README.md` 和 `.hermes/shared-memory/document-map.md` 已补齐入口;后续按 phase 扩展门禁。 |
|
||||
| Phase 0 总计划与门禁 | 已完成 | 本文档、`docs/planning/README.md`、`docs/README.md` 和 `docs/project-memory/shared-memory/document-map.md` 已补齐入口;后续按 phase 扩展门禁。 |
|
||||
| Phase 1 首批统一壳 | 已收口 | `puzzle`、`match3d`、`jump-hop`、`wooden-fish` 已接入 `UnifiedCreationPage` / `UnifiedGenerationPage`,竖屏滚动和字段契约已回归。 |
|
||||
| Phase 1 补充统一壳 | 已收口 | `jump-hop` 也已接入 `UnifiedCreationPage` / `UnifiedGenerationPage`,统一创作页现在接管拼图、抓大鹅、跳一跳和敲木鱼四条入口的可见外壳与滚动。 |
|
||||
| Phase 2 契约与配置治理 | 已完成 | `creationTypes[].unifiedCreationSpec`、前端 fallback、后台配置校验和文档门禁已按现有测试与 schema 检查收口。 |
|
||||
@@ -60,7 +60,7 @@
|
||||
|
||||
状态:已完成。
|
||||
|
||||
- 新增本计划文档和 `docs/planning/README.md`,并在 `docs/README.md`、`.hermes/shared-memory/document-map.md` 中补上规划入口。
|
||||
- 新增本计划文档和 `docs/planning/README.md`,并在 `docs/README.md`、`docs/project-memory/shared-memory/document-map.md` 中补上规划入口。
|
||||
- 补齐 `当前进度`、`执行轮次` 和可并行任务表,后续每个 phase 完成后更新本文档的状态、验收命令和风险。
|
||||
|
||||
退出条件:
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
# 项目记忆目录
|
||||
|
||||
本目录保存可以进入 Git 的项目级长期知识,供开发者和 Agent 读取。`.hermes/` 只保留 Hermes 工具专用资源,不再作为项目知识库。
|
||||
|
||||
## 目录结构
|
||||
|
||||
```text
|
||||
docs/project-memory/
|
||||
├─ README.md
|
||||
├─ shared-memory/
|
||||
│ ├─ project-overview.md
|
||||
│ ├─ team-conventions.md
|
||||
│ ├─ development-workflow.md
|
||||
│ ├─ document-map.md
|
||||
│ ├─ decision-log.md
|
||||
│ ├─ pitfalls.md
|
||||
│ └─ handoff-template.md
|
||||
├─ plans/
|
||||
└─ todos/
|
||||
```
|
||||
|
||||
## 使用原则
|
||||
|
||||
- 开发前先读 `AGENTS.md`,再按任务读取 `docs/project-memory/shared-memory/` 和当前 `docs/` 文档。
|
||||
- 长期有效的架构约定、接口变化、排障经验、开发流程和协作规则写入 `shared-memory/`。
|
||||
- 阶段性计划写入 `plans/`,已确定但暂未实施的共享 TODO 写入 `todos/`。
|
||||
- 如果本目录内容与代码或最新 `docs/` 冲突,以代码和最新 `docs/` 为准,并同步修正过期记忆。
|
||||
- 禁止写入个人配置、API Key、Token、Cookie、会话记录、认证文件、本地私密路径、构建产物、日志、缓存和数据库 dump。
|
||||
|
||||
## RAG 索引
|
||||
|
||||
本目录是 Agent 本地 RAG 的高权重索引源。RAG 主要用于 Agent 检索上下文,不替代人工阅读入口或正式文档地图。索引脚本位于 `scripts/rag/`,本地生成的 `.rag/` 数据不提交 Git。
|
||||
@@ -0,0 +1,399 @@
|
||||
# Genarrative(陶泥儿)项目架构优化计划书
|
||||
|
||||
**文档版本**:v1.0
|
||||
**编制日期**:2026-05-26
|
||||
**项目名称**:Genarrative(陶泥儿)
|
||||
**文档密级**:内部
|
||||
|
||||
---
|
||||
|
||||
## 一、项目概况
|
||||
|
||||
| 维度 | 详情 |
|
||||
|---|---|
|
||||
| **项目名** | Genarrative(陶泥儿) |
|
||||
| **项目定位** | AI Native 互动视觉 RPG 平台——支持多种玩法模板的 AI 创作、运行与分享("玩法类型平台") |
|
||||
| **核心玩法** | 拼图、视觉小说、Match3D、Bark Battle、Big Fish、Jump-Hop、Square Hole、木鱼、教娱等 10+ 种玩法模板 |
|
||||
| **代码规模** | 前端 ~823 个 TS/TSX 文件,后端 ~1532 个 Rust 文件,属于大型项目 |
|
||||
|
||||
### 1.1 计划目的
|
||||
|
||||
本计划书基于对 Genarrative 项目当前架构的全面分析,识别架构层面的关键问题,并提出分阶段、可落地的优化方案。旨在:
|
||||
|
||||
- 统一前端架构模式,降低团队认知成本和新人上手门槛
|
||||
- 提升模块内聚性,减少不必要的耦合与依赖
|
||||
- 建立自动化契约保障机制,降低跨语言同步出错风险
|
||||
- 优化工程基础设施,提高开发效率和运维可观测性
|
||||
|
||||
---
|
||||
|
||||
## 二、技术栈总览
|
||||
|
||||
| 层级 | 技术选型 | 版本 |
|
||||
|---|---|---|
|
||||
| **前端框架** | React + TypeScript + Vite | React 19 / TS 5.8 / Vite 6 |
|
||||
| **样式** | TailwindCSS | v4 |
|
||||
| **3D / 动画** | Three.js、Motion、cannon-es | - |
|
||||
| **后端 HTTP** | Rust + Axum(BFF 门面) | Axum 0.8 |
|
||||
| **游戏状态 DB** | SpacetimeDB(实时反应式数据库) | v2.2 |
|
||||
| **AI / LLM** | LangChain-Rust + LLM Proxy | - |
|
||||
| **小程序** | 微信小程序(含微信支付) | - |
|
||||
| **容器化** | Docker Compose(Nginx + API Server + OTel Collector) | - |
|
||||
| **运维** | systemd、Nginx、Jenkins CI/CD、k6 压测 | - |
|
||||
| **可观测性** | OpenTelemetry(OTLP → Grafana) | - |
|
||||
|
||||
---
|
||||
|
||||
## 三、当前架构分层图
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ 入口层 │
|
||||
│ index.html → main.tsx → resolveAppRoute() → RouteComponent │
|
||||
│ (多入口路由:平台主页 / 拼图 / BigFish / Match3D / ...) │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ 前端应用层 (src/) │
|
||||
│ ┌─────────────┐ ┌──────────────┐ ┌──────────────────────┐│
|
||||
│ │ components/ │ │ services/ │ │ games/ ││
|
||||
│ │ *-creation │ │ *-creation │ │ bark-battle/ ││
|
||||
│ │ *-result │ │ *-runtime │ │ domain/ ││
|
||||
│ │ *-runtime │ │ *-works │ │ application/ ││
|
||||
│ │ common/ │ │ storyEngine │ │ infrastructure/ ││
|
||||
│ │ auth/ │ │ payment │ │ ui/ ││
|
||||
│ └─────────────┘ └──────────────┘ └──────────────────────┘│
|
||||
│ ┌─────────────┐ ┌──────────────┐ ┌──────────────────────┐│
|
||||
│ │ hooks/ │ │ data/ │ │ routing/ ││
|
||||
│ │ persistence/│ │ functionCat. │ │ config/ editor/ ││
|
||||
│ └─────────────┘ └──────────────┘ └──────────────────────┘│
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
│ Vite Proxy (/api/*)
|
||||
▼
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ Rust 后端 (server-rs/) │
|
||||
│ ┌──────────────────────────────────────────────────────┐ │
|
||||
│ │ api-server (Axum HTTP / SSE / BFF 门面) │ │
|
||||
│ └──────────────────────────────────────────────────────┘ │
|
||||
│ │ │ │ │
|
||||
│ ▼ ▼ ▼ │
|
||||
│ ┌──────────┐ ┌──────────────────┐ ┌──────────────┐ │
|
||||
│ │ platform │ │ module-* │ │ shared │ │
|
||||
│ │ -auth │ │ -puzzle │ │ -contracts │ │
|
||||
│ │ -llm │ │ -visual-novel │ │ -kernel │ │
|
||||
│ │ -image │ │ -match3d │ │ -logging │ │
|
||||
│ │ -oss │ │ -bark-battle │ └──────────────┘ │
|
||||
│ │ -speech │ │ -big-fish ... │ │
|
||||
│ │ -agent │ │ -runtime │ │
|
||||
│ └──────────┘ │ -combat/npc │ │
|
||||
│ └──────────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌──────────────────────────────────────────────────────┐ │
|
||||
│ │ spacetime-module + spacetime-client → SpacetimeDB │ │
|
||||
│ └──────────────────────────────────────────────────────┘ │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ 辅助子系统 │
|
||||
│ apps/admin-web (React 后台管理) │
|
||||
│ miniprogram/ (微信小程序) │
|
||||
│ packages/shared (前后端共享契约/LLM工具) │
|
||||
│ deploy/ (Docker/Nginx/systemd/OTel) │
|
||||
│ scripts/ (40+ 构建/部署/检查脚本) │
|
||||
│ jenkins/ (CI/CD Pipeline) │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、架构亮点
|
||||
|
||||
1. **清晰的"玩法模板"平台模式**
|
||||
每种玩法遵循统一的 `creation → result → runtime` 三段式生命周期,前端 `components/` 和 `services/` 均按此模式组织。新增玩法可快速套用模板,极大降低了横向扩展成本。
|
||||
|
||||
2. **后端严格的分层约束**
|
||||
`api-server`(门面)→ `module-*`(领域)→ `spacetime-module`(持久化),`module-*` 不直接依赖 Axum、HTTP、SpacetimeDB table、LLM、文件系统,保证了领域纯净性和可测试性。
|
||||
|
||||
3. **SpacetimeDB 作为游戏状态核心**
|
||||
采用反应式实时数据库替代传统 Redis + PostgreSQL 组合,天然适合多人实时游戏状态同步,减少了中间层复杂度和延迟。
|
||||
|
||||
4. **前端 DDD 探索**
|
||||
`src/games/bark-battle/` 采用 `domain / application / infrastructure / ui` 四层 DDD 结构,为复杂玩法的前端架构提供了良好范本。
|
||||
|
||||
5. **完善的多入口路由体系**
|
||||
通过 `resolveAppRoute()` 按 URL path 分发到不同的 lazy-loaded 组件,实现了按需加载和良好的首屏性能。
|
||||
|
||||
6. **运维体系完备**
|
||||
Docker Compose + systemd + Nginx + OpenTelemetry + k6 压测 + Jenkins CI/CD,覆盖了构建、部署、监控、压测全链路。
|
||||
|
||||
---
|
||||
|
||||
## 五、问题诊断与改进方案
|
||||
|
||||
### 问题 1:前端 services/ 与 components/ 的耦合不统一
|
||||
|
||||
**现状**
|
||||
大部分玩法遵循 `services/` + `components/` 分离模式,但部分玩法运行时直接放在 `components/` 下,services 层职责模糊——有的承担了业务逻辑编排,有的仅作简单 API 调用。
|
||||
|
||||
**影响**
|
||||
- 新人阅读代码时无法预判某个逻辑应位于哪个目录
|
||||
- 单元测试困难:services 与组件耦合的业务逻辑无法独立测试
|
||||
- 跨玩法复用时需要额外的迁移成本
|
||||
|
||||
**改进方案**
|
||||
1. 制定规范:`services/` 仅负责纯 API 调用和数据转换,不包含业务判断逻辑
|
||||
2. 业务逻辑统一归到 `hooks/` 或 `games/<play-type>/application/`
|
||||
3. 以 puzzle 玩法为样板,先行重构并形成迁移指南,再推广至其余玩法
|
||||
4. 在 CI 中增加 ESLint 规则,禁止 `components/` 直接 import `services/` 之外的外部模块
|
||||
|
||||
**预期收益**
|
||||
- 职责边界清晰,降低认知成本约 30%
|
||||
- services 层可独立单测覆盖率达到 80%+
|
||||
- 新玩法开发上手时间从 2 天缩短至 0.5 天
|
||||
|
||||
---
|
||||
|
||||
### 问题 2:前端 DDD 与模板模式并存,架构不统一
|
||||
|
||||
**现状**
|
||||
`bark-battle` 采用 DDD 四层结构(`domain/application/infrastructure/ui`),其余玩法散落在 `components/` + `services/` 下,存在两种截然不同的组织范式。
|
||||
|
||||
**影响**
|
||||
- 团队内部对"正确"的代码组织方式缺乏共识
|
||||
- 代码审查时需要切换判断标准
|
||||
- DDD 玩法的优势无法在全局范围内发挥
|
||||
|
||||
**改进方案**
|
||||
1. 所有玩法统一迁移到 `src/games/<play-type>/` 下,采用 `domain / application / ui` 三层结构(infrastructure 按需保留)
|
||||
2. 以 puzzle 为样板完成首例迁移,产出迁移 Checklist 和模板生成脚本
|
||||
3. 新增玩法脚手架直接生成 DDD 结构目录
|
||||
4. 旧玩法分批次迁移,每批次 2-3 个玩法,在 4 个迭代内完成
|
||||
|
||||
**预期收益**
|
||||
- 架构一致性提升至 100%
|
||||
- 跨玩法逻辑复用变得可能(domain 层可共享)
|
||||
- 为后续 monorepo 改造打下基础
|
||||
|
||||
---
|
||||
|
||||
### 问题 3:后端 module-* 粒度偏细,存在潜在循环依赖风险
|
||||
|
||||
**现状**
|
||||
后端共 35 个 crate。`module-runtime` 被拆分为 3 个独立 crate,`module-combat / npc / inventory` 各自独立。部分紧密协作的模块之间可能存在隐式耦合。
|
||||
|
||||
**影响**
|
||||
- 编译时间增长(35 个 crate 独立编译)
|
||||
- 跨 crate 重构时需要同时修改多处
|
||||
- 循环依赖风险增加,可能在特定组合下触发编译失败
|
||||
|
||||
**改进方案**
|
||||
1. 评估合并方案:
|
||||
- `module-runtime-*` 系列合并为单 crate `module-runtime`,内部用 `mod` 做逻辑隔离
|
||||
- `module-combat / npc / inventory` 评估合并为 `module-combat`,npc 和 inventory 作为子模块
|
||||
2. 保留 trait/interface 抽象层,确保 module 之间不直接依赖具体实现
|
||||
3. 在 CI 中引入 `cargo-deny` 或自定义脚本,自动检测 module 间的依赖方向是否违反分层约束
|
||||
4. 目标:将 35 个 crate 精简至 25 个以内
|
||||
|
||||
**预期收益**
|
||||
- 全量编译时间预计缩短 15%-20%
|
||||
- 循环依赖风险归零
|
||||
- module 内部重构成本降低
|
||||
|
||||
---
|
||||
|
||||
### 问题 4:根目录 env 文件过多且混乱
|
||||
|
||||
**现状**
|
||||
根目录存在 4 个 env 文件(`.env`、`.env.example`、`.env.production` 等),且 `deploy/` 下另有多个 env 文件。配置分散在多处,部分文件之间字段不一致。
|
||||
|
||||
**影响**
|
||||
- 排查配置问题时需要翻阅多个文件
|
||||
- 新人无法快速确定本地开发需要哪些环境变量
|
||||
- 部署时可能遗漏或错误覆盖某项配置
|
||||
|
||||
**改进方案**
|
||||
1. 收敛为三层配置体系:
|
||||
- `.env.example`:包含所有可配置项的说明和默认值(唯一提交到仓库的 env 文件)
|
||||
- `.env.local`:本地开发覆盖(加入 .gitignore)
|
||||
- `deploy/env/<env-name>.env`:各部署环境专用配置
|
||||
2. 引入 config crate,支持层次覆盖(default → local → env-specific),启动时自动校验必填字段
|
||||
3. 在 CI 中加入 env 校验步骤:对比 `.env.example` 与部署环境的 env 文件,标记缺失或多余字段
|
||||
|
||||
**预期收益**
|
||||
- 配置查找时间从分钟级降至秒级
|
||||
- 部署配置遗漏导致的线上事故减少 90%+
|
||||
- 新成员本地环境搭建时间从 30 分钟缩短至 10 分钟
|
||||
|
||||
---
|
||||
|
||||
### 问题 5:scripts/ 目录膨胀为"万能工具箱"
|
||||
|
||||
**现状**
|
||||
`scripts/` 目录共 42 个文件,涵盖构建、部署、检查、迁移、生成、压测等,全部平铺在同一层级,缺乏分类。
|
||||
|
||||
**影响**
|
||||
- 难以快速定位所需脚本
|
||||
- 同类脚本缺乏命名规范
|
||||
- 新增脚本时不知道放在何处
|
||||
|
||||
**改进方案**
|
||||
1. 按功能域分类重组:
|
||||
|
||||
```
|
||||
scripts/
|
||||
├── build/ # 构建相关(vite、cargo、wasm 等)
|
||||
├── deploy/ # 部署相关(docker、systemd、rsync)
|
||||
├── check/ # 检查/校验(lint、format、type-check)
|
||||
├── spacetime/ # SpacetimeDB 相关(migration、seed)
|
||||
├── generate/ # 代码生成(scaffold、proto、types)
|
||||
└── loadtest/ # 压测脚本(k6 配置及辅助)
|
||||
```
|
||||
|
||||
2. 为每个子目录添加 README.md,说明各脚本用途和调用方式
|
||||
3. 将重复逻辑抽取为共享函数库
|
||||
|
||||
**预期收益**
|
||||
- 脚本查找效率提升 60%+
|
||||
- 降低脚本重复概率
|
||||
- 便于 CI Pipeline 直接引用标准化路径
|
||||
|
||||
---
|
||||
|
||||
### 问题 6:前端路由系统缺乏统一的"玩法注册"机制
|
||||
|
||||
**现状**
|
||||
`appRoutes.tsx` 中硬编码 `switch-case` 逻辑,每新增一种玩法需手动修改路由文件、入口组件、资源加载等多个位置。
|
||||
|
||||
**影响**
|
||||
- 新增玩法的接入点分散,容易遗漏
|
||||
- 路由文件随玩法增多持续膨胀
|
||||
- 无法实现"按需注册"——即使某环境不包含某玩法,路由代码仍然存在
|
||||
|
||||
**改进方案**
|
||||
1. 建立 `PlayTypeRegistry` 模式:每个玩法导出一个注册项对象,包含 `path`、`lazyComponent`、`preload` 等字段
|
||||
2. `resolveAppRoute()` 改为动态聚合所有注册项,替代硬编码 switch-case
|
||||
3. 支持环境级玩法开关:通过配置控制某环境启用哪些玩法,路由系统自动忽略未启用的
|
||||
|
||||
**预期收益**
|
||||
- 新增玩法零侵入路由系统,只需在玩法目录内添加注册文件
|
||||
- 路由文件体积与玩法数量解耦
|
||||
- 灰度发布和 A/B 测试变得可能
|
||||
|
||||
---
|
||||
|
||||
### 问题 7:前端缺少统一的状态管理层
|
||||
|
||||
**现状**
|
||||
前端状态管理依赖 React hooks + props drilling + services 层手动管理。未使用任何状态管理库(如 Zustand、Jotai、Redux)。
|
||||
|
||||
**影响**
|
||||
- 全局状态(用户认证、会话、通知)通过多层 props 传递,组件耦合度高
|
||||
- 跨页面状态无法优雅共享
|
||||
- 状态变更难以追踪和调试
|
||||
|
||||
**改进方案**
|
||||
1. 引入 Zustand(轻量、无 boilerplate、TS 友好)管理全局状态:
|
||||
- `useAuthStore`:认证状态
|
||||
- `useSessionStore`:当前会话/游戏状态
|
||||
- `useNotificationStore`:全局通知
|
||||
2. 玩法内状态继续使用 React hooks + `useReducer`,保持局部自治
|
||||
3. 全局 store 与玩法内 state 通过事件总线松耦合通信
|
||||
|
||||
**预期收益**
|
||||
- props drilling 层级从 5+ 层降至 1-2 层
|
||||
- 全局状态可追溯,支持 Redux DevTools 调试
|
||||
- 跨玩法状态共享(如用户余额、道具)变得自然
|
||||
|
||||
---
|
||||
|
||||
### 问题 8:shared-contracts 的实际复用程度待验证
|
||||
|
||||
**现状**
|
||||
前后端通过 `packages/shared` 共享 DTO 类型定义,但依赖手动同步 TypeScript 类型。可能存在前后端契约不一致但编译期无法检出的情况。
|
||||
|
||||
**影响**
|
||||
- 后端修改 DTO 字段后,前端可能遗漏更新导致运行时错误
|
||||
- 手动同步耗时且易出错
|
||||
- Code Review 时难以判断契约一致性
|
||||
|
||||
**改进方案**
|
||||
1. 引入 `ts-rs`:从 Rust 结构体自动生成 TypeScript 类型定义
|
||||
2. 将生成步骤集成到 CI Pipeline:
|
||||
- 每次 Rust PR 触发 `ts-rs` 重新生成 TS 类型
|
||||
- 对比生成的类型与仓库中的类型是否一致,不一致则 CI 失败
|
||||
3. 长期考虑引入 Protobuf / OpenAPI 作为跨语言契约的单一事实来源
|
||||
|
||||
**预期收益**
|
||||
- 前后端契约不一致导致的线上 bug 减少 95%+
|
||||
- 手动同步工作量归零
|
||||
- PR Review 时契约一致性问题自动拦截
|
||||
|
||||
---
|
||||
|
||||
## 六、改进优先级路线图
|
||||
|
||||
| 优先级 | 改进项 | 涉及层 | 建议时间 | 预期收益 |
|
||||
|---|---|---|---|---|
|
||||
| **P0** | 统一前端 services/hooks/components 职责边界 | 前端 | 第 1-2 周 | 降低认知成本,提升可测试性 |
|
||||
| **P1** | 建立 PlayType 注册机制 | 前端 | 第 2-3 周 | 新增玩法零侵入路由 |
|
||||
| **P1** | 评估 module-* 合并方案并执行 | 后端 | 第 3-4 周 | 减少编译时间 15%-20% |
|
||||
| **P1** | 引入 Zustand 全局状态管理 | 前端 | 第 4-5 周 | 改善状态追踪与跨组件共享 |
|
||||
| **P2** | 清理 scripts/ 目录结构 | 工程 | 第 5-6 周 | 提高可发现性 |
|
||||
| **P2** | 前端玩法统一迁移至 DDD 结构 | 前端 | 第 5-8 周 | 架构一致性 100% |
|
||||
| **P2** | env 配置收敛 | 工程 | 第 6-8 周 | 减少部署配置事故 |
|
||||
| **P3** | 前后端共享 DTO 自动化(ts-rs) | 全栈 | 第 6-8 周 | 消除契约不一致风险 |
|
||||
| **P3** | CI 分层约束检查(cargo-deny) | 后端 | 第 8-10 周 | 循环依赖归零 |
|
||||
|
||||
> **说明**:P0 为阻塞项,必须最先完成。P1 项可部分并行推进(PlayType 注册与 module 合并互不依赖)。P2/P3 为优化项,可在日常迭代中穿插推进。
|
||||
|
||||
---
|
||||
|
||||
## 七、架构健康度评分卡
|
||||
|
||||
| 维度 | 评分 | 当前状态 | 目标状态 |
|
||||
|---|---|---|---|
|
||||
| **分层清晰度** | ★★★★☆ | 后端分层严格,前端分层存在不一致 | ★★★★★ 前后端均严格分层 |
|
||||
| **模块化程度** | ★★★★☆ | 后端 35 crate 粒度偏细,前端结构化较好 | ★★★★☆ 后端精简至 25 crate |
|
||||
| **可扩展性** | ★★★★★ | 玩法模板模式使新增玩法成本低 | ★★★★★ 维持 |
|
||||
| **代码复用** | ★★★☆☆ | shared 层作用有限,services 层有重复 | ★★★★☆ DDD 统一后 domain 可复用 |
|
||||
| **DevOps 成熟度** | ★★★★★ | Docker + k6 + OTel + Jenkins 覆盖完整 | ★★★★★ 维持 |
|
||||
| **文档完备性** | ★★★★★ | docs/ 分类清晰,基线文档齐全 | ★★★★★ 维持 |
|
||||
| **技术债务管控** | ★★★★☆ | 有明确的"历史残留"标记和废弃策略 | ★★★★★ 增加自动化检测 |
|
||||
|
||||
**综合评级:A-(优秀,存在可优化空间)**
|
||||
|
||||
---
|
||||
|
||||
## 八、附录:代码规模统计
|
||||
|
||||
| 维度 | 数量 |
|
||||
|---|---|
|
||||
| **前端 TypeScript/TSX 文件** | ~823 个 |
|
||||
| **后端 Rust 源文件** | ~1532 个 |
|
||||
| **后端 Crate 数量** | 35 个 |
|
||||
| **核心玩法类型** | 10+ 种 |
|
||||
| **scripts/ 脚本数量** | 42 个 |
|
||||
| **根目录 env 文件** | 4 个 + deploy 下多个 |
|
||||
|
||||
### 模块规模明细(后端)
|
||||
|
||||
| Crate | 职责 | 建议 |
|
||||
|---|---|---|
|
||||
| `api-server` | Axum HTTP 门面,路由聚合 | 保持 |
|
||||
| `platform-*` (auth/llm/image/oss/speech/agent) | 平台级跨玩法能力 | 保持 |
|
||||
| `module-puzzle` | 拼图玩法 | 作为 DDD 迁移样板 |
|
||||
| `module-visual-novel` | 视觉小说 | 后续迁移 |
|
||||
| `module-match3d` | Match3D 三消 | 后续迁移 |
|
||||
| `module-bark-battle` | 犬吠对战 | 已对接前端 DDD |
|
||||
| `module-big-fish` | Big Fish | 后续迁移 |
|
||||
| `module-runtime*` (3 crates) | 通用运行时 | **建议合并为单 crate** |
|
||||
| `module-combat / npc / inventory` | 战斗系统 | **建议合并** |
|
||||
| `spacetime-module` + `spacetime-client` | SpacetimeDB 接入 | 保持 |
|
||||
| `shared-contracts / kernel / logging` | 共享基础设施 | 保持 |
|
||||
|
||||
---
|
||||
|
||||
> **文档结束**
|
||||
> 本计划书由 Genarrative 架构分析报告衍生,所有改进项均基于对当前项目代码库的实际分析。执行过程中如遇阻力或新发现,应及时更新本计划书并同步相关方。
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,301 @@
|
||||
# 开发工作流
|
||||
|
||||
> 用途:给本地 Agent 和开发人员提供统一的开发、测试、提交流程。具体命令以 `package.json`、`server-rs/Cargo.toml`、`AGENTS.md` 和相关 `docs/` 最新文档为准。
|
||||
|
||||
## 标准任务流程
|
||||
|
||||
```text
|
||||
同步代码 → 读取 AGENTS.md → 读取 docs/project-memory/shared-memory → 查找/完善 docs → 制定计划 → 小步实现 → 本地验证 → 更新文档/记忆 → 提交
|
||||
```
|
||||
|
||||
## 建议启动方式
|
||||
|
||||
在项目根目录启动本地 Agent:
|
||||
|
||||
```bash
|
||||
cd /path/to/Genarrative
|
||||
hermes
|
||||
```
|
||||
|
||||
在本机当前常见路径为:
|
||||
|
||||
```bash
|
||||
/home/dsk/workspace/Genarrative
|
||||
```
|
||||
|
||||
其他开发者以自己本地实际路径为准,不要把个人绝对路径写入共享文档作为通用规则。
|
||||
|
||||
## 开发前检查清单
|
||||
|
||||
- [ ] 当前分支是否正确
|
||||
- [ ] 是否已拉取最新代码
|
||||
- [ ] 是否阅读 `AGENTS.md`
|
||||
- [ ] 是否阅读 `docs/project-memory/shared-memory/` 相关文件
|
||||
- [ ] 是否阅读 `README.md` 中的运行和检查命令
|
||||
- [ ] 是否阅读 `docs/README.md` 及任务相关分类 README
|
||||
- [ ] 是否存在足够具体的 PRD / 设计 / 技术文档
|
||||
- [ ] 是否明确测试、验收和文档更新方式
|
||||
|
||||
## 本地运行命令
|
||||
|
||||
安装依赖:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
完整联调开发环境:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Linux 多用户共享同一台机器开发时,本地 dev 脚本会为当前 Linux 用户分配一个固定端口段并写入系统级注册表 `/var/tmp/genarrative-dev-port-ranges/registry.json`,自动分配从 `10000-10099` 开始,每段 100 个端口,四个 dev 服务依次使用 `start` 到 `start + 3`。可用 `GENARRATIVE_DEV_PORT_RANGE` 或 `npm run dev -- --port-range` 手动指定端口段用于特殊场景;注册表会阻止不同用户使用相同或重叠段,并让同一用户后续启动继续复用自己已占用的固定段。该机制只在 Linux 生效,Windows 仍沿用原有端口探测与漂移逻辑。
|
||||
|
||||
本地 `npm run dev`、`npm run dev:spacetime` 和 `npm run dev:api-server` 会在 Rust 子进程环境中绕过项目默认 `sccache` wrapper,避免损坏的本机 cache daemon 阻断 `spacetime publish` 或 `api-server` 启动;显式设置的非 sccache 自定义 wrapper 会被保留。生产 / Jenkins 构建仍按流水线自身的 sccache 策略执行。
|
||||
|
||||
该命令会启动:
|
||||
|
||||
- SpacetimeDB standalone
|
||||
- Rust `api-server`
|
||||
- 主站 Vite
|
||||
- 后台 Vite
|
||||
|
||||
`npm run dev` 和单模块 `dev:*` 命令会更新根目录 `.app/dev-stack.json`,记录四个本地服务的 pid、端口、URL、启动状态和当前命令。该目录只作本机运行态观测,不提交 Git。
|
||||
|
||||
开启自动刷新:
|
||||
|
||||
```bash
|
||||
npm run dev -- --watch
|
||||
```
|
||||
|
||||
watch 模式只由外层调度器自动处理后端侧刷新:`spacetime-module` 改动后重新发布模块但不重启 standalone 宿主,`api-server` 改动后重启 Rust 进程。主站 Vite 与后台 Vite 的源码变化交给 Vite 自身 HMR,避免外层 watcher 监听到依赖缓存或临时文件后循环重启。
|
||||
|
||||
非 watch 模式下,`npm run dev` 终端支持输入 `rs spacetime`、`rs api-server`、`rs web`、`rs admin-web` 或 `rs all`。其中 `rs spacetime` 只会重新发布 `spacetime-module`,不会重启 standalone 宿主;其他模块仍按进程重启。
|
||||
|
||||
单独启动 SpacetimeDB:
|
||||
|
||||
```bash
|
||||
npm run dev:spacetime
|
||||
```
|
||||
|
||||
单独启动 Rust API server:
|
||||
|
||||
```bash
|
||||
npm run dev:api-server
|
||||
```
|
||||
|
||||
单独启动前端:
|
||||
|
||||
```bash
|
||||
npm run dev:web
|
||||
```
|
||||
|
||||
单独启动后台管理前端:
|
||||
|
||||
```bash
|
||||
npm run dev:admin-web
|
||||
```
|
||||
|
||||
本地 SSH 服务器管理面板:
|
||||
|
||||
```bash
|
||||
npm run server-manager:panel
|
||||
```
|
||||
|
||||
该命令启动 `server-rs/crates/server-manager-panel` 的 egui 桌面工具,从本机 `~/.ssh/config` 读取可用 `Host` alias,支持多服务器健康巡检、可折叠侧边栏和受控 systemd 服务启停。服务操作通过远端 `sudo -n systemctl start|stop|restart <unit>` 执行,目标服务器需要提前配置对应 unit 的免交互 sudo 权限。
|
||||
面板启动时会自动注入本机中文字体;如开发机中文仍显示为方块,可设置 `GENARRATIVE_SERVER_PANEL_CJK_FONT=/path/to/font.ttc|index` 指向本机 CJK 字体。
|
||||
|
||||
`npm run dev:api-server` 会保留终端实时输出,并把同一份输出持久化到 `logs/api-server/api-server-<timestamp>.log`。完整联调入口 `npm run dev` 启动的 Rust `api-server` 使用同一套日志规则。如需改写路径,可设置 `GENARRATIVE_API_SERVER_LOG_FILE`;如只改目录,可设置 `GENARRATIVE_API_SERVER_LOG_DIR`。
|
||||
|
||||
开发态 `npm run dev` / `npm run dev:api-server` 默认打开 `GENARRATIVE_DEV_PASSWORD_ENTRY_AUTO_REGISTER_ENABLED=true`,密码入口可以直接注册未知手机号账号;生产默认仍关闭该开关。
|
||||
|
||||
生产 `Genarrative-Stdb-Module-Publish` 的备份默认使用 `DATABASE_BACKUP_MODE=async`:流水线在 publish 前先生成本地冷备份,随后继续 publish,并把同一份发布前备份交给后台 Node 进程上传 OSS,避免低带宽 OSS 上传长时间占住部署窗口。需要强制在 publish 前等待打包和上传并让失败阻断发布时,手动选择 `DATABASE_BACKUP_MODE=sync`;已有其他备份窗口且明确接受风险时才选择 `skip`。
|
||||
|
||||
生产 API / Web / Stdb 发布流水线不在目标机器 checkout Git。对应 Build 流水线必须把发布产物、校验文件、`release-manifest.json` 和部署 / 发布脚本一起归档;Deploy / Publish 流水线只通过 `copyArtifacts` 复制上游构建归档并执行随产物归档的脚本,避免目标机器 Git 访问和产物 commit 与部署脚本 commit 漂移。
|
||||
|
||||
查看本地 Rust/SpacetimeDB 日志:
|
||||
|
||||
```bash
|
||||
npm run dev:spacetime:logs
|
||||
```
|
||||
|
||||
本机隔离验证外部生成 worker 队列、API-only 更新和 worker 动态扩缩容时,优先使用:
|
||||
|
||||
```bash
|
||||
npm run container:worker-smoke -- smoke
|
||||
```
|
||||
|
||||
该命令生成 `deploy/container/worker-smoke/` 下的 gitignored env 与端口 state,启动独立 compose project 和独立 SpacetimeDB,用 unsupported job 验证 worker claim / fail 回写;排查时用 `api-update` 确认 API 重建不触碰 worker,用 `scale <n>` 调整 worker 数量。
|
||||
`external_generation_job` 是 private table,worker-smoke 通过 worker 日志里的 job_id 和 unsupported 记录确认消费,不通过 CLI SQL 查询队列表。
|
||||
worker-smoke 默认把本机 `spacetime` CLI 打成轻量 SpacetimeDB 镜像,避免首次 smoke 依赖官方大镜像下载。若容器内 Cargo 下载依赖不稳定,追加 `--local-binary`,让容器内 Cargo 复用本机 Cargo 缓存构建当前 `api-server` 二进制,并把产物放进 Debian bookworm smoke runtime;可用 `GENARRATIVE_WORKER_SMOKE_LOCAL_BASE_IMAGE` 覆盖运行时基础镜像;隔离端口或库数据需要重建时追加 `--force`。
|
||||
|
||||
后台管理前端:
|
||||
|
||||
```bash
|
||||
npm run admin-web:build
|
||||
npm run admin-web:typecheck
|
||||
```
|
||||
|
||||
SpacetimeDB bindings 生成:
|
||||
|
||||
```bash
|
||||
npm run spacetime:generate
|
||||
```
|
||||
|
||||
CodeGraph 本地语义索引:
|
||||
|
||||
```bash
|
||||
npm run codegraph:init
|
||||
npm run codegraph:status
|
||||
npm run codegraph:sync
|
||||
npm run codegraph:index
|
||||
```
|
||||
|
||||
`.codegraph/config.json` 可随仓库共享;`.codegraph/codegraph.db`、缓存和日志为本机生成物,不提交。
|
||||
|
||||
Codex 项目级 hook 保存在 `.codex/config.toml` 与 `.codex/hooks/`:
|
||||
|
||||
- `PreToolUse` hook:`node .codex/hooks/pre-submit-compile-check.mjs`,Codex 准备执行 `git commit` 前检查 `npm run typecheck`、`npm run admin-web:typecheck`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`。
|
||||
- `PostToolUse` hook:`node .codex/hooks/post-edit-codegraph-sync.mjs`,工具修改文件后执行 `npm run codegraph:sync`。
|
||||
|
||||
个人 token、模型路由、MCP server 仍属于个人环境;需要时由成员本机执行 `codegraph install` 或查看 `codegraph install --print-config codex`,不要提交个人全局配置。
|
||||
|
||||
Agent 本地 RAG 文档索引:
|
||||
|
||||
```bash
|
||||
npm run rag:index
|
||||
npm run rag:search -- --query "搜索内容"
|
||||
```
|
||||
|
||||
RAG 主要供 Agent 检索项目上下文,开发者仍按 `AGENTS.md`、`docs/README.md` 和 `docs/project-memory/` 阅读正式文档。RAG 仅索引项目文档和项目共享记忆,默认不把 LanceDB、Transformers.js 或本地 embedding 模型装入根 `package.json`。需要启用 RAG 时,Agent 必须先询问用户是否安装本地运行时依赖;用户确认后只安装到 gitignored 的 `.rag/runtime/`,模型缓存和向量库也留在 `.rag/`。具体命令见 `scripts/rag/README.md`。
|
||||
|
||||
## 常用检查命令
|
||||
|
||||
- 后端通用用户行为埋点统一通过 `record_tracking_event_and_return` procedure、`SpacetimeRuntimeClient::record_tracking_event(...)` 与 api-server `tracking` 中间件写入 `tracking_event` / `tracking_daily_stat`;后台、RPG、大鱼吃小鱼、Visual Novel、Story、Combat 默认排除;作品级游玩埋点统一使用 `work_play_start`,详细事件清单见 `docs/technical/BACKEND_TRACKING_EVENT_COVERAGE_2026-05-09.md`。
|
||||
|
||||
编码检查:
|
||||
|
||||
```bash
|
||||
npm run check:encoding
|
||||
```
|
||||
|
||||
ESLint:
|
||||
|
||||
```bash
|
||||
npm run lint:eslint
|
||||
```
|
||||
|
||||
类型检查:
|
||||
|
||||
```bash
|
||||
npm run typecheck
|
||||
```
|
||||
|
||||
综合 lint:
|
||||
|
||||
```bash
|
||||
npm run lint
|
||||
```
|
||||
|
||||
测试:
|
||||
|
||||
```bash
|
||||
npm run test
|
||||
```
|
||||
|
||||
生产构建:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
内容检查:
|
||||
|
||||
```bash
|
||||
npm run check:data
|
||||
npm run check:overrides
|
||||
npm run check:smoke
|
||||
npm run check:content
|
||||
```
|
||||
|
||||
全量检查:
|
||||
|
||||
```bash
|
||||
npm run check
|
||||
```
|
||||
|
||||
DDD 边界检查:
|
||||
|
||||
```bash
|
||||
npm run check:server-rs-ddd
|
||||
```
|
||||
|
||||
## 后端相关默认验证
|
||||
|
||||
后端修改后,按 DDD 文档中的验收命令执行。涉及 API smoke 时:
|
||||
|
||||
- 使用 `npm run dev:api-server` 重新拉起后端。
|
||||
- 禁止使用 `npm run api-server:maincloud`、`npm.cmd run api-server:maincloud` 或任何 `GENARRATIVE_SPACETIME_MAINCLOUD_*` 口径;这些只属于历史残留。
|
||||
- 本地 smoke 检查 `/healthz`;发布后或确认实例可接生产流量时检查 `/readyz`。
|
||||
- 执行对应自动测试。
|
||||
- 涉及 SpacetimeDB 表、reducer、procedure、row shape 或绑定变化时,同步更新 `migration.rs`、表目录和生成绑定。
|
||||
- SpacetimeDB 已有表新增字段必须放在 Rust 表结构体最后,并设置明确默认值;需要修改字段名时,先询问用户并确认迁移计划,再同步更新 `server-rs/crates/spacetime-module/src/migration.rs`、表目录和生成绑定。
|
||||
- 修改 SpacetimeDB schema 后运行 `npm run check:spacetime-schema`,用自动检查拦截缺 default、插入中间、字段删除/改名/重排/改类型,以及漏改迁移、表目录或绑定。
|
||||
|
||||
关键文档:
|
||||
|
||||
- `docs/technical/CURRENT_BACKEND_IMPLEMENTATION_BASELINE_2026-04-25.md`
|
||||
- `docs/technical/SERVER_RS_DDD_FULL_REFACTOR_2026-04-28.md`
|
||||
- `docs/technical/SERVER_RS_DDD_PARALLEL_TASKLIST_2026-04-29.md`
|
||||
- `docs/technical/SERVER_RS_DDD_G1_CONTRACT_AND_ROUTE_MATRIX_2026-04-29.md`
|
||||
- `docs/technical/SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md`
|
||||
- `docs/technical/SPACETIMEDB_TABLE_CATALOG.md`
|
||||
- `docs/technical/MAINCLOUD_REFERENCE_REMOVAL_POLICY_2026-05-06.md`
|
||||
|
||||
## 生产压测与观测默认口径
|
||||
|
||||
- 作品列表 50 HTTP req/s 压测使用 `scripts/loadtest/README.md` 中的 K6 命令;当前脚本一次 iteration 请求两个公开列表接口,因此目标 50 HTTP req/s 对应 `PEAK_RPS=25`。
|
||||
- 生产 `api-server` 默认 backlog、worker threads、HTTP 并发背压、`/readyz` 接流检查、systemd 优雅停机窗口、Nginx upstream timing log 和 OTLP 开关以 `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md` 为准。
|
||||
- OpenTelemetry 现阶段可选发送 traces / metrics / logs,但不会取代本地 `journalctl -u genarrative-api.service`、`logs/api-server/` 与 `/var/log/nginx/genarrative.*.log`。
|
||||
- 指标 label 不写 raw URI、userId、profileId 或 request_id;request_id 只用于 trace/log 串联。
|
||||
|
||||
## 前端相关默认验证
|
||||
|
||||
前端修改后,应根据修改范围选择:
|
||||
|
||||
- `npm run check:encoding`
|
||||
- `npm run lint:eslint`
|
||||
- `npm run typecheck`
|
||||
- `npm run test`
|
||||
- 页面交互 smoke
|
||||
- 移动端视口检查
|
||||
|
||||
前端原则:
|
||||
|
||||
- 移动端优先,再兼容网页端。
|
||||
- 页面只展示后端返回的状态,不自行计算结论型业务状态。
|
||||
- 创作中心入口配置事实源在 SpacetimeDB,通过 `GET /api/creation-entry/config` 下发;前端只在 `platformEntryCreationTypes.ts` 做展示派生,api-server 路由熔断也使用同一份配置,禁止恢复前端硬编码入口配置文件。底部加号创作入口页公告位也跟随后端 `eventBanners` 配置,前端只做展示和轮播;后台公告用表单维护标题与 HTML 内容,保存时再序列化为后端 `eventBannersJson` 传输字段。`最近创作` 不属于模板分类,不能作为分类缺失兜底;生成中和生成失败的真实草稿摘要都应进入最近创作。
|
||||
- 一期统一创作页字段 spec 同样跟随 `GET /api/creation-entry/config`,由 `creationTypes[].unifiedCreationSpec` 下发;拼图、抓大鹅、敲木鱼之外的模板不接入该扩展位,前端只保留旧后端缺字段时的兜底默认。
|
||||
- 优先复用现有面板、抽屉、弹窗,不新建独立大系统。
|
||||
- 不在 UI 中默认写功能说明类文本。
|
||||
- 弹出独立面板的交互不要实现成在当前面板下方追加内容。
|
||||
|
||||
## 文档更新规则
|
||||
|
||||
- 工程修改要同步更新对应文档。
|
||||
- 如果没有现成文档,新文档统一放入 `docs/` 下合适分类。
|
||||
- `docs/project-memory/shared-memory/` 只记录高频、长期、团队共享的摘要和索引,不替代完整 PRD/技术文档。
|
||||
- 如果 `docs/project-memory/shared-memory/` 与代码或 `docs/` 冲突,以代码和最新 `docs/` 为准,并同步修正共享记忆。
|
||||
|
||||
## 提交前建议让 Agent 执行
|
||||
|
||||
涉及拼图、抓大鹅、敲木鱼统一创作 / 生成链路、Phase 2 之后的跨玩法回归或本地 dev 栈时,先按 `quality-gates/README.md`、`quality-gates/【玩法创作】跨玩法回归与冒烟门禁-2026-05-30.md` 和对应单项门禁文档执行自动脚本与体验检查。
|
||||
|
||||
```text
|
||||
请检查当前 git diff,指出:
|
||||
1. 是否违反 AGENTS.md 或 docs/project-memory/shared-memory 约定;
|
||||
2. 是否需要补充 docs;
|
||||
3. 是否有长期知识需要写入 docs/project-memory/shared-memory;
|
||||
4. 建议的测试命令和提交信息。
|
||||
```
|
||||
@@ -0,0 +1,54 @@
|
||||
# 文档地图与阅读索引
|
||||
|
||||
更新时间:`2026-05-15`
|
||||
|
||||
## 当前文档入口
|
||||
|
||||
| 场景 | 优先阅读 |
|
||||
| --- | --- |
|
||||
| 建立项目背景 | `README.md`、`AGENTS.md`、`docs/project-memory/shared-memory/project-overview.md` |
|
||||
| 找当前文档 | `docs/README.md` |
|
||||
| 产品、命名、UI、协作和废弃路线 | `docs/【项目基线】当前产品与工程约束-2026-05-15.md` |
|
||||
| 后端、DDD、API、SpacetimeDB schema 和表目录 | `docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md` |
|
||||
| 创作入口、草稿架和玩法链路 | `docs/【玩法创作】平台入口与玩法链路-2026-05-15.md` |
|
||||
| 创作流程统一阶段计划 | `docs/planning/【玩法创作】创作流程统一总计划-2026-05-30.md` |
|
||||
| 本地启动、验证、部署、埋点和运营查询 | `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md` |
|
||||
| 微信小程序虚拟支付 | `docs/【技术方案】微信虚拟支付接入-2026-05-26.md` |
|
||||
| UI 像素资产与 9-slice 规范 | `UI_CODING_STANDARD.md` |
|
||||
|
||||
## 阅读顺序
|
||||
|
||||
通用复杂任务:
|
||||
|
||||
1. `AGENTS.md`
|
||||
2. `docs/project-memory/shared-memory/`
|
||||
3. `docs/README.md`
|
||||
4. 与任务匹配的当前融合文档
|
||||
|
||||
后端 / 数据真相 / SpacetimeDB:
|
||||
|
||||
1. `docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`
|
||||
2. `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
|
||||
3. 对应 crate README 或源码
|
||||
|
||||
玩法 / 创作入口 / 运行态:
|
||||
|
||||
1. `docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
|
||||
2. 若任务涉及跨玩法创作流程统一,读取 `docs/planning/【玩法创作】创作流程统一总计划-2026-05-30.md`
|
||||
3. `docs/【项目基线】当前产品与工程约束-2026-05-15.md`
|
||||
4. 相关前端组件、service、shared contract 和后端 module
|
||||
|
||||
生产部署 / 服务器 / Jenkins:
|
||||
|
||||
1. `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
|
||||
2. `deploy/env/api-server.env.example`
|
||||
3. `deploy/nginx/README.md`
|
||||
|
||||
## 维护规则
|
||||
|
||||
- 当前 `docs/` 只保留少量融合文档。
|
||||
- 新增工程实现时,如果已有对应当前文档,必须同步更新。
|
||||
- 如果没有合适位置,新文档文件名必须使用 `【标签名】中文标题-YYYY-MM-DD.md`。
|
||||
- 阶段性流水账、一次性修复记录和已关闭实验不要再新增为长期文档。
|
||||
- 阶段性计划和一次性 TODO 不再作为长期文档目录;需要保留的决策、流程和坑点应进入 `docs/` 当前文档或 `docs/project-memory/shared-memory/`。
|
||||
- 如果文档与代码冲突,先确认代码事实,再更新过期文档和共享记忆。
|
||||
@@ -0,0 +1,53 @@
|
||||
# 任务交接模板
|
||||
|
||||
> 用途:当一名开发者把任务交给另一名开发者,或让 Hermes 接续上下文时,复制本模板并填写。
|
||||
|
||||
## 基本信息
|
||||
|
||||
- 任务名称:
|
||||
- 负责人:
|
||||
- 当前分支:
|
||||
- 相关需求/Issue:
|
||||
- 相关文档:
|
||||
|
||||
## 背景
|
||||
|
||||
简要说明为什么做这个任务,以及业务/技术目标。
|
||||
|
||||
## 已完成
|
||||
|
||||
- [ ]
|
||||
- [ ]
|
||||
- [ ]
|
||||
|
||||
## 未完成
|
||||
|
||||
- [ ]
|
||||
- [ ]
|
||||
- [ ]
|
||||
|
||||
## 关键文件
|
||||
|
||||
- `path/to/file`:说明
|
||||
- `path/to/file`:说明
|
||||
|
||||
## 当前问题/风险
|
||||
|
||||
-
|
||||
|
||||
## 已执行验证
|
||||
|
||||
```bash
|
||||
# 粘贴已执行命令和结果摘要
|
||||
```
|
||||
|
||||
## 建议下一步
|
||||
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
## 是否需要更新团队记忆
|
||||
|
||||
- [ ] 不需要
|
||||
- [ ] 需要,建议更新:`docs/project-memory/shared-memory/...`
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,59 @@
|
||||
# Genarrative 项目共享概览
|
||||
|
||||
更新时间:`2026-06-12`
|
||||
|
||||
## 一句话定位
|
||||
|
||||
Genarrative / 陶泥儿是一个 AI 原生互动内容与小游戏平台,把 AI 创作、作品草稿、公开分发、运行态、用户账号、钱包任务、后台管理和小程序外壳收在同一套工程中。
|
||||
|
||||
## 当前主要能力
|
||||
|
||||
- RPG / 自定义世界创作与运行时。
|
||||
- 拼图玩法创作、草稿、发布、运行态和排行榜。
|
||||
- 拼消消玩法创作、素材图集生成、结果页、发布、统一作品详情、正式运行态和基础统计。
|
||||
- 敲木鱼玩法创作、草稿、发布、运行态、公开详情和分享码。
|
||||
- 抓大鹅 Match3D 创作、2D 多视角素材生成、发布和运行态。
|
||||
- 大鱼吃小鱼、方洞挑战、视觉小说、汪汪声浪和儿童向寓教于乐玩法。
|
||||
- 账号、短信 / 密码 / 微信登录、个人资料、任务、钱包、邀请码、充值、反馈、法律信息和后台管理。
|
||||
|
||||
## 当前入口
|
||||
|
||||
- 主站:`http://127.0.0.1:3000`。
|
||||
- 后台:`http://127.0.0.1:3000/admin/` 或 `http://127.0.0.1:3102`。
|
||||
- 后台前端工程:`apps/admin-web`。
|
||||
- 小程序 WebView 外壳:`miniprogram/`。
|
||||
- 法律文本:`media/files/user_agreement.md`、`media/files/privacy_policy.md`、`media/files/disclaimer.md`。
|
||||
|
||||
移动端一级 Tab:`推荐 / 发现 / 创作 / 草稿 / 我的`。
|
||||
|
||||
## 当前后端路线
|
||||
|
||||
唯一有效后端路线:
|
||||
|
||||
```text
|
||||
server-rs + Axum + SpacetimeDB
|
||||
```
|
||||
|
||||
当前 SpacetimeDB crate、SDK、CLI / standalone、生成 bindings 和容器压测镜像统一按 `2.5.0` 对齐;遇到版本不匹配时先升级到 `server-rs/Cargo.toml` 锁定版本,升级后重启对应 SpacetimeDB 进程再重试。
|
||||
|
||||
职责边界:
|
||||
|
||||
- `api-server`:HTTP / SSE / BFF 门面和外部副作用编排。
|
||||
- `spacetime-module`:SpacetimeDB 表、reducer、procedure、事务 adapter 和 row mapper。
|
||||
- `spacetime-client`:后端访问 SpacetimeDB 的 typed facade。
|
||||
- `module-*`:纯领域模型、命令、应用规则、领域事件和领域错误。
|
||||
- `platform-*`:OSS、LLM、认证、语音等外部平台能力。
|
||||
- `shared-contracts` / `packages/shared`:前后端 DTO 和公开契约。
|
||||
- 前端:表现、交互、临时 UI 状态和后端结果渲染。
|
||||
|
||||
明确废弃:旧 `server-node`、Express、PostgreSQL、Go 服务端、`maincloud`、人工 `spacetime --root-dir` 口径,以及前端承接正式业务真相的路线。
|
||||
|
||||
## 当前文档入口
|
||||
|
||||
- `docs/README.md`
|
||||
- `docs/【项目基线】当前产品与工程约束-2026-05-15.md`
|
||||
- `docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`
|
||||
- `docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
|
||||
- `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
|
||||
|
||||
旧 PRD、设计、审计、阶段计划和技术流水账已融合进上述文档;没有融合的旧材料不再作为实现依据。
|
||||
@@ -0,0 +1,100 @@
|
||||
# 团队协作约定
|
||||
|
||||
> 用途:约定 3 名开发人员在各自本地开发环境和 Agent 中协作开发、共享项目记忆的方式。
|
||||
|
||||
## 基本模式
|
||||
|
||||
- 每位开发人员在自己的电脑上使用本地 Agent。
|
||||
- 每位开发人员本地拉取同一个项目仓库,独立修改代码、运行测试、提交分支。
|
||||
- 团队共享内容优先放在本仓库 `docs/project-memory/` 与 `docs/` 中,通过 Git 同步。
|
||||
- 不共享个人 `~/.hermes` 目录。
|
||||
|
||||
## 共享与禁止共享
|
||||
|
||||
推荐共享:
|
||||
|
||||
- `docs/project-memory/shared-memory/` 团队级长期记忆
|
||||
- `docs/project-memory/plans/` 阶段性实施计划
|
||||
- `docs/project-memory/todos/` 已确定需要执行、但尚未进入实施的共享 TODO 计划
|
||||
- `.hermes/skills/` Hermes 专用仓库级 skills
|
||||
- `docs/` 中 PRD、设计、技术、经验、审计、查询手册
|
||||
- `AGENTS.md` 项目级 Agent 约束
|
||||
|
||||
禁止提交:
|
||||
|
||||
- 个人 `~/.hermes/config.yaml`
|
||||
- 个人 `~/.hermes/.env`
|
||||
- 个人 `~/.hermes/sessions/`
|
||||
- API Key、Token、Cookie、认证文件
|
||||
- 个人本地私密路径和个人隐私信息
|
||||
- 构建产物、日志、缓存、数据库 dump
|
||||
|
||||
## 开发前
|
||||
|
||||
1. 拉取最新代码。
|
||||
2. 阅读 `AGENTS.md`。
|
||||
3. 阅读 `docs/project-memory/shared-memory/` 中与任务相关的文件。
|
||||
4. 阅读 `docs/README.md` 和任务相关分类 README。
|
||||
5. 阅读对应 PRD、设计、技术、经验或审计文档。
|
||||
6. 如果文档不足以指导编码,先补充或修正文档。
|
||||
|
||||
## 开发中
|
||||
|
||||
- 保持修改范围聚焦,不做无关重构。
|
||||
- 复用、修改、扩展现有系统优先,避免新建重复系统或页面。
|
||||
- 新增 Markdown 文档时,文件名必须以分类标签开头,格式为 `【标签名】中文标题-日期.md`;只在任务需要时重命名历史文档,避免无关大 diff。
|
||||
- 涉及中文文本时注意 UTF-8 编码和乱码排查。
|
||||
- 涉及后端时遵循 DDD 分层,不把业务真相下沉到前端或临时兼容层。
|
||||
- `maincloud` / `Maincloud` / `MAINCLOUD` 相关代码、脚本、测试、环境变量、命令和文档要求均视为历史残留,禁止新增、运行或引用;API smoke 统一使用 `npm run dev:api-server` 与 `/healthz`。
|
||||
- 涉及 SpacetimeDB 表结构、发布或迁移时,先看 `SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md` 和 `SPACETIMEDB_TABLE_CATALOG.md`。
|
||||
- 涉及生产发布、服务器配置、Jenkins Job 重建或回滚时,先看 `PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md`。
|
||||
|
||||
## 开发后
|
||||
|
||||
1. 运行与修改范围匹配的测试或验证命令。
|
||||
2. 更新相关 `docs/` 文档。
|
||||
3. 新增或沉淀 Markdown 文档时,确认文件名已使用 `【标签名】` 前缀。
|
||||
4. 若产生长期有效知识,更新 `docs/project-memory/shared-memory/`。
|
||||
5. 若形成 Hermes 专用可复用流程,考虑沉淀到 `.hermes/skills/`。
|
||||
6. 提交代码时,提交标题使用中文;标题后逐行写明本次提交修改了什么,每条变更单独一行。
|
||||
|
||||
## 文档阅读顺序
|
||||
|
||||
通用任务建议:
|
||||
|
||||
1. `README.md`
|
||||
2. `AGENTS.md`
|
||||
3. `docs/project-memory/shared-memory/`
|
||||
4. `docs/README.md`
|
||||
5. `docs/experience/README.md`
|
||||
6. `docs/audits/README.md`
|
||||
7. 任务所属分类:`docs/design/`、`docs/technical/`、`docs/planning/`、`docs/prd/`、`docs/reference/`、`docs/tracking/`、`docs/operations/`
|
||||
|
||||
后端任务建议:
|
||||
|
||||
1. `docs/technical/CURRENT_BACKEND_IMPLEMENTATION_BASELINE_2026-04-25.md`
|
||||
2. `docs/technical/SERVER_RS_DDD_FULL_REFACTOR_2026-04-28.md`
|
||||
3. `docs/technical/SERVER_RS_DDD_G1_CONTRACT_AND_ROUTE_MATRIX_2026-04-29.md`
|
||||
4. `docs/technical/SERVER_RS_DDD_PARALLEL_TASKLIST_2026-04-29.md`
|
||||
5. `docs/technical/SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md`
|
||||
6. `docs/technical/SPACETIMEDB_TABLE_CATALOG.md`
|
||||
7. `docs/technical/MAINCLOUD_REFERENCE_REMOVAL_POLICY_2026-05-06.md`
|
||||
|
||||
## 共享记忆更新准则
|
||||
|
||||
适合更新:
|
||||
|
||||
- 新增稳定架构约定
|
||||
- 新增长期开发流程
|
||||
- 已验证的踩坑和排障步骤
|
||||
- 重要接口契约变化
|
||||
- 团队协作规范变化
|
||||
- 文档索引或阅读顺序变化
|
||||
|
||||
不适合更新:
|
||||
|
||||
- 一次性临时计划
|
||||
- 未验证猜测
|
||||
- 个人偏好和个人路径
|
||||
- 敏感信息
|
||||
- 大段聊天记录
|
||||
@@ -0,0 +1,27 @@
|
||||
# 前端直订阅公开作品列表准入待办
|
||||
|
||||
## 背景
|
||||
|
||||
未来可以考虑让前端直接订阅公开作品列表,以减少列表读取链路中的 HTTP 往返,并复用 SpacetimeDB 的实时同步能力。
|
||||
|
||||
## 当前结论
|
||||
|
||||
短期仍由 `api-server` / BFF 订阅 SpacetimeDB public read model,并从本地 cache 读取后对外提供 HTTP 列表接口。前端不直接订阅作品源表,也不把正式列表排序、分页、权限裁剪逻辑下放到 UI。
|
||||
|
||||
## 落地前置条件
|
||||
|
||||
- 建立专用、稳定、低基数的 public read model,例如 `public_work_gallery_entry`。
|
||||
- 明确权限边界,只暴露公开列表所需字段,不泄露作者私有信息、审核内部状态或运营字段。
|
||||
- 固化字段契约,明确字段含义、默认值、兼容策略和生成绑定更新流程。
|
||||
- 明确排序与分页语义,避免依赖自增 ID 顺序,优先使用时间戳或显式排序字段。
|
||||
- 补齐埋点方案,能区分直订阅首屏、增量更新、分页加载和 fallback 命中。
|
||||
- 保留 BFF HTTP fallback,用于低版本客户端、订阅失败、权限策略调整和灰度回滚。
|
||||
- 禁止前端订阅 `puzzle_work_profile`、`custom_world_profile` 等作品源表。
|
||||
|
||||
## 建议验收
|
||||
|
||||
- 文档确认直订阅只面向专用 public read model,不绕过 BFF 读取源表。
|
||||
- schema、绑定、字段契约、排序分页和权限说明同步更新。
|
||||
- 前端具备订阅失败后的 BFF HTTP fallback。
|
||||
- 自动测试覆盖公开字段裁剪、排序分页稳定性和 fallback 路径。
|
||||
- 监控可观察直订阅成功率、首屏耗时、增量更新延迟和 fallback 比例。
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
`PlatformEntryFlowShellImpl.tsx` 曾内联维护拼图生成完成后刷新恢复的两个纯函数:`normalizeRecoveredPuzzleDraftSession` 与 `hasRecoverableGeneratedPuzzleDraft`。旧逻辑只要草稿有 `coverImageSrc`、首关 cover 或候选图,就会把恢复会话的 draft 和首关 `generationStatus` 抬成 `ready`,再进入结果页。
|
||||
|
||||
`.hermes/shared-memory/pitfalls.md` 已记录:拼图待发布判定偏弱时,只有首图但缺关卡画面、UI spritesheet 或关卡背景的半成品会被误当完成,用户进入结果页后仍可能空图或无法发布。
|
||||
`docs/project-memory/shared-memory/pitfalls.md` 已记录:拼图待发布判定偏弱时,只有首图但缺关卡画面、UI spritesheet 或关卡背景的半成品会被误当完成,用户进入结果页后仍可能空图或无法发布。
|
||||
|
||||
本切片先修前端恢复链路:只有完整首关资产包存在时,恢复流程才视为可完成。后端 `build_result_preview` / `validate_publish_requirements` / `is_puzzle_session_snapshot_publish_ready` 的发布门槛收紧另作后续切片,不混入本次前端模型收口。
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,204 @@
|
||||
# 外部生成 Worker 化方案
|
||||
|
||||
更新时间:`2026-06-12`
|
||||
|
||||
## 背景
|
||||
|
||||
当前 VectorEngine `gpt-image-2`、音频、LLM 等外部生成链路多数由 `api-server` 的 HTTP handler 直接等待上游、OSS 持久化和 SpacetimeDB 回写完成。前端虽然有生成页和会话轮询,但 HTTP 进程仍承担长耗时副作用,导致接入更多玩法或大图生成时只能放大 API 进程,而不能单独扩展外部生成吞吐。
|
||||
|
||||
## 目标
|
||||
|
||||
- 默认 `queue` 模式下,`api-server` 的 HTTP 角色只负责鉴权、入参校验、扣费前置/状态初始化、任务入队和返回 `queued` 操作结果。
|
||||
- 外部生成副作用由独立 `external-generation-worker` 角色执行。
|
||||
- 多个 worker 进程通过 SpacetimeDB 任务表抢占任务,依赖 lease 超时恢复,支持按进程数和单进程并发动态缩扩容。
|
||||
- 本地或小流量同步排查可显式启用 `inline` 模式,由 HTTP handler 复用同一 worker executor 同步执行并返回 `completed`;该模式不创建队列任务,也不具备 worker 横向扩容能力。
|
||||
- SpacetimeDB reducer / procedure 只做任务状态流转,不做网络、文件系统或外部 provider I/O。
|
||||
- 已接入拼图 `compile_puzzle_draft`、结果页 `generate_puzzle_images` 与结果页 `generate_puzzle_ui_background`;本轮扩展到跳一跳、拼消消和敲木鱼的外部图片生成动作。后续玩法继续复用同一队列 Module,不再为每个玩法发明独立队列。
|
||||
- 第一版外部生成队列粒度固定为“单个用户动作对应单个 job”。例如草稿编译、结果页单槽重生、图集重生都各自入一个 job;job 内部可以串行或并行调用 provider、OSS、SpacetimeDB 写回,但不再拆成“提示词 / 生图 / 切图 / 去背景 / 持久化 / 回写”等阶段 job。阶段进度只作为 `request_payload_json` / 业务 session 的展示状态,不作为队列调度单位。
|
||||
- 不调用外部图片 / 音频 / LLM provider 的动作继续 inline 执行,不为了统一排队而进入 `external_generation_job`。
|
||||
|
||||
## Module 与 Interface
|
||||
|
||||
新增深一点的 **外部生成任务 Module**,Interface 收敛为:
|
||||
|
||||
- `enqueue_external_generation_job_and_return`:按 `dedupe_key` 幂等创建或返回现有任务。
|
||||
- `claim_external_generation_jobs_and_return`:worker 按 `worker_id`、`limit` 和 lease 时长抢占 `pending` 或 lease 过期的 `running` 任务,返回本次 claim 的 `lease_token`。
|
||||
- `renew_external_generation_job_lease_and_return`:worker 长任务执行期间按 `worker_id + lease_token` 续租,防止外部生成超过单次 lease 后被重复领取。
|
||||
- `complete_external_generation_job_and_return`:worker 成功后按 `worker_id + lease_token` 写入 `result_payload_json`,任务进入 `completed`。
|
||||
- `fail_external_generation_job_and_return`:worker 失败后按 `worker_id + lease_token` 回写错误,并按 `max_attempts` 决定回到 `pending` 重试或进入 `failed`。
|
||||
- `get_external_generation_queue_stats_and_return`:controller 读取队列积压、运行中任务和过期 lease 数量,用于计算 worker 目标实例数;该 procedure 只读 `external_generation_job`,不直接操作 systemd。
|
||||
- `get_external_generation_job_and_return`:按 `job_id` 读取单个任务状态,给 BFF 和生成页展示使用;必须只返回调用者有权读取的任务,不能暴露其它用户的 payload、错误详情或 worker 内部字段。
|
||||
|
||||
这个 Module 的 **Seam** 在 SpacetimeDB procedure + `spacetime-client` facade;`api-server` HTTP role 和 worker role 都只依赖这个 Interface。外部 provider、OSS、计费补偿、玩法草稿回写仍留在 `api-server` worker implementation 内,不进入 SpacetimeDB reducer。
|
||||
|
||||
## BFF 状态接口
|
||||
|
||||
队列状态对前端只通过 `api-server` BFF 暴露,不允许前端直接查询 SpacetimeDB private table:
|
||||
|
||||
- `GET /api/runtime/external-generation/queue-overview`:队列概览,用于 `我的` 页签、调试面板或后台观测当前用户可见的等待状态。返回 pending / running / completed / failed / cancelled 数量、最早等待时间、当前可见 job 摘要,以及是否存在过期 lease 需要等待 worker 重领。
|
||||
- `GET /api/runtime/external-generation/jobs/{jobId}`:单 job 状态,用于生成页轮询某次动作。返回 `jobId`、`jobKind`、`sourceModule`、`sourceEntityId`、`status`、`attempt`、`maxAttempts`、`createdAt`、`startedAt`、`completedAt`、`updatedAt`、可展示的 `requestLabel`、可展示的 `lastErrorMessage`、以及业务侧下一次轮询所需的 source 标识。
|
||||
|
||||
BFF 只做鉴权、授权裁剪、字段脱敏和契约映射;队列事实仍以 `external_generation_job` 为准,业务结果仍以玩法 session / work profile 为准。生成页 / 进度页只展示当前玩法业务进度;用户可见队列概览放在 `我的` 页签,必要时再用单 job 状态补充排障信息,并继续按原玩法 session/detail 接口收敛到 ready 或 failed。队列接口不替代玩法恢复接口,也不把 private `request_payload_json` 原样传给前端。
|
||||
|
||||
## 任务表
|
||||
|
||||
新增私有表 `external_generation_job`:
|
||||
|
||||
| 字段 | 说明 |
|
||||
| --- | --- |
|
||||
| `job_id` | 主键,`extgen-` 前缀 UUID |
|
||||
| `dedupe_key` | 唯一键,建议为 `play/action/session/scope` |
|
||||
| `job_kind` | 执行类型,当前拼图为 `puzzle_compile_draft`、`puzzle_generate_images`、`puzzle_generate_ui_background` |
|
||||
| `owner_user_id` | 触发用户 |
|
||||
| `source_module` | 玩法或能力名,例如 `puzzle` |
|
||||
| `source_entity_id` | session/profile/work 等作用域 |
|
||||
| `request_label` | 排障标签 |
|
||||
| `request_payload_json` | worker 执行入参 JSON |
|
||||
| `status` | `pending/running/completed/failed/cancelled` |
|
||||
| `attempt` / `max_attempts` | 当前尝试次数与最大尝试次数 |
|
||||
| `last_error_message` | 最近失败原因 |
|
||||
| `worker_id` | 当前 lease owner |
|
||||
| `lease_expires_at` | lease 到期时间 |
|
||||
| `lease_token` | 本次 claim 的 fencing token,用于阻止过期 worker 回写 |
|
||||
| `available_at` | 下次可领取时间 |
|
||||
| `result_payload_json` | 完成摘要 |
|
||||
| `created_at/started_at/completed_at/updated_at` | 审计时间 |
|
||||
|
||||
索引:
|
||||
|
||||
- `by_external_generation_job_status_available(status, available_at)`
|
||||
- `by_external_generation_job_worker_id(worker_id)`
|
||||
- `by_external_generation_job_source(source_module, source_entity_id)`
|
||||
- `by_external_generation_job_owner_user_id(owner_user_id)`
|
||||
|
||||
## 状态机
|
||||
|
||||
```text
|
||||
pending -> running -> completed
|
||||
pending -> running -> pending (可重试失败)
|
||||
pending -> running -> failed (达到最大重试次数)
|
||||
pending/running -> cancelled (预留)
|
||||
```
|
||||
|
||||
`claim` 只领取 `pending` 且 `available_at <= now` 的任务,或 `running` 且 `lease_expires_at <= now` 的任务。领取时递增 `attempt`、写入 `worker_id`、`started_at`、新的 `lease_expires_at` 和 `lease_token`。SpacetimeDB procedure 使用 `ctx.timestamp` 作为状态流转时间,只从 worker 入参读取“时长差值”,不信任 worker 本机绝对时间。worker 每次执行只处理自己 claim 到的任务;续租、完成或失败时必须带同一个 `worker_id + lease_token`,且当前 lease 尚未过期,防止过期 worker 覆盖新 lease。
|
||||
|
||||
玩法业务写回也必须在 SpacetimeDB 同一事务里校验 lease fencing。拼图的 `compile_puzzle_agent_draft` worker 调用、`save_puzzle_generated_images`、`save_puzzle_ui_background`、`mark_puzzle_draft_generation_failed` 和 `mark_puzzle_level_generation_failed` 在 `queue` 模式下会带 `external_generation_job_id / worker_id / lease_token`,并校验 job 仍为 `running`、token 未过期、`job_kind`、`owner_user_id`、`source_module` 和 `source_entity_id` 均匹配后才写 session / work profile。`inline` 模式不创建 `external_generation_job`,因此这三个 guard 字段必须同时为空;transaction 只把三项全空识别为 api-server 受控同步写回,三项半空仍按非法请求拒绝。worker 路径的核心业务写回失败不能返回内存快照并把 job 标为 `completed`;失败态业务回写成功后才允许把队列 job 标为 `failed`,失败态仍未写回时保留当前租约并等待后续 lease 过期重领,避免队列状态和真实 session 脱节。api-server 的资产扣费包装遇到这类 stale worker lease guard 错误时不执行补偿退款,避免旧 worker 冲掉后续合法 worker 的同一账本扣费。
|
||||
|
||||
## 执行模式与进程角色
|
||||
|
||||
外部生成执行模式由 `GENARRATIVE_EXTERNAL_GENERATION_MODE` 控制:
|
||||
|
||||
- `queue`:默认值,HTTP handler 入队 `external_generation_job`,由 `external-generation-worker` 角色 claim lease 后执行;生产、预发和压测默认使用该模式。
|
||||
- `inline`:HTTP handler 直接调用同一个 worker executor,同步等待 provider、OSS 和 SpacetimeDB 写回完成后返回 `operation.status = completed`;只用于本地或低并发排查,不提供队列持久化、lease 重领和 worker 横向扩容。
|
||||
|
||||
同一个 Rust binary 通过 `GENARRATIVE_PROCESS_ROLE` 切换:
|
||||
|
||||
- `api`:只启动 HTTP server。
|
||||
- `external-generation-worker`:只启动外部生成 worker,不监听 HTTP。
|
||||
- `external-generation-controller`:只启动 worker controller,不监听 HTTP,也不直接执行外部生成任务。
|
||||
- `all`:本地开发可同时启动 HTTP 与 worker。
|
||||
|
||||
worker 配置:
|
||||
|
||||
- `GENARRATIVE_EXTERNAL_GENERATION_WORKER_ID`:实例 ID;未配置时用 hostname/pid 派生。
|
||||
- `GENARRATIVE_EXTERNAL_GENERATION_WORKER_CONCURRENCY`:单进程并发领取/执行数量。
|
||||
- `GENARRATIVE_EXTERNAL_GENERATION_WORKER_POLL_INTERVAL_MS`:空队列轮询间隔。
|
||||
- `GENARRATIVE_EXTERNAL_GENERATION_WORKER_LEASE_SECONDS`:任务 lease 时长;worker 会按约三分之一 lease、最长 30 秒的间隔续租。该值应覆盖一次心跳网络抖动窗口,不需要大于完整外部生成链路耗时。
|
||||
|
||||
controller 配置:
|
||||
|
||||
- `GENARRATIVE_EXTERNAL_GENERATION_CONTROLLER_MIN_WORKERS`:保底 worker 实例数,生产默认 `1`,controller 不会主动停止 `@1`。
|
||||
- `GENARRATIVE_EXTERNAL_GENERATION_CONTROLLER_MAX_WORKERS`:自动扩容上限,生产模板默认 `8`。
|
||||
- `GENARRATIVE_EXTERNAL_GENERATION_CONTROLLER_TARGET_JOBS_PER_WORKER`:每个 worker 实例承担的目标未完成任务数,默认 `2`;目标实例数按 `claimable_pending + running_active + expired_running` 计算后夹在 min/max 之间,避免把已包含过期 running 的 `claimable_count` 重复计入。
|
||||
- `GENARRATIVE_EXTERNAL_GENERATION_CONTROLLER_POLL_INTERVAL_MS`:controller 轮询队列统计的间隔,默认 `10000`。
|
||||
- `GENARRATIVE_EXTERNAL_GENERATION_CONTROLLER_SCALE_DOWN_IDLE_ROUNDS`:连续多少轮无可领取、无运行中、无过期 running 后才允许缩容,默认 `6`;缩容每轮只停止最高编号的一个实例。
|
||||
- `GENARRATIVE_EXTERNAL_GENERATION_CONTROLLER_SERVICE_TEMPLATE`:systemd worker 模板,默认 `genarrative-external-generation-worker@{}.service`。
|
||||
- `GENARRATIVE_EXTERNAL_GENERATION_CONTROLLER_DRY_RUN`:只记录决策不执行 systemctl,默认 `false`。
|
||||
|
||||
动态缩扩容方式:生产默认由 `deploy/systemd/genarrative-external-generation-controller.service` 启动 `GENARRATIVE_PROCESS_ROLE=external-generation-controller`,controller 读取 `get_external_generation_queue_stats_and_return` 后对 `genarrative-external-generation-worker@N.service` 执行精确 `systemctl start/stop`;无需改变 HTTP 进程数。controller 只操作 `@1..@MAX` 中的缺口或最高编号多余实例,保留 `@1` 作为保底 worker。缩容或发布重启 worker 时,进程收到 SIGINT/SIGTERM 后会停止 claim 新任务并等待当前任务完成;若进程被硬杀、机器断电或超过 systemd `TimeoutStopSec`,未完成任务会在 lease 过期后被其它 worker 重新领取。容器链路已有独立 `external-generation-worker` compose service;扩 worker 必须扩这个 worker service,不能只扩 `api-server` HTTP service。
|
||||
|
||||
## 已接入的拼图纵切
|
||||
|
||||
### 拼图
|
||||
|
||||
`compile_puzzle_draft`:
|
||||
|
||||
1. HTTP handler 保存拼图表单草稿;`queue` 模式下 `queued/running` 的持久事实源是 `external_generation_job`,不把 HTTP 进程变成外部生成执行者。
|
||||
2. `queue` 模式下 HTTP handler 入队 `puzzle_compile_draft`,返回 `operation.status = queued` 和当前 session。拼图 dedupe key 包含本次 `extgen-` job id,只保证同一任务行唯一,不把同一 session 后续重新生成吞掉。`inline` 模式下 HTTP handler 复用同一 executor 同步执行,成功后直接返回 `completed` 和最新 session。
|
||||
3. 前端保持 `puzzle-generating`,继续轮询 `getPuzzleAgentSession`;首期不把 `queued/running` 写回 `puzzle_agent_session`,因此刷新或跨设备恢复生成中状态仍是后续 read model 工作。
|
||||
4. worker claim 后执行原有 `compile_puzzle_draft_with_initial_cover` 或 `compile_puzzle_draft_with_uploaded_cover`;前置 `compile_puzzle_agent_draft` 也必须携带本次 `job_id / worker_id / lease_token`,防止过期 worker 先把草稿卡和 session 写到 ready。
|
||||
5. 成功后沿原有 SpacetimeDB 拼图会话/作品写回,前端轮询看到 `progressPercent >= 94/96/100` 和 ready 草稿。
|
||||
6. 失败后调用 `mark_puzzle_draft_generation_failed`,拼图首期业务失败直接进入 failed;只有失败态写回成功才把队列 job 标为 failed,失败态写回失败则保留租约等待重领。队列仍保留 lease 过期后的崩溃重领,避免 worker 退款后再次成功导致钱包账本漂移。前端通过现有失败草稿/弹窗机制展示来源错误。
|
||||
|
||||
`generate_puzzle_images`:
|
||||
|
||||
1. HTTP handler 校验本次 `levelsJson` 快照;`queue` 模式下入队 `puzzle_generate_images` 并返回 `operation.status = queued/running/completed/failed`,`inline` 模式下同步执行原 worker executor 并在成功后返回 `completed`。
|
||||
2. worker 执行原结果页关卡图链路:自动命名、VectorEngine / 上传图直用、关卡场景图、UI spritesheet、关卡背景资产包、OSS 持久化和 SpacetimeDB 回写。
|
||||
3. 成功后 `save_puzzle_generated_images` 写回目标关卡和草稿卡;失败后 `mark_puzzle_level_generation_failed` 只标记目标关卡 `failed`,不污染已 ready 的其它关卡。队列 job 只有在目标关卡失败态写回成功后才进入 failed。
|
||||
4. 前端结果页对 `queued/running` 操作继续轮询 `getPuzzleAgentSession`,目标关卡变为 ready 或 failed 后收敛。
|
||||
|
||||
`generate_puzzle_ui_background`:
|
||||
|
||||
1. HTTP handler 校验本次 `levelsJson` 快照;`queue` 模式下入队 `puzzle_generate_ui_background` 并返回 `operation.status = queued/running/completed/failed`,`inline` 模式下同步执行原 worker executor 并在成功后返回 `completed`。
|
||||
2. worker 执行原结果页 UI 背景链路:归一化提示词、VectorEngine 生成、OSS 持久化和 `save_puzzle_ui_background` 写回。
|
||||
3. 成功后目标关卡写入 `uiBackgroundPrompt/uiBackgroundImageSrc/uiBackgroundImageObjectKey`;失败后复用 `mark_puzzle_level_generation_failed` 标记目标关卡 `failed`,并在失败态写回成功后才终结队列 job,让前端轮询能收敛。
|
||||
|
||||
### 跳一跳、拼消消和敲木鱼扩展范围
|
||||
|
||||
以下动作按同一 worker 模式迁移。命名以现有玩法 action 为准,队列 `job_kind` 采用后端稳定 snake_case,不新增平行队列:
|
||||
|
||||
- 跳一跳 `jump-hop`
|
||||
- `compile-draft`:草稿编译阶段需要生成地块 / 视觉资产时入队,例如 `jump_hop_compile_draft`。
|
||||
- `regenerate-tiles`:结果页地块图集重生入队,例如 `jump_hop_regenerate_tiles`。
|
||||
- 拼消消 `puzzle-clear`
|
||||
- `compile-draft`:草稿编译阶段需要生成场地底图和卡片 atlas 时入队,例如 `puzzle_clear_compile_draft`。
|
||||
- `regenerate-atlas`:结果页素材 atlas 重生入队,例如 `puzzle_clear_regenerate_atlas`。
|
||||
- 敲木鱼 `wooden-fish`
|
||||
- `compile-draft`:草稿编译阶段需要生成背景、敲击物或其它图片资产时入队,例如 `wooden_fish_compile_draft`。
|
||||
- `regenerate-hit-object`:结果页敲击物图片重生入队,例如 `wooden_fish_regenerate_hit_object`。
|
||||
|
||||
这些动作首版都保持“单动作单 job”:一次 `compile-draft` 或一次 `regenerate-*` 请求只创建一个 job,worker 内部负责该动作所需的 provider 调用、素材处理、OSS 持久化、失败态写回和业务成功写回。非外部图片生成动作,例如纯元信息保存、标签编辑、发布、试玩启动、运行态动作、删除和公开 read model 读取,继续 inline 执行。
|
||||
|
||||
每个玩法迁移时必须同时接入业务写回 lease guard:worker 路径带 `external_generation_job_id / worker_id / lease_token`,inline 路径三项同时为空。过期 worker 不得写 session / work profile;业务失败态写回成功后才允许 job 进入 `failed`。
|
||||
|
||||
## 验收
|
||||
|
||||
基础检查:
|
||||
|
||||
```bash
|
||||
npm run spacetime:generate
|
||||
npm run check:spacetime-schema
|
||||
npm run check:server-rs-ddd
|
||||
cargo check -p api-server --manifest-path server-rs/Cargo.toml
|
||||
```
|
||||
|
||||
定向测试:
|
||||
|
||||
```bash
|
||||
cargo test -p spacetime-module external_generation --manifest-path server-rs/Cargo.toml
|
||||
cargo test -p spacetime-module level_generation_failure --manifest-path server-rs/Cargo.toml
|
||||
cargo test -p api-server external_generation_worker --manifest-path server-rs/Cargo.toml
|
||||
npm run test -- src/components/puzzle-result/PuzzleResultView.test.tsx -t "keeps generation progress visible"
|
||||
npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "compile_puzzle_draft"
|
||||
```
|
||||
|
||||
本地 smoke:
|
||||
|
||||
```bash
|
||||
GENARRATIVE_PROCESS_ROLE=all npm run dev
|
||||
curl -f http://127.0.0.1:<api-port>/healthz
|
||||
```
|
||||
|
||||
本地 `npm run dev` 默认保持 `inline` 开发体验:未显式配置 `GENARRATIVE_EXTERNAL_GENERATION_MODE=queue` 时,普通本地联调可以同步确认 provider、OSS 和 SpacetimeDB 写回链路本身是否可行。需要验证 worker 队列、BFF 队列状态、lease 重领或扩缩容时,必须显式使用 `queue`,并启动 worker 角色;可以用 `GENARRATIVE_EXTERNAL_GENERATION_MODE=queue GENARRATIVE_PROCESS_ROLE=all npm run dev:api-server` 做临时单进程 smoke,也可以使用隔离容器 smoke。
|
||||
|
||||
生产 smoke 需要保持 `GENARRATIVE_EXTERNAL_GENERATION_MODE=queue`,并至少启动一个 `api` 角色、一个 `external-generation-worker` 角色和一个 `external-generation-controller` 角色;发布脚本会在默认 worker pattern 下自动启用并启动 `genarrative-external-generation-worker@1.service`,重启并验活 `genarrative-external-generation-controller.service`。若 worker 数量归零,生成任务会保持 `queued/running`,不会由 HTTP 进程偷偷执行。部署验证除 `/healthz` / `/readyz` 外,还要确认队列概览 BFF 可读、单 job 状态能从 `queued/running` 收敛到业务 session/detail 的 ready 或 failed。
|
||||
|
||||
systemd 生产 controller 与手动兜底示例:
|
||||
|
||||
```bash
|
||||
systemctl enable --now genarrative-external-generation-worker@1.service
|
||||
systemctl enable --now genarrative-external-generation-controller.service
|
||||
systemctl start genarrative-external-generation-worker@2.service
|
||||
systemctl stop genarrative-external-generation-worker@2.service
|
||||
systemctl status genarrative-external-generation-controller.service 'genarrative-external-generation-worker@*.service'
|
||||
```
|
||||
@@ -0,0 +1,82 @@
|
||||
# 本地 SSH 服务器管理面板技术方案
|
||||
|
||||
日期:`2026-06-11`
|
||||
|
||||
## 背景
|
||||
|
||||
release / dev 等服务器的日常巡检已经有 `genarrative-health-patrol.timer`、`/readyz`、`/healthz`、SpacetimeDB `/v1/ping` 和 systemd 状态文件,但开发者本地仍需要在多个 SSH alias 之间切换命令。服务器管理面板用于把这些只读巡检和少量 systemd 服务操作收敛到一个本地桌面入口。
|
||||
|
||||
## 范围
|
||||
|
||||
- 使用 Rust `egui` / `eframe` 实现本地桌面面板,不接入线上 Web 后台,不暴露公网端口。
|
||||
- 从本机 `~/.ssh/config` 的 `Host` alias 发现服务器;只展示不含通配符的 alias。
|
||||
- 支持多个服务器,左侧服务器侧边栏可收起。
|
||||
- 主面板展示硬件状态、服务状态、HTTP 健康探测和生产健康巡检状态。
|
||||
- 支持对允许的 systemd unit 执行启动、关闭、重启。
|
||||
|
||||
## 命令入口
|
||||
|
||||
```bash
|
||||
npm run server-manager:panel
|
||||
```
|
||||
|
||||
等价于:
|
||||
|
||||
```bash
|
||||
cargo run -p server-manager-panel --manifest-path server-rs/Cargo.toml
|
||||
```
|
||||
|
||||
面板启动时会自动查找本机中文字体并注入 egui 字体集,优先使用 `Noto Sans CJK SC`,其次使用文泉驿 / Droid fallback。若某台开发机字体路径特殊,可用 `GENARRATIVE_SERVER_PANEL_CJK_FONT=/path/to/font.ttc|index` 指定;普通 `.ttf` 可省略 `|index`。
|
||||
|
||||
## SSH 约定
|
||||
|
||||
本地 `~/.ssh/config` 中需要存在类似:
|
||||
|
||||
```sshconfig
|
||||
Host dev
|
||||
HostName 10.2.0.10
|
||||
User genarrative
|
||||
|
||||
Host release
|
||||
HostName genarrative.world
|
||||
User genarrative
|
||||
```
|
||||
|
||||
面板通过 `ssh <alias> sh -s` 执行远端只读巡检脚本。服务操作使用:
|
||||
|
||||
```bash
|
||||
sudo -n systemctl <start|stop|restart> <unit>
|
||||
```
|
||||
|
||||
若 SSH 用户是 root,则直接执行 `systemctl`。非 root 用户需要提前配置只允许目标 unit 的无密码 sudo;否则面板会显示 sudo 权限错误,不会弹出交互密码输入。
|
||||
|
||||
## 健康检查内容
|
||||
|
||||
只读巡检覆盖:
|
||||
|
||||
- 主机名、内核、运行时长、CPU 核数 / 型号、load average。
|
||||
- 内存 / swap 使用情况。
|
||||
- `/`、`/var`、`/opt`、`/stdb`、`/data` 中存在路径的磁盘使用率。
|
||||
- `genarrative-api.service`、`spacetimedb.service`、`nginx.service`、`genarrative-health-patrol.timer`、`genarrative-database-backup.timer` 的 systemd 状态。
|
||||
- `http://127.0.0.1:8082/healthz`、`/readyz`、`http://127.0.0.1:3101/v1/ping` 和代表性公开接口。
|
||||
- `/var/lib/genarrative/health-patrol/status.json` 的最近巡检状态。
|
||||
- 若服务器安装了 `sensors`,附带温度 / 风扇等硬件传感器摘要。
|
||||
|
||||
## 服务操作安全边界
|
||||
|
||||
面板只允许 `start`、`stop`、`restart` 三种动作,并且 unit 名必须匹配安全字符集:
|
||||
|
||||
```text
|
||||
A-Z a-z 0-9 . _ - @ :
|
||||
```
|
||||
|
||||
服务操作会先出现确认弹窗,避免误点。第一版默认列出 Genarrative 生产相关 unit,并提供“其他 unit”输入框;该输入框仍只会执行 `systemctl` 的三种受控动作,不提供任意命令执行入口。
|
||||
|
||||
## 状态判定
|
||||
|
||||
- service / HTTP 探测失败:`CRITICAL`。
|
||||
- 磁盘使用率 `>= 95%`:`CRITICAL`,`>= 85%`:`WARNING`。
|
||||
- 内存使用率 `>= 95%`:`CRITICAL`,`>= 85%`:`WARNING`。
|
||||
- 生产健康巡检状态沿用 `OK / WARNING / CRITICAL`。
|
||||
|
||||
面板状态只是本地巡检视图,最终运维事实仍以服务器上的 systemd、journal、Nginx 日志、`production-health-patrol.mjs` 输出和现有部署文档为准。
|
||||
@@ -0,0 +1,40 @@
|
||||
# SpacetimeDB 连接池租约 Drop 兜底与取消安全
|
||||
|
||||
- 日期:2026-06-11
|
||||
- 关联故障:release 环境 api-server 周期性全量 `spacetime_stage="pool_acquire" elapsed_ms=45000` 超时,`/readyz` 503(`reason=spacetime_unhealthy, stage=pool_acquire`),重启后临时恢复。
|
||||
- 涉及代码:`server-rs/crates/spacetime-client/src/lib.rs`
|
||||
|
||||
## 故障根因
|
||||
|
||||
修复前的连接池存在两个叠加缺陷:
|
||||
|
||||
1. **租约没有 Drop 兜底**。`PooledConnectionLease` 只能通过显式 `release_connection` 归还。当 HTTP 请求方在等待 StDB 回包期间断开(前端超时、用户刷新、Nginx 截断),axum/hyper 会直接丢弃 handler future,租约被 Drop:permit 因 `OwnedSemaphorePermit` 自动归还,但槽位的 `in_use` 标记永远不会复位。
|
||||
2. **acquire 在槽位泄漏后永久空转**。后续请求拿到 permit 后进入 `loop { 扫描槽位; yield_now }`,找不到空闲槽位就无限自旋,且这段自旋不受 `procedure_timeout` 约束,自旋期间 permit 不归还。
|
||||
|
||||
叠加效果:StDB 一旦变慢(请求占用连接接近 45 秒),客户端取消请求的概率大增,每次取消泄漏一个槽位并连带吞掉一个 permit;泄漏数量达到 `pool_size`(release 为 8)后,所有业务请求与健康检查全部在 `pool_acquire` 阶段 45 秒超时,服务表现为"连不上 StDB",只有重启能恢复。
|
||||
|
||||
## 本地复现
|
||||
|
||||
不需要真实 SpacetimeDB,单元测试即可复现机制(位于 `spacetime-client` tests 模块):
|
||||
|
||||
- 修复前:将一个槽位置为 `in_use=true` 后调用 `acquire_connection_with_timeout(200ms)`,acquire 在 5 秒守护窗口内不返回(永久自旋),测试红。
|
||||
- `dropped_lease_releases_slot_and_permit`:模拟"请求被取消、租约未经 release 直接 Drop",断言槽位与 permit 都被复位归还。
|
||||
- `acquire_times_out_at_pool_acquire_when_pool_is_busy`:池内 permit 全部被占用时,acquire 必须在超时窗口内返回 `PoolAcquire + Timeout`,不允许无限等待。
|
||||
|
||||
## 修复方案
|
||||
|
||||
1. `PooledConnectionSlot` 改为 `in_use: AtomicBool + connection: Mutex<Option<PooledConnection>>`,槽位占用标记不再依赖异步锁。
|
||||
2. `PooledConnectionLease` 持有 `Arc<SpacetimeConnectionPool>` 并实现 `Drop`:无论显式归还还是 future 被取消,统一在 Drop 中复位槽位、按 broken 状态决定连接是否回池,permit 随后自动归还。Drop 体先复位 `in_use` 再释放 permit(字段在 Drop 体之后析构),保证新请求拿到 permit 时必有空闲槽位。
|
||||
3. acquire 改为 CAS 抢占槽位:持有 permit 即保证并发持有者不超过 `pool_size`,扫描一轮必然命中空闲槽位,彻底删除自旋循环;建连失败直接返回错误,槽位由租约 Drop 复位。
|
||||
4. `release_connection` 退化为 `drop(lease)`,显式与隐式归还共用同一条兜底路径。
|
||||
|
||||
## 验收
|
||||
|
||||
- `cargo test -p spacetime-client --manifest-path server-rs/Cargo.toml --lib`(35 通过,含上述新测试)
|
||||
- `cargo test -p api-server --manifest-path server-rs/Cargo.toml readyz`(2 通过)
|
||||
- `cargo check -p api-server --manifest-path server-rs/Cargo.toml`
|
||||
|
||||
## 运维提示
|
||||
|
||||
- 此修复解决的是"取消导致的永久泄漏"。StDB 真慢时仍会出现成批 45 秒超时(连接被在途请求合法占用),那是容量/上游问题,应结合 `GENARRATIVE_SPACETIME_POOL_SIZE` 与 StDB 负载排查,不要再怀疑池泄漏。
|
||||
- 健康检查 `/readyz` 在池被在途请求占满时仍可能短暂 503(stage=pool_acquire),恢复后自动转好,无需重启。
|
||||
@@ -1,6 +1,6 @@
|
||||
# server-rs 与 SpacetimeDB 数据契约
|
||||
|
||||
更新时间:`2026-06-10`
|
||||
更新时间:`2026-06-12`
|
||||
|
||||
## 后端主线
|
||||
|
||||
@@ -16,7 +16,7 @@ server-rs + Axum + SpacetimeDB
|
||||
|
||||
`server-rs/Cargo.toml` 是 workspace 事实源。默认构建成员为 `crates/api-server`;第三方依赖版本和 workspace 内 crate path 统一放在 `[workspace.dependencies]`。
|
||||
|
||||
SpacetimeDB 版本口径:当前 Rust crate `spacetimedb`、`spacetimedb-sdk`、`spacetimedb-lib` 统一锁定 `2.4.1`;本地 `spacetime` CLI / standalone、生成的 `spacetime-client` bindings 和容器压测镜像也必须与 `2.4.1` 对齐,避免 BSATN / procedure result 反序列化错配。
|
||||
SpacetimeDB 版本口径:当前 Rust crate `spacetimedb`、`spacetimedb-sdk`、`spacetimedb-lib` 统一锁定 `2.5.0`;本地 `spacetime` CLI / standalone、生成的 `spacetime-client` bindings 和容器压测镜像也必须与 `server-rs/Cargo.toml` 锁定版本对齐,避免 BSATN / procedure result 反序列化错配。遇到版本不匹配时,不继续沿着业务超时排查,先把 CLI / standalone 直接升级到锁定版本并重启后再重试。
|
||||
|
||||
当前主要 crate:
|
||||
|
||||
@@ -113,7 +113,7 @@ npm run check:server-rs-ddd
|
||||
- `server-rs/crates/api-server/src/state.rs` 中的 `PuzzleApiState` 是拼图 HTTP/BFF 的 Feature State,集中暴露 `SpacetimeClient`、`PuzzleGalleryCache`、OSS client、作者查询所需认证服务、拼图 LLM client 和少量 VectorEngine / Agent 配置快照。拼图 handler 只提取 `State<PuzzleApiState>`,不得重新改回 `State<AppState>`。
|
||||
- `server-rs/crates/api-server/src/puzzle.rs` 只作为聚合入口,保留共享 import / 常量、内部模块声明和 handler re-export,不继续承载大段实现。
|
||||
- `server-rs/crates/api-server/src/puzzle/handlers.rs` 承接 Axum handler,负责 extract、鉴权上下文、调用 SpacetimeDB facade / 编排 helper,并返回 HTTP/SSE 响应。
|
||||
- `server-rs/crates/api-server/src/puzzle/draft.rs` 承接表单草稿保存、草稿编译、首关命名、UI 背景 prompt、降级 snapshot 和初始资产就绪校验。
|
||||
- `server-rs/crates/api-server/src/puzzle/draft.rs` 承接表单草稿保存、草稿编译、首关命名、UI 背景 prompt 和初始资产就绪校验。
|
||||
- `server-rs/crates/api-server/src/puzzle/generation.rs` 承接拼图图片与 UI 背景的生成编排、计费包裹和 reference image 路径选择。
|
||||
- `server-rs/crates/api-server/src/puzzle/vector_engine.rs` 承接 VectorEngine 请求体、HTTP 调用、下载 / base64 解码、OSS 写入、asset object / binding 持久化和上游错误归一。
|
||||
- `server-rs/crates/api-server/src/puzzle/mappers.rs` 承接 SpacetimeDB record 到 shared-contracts DTO 的映射。
|
||||
@@ -190,6 +190,10 @@ npm run check:server-rs-ddd
|
||||
1. `creation_entry_type_config.unified_creation_spec_json` 内的 `mudPointCost` 是玩法新建草稿初始生成的泥点成本真相源,同时供入口卡展示和前端余额前置校验使用;旧契约缺失时允许按代码默认成本兜底。
|
||||
2. `api-server` 执行拼图首图生成、抓大鹅完整草稿生成和汪汪声浪初始三图生成时,必须通过 `GET /api/creation-entry/config` 同源配置解析对应玩法成本后再调用钱包扣费 procedure,不得继续使用前端或后端硬编码常量作为实际扣费真相。
|
||||
3. 结果页单图重生成、发布、道具使用和其它独立资产操作仍按各自业务操作成本执行;不要把初始草稿成本误套到这些单次操作上。
|
||||
4. 资产操作的预扣费必须 fail-closed:钱包或 SpacetimeDB 预扣费不可达、超时或返回业务错误时,`api-server` 直接返回错误,不允许继续调用图片、音频、GLB 等外部生成 provider。
|
||||
5. 需要支持 HTTP retry 的计费 ledger id 必须包含当前请求的 `request_id`;前端 `fetchWithApiAuth` 同一次业务请求的静默刷新重试复用同一个 `x-request-id`,后端不得再使用 prompt 指纹或随机 asset id 作为扣费幂等键。
|
||||
6. 外部生成已预扣费但后续失败时必须先同步调用钱包退款;若 SpacetimeDB 暂不可用,退款请求写入 `wallet-refund-outbox` 本地文件并由后台 worker 重放。默认启用,配置项为 `GENARRATIVE_WALLET_REFUND_OUTBOX_ENABLED`、`GENARRATIVE_WALLET_REFUND_OUTBOX_DIR`、`GENARRATIVE_WALLET_REFUND_OUTBOX_BATCH_SIZE`、`GENARRATIVE_WALLET_REFUND_OUTBOX_FLUSH_INTERVAL_MS` 和 `GENARRATIVE_WALLET_REFUND_OUTBOX_MAX_BYTES`。outbox 文件按 refund ledger id 幂等落盘;成功重放后删除,坏文件隔离为 `corrupt-*`。
|
||||
7. 拼图首图后台生成的跨实例互斥锁必须落在 SpacetimeDB `puzzle_background_compile_task` 表,claim id 由 `task_id + request_id` 构成,释放时必须校验 claim id,避免旧后台任务释放新请求抢到的租约。
|
||||
|
||||
## 外部服务与资产
|
||||
|
||||
@@ -229,6 +233,12 @@ npm run check:server-rs-ddd
|
||||
- Rust 结构体:`AiTaskStage`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/ai/stages.rs`
|
||||
|
||||
### `external_generation_job`
|
||||
|
||||
- Rust 结构体:`ExternalGenerationJob`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/external_generation.rs`
|
||||
- 用途:外部生成 worker 的持久任务队列;`GENARRATIVE_EXTERNAL_GENERATION_MODE=queue` 时,`api-server` HTTP 角色只入队,`external-generation-worker` 角色通过 claim lease 领取、续租、执行,并用 `lease_token` 栅栏回写完成 / 失败。拼图 `compile_puzzle_draft` 的前置 `compile_puzzle_agent_draft`、`generate_puzzle_images` 与 `generate_puzzle_ui_background` 的业务写回也在对应 SpacetimeDB transaction 内校验 `job_id + worker_id + lease_token`、job kind、owner 和 source entity,避免过期 worker 写 session / work profile;`GENARRATIVE_EXTERNAL_GENERATION_MODE=inline` 时不创建该队列行,三个 external generation guard 字段必须同时为空才允许 api-server 受控同步写回,半空 guard 仍会拒绝。worker 成功写回业务事实后才能 complete job;业务失败态写回成功后才能 fail job,失败态未写回时保留租约等待后续重领。
|
||||
|
||||
### `ai_text_chunk`
|
||||
|
||||
- Rust 结构体:`AiTextChunk`
|
||||
@@ -640,6 +650,12 @@ npm run check:server-rs-ddd
|
||||
- Rust 结构体:`PuzzleAgentSessionRow`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/puzzle.rs`
|
||||
|
||||
### `puzzle_background_compile_task`
|
||||
|
||||
- Rust 结构体:`PuzzleBackgroundCompileTaskRow`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/puzzle.rs`
|
||||
- 说明:拼图首图后台生成的跨 api-server 实例互斥 claim 表,只保存活动任务租约,不表达最终生成结果;`task_id` 为主键,`claim_id` 用于释放时防止误删新租约,租约超时时间为 30 分钟。
|
||||
|
||||
### `puzzle_event`
|
||||
|
||||
- Rust 结构体:`PuzzleEvent`
|
||||
@@ -659,6 +675,8 @@ npm run check:server-rs-ddd
|
||||
|
||||
- Rust 结构体:`PuzzleWorkProfileRow`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/puzzle.rs`
|
||||
- 说明:拼图作品 profile 表,保存草稿 / 已发布作品的标题、作者、关卡、封面、发布状态、可见性、基础游玩数、点赞数、改造数和积分激励领取状态。
|
||||
- 字段变更:`visible` 控制是否进入公开列表 / 详情、通关后的推荐下一作品候选、公开点赞 / Remix 和正式公开 runtime;默认 `true`。后台隐藏后作品可保留 `publication_status = Published`,但公开消费路径必须按 `Published + visible=true` 判断。
|
||||
|
||||
### `puzzle_clear_agent_session`
|
||||
|
||||
@@ -704,14 +722,14 @@ npm run check:server-rs-ddd
|
||||
- Rust view:`puzzle_gallery_view`
|
||||
- 返回类型:`Vec<PuzzleWorkProfile>`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/puzzle.rs`
|
||||
- 说明:拼图广场公开详情 source / 兼容投影,只暴露 `publication_status = Published` 的作品,但返回完整 `PuzzleWorkProfile`,包含 levels / anchor_pack 等详情级字段;统一公开详情主路径通过 `public_work_detail_entry` 消费该 view,只保留平台详情页展示摘要。
|
||||
- 说明:拼图广场公开详情 source / 兼容投影,只暴露 `publication_status = Published` 且 `visible = true` 的作品,但返回完整 `PuzzleWorkProfile`,包含 levels / anchor_pack 等详情级字段;统一公开详情主路径通过 `public_work_detail_entry` 消费该 view,只保留平台详情页展示摘要。
|
||||
|
||||
### SpacetimeDB view:`puzzle_gallery_card_view`
|
||||
|
||||
- Rust view:`puzzle_gallery_card_view`
|
||||
- 返回类型:`Vec<PuzzleGalleryCardViewRow>`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/puzzle.rs`
|
||||
- 说明:拼图公开列表 source 投影,只暴露前端列表卡片需要的公开字段,不携带 levels / anchor_pack 等详情级载荷;统一公开列表主路径通过 `public_work_gallery_entry` 消费该 view,`/api/runtime/puzzle/gallery` 保留旧 HTTP shape,并从统一 public cache 映射回 `PuzzleGalleryResponse`。
|
||||
- 说明:拼图公开列表 source 投影,只暴露 `publication_status = Published` 且 `visible = true` 的公开字段,不携带 levels / anchor_pack 等详情级载荷;统一公开列表主路径通过 `public_work_gallery_entry` 消费该 view,`/api/runtime/puzzle/gallery` 保留旧 HTTP shape,并从统一 public cache 映射回 `PuzzleGalleryResponse`。
|
||||
|
||||
### 拼图公开列表 HTTP 窗口缓存
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -36,7 +36,7 @@ RPG Agent 结果页发布门禁展示由 `platformRpgAgentResultPreviewModel.ts`
|
||||
|
||||
平台入口、生成页、结果页、作品详情、作品架和运行态的跨流程错误统一收口到 `PlatformErrorDialog`。弹窗必须带明确错误来源,例如某个草稿、某次生成、作品详情或某个游玩实例,并提供复制按钮复制“错误来源 + 错误内容”。页面内不再重复渲染裸错误 banner;表单校验、发布确认弹窗里的局部业务错误可以保留在原弹窗内。生成任务在用户离开生成页后异步失败时,也必须通过同一弹窗通知用户,并把失败消息写入该 session 的草稿 notice,供草稿页和失败重试页恢复使用。
|
||||
|
||||
生成任务在用户离开生成页后异步完成时,平台壳层必须弹出 `PlatformTaskCompletionDialog`。完成弹窗同样要带来源,例如某个草稿或生成会话,并提供复制按钮复制“来源 + 状态”;如果用户仍停留在生成页并被自动带入结果页或试玩页,生成页 / 结果页本身即为完成反馈,不再额外叠加完成弹窗。
|
||||
生成任务在用户离开生成页后异步完成时,平台壳层必须弹出 `PlatformTaskCompletionDialog`。完成弹窗同样要带来源,例如某个草稿或生成会话,并提供复制按钮复制“来源 + 状态”;如果用户仍停留在生成页并被自动带入结果页或试玩页,生成页 / 结果页本身即为完成反馈,不再额外叠加完成弹窗。外部生成队列的用户可见概览统一放在移动端一级 `我的` 页签,生成页 / 进度页只展示当前玩法的阶段、步骤、总进度、错误和重试动作;用户离开生成页后仍可在 `我的` 页查看当前账号可见的排队与生成数量。队列概览只作为等待状态补充,草稿 ready / failed 与作品结果仍以后端玩法 session/detail 回读为准。
|
||||
|
||||
入口配置中的 `open=false` 表示关闭新建创作入口,不表示下架已有草稿、私有作品或公开作品。api-server 的入口熔断只允许拦截新建创作、新建草稿、首次生成入口和 Remix 成草稿等会产生新创作的请求;公开广场列表、公开详情、点赞、已发布作品启动、运行态过程请求、存档 / 浏览记录和已有作品回读不能因为创作入口关闭而返回 `creation_entry_disabled`。平台首页如果遇到旧服务端返回的 `creation_entry_disabled`,只能降级为空列表或隐藏入口,不弹平台级错误弹窗。
|
||||
|
||||
@@ -148,6 +148,7 @@ RPG / 拼图等运行态存档仍以 `/api/profile/save-archives` 的后端列
|
||||
- 拼图运行态背景优先读取当前关卡 `levelBackgroundImageSrc/levelBackgroundImageObjectKey`,旧数据才兼容 `uiBackgroundImageSrc/uiBackgroundImageObjectKey`;本地试玩、直达指定关卡和正式 `next-level` 推进时,目标关卡缺关卡背景时必须继承同作品首个可用关卡背景,仍缺失时才沿用当前运行态快照背景或默认 UI。运行态按钮视觉优先读取当前关卡 `uiSpritesheetImageSrc/uiSpritesheetImageObjectKey`,先按透明 alpha 自动边界检测识别 spritesheet 中的独立按钮展示矩形,再按原图位置从左到右、从上到下映射到返回、设置、下一关、提示、原图、冻结;同一组件还要按较高 alpha 阈值派生紧致点击热区,透明留白和柔边低 alpha 区域尽量不响应点击。检测失败时回退旧固定六格裁切,缺失时才用现有图标按钮兜底。有 spritesheet 时,返回、设置和下一关的点击容器只提供透明点击区,不再叠加默认白色圆形底、胶囊主按钮底或额外文字;下一关按钮在通关弹窗和底部入口中都直接使用 spritesheet 裁切出的 next 素材作为按钮本体。底部提示、原图、冻结三枚素材按检测矩形的原始宽高比显示,不能强行拉伸成正圆或铺满整列。底部道具区不再使用连片胶囊背景,提示、原图、冻结三个按钮均匀分布;运行态只展示按钮素材本身,不额外叠加“提示 / 原图 / 冻结”文字。
|
||||
- 推荐页本身不是登录门禁入口,未登录用户点击底部或侧边栏的推荐 Tab 应直接进入嵌入运行态,不主动打开登录弹窗。推荐页嵌入运行态必须按真实身份分流:已登录用户或本地已有 access token 时,正式 runtime 启动与后续局内动作继续走账号 Bearer;只有确认为匿名访客时才申请并透传 runtime guest token。平台壳统一通过 `buildRecommendRuntimeRequestOptions(...)` 为各玩法的 start / checkpoint / finish / input / drop / click / restart / time-up / leaderboard / next-level 等动作生成局部 request options,不允许每个玩法各写一套匿名分支。后端 `/api/runtime/*` 正式运行态写请求统一接受 `RuntimePrincipal`,可识别账号用户和匿名 runtime guest;推荐卡片的后台读写请求仍使用 local auth impact,避免单卡 401 清空整站登录态。创作、个人作品、删除、发布、Remix 等账号或所有权动作仍保持普通用户鉴权。
|
||||
- 推荐页作品队列只能通过 `buildPlatformRecommendFeedEntries(...)` 生成,首页卡片窗口、桌面推荐格、嵌入 runtime 自动启动和上一条 / 下一条切换都必须消费同一队列。不得在首页和 `PlatformEntryFlowShellImpl` 内分别按“最新列表顺序”和“评分推荐顺序”各算一套相邻作品,否则连续切换会出现视觉上跳过作品或回跳。
|
||||
- 推荐页作品信息区的分享按钮统一唤起发布分享弹窗 `PublishShareModal`,不在推荐卡内部单独拼接分享文案或只做剪贴板复制反馈;拼图推荐作品的 H5 分享链接继续沿用 `/gallery/puzzle/detail?work=...`,其它统一公开作品默认走 `/works/detail?work=...`。微信小程序 WebView 内复制动作必须改为小程序 `pages/web-view/index` 路径并补齐 `targetPath=/works/detail` 与 `work` 参数。推荐页当前 active 作品必须通过 `wx.miniProgram.postMessage` 同步给原生 `web-view` 页,让右上角系统“转发给朋友”和“分享到朋友圈”也使用当前作品参数生成小程序短链背后的 path。微信小程序 WebView 内的推荐页运行态需要启用分享快照安全区,把游戏画面等比缩放并保持在页面中部,避免用户直接点击小程序自带“分享到聊天”时只截到游戏画面局部。
|
||||
- 拼图运行态棋盘不叠加分块蒙版、描边、阴影、选中底色或合并块 SVG 轮廓;拼图片本体需要裁切为圆角形状,单块使用独立圆角裁切,合并块使用 SVG 原生 `clipPath` 裁切整体外轮廓,外凸角和内凹角分别计算半径,内凹角半径要比外凸角更明显以避免手机 WebView 中看起来仍是直角。原图道具只在用户主动确认后打开独立原图查看层,不在当前拼图棋盘上叠加原图。
|
||||
- 拼图运行态拖拽必须完全跟随手指或鼠标位置,`pointermove` 期间即时写入可见拼块的 transform,不依赖等待后端回包、React 重渲染或下一帧动画队列;进入拖动后不展示拼块选中态或“已选择”提示,松手后再提交目标格同步规则真相。
|
||||
- 拼图运行态的提示、设置等点击弹层跟随当前运行态主色主题,使用普通圆角主题面板,不复用像素九宫格素材框。
|
||||
|
||||
@@ -121,9 +121,9 @@ server-rs + Axum + SpacetimeDB
|
||||
|
||||
- Issue tracker 是自托管 Gitea。可用 Gitea UI/API 或 `tea` CLI;不要用 GitHub `gh` 或 GitLab `glab`。
|
||||
- 默认 triage labels:`needs-triage`、`needs-info`、`ready-for-agent`、`ready-for-human`、`wontfix`。
|
||||
- 根 `CONTEXT.md` 是当前领域语言入口;架构决策以本文档和 `.hermes/shared-memory/decision-log.md` 的最新稳定摘要为准。
|
||||
- 根 `CONTEXT.md` 是当前领域语言入口;架构决策以本文档和 `docs/project-memory/shared-memory/decision-log.md` 的最新稳定摘要为准。
|
||||
- `.hermes/` 只保存可进入 Git 的团队共享记忆、计划和可公开 skill,不提交个人 Hermes 配置、会话、密钥、Token 或本地私密路径。
|
||||
- 每次工程修改都应同步更新本目录当前文档;如果产生长期有效知识,再同步 `.hermes/shared-memory/`。
|
||||
- 每次工程修改都应同步更新本目录当前文档;如果产生长期有效知识,再同步 `docs/project-memory/shared-memory/`。
|
||||
|
||||
## 当前文档策略
|
||||
|
||||
|
||||
Reference in New Issue
Block a user