--- title: 重构计划 date: 2026-02-26 based-on: "[[review-2026-02-26]]" status: 待执行 --- # 重构计划 基于 [[review-2026-02-26]] 的审查结果,按优先级分三个阶段执行。 --- ## Phase 1 · 数据安全与正确性(Critical) > 目标:消除静默数据损坏和误隔离问题。不改变接口,不影响现有功能。 ### P1-1 修复 blacklist.py — 删除过宽字面量 **文件**:`blacklist.py` **问题**:C4,49% 误隔离率 **改动**:从 `SENSITIVE_LITERAL_MARKERS` 删除 `'api_key'` 和 `'access_key'`,这两个已被第 33 行的正则覆盖(要求后跟赋值符号)。 **验收**:重建索引后 `memory_primary` 条数应显著增加,`memory_secure_audit` 条数应下降。 ```python # 删除这两行 'api_key', 'access_key', ``` --- ### P1-2 修复 ingest_vault.py — stale 清理排除 failed_ids **文件**:`ingest_vault.py` 第 88 行 **问题**:C1,embed 失败的文档在下次全量重建时被删除 **改动**:一行修改 ```python # 修改前 stale_primary_ids = sorted(db_primary_ids - valid_ids) # 修改后 stale_primary_ids = sorted(db_primary_ids - valid_ids - set(failed_ids)) ``` --- ### P1-3 修复 install-hook.sh — 清理 changes 临时文件 **文件**:`install-hook.sh`(以及 vault 里已安装的 `.git/hooks/post-commit`) **问题**:C3,每次 commit 留下临时文件 **改动**:用子 shell 包装 nohup 调用,处理完后删除临时文件 ```bash # 修改前 nohup uv run --project "$vault_dir/.scripts/memory" python \ "$vault_dir/.scripts/memory/incremental_ingest.py" \ --changes-file "$changes_file" >> "$log_file" 2>&1 & # 修改后 nohup bash -c "uv run --project '$vault_dir/.scripts/memory' python \ '$vault_dir/.scripts/memory/incremental_ingest.py' \ --changes-file '$changes_file' >> '$log_file' 2>&1; \ rm -f '$changes_file'" & ``` 修改后需重新运行 `install-hook.sh` 更新已安装的 hook。 --- ### P1-4 重建全量索引验收 完成 P1-1 后必须重建索引,验收隔离率是否恢复正常: ```bash # 清空现有数据 psql $PG_DSN -c "TRUNCATE memory_primary, memory_secure_audit;" # 重建 uv run python ingest_vault.py # 检查结果 psql $PG_DSN -c "SELECT count(*) FROM memory_primary;" psql $PG_DSN -c "SELECT count(*) FROM memory_secure_audit;" ``` 预期:`memory_primary` 应接近 81+,`memory_secure_audit` 应大幅下降。 --- ## Phase 2 · 性能与一致性(Important) > 目标:修复 P95 延迟超标问题,统一两个 ingest 脚本的行为。 ### P2-1 增量索引加入 hash 检查 **文件**:`incremental_ingest.py`,`upsert_file()` 函数 **问题**:C2,每次 M 事件都调 API,P95=1.4s 的直接原因 **改动**:在调用 `embed_text` 前查询现有 hash,匹配则跳过 ```python def upsert_file(cur, rel_path: str) -> None: # ... 现有的 excluded / pruned 检查 ... text = abs_path.read_text(encoding='utf-8', errors='ignore') new_hash = sha256_text(text) # 新增:hash 未变则跳过 embed cur.execute('SELECT content_hash FROM memory_primary WHERE id=%s', (rel_path,)) row = cur.fetchone() if row and row[0] == new_hash: print(f'[SKIPPED] {rel_path}') return # 继续 embed ... ``` **预期效果**:纯格式调整或无关文件的 commit 不再触发 API 调用,P95 应降至网络延迟本身(~0.3-0.5s)。 --- ### P2-2 统一两个 ingest 脚本的目录范围 **文件**:`incremental_ingest.py` **问题**:I2,增量索引处理任意目录,全量索引只处理 PRIMARY_DIRS **改动**:在 `upsert_file()` 入口加目录过滤 ```python PRIMARY_DIRS = {'01_Projects', '02_Areas'} # 与 ingest_vault.py 保持一致 def upsert_file(cur, rel_path: str) -> None: # 新增:只处理 PRIMARY_DIRS 内的文件 top_dir = Path(rel_path).parts[0] if Path(rel_path).parts else '' if top_dir not in PRIMARY_DIRS: print(f'[SKIPPED-OUT-OF-SCOPE] {rel_path}') return # ... 其余逻辑不变 ... ``` --- ### P2-3 修复 ivfflat index lists 参数 **文件**:`schema.sql` **问题**:I1,lists=100 对 81 行数据无效 **改动**:重建索引时使用动态 lists 值,或改用 HNSW(更适合小数据集) ```sql -- 方案 A:删除旧索引,改用 HNSW(推荐,无需调参) DROP INDEX IF EXISTS memory_primary_embedding_idx; CREATE INDEX memory_primary_embedding_idx ON memory_primary USING hnsw (embedding vector_cosine_ops); -- 方案 B:保留 ivfflat,修正 lists DROP INDEX IF EXISTS memory_primary_embedding_idx; CREATE INDEX memory_primary_embedding_idx ON memory_primary USING ivfflat (embedding vector_cosine_ops) WITH (lists = 1); ``` 推荐方案 A:HNSW 在小数据集上性能更好,且不需要手动维护 lists 参数。 --- ### P2-4 修复 agent-with-memory.sh 的 VAULT_DIR 推导 **文件**:`agent-with-memory.sh` **问题**:I4,路径推导假设脚本在特定目录层级 **改动**:从 `.env.memory` 读取 VAULT_DIR,同时修正用法提示 ```bash # 修改前 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" VAULT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)" # 修改后 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ENV_FILE="$SCRIPT_DIR/.env.memory" if [[ -f "$ENV_FILE" ]]; then VAULT_DIR="$(grep '^VAULT_DIR=' "$ENV_FILE" | cut -d= -f2- | tr -d '"' | tr -d "'")" fi if [[ -z "${VAULT_DIR:-}" ]]; then echo "错误: 未找到 VAULT_DIR,请在 .env.memory 中配置" >&2 exit 1 fi ``` --- ### P2-5 查询加入相似度阈值 **文件**:`query_pgvector.py` **问题**:I5,无关查询仍返回 top_k 结果污染上下文 **改动**:加 `--threshold` 参数,默认 0.5(余弦距离,越小越相似) ```python def query(text: str, top_k: int = 5, max_chars: int = 2500, threshold: float = 0.5) -> str: # ... cur.execute( ''' SELECT source, content FROM memory_primary WHERE embedding <=> %s::vector < %s ORDER BY embedding <=> %s::vector LIMIT %s ''', (vector, threshold, vector, top_k), ) ``` 阈值 0.5 为初始值,需根据实际 Recall@5 评测结果调整。 --- ## Phase 3 · 可观测性与运营(Nice to have) > 目标:让系统状态可见,减少静默失败。 ### P3-1 audit 表记录触发原因 **文件**:`schema.sql`、`blacklist.py`、`index_common.py` **问题**:I6,无法区分隔离原因 **改动**:`is_excluded` 返回触发原因字符串而非布尔值,audit 表 `risk` 列存具体规则名 --- ### P3-2 hook 失败时写入可见日志 **文件**:`install-hook.sh` **问题**:I7,所有错误静默 **改动**:在 `.memory-sync.log` 里写入带时间戳的错误行,并在 hook 末尾检查日志最后一行是否为错误 --- ### P3-3 添加 health-check 脚本 新增 `health-check.sh`,检查: - PostgreSQL 是否可连接 - `memory_primary` 条数是否 > 0 - 最近一次 `updated_at` 是否在 24h 内 - `.env.memory` 是否存在且关键字段非空 --- ### P3-4 日志轮转 **文件**:`install-hook.sh` **问题**:O1,`.memory-sync.log` 无限增长 **改动**:hook 里加简单的大小检查,超过 5MB 时截断旧内容 --- ## 执行顺序 ``` Phase 1(必须先做) P1-1 → P1-2 → P1-3 → P1-4(验收) Phase 2(P1 完成后) P2-1 → P2-2 → P2-3 → P2-4 → P2-5 Phase 3(可选,按需) P3-1 → P3-2 → P3-3 → P3-4 ``` ## 预期收益 | 问题 | 修复后预期 | |---|---| | 49% 误隔离 | `memory_primary` 恢复到 ~140+ 文档 | | P95=1.4s | hash 命中时降至 <0.1s,实际 embed 时降至 ~0.8s(HNSW 加速)| | 临时文件积累 | 每次 commit 后自动清理 | | 数据静默丢失 | embed 失败不再删除已有向量 |