7.0 KiB
title, date, reviewer, status
| title | date | reviewer | status |
|---|---|---|---|
| 代码审查报告 | 2026-02-26 | Claude Sonnet 4.6 | 完成 |
代码审查报告(2026-02-26)
总体评价
架构设计合理,双层记忆模型思路清晰,核心链路(embedding → pgvector → 检索注入)实现正确。ON CONFLICT DO UPDATE 幂等 upsert、异步 post-commit hook、<retrieved_context> 注入防护均为正确决策。
主要问题集中在:数据安全(静默删除好数据)、性能浪费(每次都重新 embed)、误隔离(blacklist 过宽导致 49% 文档未被索引)三个方向。
Critical 问题
C1 · 全量索引会静默删除 embedding 失败的文档
文件:ingest_vault.py 第 86-90 行
stale 清理逻辑 db_primary_ids - valid_ids 未排除 failed_ids。一次网络抖动导致 embedding 失败,下次全量重建就把之前已有的好向量删掉。数据静默丢失。
# 当前(有 bug)
stale_primary_ids = sorted(db_primary_ids - valid_ids)
# 修复
stale_primary_ids = sorted(db_primary_ids - valid_ids - set(failed_ids))
C2 · 增量索引从不检查 content_hash,每次都重新 embed
文件:incremental_ingest.py,upsert_file() 函数
content_hash 列已存在于 schema 并由全量索引填充,但增量索引完全忽略它。每个 M 事件都调一次 OpenRouter API,即使文件内容没有变化。这是 P95=1.4s 的直接原因,也在浪费 API 额度。
修复:在调用 embed_text 前,先查 memory_primary 的 content_hash,若匹配则跳过。
C3 · changes 临时文件永远不删除
文件:install-hook.sh 第 24、29-30 行
hook 通过 nohup 后台运行 Python 进程,没有任何机制在处理完成后删除 .memory-changes-<timestamp>-<pid>.txt。每次涉及 .md 文件的 commit 都在 vault 根目录留下一个文件,长期无限积累。
# 修复:用子 shell 包装,处理完后清理
nohup bash -c "uv run ... --changes-file '$changes_file' >> '$log_file' 2>&1; rm -f '$changes_file'" &
C4 · api_key 字面量过宽,导致 49% 文档被误隔离
文件:blacklist.py 第 17 行
字面量 'api_key' 会匹配任何包含该字符串的笔记,包括架构文档、项目笔记、本项目自身的文档。实测结果:79/160 文档被隔离(49%),接近一半的 vault 内容未被索引。
同文件第 33 行的正则 (?i)\b(password|...api[_-]?key)\b\s*[:=]\s*\S{4,} 才是正确做法(要求后面跟赋值符号)。应删除 'api_key' 字面量,或改为 'api_key='、'api_key:'。
C5 · Windows 文件锁可能是非阻塞的
文件:index_common.py 第 97、101 行
msvcrt.LK_LOCK 在部分 Python/Windows 版本下行为不一致,可能不阻塞直接抛 OSError。两个并发 ingest 进程可能同时通过锁,导致数据竞争。需要用带重试的循环或换用更可靠的 Windows 锁原语。
Important 问题
I1 · ivfflat lists=100 对当前数据量完全无效
文件:schema.sql 第 20 行
pgvector 建议 lists = rows / 1000,81 行数据应用 lists=1。当前 lists 数量多于行数,查询规划器会忽略索引直接走全表扫描,索引只有写开销没有读收益。
I2 · 两个 ingest 脚本的目录范围不一致
ingest_vault.py 只扫 01_Projects 和 02_Areas,但 incremental_ingest.py 处理 git diff 里任何 .md 文件。03_Resources/ 里的文件会被增量索引,但下次全量重建时被删掉,造成数据不一致。
I3 · 大文件被 API 静默截断
text-embedding-3-small 上限 8191 tokens,超长笔记的后半部分永远不会被 embed,且没有任何警告。_sample_head_mid_tail 函数已存在于 index_common.py(用于敏感扫描),但未用于 embedding。
I4 · agent-with-memory.sh 的 VAULT_DIR 推导方式脆弱
脚本假设自己在 vault 根目录下两层(.scripts/memory/),迁移到独立项目后这个假设已不成立。应从 .env.memory 读取 VAULT_DIR 作为权威来源。
I5 · 查询没有相似度阈值
无论相关性多低,始终返回 top_k 结果。查询完全不相关的内容时,会把最不相关的 5 个文档注入 Claude 上下文,产生噪音甚至误导。
建议加 WHERE embedding <=> %s::vector < 0.5(阈值需实测调整)。
I6 · memory_secure_audit 不记录触发原因
risk 列永远是 'excluded_or_sensitive',无法区分是路径规则、文件名规则还是内容规则触发的。调整 blacklist 时完全没有依据。
I7 · hook 静默失败,用户无感知
PostgreSQL 挂了、.env.memory 不存在、Python 环境损坏,全部静默失败。用户不知道索引已经落后,只能手动查看 .memory-sync.log。
I8 · 全量索引持有超长 DB 事务
ingest_vault.py 在单个事务内完成所有 embedding API 调用(81 次 HTTP 请求,可能数分钟)。事务期间持有连接和行锁,进程被杀时虽然回滚干净,但锁文件同时释放,可能导致并发 ingest 在部分更新状态下运行。
文档问题
| # | 文件 | 问题 |
|---|---|---|
| D1 | agent-with-memory.sh |
用法提示仍写旧路径 .scripts/memory/ |
| D2 | README.md step 6 |
install-hook.sh 路径是占位符,未说明如何确定实际路径 |
| D3 | index.md vs status.md |
冷启动状态描述不一致("未测" vs "未完成量化") |
| D4 | README.md |
OPENROUTER_EMBED_DIM 标为必填,但代码有默认值 1536 |
| D5 | 所有文档 | Docker 示例使用默认密码 postgres:postgres,未提示修改 |
| D6 | 所有文档 | eval_cold_start.py 完全未被文档化 |
| D7 | status.md |
49% 隔离率记录为已知问题,但未分析根因(实为 C4 的直接证据) |
运营问题
| # | 问题 |
|---|---|
| O1 | .memory-sync.log 无限增长,无轮转机制 |
| O2 | 无健康检查命令,无法快速验证系统是否正常运行 |
| O3 | 无索引漂移检测机制,hook 连续失败时用户无感知 |
问题汇总
| 编号 | 严重度 | 文件 | 问题 |
|---|---|---|---|
| C1 | Critical | ingest_vault.py |
stale 清理删除 embed 失败的文档 |
| C2 | Critical | incremental_ingest.py |
未用 content_hash,每次都重新 embed |
| C3 | Critical | install-hook.sh |
changes 临时文件永远不删 |
| C4 | Critical | blacklist.py |
api_key 字面量过宽,49% 误隔离 |
| C5 | Critical | index_common.py |
Windows 锁可能非阻塞 |
| I1 | Important | schema.sql |
ivfflat lists=100 对 81 行无效 |
| I2 | Important | 两个 ingest 脚本 | 目录范围不一致 |
| I3 | Important | 两个 ingest 脚本 | 大文件静默截断 |
| I4 | Important | agent-with-memory.sh |
VAULT_DIR 推导脆弱 |
| I5 | Important | query_pgvector.py |
无相似度阈值 |
| I6 | Important | schema.sql |
audit 表不记录触发原因 |
| I7 | Important | install-hook.sh |
hook 静默失败 |
| I8 | Important | ingest_vault.py |
全量索引持有超长事务 |