整理项目记忆与Agent RAG入口

迁移项目共享记忆到 docs/project-memory,保留 .hermes 仅作为工具目录

新增 Agent 本地 RAG 索引与上下文包检索脚本

记录 RAG 依赖只安装到 .rag/runtime 并加入忽略规则

同步文档与检查脚本中的项目记忆路径
This commit is contained in:
2026-06-16 16:06:54 +08:00
parent a51e63415f
commit 15a527d7f4
29 changed files with 738 additions and 97 deletions

1
.gitignore vendored
View File

@@ -39,6 +39,7 @@ temp*build*/
/logs /logs
/server-rs/crates/*/logs/ /server-rs/crates/*/logs/
.worktrees/ .worktrees/
.rag/
.env.secrets.local .env.secrets.local
spacetime.local.json spacetime.local.json
deploy/container/api-server.env deploy/container/api-server.env

View File

@@ -1,30 +1,21 @@
# Genarrative 团队 Hermes 共享记忆 # Genarrative Hermes 工具目录
本目录用于在仓库内共享团队级 Hermes 上下文,供 3 名开发人员在各自本地 Hermes 中读取、更新和同步 本目录只保留 Hermes 专用的仓库级工具资源,例如 Hermes skills、plugins 和启用说明。项目知识本体、长期记忆、计划和 TODO 不再放在 `.hermes/`,统一迁移到 `docs/project-memory/`
## 使用原则 ## 使用原则
- `.hermes/` 中只保存可以进入 Git 的团队共享内容。 - `.hermes/` 中只保存 Hermes 工具运行或加载所需内容。
- 项目长期知识、架构约定、排障经验、协作规则、计划和 TODO 统一放在 `docs/project-memory/`
- 不提交个人配置、API Key、会话转录、模型密钥、本地路径密钥等敏感内容。 - 不提交个人配置、API Key、会话转录、模型密钥、本地路径密钥等敏感内容。
- 个人 Hermes 的 `~/.hermes/config.yaml``~/.hermes/.env``~/.hermes/sessions/` 不应复制到本仓库。 - 个人 Hermes 的 `~/.hermes/config.yaml``~/.hermes/.env``~/.hermes/sessions/` 不应复制到本仓库。
- 开发前先阅读本目录下与任务相关的记忆文件;开发后如产生稳定知识,更新对应文档。
- 后续新增的 Markdown 文档文件名必须以分类标签开头,格式为 `【标签名】中文标题-日期.md`,便于团队跨目录检索。 - 后续新增的 Markdown 文档文件名必须以分类标签开头,格式为 `【标签名】中文标题-日期.md`,便于团队跨目录检索。
-本目录内容与 `docs/` 或代码事实冲突,以当前代码和最新 `docs/` 为准,并同步修正过期记忆 - `.hermes/` 中的工具说明与代码或 `docs/` 冲突,以当前代码和最新 `docs/` 为准。
- 阶段性计划、一次性 TODO 和已关闭实验不再长期沉淀为仓库文档;仍有效内容应合并进 `docs/` 当前融合文档或 `.hermes/shared-memory/`
## 目录结构 ## 目录结构
```text ```text
.hermes/ .hermes/
├─ README.md # 说明 ├─ README.md # Hermes 工具目录说明
├─ shared-memory/
│ ├─ project-overview.md # 项目概览与当前技术路线
│ ├─ team-conventions.md # 团队协作约定
│ ├─ development-workflow.md # 开发、测试、提交流程
│ ├─ document-map.md # README / AGENTS / docs 阅读索引
│ ├─ decision-log.md # 长期决策记录
│ ├─ pitfalls.md # 踩坑与排障记录
│ └─ handoff-template.md # 任务交接模板
├─ skills/ # 仓库级 Hermes skills ├─ skills/ # 仓库级 Hermes skills
└─ plugins/ # 仓库级 Hermes plugins需显式启用项目 plugin └─ plugins/ # 仓库级 Hermes plugins需显式启用项目 plugin
``` ```
@@ -71,22 +62,5 @@ HERMES_ENABLE_PROJECT_PLUGINS=1 HERMES_PLUGINS_DEBUG=1 hermes chat -q "请读取
在本仓库中开始复杂任务时,可以先对 Hermes 说: 在本仓库中开始复杂任务时,可以先对 Hermes 说:
```text ```text
请先读取 AGENTS.md 以及 .hermes/shared-memory/ 下与本任务相关的团队共享记忆,再开始分析。若任务完成后产生稳定项目知识,请更新 .hermes/shared-memory/ 对应文件。 请先读取 AGENTS.md 以及 docs/project-memory/shared-memory/ 下与本任务相关的团队共享记忆,再开始分析。若任务完成后产生稳定项目知识,请更新 docs/project-memory/shared-memory/ 对应文件。
``` ```
## 需要沉淀到这里的内容
- 长期有效的架构约定
- 反复会用到的本地开发/测试流程
- 已确认的接口契约或模块边界
- 重要技术决策及原因
- 踩坑、排障方式、验证命令
- 团队协作规则和任务交接规范
## 不应沉淀到这里的内容
- API Key、Token、Cookie、私有密钥
- 个人账号、个人本地绝对路径、个人隐私信息
- 大段临时聊天记录
- 尚未确认的一次性猜测
- 构建产物、日志、缓存、数据库 dump

View File

@@ -284,7 +284,7 @@ fn anonymous_user_cannot_publish_generated_draft() {
| 正式产品验收 / PRD 场景 | 当前 `docs/` 融合文档,必要时新增 `docs/【产品验收】<功能名>BDD场景-YYYY-MM-DD.md` | 产品、测试、开发都需要长期参考的验收标准、用户故事、功能边界。 | | 正式产品验收 / PRD 场景 | 当前 `docs/` 融合文档,必要时新增 `docs/【产品验收】<功能名>BDD场景-YYYY-MM-DD.md` | 产品、测试、开发都需要长期参考的验收标准、用户故事、功能边界。 |
| 技术/API/领域行为场景 | 当前 `docs/` 融合文档,必要时新增 `docs/【技术验收】<功能名>BDD场景-YYYY-MM-DD.md` | 后端 API、领域规则、状态机、SpacetimeDB reducer/table、SSE/异步任务、埋点副作用。 | | 技术/API/领域行为场景 | 当前 `docs/` 融合文档,必要时新增 `docs/【技术验收】<功能名>BDD场景-YYYY-MM-DD.md` | 后端 API、领域规则、状态机、SpacetimeDB reducer/table、SSE/异步任务、埋点副作用。 |
| 自动化 Gherkin feature 文件 | `tests/features/*.feature``e2e/features/*.feature` | 项目已接入 Cucumber/Playwright BDD 等 Gherkin runner 时。未接入前不要随意新建测试 runner 目录。 | | 自动化 Gherkin feature 文件 | `tests/features/*.feature``e2e/features/*.feature` | 项目已接入 Cucumber/Playwright BDD 等 Gherkin runner 时。未接入前不要随意新建测试 runner 目录。 |
| 稳定流程或团队经验 | `.hermes/shared-memory/``.hermes/skills/` | 不是某个功能验收,而是长期可复用的团队流程、坑点、执行规范。 | | 稳定流程或团队经验 | `docs/project-memory/shared-memory/``.hermes/skills/` | 不是某个功能验收,而是长期可复用的团队流程、坑点、执行规范。 |
默认规则: 默认规则:
@@ -311,7 +311,7 @@ e2e/features/invite-code.feature
- 实施计划:当前任务上下文或 `.tmp/<task-name>.md` - 实施计划:当前任务上下文或 `.tmp/<task-name>.md`
- 产品/验收文档:当前 `docs/` 融合文档,必要时新增 `docs/【产品验收】中文标题-YYYY-MM-DD.md` - 产品/验收文档:当前 `docs/` 融合文档,必要时新增 `docs/【产品验收】中文标题-YYYY-MM-DD.md`
- 技术设计:当前 `docs/` 融合文档,必要时新增 `docs/【技术方案】中文标题-YYYY-MM-DD.md` - 技术设计:当前 `docs/` 融合文档,必要时新增 `docs/【技术方案】中文标题-YYYY-MM-DD.md`
- 共享经验或稳定流程:`.hermes/shared-memory/``.hermes/skills/` - 共享经验或稳定流程:`docs/project-memory/shared-memory/``.hermes/skills/`
BDD 文档建议包含: BDD 文档建议包含:
@@ -389,4 +389,4 @@ npm run test -- --run <相关测试文件>
``` ```
- [ ] 若涉及后端 Rust/API按相关 DDD/SpacetimeDB 文档运行对应 cargo/npm/API smoke 验证。 - [ ] 若涉及后端 Rust/API按相关 DDD/SpacetimeDB 文档运行对应 cargo/npm/API smoke 验证。
- [ ] 若产生长期有效经验,已同步到 `.hermes/shared-memory/` 或合适的仓库级 skill。 - [ ] 若产生长期有效经验,已同步到 `docs/project-memory/shared-memory/` 或合适的仓库级 skill。

View File

@@ -21,7 +21,7 @@ metadata:
- 处理 `3000``3101``3102``8082` 等端口被占用导致本地开发栈启动失败。 - 处理 `3000``3101``3102``8082` 等端口被占用导致本地开发栈启动失败。
- 排查 Vite 代理仍指向旧 api-server 端口、前端打开了旧 dev server、后台代理错配。 - 排查 Vite 代理仍指向旧 api-server 端口、前端打开了旧 dev server、后台代理错配。
- 调整 SpacetimeDB standalone、publish、Rust `api-server`、主站 Vite、后台 Vite 的启动顺序。 - 调整 SpacetimeDB standalone、publish、Rust `api-server`、主站 Vite、后台 Vite 的启动顺序。
- 修改本地联调文档或 `.hermes/shared-memory/pitfalls.md` 中的 dev 启动口径。 - 修改本地联调文档或 `docs/project-memory/shared-memory/pitfalls.md` 中的 dev 启动口径。
## 当前端口职责 ## 当前端口职责
@@ -75,7 +75,7 @@ Linux 多用户并发开发时,`GENARRATIVE_DEV_PORT_RANGE` 或 `--port-range`
- `scripts/dev.mjs` - `scripts/dev.mjs`
- `scripts/dev-utils.mjs` - `scripts/dev-utils.mjs`
- `docs/technical/RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md` - `docs/technical/RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md`
- `.hermes/shared-memory/pitfalls.md` - `docs/project-memory/shared-memory/pitfalls.md`
2. 优先改公共端口工具,不要把端口探测逻辑复制到多个脚本。 2. 优先改公共端口工具,不要把端口探测逻辑复制到多个脚本。
3. 修改 `scripts/dev.mjs` 时确认变量顺序:先解析参数和端口,再构造 `SPACETIME_SERVER` / `RUST_SERVER_TARGET`,最后启动对应 service。 3. 修改 `scripts/dev.mjs` 时确认变量顺序:先解析参数和端口,再构造 `SPACETIME_SERVER` / `RUST_SERVER_TARGET`,最后启动对应 service。
4. 修改 watch 时保持模块边界SpacetimeDB 只监听 `spacetime-module` 且改动后重新 publish不重启 standalone 宿主api-server 排除 `spacetime-module`web/admin-web 源码变化交给 Vite 自身 HMR外层调度器不要再监听前端目录重启 Vite。 4. 修改 watch 时保持模块边界SpacetimeDB 只监听 `spacetime-module` 且改动后重新 publish不重启 standalone 宿主api-server 排除 `spacetime-module`web/admin-web 源码变化交给 Vite 自身 HMR外层调度器不要再监听前端目录重启 Vite。
@@ -124,5 +124,5 @@ node scripts/dev-stack-port-utils.mjs resolve-dev-stack spacetime:127.0.0.1:0 ap
- [ ] `npm run dev` 的 SpacetimeDB、publish、api-server、主站 Vite、后台 Vite 都使用实际端口。 - [ ] `npm run dev` 的 SpacetimeDB、publish、api-server、主站 Vite、后台 Vite 都使用实际端口。
- [ ] `npm run dev:web` 在主站端口不可用时能切换到可用端口。 - [ ] `npm run dev:web` 在主站端口不可用时能切换到可用端口。
- [ ] 文档同步更新 `docs/technical/RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md` - [ ] 文档同步更新 `docs/technical/RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md`
- [ ] 长期踩坑同步更新 `.hermes/shared-memory/pitfalls.md` - [ ] 长期踩坑同步更新 `docs/project-memory/shared-memory/pitfalls.md`
- [ ] 修改中文文件后运行 `npm run check:encoding` - [ ] 修改中文文件后运行 `npm run check:encoding`

View File

@@ -47,13 +47,13 @@ description: 在 Genarrative 新增、开放或重构玩法创作工具时,按
先读: 先读:
- `AGENTS.md` - `AGENTS.md`
- `.hermes/shared-memory/` - `docs/project-memory/shared-memory/`
- `CONTEXT.md` - `CONTEXT.md`
- `docs/README.md` - `docs/README.md`
- `docs/【玩法创作】平台入口与玩法链路-2026-05-15.md` - `docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
- 相关玩法 PRD 或设计文档 - 相关玩法 PRD 或设计文档
如果文档不能精确指导字段、契约、资产槽位、生成流程和恢复语义,先补文档再编码。新增长期约定时同步 `.hermes/shared-memory/` 如果文档不能精确指导字段、契约、资产槽位、生成流程和恢复语义,先补文档再编码。新增长期约定时同步 `docs/project-memory/shared-memory/`
### 2. 定玩法边界 ### 2. 定玩法边界

View File

@@ -28,7 +28,7 @@
11.`api-server` 11.`api-server`
- `src/runtime_profile.rs`Query params / parser / handler / response builder。 - `src/runtime_profile.rs`Query params / parser / handler / response builder。
- `src/app.rs`:挂路由,例如 profile 或 admin analytics endpoint选择路径前确认产品定位。 - `src/app.rs`:挂路由,例如 profile 或 admin analytics endpoint选择路径前确认产品定位。
12. 最后更新当前 `docs/` 文档和必要的 `.hermes/shared-memory/` 摘要,并确认 diff 不只是生成物。 12. 最后更新当前 `docs/` 文档和必要的 `docs/project-memory/shared-memory/` 摘要,并确认 diff 不只是生成物。
## 验证命令示例 ## 验证命令示例

View File

@@ -1,16 +1,24 @@
# AGENTS.md # AGENTS.md
## 团队 Hermes 共享记忆 ## 项目共享记忆
- 本仓库的团队级 Hermes 共享内容位于 [`.hermes/`](.hermes/),用于在 3 名开发人员各自本地 Hermes 之间同步长期项目记忆 - 本仓库的团队级项目记忆位于 [`docs/project-memory/`](docs/project-memory/),用于在 3 名开发人员各自本地 Agent 之间同步长期项目知识
- [`.hermes/`](.hermes/) 只保存 Hermes 专用的仓库级工具资源,例如 skills、plugins 和启用说明,不作为项目知识库。
- 开始复杂开发任务前,除阅读本文件外,还应优先读取: - 开始复杂开发任务前,除阅读本文件外,还应优先读取:
- [`.hermes/README.md`](.hermes/README.md) - [`.hermes/README.md`](.hermes/README.md)
- [`.hermes/shared-memory/project-overview.md`](.hermes/shared-memory/project-overview.md) - [`docs/project-memory/README.md`](docs/project-memory/README.md)
- [`.hermes/shared-memory/team-conventions.md`](.hermes/shared-memory/team-conventions.md) - [`docs/project-memory/shared-memory/project-overview.md`](docs/project-memory/shared-memory/project-overview.md)
- [`.hermes/shared-memory/development-workflow.md`](.hermes/shared-memory/development-workflow.md) - [`docs/project-memory/shared-memory/team-conventions.md`](docs/project-memory/shared-memory/team-conventions.md)
- 与任务相关的 [`.hermes/shared-memory/decision-log.md`](.hermes/shared-memory/decision-log.md) 和 [`.hermes/shared-memory/pitfalls.md`](.hermes/shared-memory/pitfalls.md) - [`docs/project-memory/shared-memory/development-workflow.md`](docs/project-memory/shared-memory/development-workflow.md)
- 如果本次任务产生长期有效的架构约定、接口变化、排障经验、开发流程或协作规则,应同步更新 `.hermes/shared-memory/` 中对应文件。 - 与任务相关的 [`docs/project-memory/shared-memory/decision-log.md`](docs/project-memory/shared-memory/decision-log.md) 和 [`docs/project-memory/shared-memory/pitfalls.md`](docs/project-memory/shared-memory/pitfalls.md)
- 仓库 `.hermes/` 只保存可进入 Git 的团队共享内容;禁止提交个人 `~/.hermes` 配置、`.env`、API Key、Token、会话记录、认证文件和本地私密路径 - 如果本次任务产生长期有效的架构约定、接口变化、排障经验、开发流程或协作规则,应同步更新 `docs/project-memory/shared-memory/` 中对应文件
- `.hermes/shared-memory/` 与当前代码或 `docs/` 最新文档冲突,以代码和最新 `docs/` 为准,并同步修正过期共享记忆 - 禁止提交个人 `~/.hermes` 配置、`.env`、API Key、Token、会话记录、认证文件和本地私密路径
-`docs/project-memory/shared-memory/` 与当前代码或 `docs/` 最新文档冲突,以代码和最新 `docs/` 为准,并同步修正过期共享记忆。
## Agent 本地 RAG
- 本仓库提供面向 Agent 的本地文档 RAG入口位于 [`scripts/rag/`](scripts/rag/)RAG 主要用于 Agent 检索项目上下文,不替代人工阅读 `AGENTS.md``docs/README.md``docs/project-memory/`
- 开始复杂任务、跨模块任务或不确定文档入口时Agent 可先用 `npm run rag:search -- --query "问题或关键词" --limit 8 --max-chars 12000` 取候选上下文;需要刷新索引时运行 `npm run rag:index`
- RAG 输出只作为候选上下文。涉及精确代码或文档修改时,仍需打开对应源文件核对;来源冲突时,以当前代码和最新 `docs/` 为准。
- 默认不安装 RAG 运行时依赖,也不把 LanceDB、Transformers.js 或本地 embedding 模型写入根 `package.json`。需要启用时Agent 必须先询问用户是否安装,并在确认后只安装到 gitignored 的 `.rag/runtime/`;详细命令见 [`scripts/rag/README.md`](scripts/rag/README.md)。
## Agent skills ## Agent skills

View File

@@ -9,5 +9,5 @@
## 维护规则 ## 维护规则
- 计划文档只记录可执行阶段、负责人切分、验收门禁和当前状态。 - 计划文档只记录可执行阶段、负责人切分、验收门禁和当前状态。
- 已经稳定为长期约定的内容,应同步沉淀到 `docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``.hermes/shared-memory/` - 已经稳定为长期约定的内容,应同步沉淀到 `docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/project-memory/shared-memory/`
- 若代码事实与计划冲突,以代码和当前融合文档为准,并回写更新本目录。 - 若代码事实与计划冲突,以代码和当前融合文档为准,并回写更新本目录。

View File

@@ -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 首批统一壳 | 已收口 | `puzzle``match3d``jump-hop``wooden-fish` 已接入 `UnifiedCreationPage` / `UnifiedGenerationPage`,竖屏滚动和字段契约已回归。 |
| Phase 1 补充统一壳 | 已收口 | `jump-hop` 也已接入 `UnifiedCreationPage` / `UnifiedGenerationPage`,统一创作页现在接管拼图、抓大鹅、跳一跳和敲木鱼四条入口的可见外壳与滚动。 | | Phase 1 补充统一壳 | 已收口 | `jump-hop` 也已接入 `UnifiedCreationPage` / `UnifiedGenerationPage`,统一创作页现在接管拼图、抓大鹅、跳一跳和敲木鱼四条入口的可见外壳与滚动。 |
| Phase 2 契约与配置治理 | 已完成 | `creationTypes[].unifiedCreationSpec`、前端 fallback、后台配置校验和文档门禁已按现有测试与 schema 检查收口。 | | 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 完成后更新本文档的状态、验收命令和风险。 - 补齐 `当前进度``执行轮次` 和可并行任务表,后续每个 phase 完成后更新本文档的状态、验收命令和风险。
退出条件: 退出条件:

View File

@@ -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。

View File

@@ -994,7 +994,7 @@
- 决策:平台壳在生成失败时必须同时标记草稿 notice 和 pending 作品架条目为 `failed`,不得删除 pending 条目。失败 notice 要保存错误消息并在用户离开生成页后触发带来源的 `PlatformErrorDialog`;作品架本地失败 notice 要覆盖持久化生成中摘要,失败草稿仍显示为草稿卡但不显示“生成中”。点击失败草稿必须优先恢复失败 / 重试页,不能按持久化 `generating` 重新启动生成;拼图契约已允许 `generationStatus=failed`pending 拼图和后端失败回写都按 session 独立落失败态,跳一跳 / 木鱼 / 抓大鹅等也直接映射为 `failed` 或对应失败态。 - 决策:平台壳在生成失败时必须同时标记草稿 notice 和 pending 作品架条目为 `failed`,不得删除 pending 条目。失败 notice 要保存错误消息并在用户离开生成页后触发带来源的 `PlatformErrorDialog`;作品架本地失败 notice 要覆盖持久化生成中摘要,失败草稿仍显示为草稿卡但不显示“生成中”。点击失败草稿必须优先恢复失败 / 重试页,不能按持久化 `generating` 重新启动生成;拼图契约已允许 `generationStatus=failed`pending 拼图和后端失败回写都按 session 独立落失败态,跳一跳 / 木鱼 / 抓大鹅等也直接映射为 `failed` 或对应失败态。
- 影响范围:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx``src/components/custom-world-home/creationWorkShelf.ts``src/components/custom-world-home/CustomWorldCreationHub.tsx`、玩法链路文档和失败态交互测试。 - 影响范围:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx``src/components/custom-world-home/creationWorkShelf.ts``src/components/custom-world-home/CustomWorldCreationHub.tsx`、玩法链路文档和失败态交互测试。
- 验证方式:`node node_modules/vitest/vitest.mjs run src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "failed parallel puzzle|background match3d"`;失败后返回草稿 Tab 应看到对应新增草稿,且没有“生成中”标记;后台失败应弹出错误来源,点击失败草稿应进入失败 / 重试页。 - 验证方式:`node node_modules/vitest/vitest.mjs run src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "failed parallel puzzle|background match3d"`;失败后返回草稿 Tab 应看到对应新增草稿,且没有“生成中”标记;后台失败应弹出错误来源,点击失败草稿应进入失败 / 重试页。
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``.hermes/shared-memory/pitfalls.md` - 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/project-memory/shared-memory/pitfalls.md`
## 2026-05-23 所有玩法生成页统一圆环主视觉 ## 2026-05-23 所有玩法生成页统一圆环主视觉
@@ -1147,7 +1147,7 @@
- 追加决策:`Genarrative-Stdb-Module-Build` 的 Checkout 逻辑应复用 Jenkins GitSCM 已完成的工作区状态。`COMMIT_HASH` 为空或已与当前 `HEAD` 一致时,不再额外执行 `git clean` / `git checkout`;只有需要切到指定且不同的 commit 时才补 fetch、校验和切换避免在 Windows workspace 里二次清理触发权限拒绝。 - 追加决策:`Genarrative-Stdb-Module-Build` 的 Checkout 逻辑应复用 Jenkins GitSCM 已完成的工作区状态。`COMMIT_HASH` 为空或已与当前 `HEAD` 一致时,不再额外执行 `git clean` / `git checkout`;只有需要切到指定且不同的 commit 时才补 fetch、校验和切换避免在 Windows workspace 里二次清理触发权限拒绝。
- 影响范围:`jenkins/Jenkinsfile.production-stdb-module-build` 及后续所有同类 Windows 构建流水线。 - 影响范围:`jenkins/Jenkinsfile.production-stdb-module-build` 及后续所有同类 Windows 构建流水线。
- 验证方式Jenkins 日志中应能看到 `[jenkins-powershell] user:``[jenkins-powershell] exe:`Checkout 阶段会打印当前 `HEAD` 与请求 commit并在 `COMMIT_HASH` 为空或一致时直接继续;不再停在 `PipelineNodeTreeScanner... Cannot run program "powershell"` 或重复 `git clean` 的退出码 5。 - 验证方式Jenkins 日志中应能看到 `[jenkins-powershell] user:``[jenkins-powershell] exe:`Checkout 阶段会打印当前 `HEAD` 与请求 commit并在 `COMMIT_HASH` 为空或一致时直接继续;不再停在 `PipelineNodeTreeScanner... Cannot run program "powershell"` 或重复 `git clean` 的退出码 5。
- 关联文档:`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md``.hermes/shared-memory/pitfalls.md` - 关联文档:`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md``docs/project-memory/shared-memory/pitfalls.md`
## 2026-05-19 tracking outbox 改为 rotate 后异步 flush ## 2026-05-19 tracking outbox 改为 rotate 后异步 flush
@@ -1249,7 +1249,7 @@
- 决策:发布正式世界时,`spacetime-module` 不再把 `session.seed_text` 当作唯一 `setting_text` 兜底,而是调用 `module-custom-world::resolve_custom_world_publish_setting_text(...)` 从 payload、当前草稿 profile 和 seed 依次派生。 - 决策:发布正式世界时,`spacetime-module` 不再把 `session.seed_text` 当作唯一 `setting_text` 兜底,而是调用 `module-custom-world::resolve_custom_world_publish_setting_text(...)` 从 payload、当前草稿 profile 和 seed 依次派生。
- 影响范围RPG / custom-world agent 发布链路、`custom_world_profile` 编译入库、公开 gallery 投影。 - 影响范围RPG / custom-world agent 发布链路、`custom_world_profile` 编译入库、公开 gallery 投影。
- 验证方式:`cargo test -p module-custom-world publish_setting_text --manifest-path server-rs\Cargo.toml``cargo check -p spacetime-module --manifest-path server-rs\Cargo.toml`;本地 api-server 重启后检查 `/healthz` - 验证方式:`cargo test -p module-custom-world publish_setting_text --manifest-path server-rs\Cargo.toml``cargo check -p spacetime-module --manifest-path server-rs\Cargo.toml`;本地 api-server 重启后检查 `/healthz`
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``.hermes/shared-memory/pitfalls.md` - 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/project-memory/shared-memory/pitfalls.md`
## 2026-05-19 系列素材 n\*n 图集抽为 api-server 通用模块 ## 2026-05-19 系列素材 n\*n 图集抽为 api-server 通用模块
@@ -1597,7 +1597,7 @@
- 背景:项目已经全面移除 Maincloud 运行口径,但历史脚本、测试名和文档仍可能让后续开发误用 `api-server:maincloud``GENARRATIVE_SPACETIME_MAINCLOUD_*` - 背景:项目已经全面移除 Maincloud 运行口径,但历史脚本、测试名和文档仍可能让后续开发误用 `api-server:maincloud``GENARRATIVE_SPACETIME_MAINCLOUD_*`
- 决策:`maincloud` / `Maincloud` / `MAINCLOUD` 相关代码、脚本、测试、环境变量、命令和文档要求全部视为历史残留,后续禁止新增、运行或引用;后端 API smoke 统一使用 `npm run dev:api-server` 并检查 `/healthz` - 决策:`maincloud` / `Maincloud` / `MAINCLOUD` 相关代码、脚本、测试、环境变量、命令和文档要求全部视为历史残留,后续禁止新增、运行或引用;后端 API smoke 统一使用 `npm run dev:api-server` 并检查 `/healthz`
- 影响范围:`AGENTS.md``docs/technical/``.hermes/shared-memory/`、后端启动脚本、测试支撑和所有后续工程文档。 - 影响范围:`AGENTS.md``docs/technical/``docs/project-memory/shared-memory/`、后端启动脚本、测试支撑和所有后续工程文档。
- 验证方式:新增或修改后端相关文档时,检查不得要求 `api-server:maincloud``GENARRATIVE_SPACETIME_MAINCLOUD_*`;触碰历史残留时同步删除或改名。 - 验证方式:新增或修改后端相关文档时,检查不得要求 `api-server:maincloud``GENARRATIVE_SPACETIME_MAINCLOUD_*`;触碰历史残留时同步删除或改名。
- 关联文档:`docs/technical/MAINCLOUD_REFERENCE_REMOVAL_POLICY_2026-05-06.md``docs/technical/SPACETIMEDB_CLOUD_CONFIG_REMOVAL_2026-05-02.md` - 关联文档:`docs/technical/MAINCLOUD_REFERENCE_REMOVAL_POLICY_2026-05-06.md``docs/technical/SPACETIMEDB_CLOUD_CONFIG_REMOVAL_2026-05-02.md`
@@ -1660,7 +1660,7 @@
## 2026-05-07 视觉小说 VN-11 负向扫描门禁 ## 2026-05-07 视觉小说 VN-11 负向扫描门禁
- 背景:视觉小说 TXT 模板进入收口后,需要一个可重复执行的守门方式,避免工程代码误入回放能力或外部平台功能。 - 背景:视觉小说 TXT 模板进入收口后,需要一个可重复执行的守门方式,避免工程代码误入回放能力或外部平台功能。
- 决策:新增 `npm run check:visual-novel-vn11`,由 `scripts/check-visual-novel-vn11-negative-scan.mjs` 扫描 `src/``packages/shared/src/``server-rs/crates/``docs/``.hermes/shared-memory/`;工程代码中不允许出现 replay / 回放 / 录制 / 复盘类直出命中;外部平台能力误入只在视觉小说实现路径内检查,避免把平台已有账号、会员、后台等能力误判为视觉小说迁入。 - 决策:新增 `npm run check:visual-novel-vn11`,由 `scripts/check-visual-novel-vn11-negative-scan.mjs` 扫描 `src/``packages/shared/src/``server-rs/crates/``docs/``docs/project-memory/shared-memory/`;工程代码中不允许出现 replay / 回放 / 录制 / 复盘类直出命中;外部平台能力误入只在视觉小说实现路径内检查,避免把平台已有账号、会员、后台等能力误判为视觉小说迁入。
- 影响范围:视觉小说 VN-11 验收、后续 `visual-novel` 增量改动、同类新玩法负向扫描脚本。 - 影响范围:视觉小说 VN-11 验收、后续 `visual-novel` 增量改动、同类新玩法负向扫描脚本。
- 验证方式:执行 `npm run check:visual-novel-vn11`,报告写入 `docs/audits/VN11_NEGATIVE_SCAN_REPORT_2026-05-07.md`;当前扫描结论为工程代码无回放类直出命中,视觉小说实现路径无外部平台能力误入。 - 验证方式:执行 `npm run check:visual-novel-vn11`,报告写入 `docs/audits/VN11_NEGATIVE_SCAN_REPORT_2026-05-07.md`;当前扫描结论为工程代码无回放类直出命中,视觉小说实现路径无外部平台能力误入。
- 关联文档:`docs/prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md``docs/audits/VN11_NEGATIVE_SCAN_REPORT_2026-05-07.md` - 关联文档:`docs/prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md``docs/audits/VN11_NEGATIVE_SCAN_REPORT_2026-05-07.md`
@@ -1677,7 +1677,7 @@
- 背景:视觉小说模板主链已经落地完成,需要把 PRD、表目录、prompt 工具说明、负向扫描报告和维护经验收成新开发者可直接接手的一组文档,避免后续仍回头查旧 TXT 迁移方案。 - 背景:视觉小说模板主链已经落地完成,需要把 PRD、表目录、prompt 工具说明、负向扫描报告和维护经验收成新开发者可直接接手的一组文档,避免后续仍回头查旧 TXT 迁移方案。
- 决策:视觉小说后续维护的正式入口固定为 `AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md``SPACETIMEDB_TABLE_CATALOG.md``VISUAL_NOVEL_PROMPT_AND_LLM_TOOLS_VN03_2026-05-05.md``VISUAL_NOVEL_IMPLEMENTATION_HANDOFF_2026-05-07.md``VISUAL_NOVEL_HANDOFF_AND_MAINTENANCE_2026-05-07.md``VN11_NEGATIVE_SCAN_REPORT_2026-05-07.md`;旧 TXT 迁移文档仅保留历史参考地位。 - 决策:视觉小说后续维护的正式入口固定为 `AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md``SPACETIMEDB_TABLE_CATALOG.md``VISUAL_NOVEL_PROMPT_AND_LLM_TOOLS_VN03_2026-05-05.md``VISUAL_NOVEL_IMPLEMENTATION_HANDOFF_2026-05-07.md``VISUAL_NOVEL_HANDOFF_AND_MAINTENANCE_2026-05-07.md``VN11_NEGATIVE_SCAN_REPORT_2026-05-07.md`;旧 TXT 迁移文档仅保留历史参考地位。
- 影响范围:视觉小说 PRD 收口、技术文档索引、经验文档索引、Hermes 共享记忆和后续维护阅读顺序。 - 影响范围:视觉小说 PRD 收口、技术文档索引、经验文档索引、项目共享记忆和后续维护阅读顺序。
- 验证方式打开上述文档即可获得当前实现边界、表目录、Prompt 口径、负向扫描和维护经验;后续维护不需要把旧 TXT 平台工程文档重新当作实现目标。 - 验证方式打开上述文档即可获得当前实现边界、表目录、Prompt 口径、负向扫描和维护经验;后续维护不需要把旧 TXT 平台工程文档重新当作实现目标。
- 关联文档:`docs/prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md``docs/technical/VISUAL_NOVEL_IMPLEMENTATION_HANDOFF_2026-05-07.md``docs/experience/VISUAL_NOVEL_HANDOFF_AND_MAINTENANCE_2026-05-07.md` - 关联文档:`docs/prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md``docs/technical/VISUAL_NOVEL_IMPLEMENTATION_HANDOFF_2026-05-07.md``docs/experience/VISUAL_NOVEL_HANDOFF_AND_MAINTENANCE_2026-05-07.md`
@@ -1781,9 +1781,9 @@
- 背景:团队有 3 名开发人员,均在各自本地安装 Hermes并需要独立拉取仓库、修改代码、本地测试团队希望形成共享的长期项目记忆。 - 背景:团队有 3 名开发人员,均在各自本地安装 Hermes并需要独立拉取仓库、修改代码、本地测试团队希望形成共享的长期项目记忆。
- 决策:不共享个人 `~/.hermes`,先在 Genarrative 仓库内使用 `.hermes/` 保存可 Git 同步的团队共享记忆、计划和未来 skills。 - 决策:不共享个人 `~/.hermes`,先在 Genarrative 仓库内使用 `.hermes/` 保存可 Git 同步的团队共享记忆、计划和未来 skills。
- 影响范围:`AGENTS.md``.hermes/README.md``.hermes/shared-memory/` - 影响范围:`AGENTS.md``.hermes/README.md``docs/project-memory/shared-memory/`
- 验证方式:任一开发者拉取仓库后,在项目根目录启动 Hermes均可读取同一套 `.hermes/shared-memory/` 文件。 - 验证方式:任一开发者拉取仓库后,在项目根目录启动 Hermes均可读取同一套 `docs/project-memory/shared-memory/` 文件。
- 关联文档:`.hermes/README.md``.hermes/shared-memory/team-conventions.md` - 关联文档:`.hermes/README.md``docs/project-memory/shared-memory/team-conventions.md`
## 2026-04-25 后端唯一落地口径固定为 Rust / SpacetimeDB ## 2026-04-25 后端唯一落地口径固定为 Rust / SpacetimeDB

View File

@@ -1,16 +1,16 @@
# 开发工作流 # 开发工作流
> 用途:给本地 Hermes 和开发人员提供统一的开发、测试、提交流程。具体命令以 `package.json``server-rs/Cargo.toml``AGENTS.md` 和相关 `docs/` 最新文档为准。 > 用途:给本地 Agent 和开发人员提供统一的开发、测试、提交流程。具体命令以 `package.json``server-rs/Cargo.toml``AGENTS.md` 和相关 `docs/` 最新文档为准。
## 标准任务流程 ## 标准任务流程
```text ```text
同步代码 → 读取 AGENTS.md → 读取 .hermes/shared-memory → 查找/完善 docs → 制定计划 → 小步实现 → 本地验证 → 更新文档/记忆 → 提交 同步代码 → 读取 AGENTS.md → 读取 docs/project-memory/shared-memory → 查找/完善 docs → 制定计划 → 小步实现 → 本地验证 → 更新文档/记忆 → 提交
``` ```
## 建议启动方式 ## 建议启动方式
在项目根目录启动 Hermes 在项目根目录启动本地 Agent
```bash ```bash
cd /path/to/Genarrative cd /path/to/Genarrative
@@ -30,7 +30,7 @@ hermes
- [ ] 当前分支是否正确 - [ ] 当前分支是否正确
- [ ] 是否已拉取最新代码 - [ ] 是否已拉取最新代码
- [ ] 是否阅读 `AGENTS.md` - [ ] 是否阅读 `AGENTS.md`
- [ ] 是否阅读 `.hermes/shared-memory/` 相关文件 - [ ] 是否阅读 `docs/project-memory/shared-memory/` 相关文件
- [ ] 是否阅读 `README.md` 中的运行和检查命令 - [ ] 是否阅读 `README.md` 中的运行和检查命令
- [ ] 是否阅读 `docs/README.md` 及任务相关分类 README - [ ] 是否阅读 `docs/README.md` 及任务相关分类 README
- [ ] 是否存在足够具体的 PRD / 设计 / 技术文档 - [ ] 是否存在足够具体的 PRD / 设计 / 技术文档
@@ -161,6 +161,15 @@ Codex 项目级 hook 保存在 `.codex/config.toml` 与 `.codex/hooks/`
个人 token、模型路由、MCP server 仍属于个人环境;需要时由成员本机执行 `codegraph install` 或查看 `codegraph install --print-config codex`,不要提交个人全局配置。 个人 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` - 后端通用用户行为埋点统一通过 `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`
@@ -276,17 +285,17 @@ npm run check:server-rs-ddd
- 工程修改要同步更新对应文档。 - 工程修改要同步更新对应文档。
- 如果没有现成文档,新文档统一放入 `docs/` 下合适分类。 - 如果没有现成文档,新文档统一放入 `docs/` 下合适分类。
- `.hermes/shared-memory/` 只记录高频、长期、团队共享的摘要和索引,不替代完整 PRD/技术文档。 - `docs/project-memory/shared-memory/` 只记录高频、长期、团队共享的摘要和索引,不替代完整 PRD/技术文档。
- 如果 `.hermes/shared-memory/` 与代码或 `docs/` 冲突,以代码和最新 `docs/` 为准,并同步修正共享记忆。 - 如果 `docs/project-memory/shared-memory/` 与代码或 `docs/` 冲突,以代码和最新 `docs/` 为准,并同步修正共享记忆。
## 提交前建议让 Hermes 执行 ## 提交前建议让 Agent 执行
涉及拼图、抓大鹅、敲木鱼统一创作 / 生成链路、Phase 2 之后的跨玩法回归或本地 dev 栈时,先按 `quality-gates/README.md``quality-gates/【玩法创作】跨玩法回归与冒烟门禁-2026-05-30.md` 和对应单项门禁文档执行自动脚本与体验检查。 涉及拼图、抓大鹅、敲木鱼统一创作 / 生成链路、Phase 2 之后的跨玩法回归或本地 dev 栈时,先按 `quality-gates/README.md``quality-gates/【玩法创作】跨玩法回归与冒烟门禁-2026-05-30.md` 和对应单项门禁文档执行自动脚本与体验检查。
```text ```text
请检查当前 git diff指出 请检查当前 git diff指出
1. 是否违反 AGENTS.md 或 .hermes/shared-memory 约定; 1. 是否违反 AGENTS.md 或 docs/project-memory/shared-memory 约定;
2. 是否需要补充 docs 2. 是否需要补充 docs
3. 是否有长期知识需要写入 .hermes/shared-memory 3. 是否有长期知识需要写入 docs/project-memory/shared-memory
4. 建议的测试命令和提交信息。 4. 建议的测试命令和提交信息。
``` ```

View File

@@ -6,7 +6,7 @@
| 场景 | 优先阅读 | | 场景 | 优先阅读 |
| --- | --- | | --- | --- |
| 建立项目背景 | `README.md``AGENTS.md``.hermes/shared-memory/project-overview.md` | | 建立项目背景 | `README.md``AGENTS.md``docs/project-memory/shared-memory/project-overview.md` |
| 找当前文档 | `docs/README.md` | | 找当前文档 | `docs/README.md` |
| 产品、命名、UI、协作和废弃路线 | `docs/【项目基线】当前产品与工程约束-2026-05-15.md` | | 产品、命名、UI、协作和废弃路线 | `docs/【项目基线】当前产品与工程约束-2026-05-15.md` |
| 后端、DDD、API、SpacetimeDB schema 和表目录 | `docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md` | | 后端、DDD、API、SpacetimeDB schema 和表目录 | `docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md` |
@@ -21,7 +21,7 @@
通用复杂任务: 通用复杂任务:
1. `AGENTS.md` 1. `AGENTS.md`
2. `.hermes/shared-memory/` 2. `docs/project-memory/shared-memory/`
3. `docs/README.md` 3. `docs/README.md`
4. 与任务匹配的当前融合文档 4. 与任务匹配的当前融合文档
@@ -50,5 +50,5 @@
- 新增工程实现时,如果已有对应当前文档,必须同步更新。 - 新增工程实现时,如果已有对应当前文档,必须同步更新。
- 如果没有合适位置,新文档文件名必须使用 `【标签名】中文标题-YYYY-MM-DD.md` - 如果没有合适位置,新文档文件名必须使用 `【标签名】中文标题-YYYY-MM-DD.md`
- 阶段性流水账、一次性修复记录和已关闭实验不要再新增为长期文档。 - 阶段性流水账、一次性修复记录和已关闭实验不要再新增为长期文档。
- 阶段性计划和一次性 TODO 不再作为长期文档目录;需要保留的决策、流程和坑点应进入 `docs/` 当前文档或 `.hermes/shared-memory/` - 阶段性计划和一次性 TODO 不再作为长期文档目录;需要保留的决策、流程和坑点应进入 `docs/` 当前文档或 `docs/project-memory/shared-memory/`
- 如果文档与代码冲突,先确认代码事实,再更新过期文档和共享记忆。 - 如果文档与代码冲突,先确认代码事实,再更新过期文档和共享记忆。

View File

@@ -50,4 +50,4 @@
## 是否需要更新团队记忆 ## 是否需要更新团队记忆
- [ ] 不需要 - [ ] 不需要
- [ ] 需要,建议更新:`.hermes/shared-memory/...` - [ ] 需要,建议更新:`docs/project-memory/shared-memory/...`

View File

@@ -131,8 +131,8 @@
## “我的”页每日任务卡不要硬编码进度,也不要跨日保留旧状态 ## “我的”页每日任务卡不要硬编码进度,也不要跨日保留旧状态
- 现象:用户完成或领取每日任务后,任务中心弹窗里的任务状态已经变化,但“我的”页卡片仍显示 `0 / 1` 和“去完成”。 - 现象:用户完成或领取每日任务后,任务中心弹窗里的任务状态已经变化,但“我的”页卡片仍显示 `0 / 1` 和“去完成”。
- 原因:卡片首版只写了静态展示文案,没有读取 `/api/profile/tasks` 返回的 `ProfileTaskCenterResponse`,领取接口返回的新 `center` 也只用于弹窗;后来虽然后端按北京时间 0 点切换业务日,但前端停留在“我的”页时不会跨日刷新,可能继续展示上一日已领取状态。 - 原因:卡片首版只写了静态展示文案,没有读取 `/api/profile/tasks` 返回的 `ProfileTaskCenterResponse`,领取接口返回的新 `center` 也只用于弹窗;后来虽然后端按北京时间 0 点切换业务日,但前端停留在“我的”页时不会跨日刷新,可能继续展示上一日已领取状态。若认证成功后把 `daily_login` 当普通埋点写入,或历史 `profile_task_config` 仍保留旧 `profile.login.daily` 事件键,新业务日也可能写了登录事件却查不到任务进度。
- 处理:进入“我的”页时读取任务中心,卡片用当前可操作任务或已领取任务派生奖励、进度条和操作状态;`claimRpgProfileTaskReward(...)` 成功后用响应里的 `center` 覆盖本地任务中心;停留在“我的”页跨过北京时间 0 点时,先非阻断 refresh 登录态写入新业务日 `daily_login`,再重拉任务中心。 - 处理:进入“我的”页时读取任务中心,卡片用当前可操作任务或已领取任务派生奖励、进度条和操作状态;`claimRpgProfileTaskReward(...)` 成功后用响应里的 `center` 覆盖本地任务中心;停留在“我的”页跨过北京时间 0 点时,先非阻断 refresh 登录态写入新业务日 `daily_login`,再重拉任务中心。后端认证成功统一走 `SpacetimeClient::record_daily_login_tracking_event(...)` 与 SpacetimeDB 专用 `record_daily_login_tracking_event_and_return`,默认每日登录任务读取时会把结算字段自愈到 canonical `daily_login`
- 验证:`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx` 应覆盖卡片从后端任务摘要显示 `1 / 1`、领取后显示已完成,以及北京时间 0 点自动 refresh 后重拉任务中心。 - 验证:`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx` 应覆盖卡片从后端任务摘要显示 `1 / 1`、领取后显示已完成,以及北京时间 0 点自动 refresh 后重拉任务中心。
- 关联:`src/components/rpg-entry/RpgEntryHomeView.tsx``src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx``docs/【项目基线】当前产品与工程约束-2026-05-15.md` - 关联:`src/components/rpg-entry/RpgEntryHomeView.tsx``src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
@@ -2006,7 +2006,7 @@
- 原因:旧 worktree 的 `api-server``spacetime-standalone` 和 Vite 还活着,或者当前 worktree 的本机 SpacetimeDB CLI 默认版本低于仓库锁定版本,`scripts/dev.mjs` 会先校验版本再启动并直接报错退出。 - 原因:旧 worktree 的 `api-server``spacetime-standalone` 和 Vite 还活着,或者当前 worktree 的本机 SpacetimeDB CLI 默认版本低于仓库锁定版本,`scripts/dev.mjs` 会先校验版本再启动并直接报错退出。
- 处理:先停掉占用端口的旧进程,再执行 `spacetime version list`,确认本机 CLI/standalone 与 `server-rs/Cargo.toml` 锁定版本一致;不一致时先直接升级 / 切换到锁定版本,再重新启动 `npm run dev -- --no-interactive --web-port 3001 --api-port 8083 --spacetime-port 3103 --admin-web-port 3104` - 处理:先停掉占用端口的旧进程,再执行 `spacetime version list`,确认本机 CLI/standalone 与 `server-rs/Cargo.toml` 锁定版本一致;不一致时先直接升级 / 切换到锁定版本,再重新启动 `npm run dev -- --no-interactive --web-port 3001 --api-port 8083 --spacetime-port 3103 --admin-web-port 3104`
- 验证:`http://127.0.0.1:3001/``http://127.0.0.1:8083/healthz``http://127.0.0.1:3103/v1/ping` 都返回 200且进程命令行指向当前 worktree 路径而不是别的仓库。 - 验证:`http://127.0.0.1:3001/``http://127.0.0.1:8083/healthz``http://127.0.0.1:3103/v1/ping` 都返回 200且进程命令行指向当前 worktree 路径而不是别的仓库。
- 关联:`scripts/dev.mjs``.hermes/shared-memory/pitfalls.md``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md` - 关联:`scripts/dev.mjs``docs/project-memory/shared-memory/pitfalls.md``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
## 微信历史孤儿作品不要让新注册账号顶替 ## 微信历史孤儿作品不要让新注册账号顶替

View File

@@ -1,22 +1,22 @@
# 团队协作约定 # 团队协作约定
> 用途:约定 3 名开发人员在各自本地 Hermes 中协作开发、共享项目记忆的方式。 > 用途:约定 3 名开发人员在各自本地开发环境和 Agent 中协作开发、共享项目记忆的方式。
## 基本模式 ## 基本模式
- 每位开发人员在自己的电脑上使用本地 Hermes - 每位开发人员在自己的电脑上使用本地 Agent
- 每位开发人员本地拉取同一个项目仓库,独立修改代码、运行测试、提交分支。 - 每位开发人员本地拉取同一个项目仓库,独立修改代码、运行测试、提交分支。
- 团队共享内容优先放在本仓库 `.hermes/``docs/` 中,通过 Git 同步。 - 团队共享内容优先放在本仓库 `docs/project-memory/``docs/` 中,通过 Git 同步。
- 不共享个人 `~/.hermes` 目录。 - 不共享个人 `~/.hermes` 目录。
## 共享与禁止共享 ## 共享与禁止共享
推荐共享: 推荐共享:
- `.hermes/shared-memory/` 团队级长期记忆 - `docs/project-memory/shared-memory/` 团队级长期记忆
- `.hermes/plans/` 阶段性实施计划 - `docs/project-memory/plans/` 阶段性实施计划
- `.hermes/todos/` 已确定需要执行、但尚未进入实施的共享 TODO 计划 - `docs/project-memory/todos/` 已确定需要执行、但尚未进入实施的共享 TODO 计划
- `.hermes/skills/` 未来可复用仓库级 skills - `.hermes/skills/` Hermes 专用仓库级 skills
- `docs/` 中 PRD、设计、技术、经验、审计、查询手册 - `docs/` 中 PRD、设计、技术、经验、审计、查询手册
- `AGENTS.md` 项目级 Agent 约束 - `AGENTS.md` 项目级 Agent 约束
@@ -33,7 +33,7 @@
1. 拉取最新代码。 1. 拉取最新代码。
2. 阅读 `AGENTS.md` 2. 阅读 `AGENTS.md`
3. 阅读 `.hermes/shared-memory/` 中与任务相关的文件。 3. 阅读 `docs/project-memory/shared-memory/` 中与任务相关的文件。
4. 阅读 `docs/README.md` 和任务相关分类 README。 4. 阅读 `docs/README.md` 和任务相关分类 README。
5. 阅读对应 PRD、设计、技术、经验或审计文档。 5. 阅读对应 PRD、设计、技术、经验或审计文档。
6. 如果文档不足以指导编码,先补充或修正文档。 6. 如果文档不足以指导编码,先补充或修正文档。
@@ -54,8 +54,8 @@
1. 运行与修改范围匹配的测试或验证命令。 1. 运行与修改范围匹配的测试或验证命令。
2. 更新相关 `docs/` 文档。 2. 更新相关 `docs/` 文档。
3. 新增或沉淀 Markdown 文档时,确认文件名已使用 `【标签名】` 前缀。 3. 新增或沉淀 Markdown 文档时,确认文件名已使用 `【标签名】` 前缀。
4. 若产生长期有效知识,更新 `.hermes/shared-memory/` 4. 若产生长期有效知识,更新 `docs/project-memory/shared-memory/`
5. 若形成可复用流程,考虑沉淀到 `.hermes/skills/` 5. 若形成 Hermes 专用可复用流程,考虑沉淀到 `.hermes/skills/`
6. 提交代码时,提交标题使用中文;标题后逐行写明本次提交修改了什么,每条变更单独一行。 6. 提交代码时,提交标题使用中文;标题后逐行写明本次提交修改了什么,每条变更单独一行。
## 文档阅读顺序 ## 文档阅读顺序
@@ -64,7 +64,7 @@
1. `README.md` 1. `README.md`
2. `AGENTS.md` 2. `AGENTS.md`
3. `.hermes/shared-memory/` 3. `docs/project-memory/shared-memory/`
4. `docs/README.md` 4. `docs/README.md`
5. `docs/experience/README.md` 5. `docs/experience/README.md`
6. `docs/audits/README.md` 6. `docs/audits/README.md`

View File

@@ -4,7 +4,7 @@
`PlatformEntryFlowShellImpl.tsx` 曾内联维护拼图生成完成后刷新恢复的两个纯函数:`normalizeRecoveredPuzzleDraftSession``hasRecoverableGeneratedPuzzleDraft`。旧逻辑只要草稿有 `coverImageSrc`、首关 cover 或候选图,就会把恢复会话的 draft 和首关 `generationStatus` 抬成 `ready`,再进入结果页。 `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` 的发布门槛收紧另作后续切片,不混入本次前端模型收口。 本切片先修前端恢复链路:只有完整首关资产包存在时,恢复流程才视为可完成。后端 `build_result_preview` / `validate_publish_requirements` / `is_puzzle_session_snapshot_publish_ready` 的发布门槛收紧另作后续切片,不混入本次前端模型收口。

View File

@@ -5,7 +5,7 @@
## 标准开发流程 ## 标准开发流程
```text ```text
同步代码 -> 读 AGENTS.md / .hermes 共享记忆 -> 查当前 docs -> 小步实现 -> 本地验证 -> 更新 docs / .hermes -> 提交 同步代码 -> 读 AGENTS.md / docs/project-memory 共享记忆 -> 查当前 docs -> 小步实现 -> 本地验证 -> 更新 docs / project-memory -> 提交
``` ```
如果当前文档不足以指导编码,先补文档再落地工程修改。 如果当前文档不足以指导编码,先补文档再落地工程修改。
@@ -141,6 +141,8 @@ npm run spacetime:generate
项目已安装 `@colbymchenry/codegraph` 作为开发期依赖,用于在本地生成语义代码索引,辅助 AI / IDE 做符号搜索、调用关系和影响范围分析。索引目录为 `.codegraph/`,其中 `config.json` 可提交,数据库、缓存和日志由 `.codegraph/.gitignore` 保持本机私有。 项目已安装 `@colbymchenry/codegraph` 作为开发期依赖,用于在本地生成语义代码索引,辅助 AI / IDE 做符号搜索、调用关系和影响范围分析。索引目录为 `.codegraph/`,其中 `config.json` 可提交,数据库、缓存和日志由 `.codegraph/.gitignore` 保持本机私有。
项目文档 RAG 索引使用 `scripts/rag/` 下的脚本和本地 `.rag/` 运行时目录,主要供 Agent 检索项目上下文,不作为人工阅读入口。默认不安装 RAG 相关依赖,不把 LanceDB、Transformers.js 或本地 embedding 模型写入根 `package.json`需要启用时Agent 必须先询问用户是否安装,并在用户确认后只安装到 gitignored 的 `.rag/runtime/`。索引范围默认包含 `AGENTS.md``CONTEXT.md``docs/project-memory/``docs/`,不把 `.hermes/` 工具目录作为项目知识库索引源。
首次拉取或需要重建索引时: 首次拉取或需要重建索引时:
```bash ```bash
@@ -422,7 +424,7 @@ cargo test -p platform-auth --manifest-path server-rs/Cargo.toml aliyun_send_sms
- `profile_task_reward_claim` - `profile_task_reward_claim`
- `profile_wallet_ledger` - `profile_wallet_ledger`
个人任务首版 scope 仅支持 `user`。每日登录任务按北京时间自然日 0 点重置;用户已登录并停留在“我的”页跨日时,前端需要先非阻断调用 refresh session 以写入新业务日 `daily_login`,再请求 `/api/profile/tasks` 刷新任务中心。后台、RPG、大鱼吃小鱼、Visual Novel、Story、Combat 等特定链路按 tracking 中间件排除规则处理;作品游玩统一使用 `work_play_start` 个人任务首版 scope 仅支持 `user`。每日登录任务按北京时间自然日 0 点重置;用户已登录并停留在“我的”页跨日时,前端需要先非阻断调用 refresh session 以写入新业务日 `daily_login`,再请求 `/api/profile/tasks` 刷新任务中心。认证成功后的 `daily_login` 必须通过 `SpacetimeClient::record_daily_login_tracking_event(...)` 调用 SpacetimeDB 专用 `record_daily_login_tracking_event_and_return` procedure由数据库事务时间生成当日幂等事件并推进任务进度不要改回普通 `record_tracking_event_after_success`、tracking outbox 或旧 `profile.login.daily` 事件键。后台、RPG、大鱼吃小鱼、Visual Novel、Story、Combat 等特定链路按 tracking 中间件排除规则处理;作品游玩统一使用 `work_play_start`
外部 API 失败审计复用 `tracking_event`,不新增表。失败事件优先写入本机 tracking outbox再由后台 worker 批量落库;如果 outbox 因权限、磁盘或保护阈值不可写,会回退同步直写 SpacetimeDB。`metadata_json` 包含 endpoint、operation、failureStage、statusCode、statusClass、timeout、retryable、errorMessage、errorSource、latencyMs、promptChars、referenceImageCount、imageModel、rawExcerpt、userId、profileId 和 requestId其中 `userId` 是触发生成的用户,`profileId` 是调用方传入的草稿 / 作品 / 场景作用域,`requestId` 用于回查同一次 HTTP 请求日志,入口拿不到上下文时允许为空。常用查询: 外部 API 失败审计复用 `tracking_event`,不新增表。失败事件优先写入本机 tracking outbox再由后台 worker 批量落库;如果 outbox 因权限、磁盘或保护阈值不可写,会回退同步直写 SpacetimeDB。`metadata_json` 包含 endpoint、operation、failureStage、statusCode、statusClass、timeout、retryable、errorMessage、errorSource、latencyMs、promptChars、referenceImageCount、imageModel、rawExcerpt、userId、profileId 和 requestId其中 `userId` 是触发生成的用户,`profileId` 是调用方传入的草稿 / 作品 / 场景作用域,`requestId` 用于回查同一次 HTTP 请求日志,入口拿不到上下文时允许为空。常用查询:

View File

@@ -121,9 +121,9 @@ server-rs + Axum + SpacetimeDB
- Issue tracker 是自托管 Gitea。可用 Gitea UI/API 或 `tea` CLI不要用 GitHub `gh` 或 GitLab `glab` - 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` - 默认 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/` 只保存可进入 Git 的团队共享记忆、计划和可公开 skill不提交个人 Hermes 配置、会话、密钥、Token 或本地私密路径。
- 每次工程修改都应同步更新本目录当前文档;如果产生长期有效知识,再同步 `.hermes/shared-memory/` - 每次工程修改都应同步更新本目录当前文档;如果产生长期有效知识,再同步 `docs/project-memory/shared-memory/`
## 当前文档策略 ## 当前文档策略

View File

@@ -65,6 +65,8 @@
"codegraph:index": "codegraph index .", "codegraph:index": "codegraph index .",
"codegraph:sync": "codegraph sync .", "codegraph:sync": "codegraph sync .",
"codegraph:status": "codegraph status .", "codegraph:status": "codegraph status .",
"rag:index": "node scripts/rag/index-docs.mjs",
"rag:search": "node scripts/rag/search-docs.mjs",
"database:backup:oss": "node scripts/database-backup-to-oss.mjs" "database:backup:oss": "node scripts/database-backup-to-oss.mjs"
}, },
"dependencies": { "dependencies": {

View File

@@ -7,7 +7,7 @@ const reportPath = join(repoRoot, '.tmp', 'VN11_NEGATIVE_SCAN_REPORT_2026-05-07.
const documentTargets = [ const documentTargets = [
'docs', 'docs',
'.hermes/shared-memory', 'docs/project-memory/shared-memory',
]; ];
const visualNovelImplementationTargets = [ const visualNovelImplementationTargets = [
@@ -202,7 +202,7 @@ const reportLines = [
'## 扫描范围', '## 扫描范围',
'', '',
'- 视觉小说工程代码视觉小说前端、service、shared contracts、Rust contracts、module、api-server、SpacetimeDB schema 与 facade 路径', '- 视觉小说工程代码视觉小说前端、service、shared contracts、Rust contracts、module、api-server、SpacetimeDB schema 与 facade 路径',
'- 文档与共享记忆:`docs/`、`.hermes/shared-memory/`', '- 文档与共享记忆:`docs/`、`docs/project-memory/shared-memory/`',
'- 外部平台误入复核视觉小说前端、service、shared contracts、Rust contracts、module、api-server、SpacetimeDB schema 与 facade 路径', '- 外部平台误入复核视觉小说前端、service、shared contracts、Rust contracts、module、api-server、SpacetimeDB schema 与 facade 路径',
'', '',
'## 扫描结论', '## 扫描结论',

83
scripts/rag/README.md Normal file
View File

@@ -0,0 +1,83 @@
# 本地 RAG
本目录提供项目文档的本地 RAG 索引脚本,主要供 Agent 在执行任务前后检索项目上下文使用。它不是新的人工阅读入口;开发者仍按 `AGENTS.md``docs/README.md``docs/project-memory/` 阅读项目文档。
项目默认不安装 RAG 运行时依赖,也不把 LanceDB、Transformers.js 或本地模型写入根 `package.json`
## 运行时依赖
RAG 运行时依赖安装在 gitignored 的 `.rag/runtime/`,模型缓存和向量库也都在 `.rag/` 下。
Agent 需要启用 RAG 检索时,应先询问用户是否安装本地依赖。用户确认后执行:
```bash
mkdir -p .rag/runtime
npm init -y --prefix .rag/runtime
npm install --prefix .rag/runtime @lancedb/lancedb@0.30.0 @huggingface/transformers@4.2.0
```
不要把这些依赖加入根 `package.json`
## 索引
首次运行会下载本地 embedding 模型到 `.rag/models/`。默认模型为 `Xenova/multilingual-e5-small`,适合中英文混合文档。
```bash
npm run rag:index
```
小样本 smoke
```bash
npm run rag:index -- --limit-files 3
```
只查看分片,不加载模型:
```bash
npm run rag:index -- --limit-files 3 --dry-run
```
## 搜索
默认输出 Agent 上下文包,包含来源、分数、候选上下文和使用规则:
```bash
npm run rag:search -- --query "SpacetimeDB schema 变更默认值" --limit 8
```
可限制上下文包大小:
```bash
npm run rag:search -- --query "SpacetimeDB schema 变更默认值" --limit 8 --max-chars 8000
```
可输出结构化格式,便于 Agent 或其它工具解析:
```bash
npm run rag:search -- --query "SpacetimeDB schema 变更默认值" --format json
npm run rag:search -- --query "SpacetimeDB schema 变更默认值" --format jsonl
```
如只想看短摘要,可使用旧式文本结果:
```bash
npm run rag:search -- --query "SpacetimeDB schema 变更默认值" --format text
```
Agent 使用规则:
- 把 RAG 输出视为候选上下文,不直接当作最终事实。
- 需要精确改代码或文档时,仍要打开对应源文件核对。
- 来源冲突时,以当前代码和最新 `docs/` 为准。
## 索引范围
索引范围由 `scripts/rag/rag-config.json` 配置,默认包含:
- `AGENTS.md`
- `CONTEXT.md`,如果存在
- `docs/project-memory/`
- `docs/`
`.hermes/` 是 Hermes 工具目录,不作为项目知识库索引源。

View File

@@ -0,0 +1,68 @@
import { mkdirSync, readFileSync } from 'node:fs';
import { join } from 'node:path';
import {
buildChunkId,
chunkText,
createEmbedder,
extractTitle,
hasFlag,
listSourceFiles,
loadRagRuntime,
parseLimitFiles,
readConfig,
repoRoot,
} from './rag-utils.mjs';
const config = readConfig();
const limitFiles = parseLimitFiles(process.argv);
const dryRun = hasFlag(process.argv, '--dry-run');
const files = listSourceFiles(config, limitFiles);
const rows = [];
for (const file of files) {
const text = readFileSync(file.path, 'utf8');
const title = extractTitle(text, file.rel);
for (const chunk of chunkText(text, config.chunk ?? {})) {
rows.push({
id: buildChunkId(file.rel, chunk.index),
path: file.rel,
title,
chunk_index: chunk.index,
source_weight: file.weight,
text: chunk.text,
});
}
}
console.log(`[rag:index] source files=${files.length}, chunks=${rows.length}`);
if (dryRun) {
for (const row of rows.slice(0, 10)) {
console.log(`- ${row.id} ${row.title}`);
}
process.exit(0);
}
if (rows.length === 0) {
throw new Error('No RAG chunks found.');
}
const { lancedb, transformers } = await loadRagRuntime(config);
const embed = await createEmbedder(transformers, config.model);
for (let index = 0; index < rows.length; index += 1) {
rows[index].vector = await embed(rows[index].text, 'passage');
if ((index + 1) % 25 === 0 || index + 1 === rows.length) {
console.log(`[rag:index] embedded ${index + 1}/${rows.length}`);
}
}
mkdirSync(join(repoRoot, config.databaseDir), { recursive: true });
const db = await lancedb.connect(join(repoRoot, config.databaseDir));
await db.createTable(config.tableName, rows, { mode: 'overwrite' });
console.log(
`[rag:index] wrote table=${config.tableName}, db=${config.databaseDir}, model=${config.model}`,
);

View File

@@ -0,0 +1,46 @@
{
"runtimeDir": ".rag/runtime",
"databaseDir": ".rag/lancedb",
"modelCacheDir": ".rag/models",
"tableName": "project_docs",
"model": "Xenova/multilingual-e5-small",
"chunk": {
"maxChars": 1600,
"overlapChars": 220
},
"sources": [
{
"path": "AGENTS.md",
"weight": 1.4
},
{
"path": "CONTEXT.md",
"weight": 1.3,
"optional": true
},
{
"path": "docs/project-memory",
"weight": 1.35
},
{
"path": "docs",
"weight": 1.0
}
],
"exclude": [
".git/",
".rag/",
".hermes/",
".codegraph/",
".app/",
"node_modules/",
"dist/",
"build/",
"coverage/",
"logs/",
"output/",
"server-rs/target/",
"server-rs/target-",
"tmp/"
]
}

221
scripts/rag/rag-utils.mjs Normal file
View File

@@ -0,0 +1,221 @@
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
import { dirname, extname, join, relative, resolve } from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
export const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), '../..');
export const configPath = join(repoRoot, 'scripts/rag/rag-config.json');
export function readConfig() {
return JSON.parse(readFileSync(configPath, 'utf8'));
}
export function normalizePath(filePath) {
return filePath.replace(/\\/gu, '/');
}
export function repoRelative(filePath) {
return normalizePath(relative(repoRoot, filePath));
}
export function resolveRepoPath(filePath) {
return resolve(repoRoot, filePath);
}
export function getRuntimeNodeModules(config) {
return join(repoRoot, config.runtimeDir, 'node_modules');
}
export function assertLocalRuntime(config) {
const runtimeModules = getRuntimeNodeModules(config);
const hasLance = existsSync(join(runtimeModules, '@lancedb/lancedb'));
const hasTransformers = existsSync(join(runtimeModules, '@huggingface/transformers'));
if (hasLance && hasTransformers) {
return runtimeModules;
}
throw new Error(
[
'本地 RAG 运行时依赖尚未安装。',
'按项目约定RAG 依赖不进入根 package.json也不默认安装。',
'需要启用 RAG 时Agent 必须先询问用户,然后在本地 gitignored 目录安装:',
'',
` mkdir -p ${config.runtimeDir}`,
` npm init -y --prefix ${config.runtimeDir}`,
` npm install --prefix ${config.runtimeDir} @lancedb/lancedb@0.30.0 @huggingface/transformers@4.2.0`,
'',
`当前检查目录:${runtimeModules}`,
].join('\n'),
);
}
export async function loadRagRuntime(config) {
const runtimeModules = assertLocalRuntime(config);
const lancedb = await import(
pathToFileURL(join(runtimeModules, '@lancedb/lancedb/dist/index.js')).href
);
const transformers = await import(
pathToFileURL(
join(runtimeModules, '@huggingface/transformers/dist/transformers.node.mjs'),
).href
);
transformers.env.cacheDir = join(repoRoot, config.modelCacheDir);
transformers.env.useFSCache = true;
transformers.env.allowRemoteModels = true;
return { lancedb, transformers };
}
export function listSourceFiles(config, limitFiles = Number.POSITIVE_INFINITY) {
const excluded = config.exclude ?? [];
const files = [];
const seen = new Set();
for (const source of config.sources ?? []) {
const sourcePath = resolveRepoPath(source.path);
if (!existsSync(sourcePath)) {
if (!source.optional) {
throw new Error(`RAG source not found: ${source.path}`);
}
continue;
}
for (const filePath of walkTextFiles(sourcePath, excluded)) {
const rel = repoRelative(filePath);
if (seen.has(rel)) {
continue;
}
seen.add(rel);
files.push({ path: filePath, rel, weight: source.weight ?? 1 });
if (files.length >= limitFiles) {
return files;
}
}
}
return files;
}
function walkTextFiles(targetPath, excluded) {
const stat = statSync(targetPath);
if (stat.isFile()) {
return shouldReadFile(targetPath, excluded) ? [targetPath] : [];
}
const files = [];
const walk = (dir) => {
for (const name of readdirSync(dir)) {
const child = join(dir, name);
const rel = `${repoRelative(child)}${statSync(child).isDirectory() ? '/' : ''}`;
if (excluded.some((prefix) => rel.startsWith(prefix))) {
continue;
}
const childStat = statSync(child);
if (childStat.isDirectory()) {
walk(child);
} else if (shouldReadFile(child, excluded)) {
files.push(child);
}
}
};
walk(targetPath);
return files.sort((a, b) => repoRelative(a).localeCompare(repoRelative(b)));
}
function shouldReadFile(filePath, excluded) {
const rel = repoRelative(filePath);
if (excluded.some((prefix) => rel.startsWith(prefix))) {
return false;
}
if (rel === 'AGENTS.md' || rel === 'CONTEXT.md' || rel.endsWith('/README.md')) {
return true;
}
return new Set(['.md', '.txt']).has(extname(filePath).toLowerCase());
}
export function chunkText(text, options) {
const maxChars = options.maxChars ?? 1600;
const overlapChars = options.overlapChars ?? 220;
const normalized = text.replace(/\r\n?/gu, '\n').trim();
if (!normalized) {
return [];
}
const blocks = normalized.split(/\n(?=#{1,6}\s+)/u);
const chunks = [];
let current = '';
const pushCurrent = () => {
const trimmed = current.trim();
if (trimmed) {
chunks.push(trimmed);
}
current = '';
};
for (const block of blocks) {
if ((current.length + block.length + 2) <= maxChars) {
current = current ? `${current}\n\n${block}` : block;
continue;
}
pushCurrent();
if (block.length <= maxChars) {
current = block;
continue;
}
for (let start = 0; start < block.length; start += Math.max(1, maxChars - overlapChars)) {
chunks.push(block.slice(start, start + maxChars).trim());
}
}
pushCurrent();
return chunks.map((chunk, index) => ({ index, text: chunk }));
}
export function buildChunkId(filePath, chunkIndex) {
return `${filePath}#${chunkIndex}`;
}
export function extractTitle(text, fallback) {
const title = text.match(/^#\s+(.+)$/mu)?.[1]?.trim();
return title || fallback;
}
export async function createEmbedder(transformers, model) {
const extractor = await transformers.pipeline('feature-extraction', model);
return async function embed(text, type) {
const prefix = type === 'query' ? 'query: ' : 'passage: ';
const output = await extractor(`${prefix}${text}`, {
pooling: 'mean',
normalize: true,
});
return Array.from(output.data, Number);
};
}
export function parseLimitFiles(argv) {
const value = readArg(argv, '--limit-files');
if (!value) {
return Number.POSITIVE_INFINITY;
}
const parsed = Number(value);
if (!Number.isInteger(parsed) || parsed <= 0) {
throw new Error(`Invalid --limit-files value: ${value}`);
}
return parsed;
}
export function readArg(argv, name, fallback = undefined) {
const index = argv.indexOf(name);
if (index === -1) {
return fallback;
}
return argv[index + 1] ?? fallback;
}
export function hasFlag(argv, name) {
return argv.includes(name);
}

195
scripts/rag/search-docs.mjs Normal file
View File

@@ -0,0 +1,195 @@
import { join } from 'node:path';
import {
createEmbedder,
hasFlag,
loadRagRuntime,
readArg,
readConfig,
repoRoot,
} from './rag-utils.mjs';
const config = readConfig();
const query = readArg(process.argv, '--query') ?? process.argv.slice(2).join(' ');
const limit = Number(readArg(process.argv, '--limit', '8'));
const maxChars = Number(readArg(process.argv, '--max-chars', '12000'));
const format = readArg(process.argv, '--format', 'context');
const includeText = !hasFlag(process.argv, '--no-text');
if (!query) {
throw new Error(
'Usage: node scripts/rag/search-docs.mjs --query "搜索内容" [--limit 8] [--format context|json|jsonl|text] [--max-chars 12000]',
);
}
if (!['context', 'json', 'jsonl', 'text'].includes(format)) {
throw new Error(`Unsupported --format value: ${format}`);
}
if (!Number.isFinite(limit) || limit <= 0 || !Number.isInteger(limit)) {
throw new Error(`Invalid --limit value: ${limit}`);
}
if (!Number.isFinite(maxChars) || maxChars <= 0 || !Number.isInteger(maxChars)) {
throw new Error(`Invalid --max-chars value: ${maxChars}`);
}
const { lancedb, transformers } = await loadRagRuntime(config);
const embed = await createEmbedder(transformers, config.model);
const queryVector = await embed(query, 'query');
const db = await lancedb.connect(join(repoRoot, config.databaseDir));
const table = await db.openTable(config.tableName);
const rawResults = await table
.vectorSearch(queryVector)
.select(['id', 'path', 'title', 'chunk_index', 'source_weight', 'text', '_distance'])
.limit(Math.max(limit * 3, limit))
.toArray();
const results = rawResults
.map((row) => ({
...row,
score: (1 / (1 + Number(row._distance ?? 0))) * Number(row.source_weight ?? 1),
}))
.sort((a, b) => b.score - a.score)
.slice(0, limit);
const payload = buildAgentPayload(query, results, {
model: config.model,
tableName: config.tableName,
maxChars,
includeText,
});
if (format === 'json') {
console.log(JSON.stringify(payload, null, 2));
} else if (format === 'jsonl') {
for (const result of payload.results) {
console.log(JSON.stringify(result));
}
} else if (format === 'text') {
printTextResults(payload.results);
} else {
console.log(formatContextPack(payload));
}
function buildAgentPayload(searchQuery, rows, options) {
const outputRows = [];
let remainingChars = options.maxChars;
for (const [index, row] of rows.entries()) {
const source = `${row.path}#${row.chunk_index}`;
const text = String(row.text ?? '').trim();
const result = {
rank: index + 1,
id: row.id,
source,
path: row.path,
title: row.title,
chunkIndex: Number(row.chunk_index),
score: Number(row.score),
distance: Number(row._distance ?? 0),
sourceWeight: Number(row.source_weight ?? 1),
};
if (options.includeText) {
const capped = capText(text, Math.max(0, remainingChars));
result.text = capped.text;
result.truncated = capped.truncated;
remainingChars -= result.text.length;
}
outputRows.push(result);
}
return {
kind: 'genarrative-rag-context',
query: searchQuery,
generatedAt: new Date().toISOString(),
model: options.model,
table: options.tableName,
maxChars: options.maxChars,
remainingChars,
resultCount: outputRows.length,
usage: [
'This context pack is primarily for Agent consumption.',
'Use sources as candidate context and inspect authoritative files before editing when exact line-level changes matter.',
'Prefer docs/project-memory and current docs over stale historical notes when sources conflict.',
],
results: outputRows,
};
}
function capText(text, budget) {
if (budget <= 0) {
return { text: '', truncated: text.length > 0 };
}
if (text.length <= budget) {
return { text, truncated: false };
}
return { text: `${text.slice(0, Math.max(0, budget - 18)).trimEnd()}\n[TRUNCATED]`, truncated: true };
}
function formatContextPack(payload) {
const lines = [
'# Genarrative RAG Context',
'',
`query: ${payload.query}`,
`model: ${payload.model}`,
`results: ${payload.resultCount}`,
`maxChars: ${payload.maxChars}`,
'',
'## Agent Usage',
'',
'- This context pack is primarily for Agent consumption.',
'- Treat sources as candidate context; inspect authoritative files before exact edits.',
'- If sources conflict, prefer current code and current docs over stale historical notes.',
'',
'## Sources',
'',
];
for (const result of payload.results) {
lines.push(
`${result.rank}. ${result.source} score=${result.score.toFixed(4)} distance=${result.distance.toFixed(4)} title=${result.title}`,
);
}
lines.push('', '## Context', '');
for (const result of payload.results) {
const fence = buildMarkdownFence(result.text ?? '');
lines.push(
`### [${result.rank}] ${result.title}`,
'',
`source: ${result.source}`,
`score: ${result.score.toFixed(4)}`,
'',
`${fence}text`,
result.text ?? '',
fence,
'',
);
}
return lines.join('\n');
}
function buildMarkdownFence(text) {
const longest = Math.max(3, ...Array.from(text.matchAll(/`+/gu), (match) => match[0].length));
return '`'.repeat(longest + 1);
}
function printTextResults(rows) {
for (const result of rows) {
const preview = String(result.text ?? '').replace(/\s+/gu, ' ').slice(0, 260);
console.log(
[
`${result.rank}. ${result.source}`,
` title: ${result.title}`,
` score: ${result.score.toFixed(4)} distance: ${result.distance.toFixed(4)}`,
` ${preview}`,
].join('\n'),
);
}
}