开源项目› 知识库整备台› 手册
手册 · 03

判定流水线

约 7 分钟阅读 Apache-2.0 本文基于 v0.1.0(2026-11-01)

一份文件从「躺在某个目录里」到「出现在投影的正确位置、进了索引、留了账」,中间要过五段流水线。这一篇说明每一段在干什么、你能插手哪一段、以及出错时从哪一段查起。manifest / journal / plan / rules.lock 四个格式契约件已由库格式公开规范 FORMAT v0.1(仓库 docs/FORMAT.md)逐字段冻结,第三方可依赖。

01五步流水线

步名称输入 → 输出有没有副作用
①采集信号文件元信息 → signals 结构无,只读
②规则判分信号 + 规则 → S1–S4 分数无,纯计算
③判定梯分数 → 库/目标/置信/理由无,纯计算
④三态分流置信 → 直落 / 建议 / 待归无,纯计算
⑤投影写盘判定 → .kb/ 全部产物有,只在 apply 时发生

②③④ 之间零 IO。这是刻意的:内核对同一份输入必须给出同一个答案,所以它不能读时钟、不能读网络、不能读环境变量。

02判分的四类信号

信号内容权重
S1 显式.xhproject 这类标记文件、.kbfold.yaml 里的显式声明0.95
S2 名称文件名与路径里的关键词(词表来自模板)0.80
S3 结构同目录下兄弟文件的类型分布是否像某个场景0.6
S4 类型扩展名本身的分布倾向0.3

权重不是概率,是「谁更该被信」的顺序。四类信号不会相加,而是按下面的梯子逐级下降。

03L0–L6 七级判定梯

从最高开始逐级下降,命中即停,不再往下看:

级依据置信说明
L0显式标记0.95见到 .xhproject 就是它,不解释
L1名称全等0.92知识库名与目录名完全一致
L2模糊唯一0.85模糊匹配只有一个候选
L3词表命中0.80关键词命中
L4历史投票0.75同目录其他文件的既有归属
L5本地打分0.40–0.70S3/S4 加权,区间宽是因为它本来就不可靠
L6兜底—进 _inbox/

残差 = 0.95 − 实际置信,会写进报告。残差大的文件就是「最该人工看一眼」的那批,比按置信排序更有用。

04三态分流

置信区间处理界面上长什么样
≥ 0.90直落,不需要确认正常行
0.70 – 0.89给出建议建议行 + 灰字理由
< 0.70 或平手进 _inbox/ 附候选待归行,没有「猜一个」按钮

第三条是这套工具的性格:宁可空着,不替你猜。

05增量:第二次跑只碰动过的文件

六步:扫描 → 指纹 → 比对 manifest → 只对变化集判定 → 只动差异的投影与索引 → 原子提交 manifest。

指纹先比 size + mtime,只有这俩变了才去算 SHA-256——这是为了在十万文件的目录上把 IO 压下来。

变化被分成四态:

态判据做什么
新增manifest 无记录判定 + 投影 + 建块
变更同路径,hash 变了重解析,重建该文件的块
移动同 hash,新路径只改链接,不重解析(省掉约 90% 的活)
消失有记录,盘上没有打 tombstone,绝不删索引,可撤回

「移动」是这里最值钱的一态。用户拖一次目录,普通工具会当成「删了 200 个文件又新增 200 个文件」,KBFold 只更新 200 条链接。

另有两个防抖机制:

  • settled 终态集:已经人工定案的文件记 settled: true,后续批次不再重复判定。
  • 组合键幂等:batch = hash(规则版本 + 变化集),同一规则、同一批变化重跑,不会产生第二批动作。

06manifest.json 长什么样

json
{
  "version": 1,
  "rules_locked": "sha256:8f3c…",
  "files": {
    "星海项目/合同/2024-采购合同-v2.pdf": {
      "size": 412903,
      "mtime": 1730188800,
      "hash": "sha256:1a2b…",
      "kb": "XH-2024",
      "target": "02-合同",
      "confidence": 0.95,
      "reason": "L0:显式标记 .xhproject",
      "settled": false
    }
  }
}

大目录上这个文件会很大,所以是分片存储的(按顶层目录切片),十万文件也不至于每次整读。

reason 字段是人话,直接显示在报告与 kbfold explain 里。这个格式是完全公开的:第三方工具可以读写 .kb/,我们不会加私有字段。一个能被别人接管的台账,才可能变成事实标准。

07apply 的六步链路

步干什么失败时
① 扫描生成 manifest(只读)整批不开始
② 规划生成 plan.json(IR)整批不开始
③ 执行按 batch_id 整批事务整批停,不留半成品
④ 记账追加 journal.jsonl写不进 journal,该步不许执行
⑤ 留底原件快照进 trash/<batch>/磁盘不足,拒绝执行
⑥ 撤销kbfold undo <batch>hash 不符,停下报冲突

④ 是整个设计的枢纽:先记账再动手,账写不下去就不动手。 所以不存在「改了但没记」的状态。

journal.jsonl 一行一条,追加不改写:

json
{"batch":"20261030-1412-a3f9","op":"rename","from":"合同/a.pdf","to":"02-合同/2024-星海-采购合同-v2.pdf","hash_before":"sha256:1a2b…","hash_after":"sha256:1a2b…","ts":1730189520}
{"batch":"20261030-1412-a3f9","op":"undo","from":"02-合同/2024-星海-采购合同-v2.pdf","to":"合同/a.pdf","hash_before":"sha256:1a2b…","hash_after":"sha256:1a2b…","ts":1730191200}

注意第二行:undo 本身也是一条 append 记录,不是把第一行删掉。所以任何时刻把 journal 从头读一遍,都能重放到「现在到底是什么状态」。

08原子性

  • 同卷(同一个盘):改名与移动走 MoveFileEx,本身是原子操作。
  • 跨卷(C: → D:):系统只提供 copy,所以只能 copy → 校验 hash → 删源,三步不可能原子。

因此跨卷批次一律按「整批成功或整批停」执行,绝不逐个文件试运气。

09归档:三档,全都不动

档时间做什么
活跃< 30 天全量投影 + 索引,不提示。约 80% 的文件
近线30 – 180 天仍全量索引,只在 audit 里列「可归档清单」并标明体积。约 15%
归档> 180 天什么都不做。只有你显式 kbfold archive --to <目标> 才生成移动计划

第三档最容易做错:很多整理工具会自动把老文件搬到「归档区」,然后你再也找不到它。KBFold 永远不自动搬,方向必须由人给。

10检索:三路融合

kbfold search 同时走三条路,只读名次不读分数,用 RRF(k=60)融合:

路实现缺席时
语义bge-m3 问句向量,取 top 8Ollama 没起 ⇒ 静默跳过,不报错
内容FTS5 取 12 个候选,2 字词退化为 LIKEsqlite 不可用 ⇒ 本路跳过
关键词五个字段 LIKE + 中文二元组,上限 16 个词元从不缺席

命中子块会回灌父块(子块 512 字符,父块上下文 1600 字符、上限 4000),这样返回的是能读的整段,而不是断句。

四条容量闸:单库 10 万块 / 单库矩阵 512MB / 全进程 1GB / 单查询 500ms,都在 80% 处预警。超闸不是崩溃,是把对应的那一路降级并在结果里注明。

所有结果都带 [n],可回溯到文件与行号——这一点写进了 AI 工具的调用说明里,见「架构与设计」中的引用纪律五条。