架构与设计取舍
这一篇解释为什么这样做,不是怎么做。写给两种人:想改它的人,和想评估它能不能用在自己资料上的人。
01一核三壳
text
┌───────────────────────────┐
│ core/ 纯函数,零 IO │
│ classify(signals, rules) │
│ → { kbId, target, │
│ confidence, reason, │
│ candidates } │
└────────────┬──────────────┘
│
┌──────────────────┼──────────────────┐
│ │ │
cli/ dsh-plugin/ mcp/
命令行外壳 DSH 插件外壳 MCP stdio 外壳
plan/apply/… 五个只读工具 给 Claude Code / Cursor三个壳都不含判断逻辑。想让某个编辑器里也能用,只需再写一个壳——这是开源版最希望别人来做的事。
02为什么内核必须是纯函数
classify() 不读时钟、不读网络、不读环境变量,同一输入两次调用结果逐字段相同。代价是它拿不到「现在几点」「这个用户的偏好」,收益是四条:
- 可复现:报告可以贴进 issue,别人跑出同一份。
- 可断言:单元测试不需要 mock 文件系统。
- 可单测:判定规则的正确性可以和 IO 完全解耦地验证。
- 可在浏览器里整段跑:所以官网那个 demo 不需要后端。
把不确定性挡在内核之外,是这一个项目里最省事的一个决定。
03.kb/ 里都是什么,为什么能删
| 文件 | 作用 | 删了会怎样 |
|---|---|---|
manifest.json | 指纹台账 | 下次跑退化为全量扫描,慢但结果一样 |
plan.json | 本次判定计划 | 无可追踪的计划,重跑生成 |
projection/ | 投影视图 | 看不见整理结果,重跑生成 |
index.sqlite | 全文索引 | 检索不可用,重跑重建 |
rules.lock.yaml | 规则快照 | 历史判定无法复现 |
journal.jsonl | 回滚日志 | 撤销能力消失,这是唯一一条真正要紧的 |
trash/<batch>/ | 原件快照 | 撤销时缺原始字节,会报缺件 |
undo/ | 批次索引 | 找不到批次,可手工从 journal 重建 |
所以「rm -rf .kb 即完全复原」这句话成立的前提是:你不需要撤销已经做过的操作。要做,就先 undo。
这也是为什么 .kb/ 不该进 git:里面有原件快照,会随每次改动膨胀。
04六个取舍
| 取舍 | 选了什么 | 为什么 | 代价 |
|---|---|---|---|
| 默认行为 | 全部不写盘,写盘要显式命令 | 一个会自己动手的整理工具,没人敢在真实资料上试 | 多敲一条命令 |
| 平手 | 不猜,进 _inbox/ | 平手时任何选择都是 50% 错误率,而用户会记住那 50% | _inbox 会积压,需要人处理 |
| 规则形态 | 一份人可读的 YAML,不是一堆设置项 | 能被审查、被 diff、被复制给别人 | 学习成本高于点开设置面板 |
| AI 边界 | apply/move 不注册为工具,只能预演 | 「AI 只能预演,落地由人执行」是安全边界,也是产品人格 | AI 场景下不能一键完成 |
| 台账格式 | manifest.json 完全公开 | 私有格式锁死生态;公开格式才有可能被别的工具接管 | 无法靠格式绑定用户 |
| 删除 | 代码里没有 unlink | 一个人一辈子最怕的不是文件乱,是文件丢 | 快照占盘,需要定期清 |
最后一行值得展开:trash/ 会占空间,所以有保留期,到期清理。但清理快照不等于删源文件——KBFold 的任何代码路径都不会删除用户的文件。这不是承诺,是可以 grep unlink 验证的事实。
05引用纪律五条
这五条写进 DSH 工具与 MCP 工具的 description 里,让接进来的 AI 直接读到:
text
1. 每条结论后紧跟 [n],不得改动顺序或另编号。 2. 只有确实出现在返回结果里的内容才能标 [n]。 3. 无命中必须明说「知识库中没有找到相关内容」,禁止推测补全。 4. 结果不足如实说「仅找到 2 条」,不得凑数。 5. 引用 low_confidence 片段须注明待人工确认。
为什么把这五条放进架构文档?因为它们不是提示词技巧,是接口契约:检索结果带 [n],就是为了让第 1 条可执行;结果里带 low_confidence 标记,就是为了让第 5 条可执行。没有这两处数据支撑,五条纪律只是道德要求。
06明确不做的四件事
- 不自动归档:搬走老文件是「找不到了」的头号原因。方向必须人给。
- 不让 LLM 决定归属:LLM 兜底判定默认关闭。理由不是不信任模型,是错一次你就要在几千个文件里找那一份。
- 不做 OCR / 不做格式转换:扫描件进库就是扫描件,能检索标题与元信息,不假装能读内容。
- 不做团队协作:没有账号、没有同步、没有冲突合并。
.kb/是个目录,谁要同步就自己用同步盘,冲突由同步盘负责。
这四条都不是「还没做」,是「不打算做」。边界写清楚,比功能列表长更有用。