diff --git a/README.md b/README.md index 3fe5777..f2a7a38 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ ST-BME(Bionic Memory Ecology)是一个 **SillyTavern 第三方前端扩展**。它会把长期聊天中出现的角色、事件、地点、规则、主线、反思和总结抽取成一张可视化记忆图谱,并在下一轮生成前自动召回最相关的记忆注入 prompt。 +> 本 README 是用户手册。想了解内部架构、算法原理、功能解析或参与维护,见 [`docs/`](docs/README.md)。 + --- ## 目录 @@ -790,6 +792,12 @@ ST-BME 已加入保护: ## 开发者参考 +> 完整的架构、算法原理、功能解析和贡献指南在 [`docs/`](docs/README.md): +> - 架构与控制平面 [`docs/architecture/`](docs/architecture/) +> - 算法原理 [`docs/algorithms/`](docs/algorithms/) +> - 功能解析 [`docs/features/`](docs/features/) +> - 参与维护 [`docs/contributing/`](docs/contributing/) + ### 目录结构 ```text diff --git a/docs/contributing/conventions.md b/docs/contributing/conventions.md new file mode 100644 index 0000000..7504d99 --- /dev/null +++ b/docs/contributing/conventions.md @@ -0,0 +1,66 @@ +# 代码约定 + +本文档记录这次重构确立的核心模式和必须维持的约定。违反它们会重新引入已经被消灭的 bug 类。 + +## 注入式控制器模式 + +核心 bug 簇(身份、持久化、加载、向量、reroll)过去都从多个异步路径读写同一份模块级可变全局。修复方式是把"做决定"(控制平面)和"执行副作用"(数据平面)分开。 + +抽出的控制器/工厂模块遵循: + +- **纯逻辑或注入式**:依赖通过 `runtime` 对象传入,不闭包 `index.js` 的模块级状态。 +- **`index.js` 保留薄委托壳**:旧函数名保留为转发到 `*Impl(runtime, ...)` 的薄壳。这些壳是真实运行时调用点,**不是死代码,不要删**。 +- **状态留在 index.js**:`currentGraph` / `graphPersistenceState` / `extractionCount` 等模块级状态留在 `index.js`,通过 accessor 注入。 + +## 关键陷阱:状态重新赋值 + +这是 Phase 5d 出过真实 bug 的地方,务必遵守。 + +`index.js` 的 `updateGraphPersistenceState()` 会**整个重新赋值** `graphPersistenceState` 对象(不是原地改属性)。所以: + +> 控制器模块**不能**在函数入口 `const state = runtime.getGraphPersistenceState()` 捕获一次后反复读——任何 helper 调用(`updateGraphPersistenceState` / `queueGraphPersist` / `applyGraphLoadState`)之后,捕获的引用就陈旧了。 + +正确做法是用 live Proxy,每次属性访问都读活值: + +```js +const graphPersistenceState = new Proxy({}, { + get(_t, k) { return (runtime.getGraphPersistenceState?.() || {})[k]; }, + set(_t, k, v) { const s = runtime.getGraphPersistenceState?.() || {}; s[k] = v; return true; }, +}); +``` + +同理:`currentGraph` 在任何 `ensureCurrentGraphRuntimeState()` 调用后必须 `runtime.getCurrentGraph()` 重取,因为该调用可能替换图谱。 + +`index.js` 的 accessor 必须是 live 闭包:`getGraphPersistenceState: () => graphPersistenceState`,不能是快照。 + +## 依赖注入完整性 + +模块引用的每个 `runtime.X` 必须由对应 `create*Runtime()` builder 提供。`tests/runtime-deps-completeness.mjs` 自动校验。 + +- 新增 `runtime.X` 依赖 → 同步更新 builder。 +- 禁止 `runtime[dynamicKey]` 计算属性访问(绕过静态检查,守卫会报错)。 + +详见 [`testing.md`](testing.md)。 + +## 不要切片 index.js + +测试直接 `import` 真实模块,绝不 `readFile(index.js)` + 切片 + `vm.runInContext`。`tests/index-slicing-ratchet.mjs` 守这条线。详见 [`testing.md`](testing.md)。 + +## 必须维持的核心不变量 + +这些在 [`../architecture/control-plane.md`](../architecture/control-plane.md) 有完整说明,这里列出最易被无意破坏的: + +1. **active identity 只来自宿主上下文**;graph-owner/queued/marker 身份只用于校验/恢复,绝不变成当前聊天。 +2. **`已确认版本 >= 排队版本 && 同身份 && 规范 tier ⟹ pendingPersist === false`**——写在 reducer 里,不要在外面打补丁绕过。 +3. **recovery-only tier(shadow/metadata)永远不能推进确认状态。** +4. **reroll 复用召回时,被 reroll 助手楼层的图谱回滚必须保留**——召回复用和图谱回滚是两件事。 +5. **向量空间不兼容时返回空向量结果回退图/词法召回**,绝不静默复用旧向量。 +6. **快照顶层六键永不增减**;演进进 meta/state/记录字段;读取保留未知字段。 + +## 行为保留 vs 重构 + +纯重构(不改行为)不需要更新算法/功能文档。改了算法参数、不变量、功能边界时,更新 `docs/` 对应文档。 + +## 高风险改动 + +涉及 `loadGraphFromChat`、`recoverHistoryIfNeeded`、持久化路由这类高风险函数时,测试绿不等于行为保真——fallback/错误路径常常没被测试覆盖。这类改动应额外做控制流复核(历史上靠 @oracle 逐行复核抓到过测试漏掉的 blocker)。 diff --git a/docs/contributing/development.md b/docs/contributing/development.md new file mode 100644 index 0000000..cd2048f --- /dev/null +++ b/docs/contributing/development.md @@ -0,0 +1,44 @@ +# 本地开发 + +## 安装依赖 + +```bash +npm install +``` + +## 常用命令 + +| 命令 | 作用 | +| --- | --- | +| `npm run check` | 语法检查(`typescript-language-server` 不可用时作为 LSP 诊断替代) | +| `npm run test:stable` | 稳定测试集(自动扫描 `tests/*.mjs`) | +| `npm run test:p0` | P0 主回归 | +| `npm run test:persistence-matrix` | 持久化矩阵(p0 + runtime-history + graph-persistence + indexeddb) | +| `npm run test:index-slicing-ratchet` | 防止测试切片 `index.js` 的防线 | +| `npm run test:runtime-deps` | 依赖注入完整性守卫 | +| `npm run build:native:wasm` | 构建 Native WASM | +| `npm run version:bump-manifest` | 更新 manifest 版本 | + +控制面/数据格式专项测试见 [`testing.md`](testing.md)。 + +## 提交前 + +至少运行: + +```bash +npm run check +npm run test:stable +``` + +涉及具体子系统时,额外跑对应专项测试。 + +## 分支工作流 + +- 功能开发推到 `dev` 分支,不直接推 `main`。 +- 合并带 `manifest.json` 版本冲突的分支时,保留更高的版本号。 +- 远端 `dev` 常有自动版本 bump,merge 时按上面规则处理,不要 force push。 + +## 环境提示 + +- `typescript-language-server` 在当前环境未安装,用 `npm run check` 作为 LSP 诊断替代。 +- git 身份缺失时,按命令注入 `GIT_AUTHOR_*` / `GIT_COMMITTER_*` 环境变量,不要改 git config。 diff --git a/docs/contributing/testing.md b/docs/contributing/testing.md new file mode 100644 index 0000000..fd03602 --- /dev/null +++ b/docs/contributing/testing.md @@ -0,0 +1,48 @@ +# 测试 + +ST-BME 的测试是 Node 回归测试(`tests/*.mjs`),`npm run test:stable` 自动扫描运行。本文档说明测试分类和两道关键防线。 + +## 测试分类 + +| 类别 | 例子 | 测什么 | +| --- | --- | --- | +| 控制平面 | `identity-resolver` / `persistence-reducer` | 身份解析、持久化状态机不变量 | +| 数据格式 | `graph-snapshot-schema` / `graph-snapshot-upgrade` / `snapshot-forward-compat` | 快照契约、宽容解析、向前兼容往返 | +| 持久化 | `graph-persistence` / `indexeddb-*` | 图谱持久化、IndexedDB 快照/增量/hydrate | +| 检索/召回 | `p0-regressions` 内相关、`trivial-user-input` | 召回、reroll 复用、注入 | +| 向量 | `vector-gate` / `vector-connection-probe` / `vector-sync-coalescer` | 向量门禁、连接探测、后台同步合并 | +| Native | `native-layout-parity` / `native-rollout-matrix` | native/JS 一致性、灰度门控 | +| 防线 | `index-slicing-ratchet` / `runtime-deps-completeness` | 见下 | + +## 防线一:禁止切片 index.js(ratchet) + +历史上部分回归测试用 `readFile(index.js)` + 标记字符串切片 + `vm.runInContext` 来测内部函数。这让 `index.js` 与字节偏移强耦合,反复出现 `X is not defined` 沙箱崩溃,并阻碍任何 `index.js` 重构。 + +这些逻辑已全部抽成可直接 `import` 的控制器/工厂模块。 + +> `tests/index-slicing-ratchet.mjs` 守住这条线:任何测试文件若读取 `index.js` 文本,CI 失败。允许名单(ALLOWLIST)现在为空,且只能缩不能增。 + +**所以:新测试必须直接 import 真实模块并注入依赖,绝不切片 `index.js`。** + +## 防线二:依赖注入完整性守卫 + +抽出的控制器模块通过 `runtime` 对象拿依赖,由 `index.js` 的 `create*Runtime()` builder 提供。如果模块用了 `runtime.someDep` 但 builder 忘了提供,运行时(尤其 fallback 路径)才炸——测试可能还是绿的。这正是历史上出过的真实 bug。 + +> `tests/runtime-deps-completeness.mjs` 扫描三个 sync 控制平面模块的直接 `runtime.X` 引用,和对应 builder 提供的键对比;漏注入则失败,并报出模块/builder/缺失键。 + +校验的模块/builder 对: + +| 模块 | builder | +| --- | --- | +| `sync/graph-persistence-io.js` | `createGraphPersistenceIoRuntime` | +| `sync/graph-load-persist.js` | `createGraphLoadPersistRuntime` | +| `sync/graph-mutation-gate.js` | `createGraphMutationGateRuntime` | + +> 禁止 `runtime[dynamicKey]` 这种计算属性访问——它绕过静态完整性检查,守卫会直接报错。新增 `runtime.X` 依赖时,必须同步更新对应 builder。 + +详见 [`conventions.md`](conventions.md)。 + +## 已知环境限制 + +- `tests/indexeddb-migration.mjs` 等需要 IndexedDB 测试依赖;某些 Node 环境缺失会失败,非代码问题。 +- `typescript-language-server` 未安装,用 `npm run check` 替代 LSP 诊断。