diff --git a/INTERNALS.md b/INTERNALS.md index 020ff21..c383b65 100644 --- a/INTERNALS.md +++ b/INTERNALS.md @@ -496,3 +496,92 @@ type: dynamic 桶正文内容... ``` + +--- + +## 7. Bug 修复记录 (B-01 至 B-10) + +### B-01 — `update(resolved=True)` 自动归档 🔴 高 + +- **文件**: `bucket_manager.py` → `update()` +- **问题**: `resolved=True` 时立即调用 `_move_bucket(archive_dir)` 将桶移入 `archive/` +- **修复**: 移除 `_move_bucket` 逻辑;resolved 桶留在 `dynamic/`,由 decay 引擎自然淘汰 +- **影响**: 已解决的桶仍可被关键词检索命中(降权但不消失) +- **测试**: `tests/regression/test_issue_B01.py`,`tests/integration/test_scenario_07_trace.py` + +### B-03 — `int()` 截断浮点 activation_count 🔴 高 + +- **文件**: `decay_engine.py` → `calculate_score()` +- **问题**: `max(1, int(activation_count))` 将 `_time_ripple` 写入的 1.3 截断为 1,涟漪加成失效 +- **修复**: 改为 `max(1.0, float(activation_count))` +- **影响**: 时间涟漪效果现在正确反映在 score 上;高频访问的桶衰减更慢 +- **测试**: `tests/regression/test_issue_B03.py`,`tests/unit/test_calculate_score.py` + +### B-04 — `create()` 初始化 activation_count=1 🟠 中 + +- **文件**: `bucket_manager.py` → `create()` +- **问题**: `activation_count=1` 导致冷启动检测条件 `== 0` 永不满足,新建重要桶无法浮现 +- **修复**: 改为 `activation_count=0`;`touch()` 首次命中后变 1 +- **测试**: `tests/regression/test_issue_B04.py`,`tests/integration/test_scenario_01_cold_start.py` + +### B-05 — 时间衰减系数 0.1 过快 🟠 中 + +- **文件**: `bucket_manager.py` → `_calc_time_score()` +- **问题**: `math.exp(-0.1 * days)` 导致 30 天后得分仅剩 ≈0.05,远快于人类记忆曲线 +- **修复**: 改为 `math.exp(-0.02 * days)`(30 天后 ≈0.549) +- **影响**: 记忆保留时间更符合人类认知模型 +- **测试**: `tests/regression/test_issue_B05.py`,`tests/unit/test_score_components.py` + +### B-06 — `w_time` 默认值 2.5 过高 🟠 中 + +- **文件**: `bucket_manager.py` → `_calc_final_score()`(或评分调用处) +- **问题**: `scoring.get("time_proximity", 2.5)` — 时间权重过高,近期低质量记忆得分高于高质量旧记忆 +- **修复**: 改为 `scoring.get("time_proximity", 1.5)` +- **测试**: `tests/regression/test_issue_B06.py`,`tests/unit/test_score_components.py` + +### B-07 — `content_weight` 默认值 3.0 过高 🟠 中 + +- **文件**: `bucket_manager.py` → `_calc_topic_score()` +- **问题**: `scoring.get("content_weight", 3.0)` — 内容权重远大于名字权重(×3),导致内容重复堆砌的桶得分高于名字精确匹配的桶 +- **修复**: 改为 `scoring.get("content_weight", 1.0)` +- **影响**: 名字完全匹配 > 标签匹配 > 内容匹配的得分层级现在正确 +- **测试**: `tests/regression/test_issue_B07.py`,`tests/unit/test_topic_score.py` + +### B-08 — `run_decay_cycle()` 同轮 auto_resolve 后 score 未降权 🟡 低 + +- **文件**: `decay_engine.py` → `run_decay_cycle()` +- **问题**: `auto_resolve` 标记后立即用旧 `meta`(stale)计算 score,`resolved_factor=0.05` 未生效 +- **修复**: 在 `bucket_mgr.update(resolved=True)` 后立即执行 `meta["resolved"] = True`,确保同轮降权 +- **测试**: `tests/regression/test_issue_B08.py`,`tests/integration/test_scenario_08_decay.py` + +### B-09 — `hold()` 用 analyze() 覆盖用户传入的 valence/arousal 🟡 低 + +- **文件**: `server.py` → `hold()` +- **问题**: 先调 `analyze()`,再直接用结果覆盖用户传入的情感值,情感准确性丢失 +- **修复**: 使用 `final_valence = user_valence if user_valence is not None else analyze_result.get("valence")` +- **影响**: 用户明确传入的情感坐标(包括 0.0)不再被 LLM 结果覆盖 +- **测试**: `tests/regression/test_issue_B09.py`,`tests/integration/test_scenario_03_hold.py` + +### B-10 — feel 桶 `domain=[]` 被填充为 `["未分类"]` 🟡 低 + +- **文件**: `bucket_manager.py` → `create()` +- **问题**: `if not domain: domain = ["未分类"]` 对所有桶类型生效,feel 桶的空 domain 被错误填充 +- **修复**: 改为 `if not domain and bucket_type != "feel": domain = ["未分类"]` +- **影响**: `breath(domain="feel")` 通道过滤逻辑现在正确(feel 桶 domain 始终为空列表) +- **测试**: `tests/regression/test_issue_B10.py`,`tests/integration/test_scenario_10_feel.py` + +--- + +### Bug 修复汇总表 + +| ID | 严重度 | 文件 | 方法 | 一句话描述 | +|---|---|---|---|---| +| B-01 | 🔴 高 | `bucket_manager.py` | `update()` | resolved 桶不再自动归档 | +| B-03 | 🔴 高 | `decay_engine.py` | `calculate_score()` | float activation_count 不被 int() 截断 | +| B-04 | 🟠 中 | `bucket_manager.py` | `create()` | 初始 activation_count=0 | +| B-05 | 🟠 中 | `bucket_manager.py` | `_calc_time_score()` | 时间衰减系数 0.02(原 0.1) | +| B-06 | 🟠 中 | `bucket_manager.py` | 评分权重配置 | w_time 默认 1.5(原 2.5) | +| B-07 | 🟠 中 | `bucket_manager.py` | `_calc_topic_score()` | content_weight 默认 1.0(原 3.0) | +| B-08 | 🟡 低 | `decay_engine.py` | `run_decay_cycle()` | auto_resolve 同轮应用 ×0.05 | +| B-09 | 🟡 低 | `server.py` | `hold()` | 用户 valence/arousal 优先 | +| B-10 | 🟡 低 | `bucket_manager.py` | `create()` | feel 桶 domain=[] 不被填充 | diff --git a/README.md b/README.md index a71a922..6105458 100644 --- a/README.md +++ b/README.md @@ -804,6 +804,51 @@ sudo systemctl restart ombre-brain # 示例 > - If `requirements.txt` changed, Docker rebuild handles it automatically; non-Docker users need `pip install -r requirements.txt` > - After updating, visit `/health` to verify the service is running +## 测试 / Testing + +测试套件覆盖规格书所有场景(场景 01–11),以及 B-01 至 B-10 全部 bug 修复的回归测试。 + +The test suite covers all spec scenarios (01–11) and regression tests for every bug fix (B-01 to B-10). + +### 快速运行 / Quick Start + +```bash +pip install pytest pytest-asyncio +pytest tests/ # 全部测试 +pytest tests/unit/ # 单元测试 +pytest tests/integration/ # 集成测试(场景全流程) +pytest tests/regression/ # 回归测试(B-01..B-10) +pytest tests/ -k "B01" # 单个回归测试 +pytest tests/ -v # 详细输出 +``` + +### 测试层级 / Test Layers + +| 目录 Directory | 内容 Contents | +|---|---| +| `tests/unit/` | 单独测试 calculate_score、topic_score、时间得分、CRUD 等核心函数 | +| `tests/integration/` | 场景全流程:冷启动、hold、search、trace、decay、feel 等 11 个场景 | +| `tests/regression/` | 每个 bug(B-01 至 B-10)独立回归测试,含边界条件 | + +### 回归测试覆盖 / Regression Coverage + +| 文件 | Bug | 核心断言 | +|---|---|---| +| `test_issue_B01.py` | resolved 桶不再自动归档 | `update(resolved=True)` 后桶留在 `dynamic/`,搜索仍可命中,得分 ×0.05 | +| `test_issue_B03.py` | float activation_count 不被 int() 截断 | 1.3 > 1.0 得分,`_time_ripple` 写入 0.3 增量 | +| `test_issue_B04.py` | create() 初始 activation_count=0 | 新建桶满足冷启动条件,touch() 后变 1 | +| `test_issue_B05.py` | 时间衰减系数 0.02(原 0.1)| 30天 ≈ 0.549,非旧值 0.049 | +| `test_issue_B06.py` | w_time 默认 1.5(原 2.5)| `BucketManager.w_time == 1.5` | +| `test_issue_B07.py` | content_weight 默认 1.0(原 3.0)| 名字完全匹配得分 > 内容模糊匹配 | +| `test_issue_B08.py` | auto_resolve 同轮应用降权因子 | stale meta 修复后 score ×0.05 立即生效 | +| `test_issue_B09.py` | hold() 保留用户传入的 valence/arousal | 用户值优先于 analyze() 结果 | +| `test_issue_B10.py` | feel 桶 domain=[] 不被填充 | feel 桶保持 `[]`;dynamic 桶正确填 `["未分类"]` | + +> **测试隔离**:所有测试运行在 `tmp_path` 临时目录,绝不触碰真实记忆数据。 +> **Test isolation**: All tests run in `tmp_path` — your real memory data is never touched. + +--- + ## License MIT diff --git a/dashboard.html b/dashboard.html index dd3ad9d..6472d9c 100644 --- a/dashboard.html +++ b/dashboard.html @@ -995,7 +995,11 @@ document.getElementById('search-input').addEventListener('input', (e) => { async function loadBuckets() { try { const res = await fetch(BASE + '/api/buckets'); - allBuckets = await res.json(); + const data = await res.json(); + if (!res.ok || !Array.isArray(data)) { + throw new Error((data && data.error) ? data.error : `HTTP ${res.status}`); + } + allBuckets = data; updateStats(); buildFilters(); renderBuckets(allBuckets);