v1.33.0 — 章节级排序、知识邻域、检索视图、续航包修正
本版把检索从「找到哪篇笔记」推进到「找到笔记里的哪一段」,并把几处会静默漂移的重复删掉而不是再加一道断言。
排序单位从整篇变成章节(#118)
search-vault 此前按整篇算 BM25,选完再挑摘要——排上来的理由和读者被送到的位置可以是同一篇笔记的不同部分。现在一篇笔记按它自己的最佳章节竞争,摘要就从那一节出。
旧单位的代价是量出来的,不是假设的:同一段证据在 0.3 KB 笔记里得 11.9 分,在 75 KB 笔记里得 2.2 分,两篇长笔记都进不了 Top-5,而五篇只是顺带提到这些词的笔记进了。有章节的那篇现在排到第 2;它那篇内容与体积几乎相同、只是没有章节的孪生笔记仍然缺席——这是测量在起作用,不是数字在动:章节只有存在才能竞争。
- 标题、别名、标签、小标题、链接描述整篇,进入每一节的分数;正文被切开,取最好的一节
- 没有小标题的笔记只有一节等于全文,所以短笔记的分数和以前逐位相同
- 一节的长度不低于本篇的典型章节长度。BM25 奖励短文档,这对短笔记是对的,对长笔记里的一小片是错的——够到它仍然要打开那篇长笔记。#118 在写代码前就列了这个风险,第一版实现照样犯:一段只有
jitter 上限。的残节以 0.580 压过真正解释答案的 0.504 - 延迟 P95 144 ms vs 138 ms(1.04x,#118 给的预算是 2x)。做到这一点靠的是不把正文切两次分词,而是把各节的计数相加——这是精确而非近似,因为换行符既不是拉丁字母也不是汉字,任何一个词元都跨不过行
还有一处:用户输入的词若只出现在本节之外,不再报进 body 信号。这条同样事先列为风险、同样实现错了,是靠打印输出发现的,不是靠为它写的断言——那条断言的第一版是空的。
知识邻域(#121)
explore-neighborhood 回答找到一篇好笔记之后的问题:这个 Vault 说什么和它连着。
每条边都是声明,没有一条是推断。不打分、不提议链接、不把链接读成「支持」或「是证据」——发现新候选是 #75 的事,证据谱系是 #85 的事,在这里做任何一件都会让一个猜测读起来像用户写下的东西。同主题、同目录、同日期不算连接。 节点顺序是稳定路径序,reference 直说它不是排名。
- 歧义名列出
candidates但一个都不用;解析不了的链接返回而不丢弃——一篇有三条失效链接的笔记不该看起来像连接更少的笔记 - 代码围栏里的链接是被引用的语法,Vault 自己的笔记天天引用它
- 目录索引与原文归档默认排除并计入
excluded:索引链接目录里的每一篇,跟着它走返回的是目录不是邻域。--include-structural可以跟
一跳。参考 Vault 上:一篇项目复盘返回一个邻居,同时经由正文链接和 related 两条路到达,去重后两个来源都标出;而 AI Bug 工作流那篇诚实地返回零,因为它没有 wikilink、related 为空,并且它自己正文里就说了是刻意不做链接的。
把问题写下来再运行(#122)
run-retrieval-view 运行 Vault 已经写下来的搜索。用户反复问的问题并不无限——「上周的学习笔记」「当前项目风险」——而每次 Agent 重新把它翻译成参数时可能翻译得不一样,同一个问题于是悄悄得到不同答案。视图就是那次翻译,做一次,存在 .obsidian-kb/retrieval-views.json。
只有结构化字段:没有命令、没有管道、没有模板、没有环境变量插值,也没有 search-vault 本来没有的字段。视图只能收窄,走的是直接调用同样的校验参数。未知的键是拒绝而不是忽略——静默丢掉一个就等于运行了一次这个文件没有描述的搜索。
--as-of 是必填的,helper 从不读时钟。含义来自「现在」的时间窗每次运行给不同答案,那正好是这东西要防的;「上周」是 Agent 转成日期的短语,不是配置能持有的值。
返回结果时一并返回解析出的 plan,并且有测试把那个 plan 重新过一遍 search-vault 做比对——一个描述不了实际调用的 plan 比没有 plan 更糟,它是一个穿着可核对外衣的错误答案。
指向已不存在目录的视图返回 invalid-view-scope,而不是回落到全库:静默变宽的视图会继续工作、返回比以往更多的东西、并且什么都不说。
续航包的三处修正
- 不再把项目自己的目录索引当作材料(#133)。
_instance_sources的 docstring 承诺排除索引文件,实际只排除了README.md/AGENTS.md/CLAUDE.md。参考 Vault 上这不是一个项目的不幸:四篇项目笔记里有实例目录的三篇,每一篇都恰好返回一个来源,且三次都是该项目自己的目录索引,fields: []。三篇现在都返回sources: []——对一个只装着一篇笔记和一份清单的目录,这才是诚实的答案。判据是笔记声明的类型,并且对每条路径生效,不只是目录扫描。 - 「章节缺失」不再和「章节没被认出来」混为一谈(#115)。 这两种读法把用户送向相反方向:一个去找,一个去写一份已经存在的东西。现在返回
headings,分成matched与unmatched。顺带修掉一个真实缺陷:词表只认识project overview,所以每一篇按本项目自己的英文模板写的笔记都把 goal 报成缺失。 - 补上 #86 点名却从未交付的两类目录外来源(#110)。
projectfrontmatter 与项目笔记自己的related。两条路都比位置弱并且说出来:origin报最强的那条,origins列全部。歧义只报不解——related名字撞上两篇返回ambiguous-related-link并且两篇都不用,选一篇会把别的项目的材料归进这个包,读起来像本项目自己的历史而读者无从分辨。顺带记一笔:参考 Vault 上project:根本没人用(三处,全空,两处是模板),所以这条路唯一的证据是它的测试。这句写在这里,而不是留给谁去发现。
复苏队列说出它的数字从哪来(#109)
open_tasks 数全文每一个未勾选的框,而那个数排的序——于是一篇装着可复用检查表的笔记压过了真有活干的项目。参考 Vault 上一篇项目笔记末尾有十五条给别的项目落地用的检查项,而它自己的计划是一个连一个 checkbox 都没有的 P0/P1/P2 编号列表;它排成了全库最忙的项目,并把一句检查表问句报成了下一步。
现在按 open_tasks_in_next_actions 排序,两个数都报,外加 open_tasks_scope。null 不是零:零是这篇在那一节里什么都没放,null 是它根本没有那一节。
next_action_heading 是新的,也是这件事不再机械的地方。那篇笔记里检查表是嵌在下一步行动章节里的——### 可复用的项目落地检查表 与 ### P0:下一次迭代前完成 并列在 ## 下一步行动 之下。没有任何结构能分开它们,分开它们的是作者给它起的名字,而那是一个关于内容的判断,这个 helper 不做。所以它报出标题,由读者判断。
归档拒绝没写完的草稿(#116)
新拒绝码 draft-incomplete。参考 Vault 上一篇标了 incomplete、四个章节全写着「待后续详细阅读后补充完整」的 web-clip 被提议迁进 20-Learning。两阶段闸门拦住了它——一个人读了计划说不——但那是注意力,不是守卫,而批准一份长计划的用户读的是列表不是每一行。
两个信号,都是笔记关于它自己的陈述而不是对内容的判断:草稿标签,和正文里没被替换的 {{placeholder}}。正文里用散文写「待后续补充」不匹配——为含义读散文正是归档不做的事。
更少的重复,而不是更多的断言
四条边界这一版是被删掉的,不是被守起来的:
| 未替换占位符正则 | audit-vault 与 template-contract 各自一份且已经分叉,归档正要加第三份 → 合并到 note_catalog,按对象同一性断言 |
| 「下一步行动」词表 | 两个检索 helper 各持一套,上一版只扩了其中一侧 → 共用 PROJECT_NOTE_NEXT_ACTION_HEADINGS |
{"folder-index", "moc"} |
两份拷贝,检索正要成为第三份 → 一个 INDEX_TYPES |
| wikilink 解析 | 从 audit_vault 提到共享的 link_graph,audit_vault 少了 146 行 |
两处能互相 import 的时候,删掉边界胜过在它上面加断言。
零结果搜索现在说出四种情况里的哪一种(#120),文本与 JSON 从同一张 ZERO_RESULT_REASONS 表生成——这也是一条被删掉的关系。要防的读法是把 no-token-overlap 报成「你的 Vault 里没有这方面的东西」:那是一个关于词的事实,不是关于知识的事实。
--updated-* 让搜索能问笔记什么时候变过,而不只是什么时候写的(#119)。参考 Vault 上一篇 2026-06-09 的项目笔记 updated 是 2026-08-12:--after 2026-08-01 返回五篇并静默漏掉那个月真正变过的那篇。200 篇里 177 篇根本没有 updated,单独计入 missing-updated——「没人记录它什么时候变的」是 Vault 的事实,「它在你的窗口外变的」是过滤器在工作。review-projects 故意用另一套口径,这条差异登记为第 28 行,是登记表第一条记录两处必须保持不同的关系。
装机:和 Skill 管理器共存(#113 / #114)
--runtime-only/-RuntimeOnly装 Skill 管理器不提供的一切,不装它提供的。安装器有六件事要做,只有一件(把 Skill 文件写进五个平台目录)和管理器重叠——「用管理器就行」装出来的环境第一次调 helper 就ModuleNotFoundError- 安装器不再毁掉不是它建的 Skill 链接。实测过:安装器打印
Installation complete五个平台全打勾,同时skillctl doctor从OK变成FAILED带八个runtime link drift。两边都不看对方,所以没有任何东西报告出问题。
评估集自己先被修了(#136)
对抗集里每一篇长笔记都是靠追加无结构填充生成的,因为作者就是那么想象长笔记的。事后在参考 Vault 上量:19 篇 ≥10 KB 的笔记里,最少的有 12 个小标题,中位数 30,没有一篇只有一个。那个集合复现的是这个 Vault 根本不表现的机制。
后果不是理论上的:一个章节级排序的候选实现在全部 22 个用例上与 master 逐字节相同——只有一个小标题的笔记恰好只有一节,任何按小标题的切分在它上面都是空操作。#117 自己立的标准是「不会失败的评估集就是一道出生即绿的守卫」;这个集合能失败,但在 #118 正要问它的那个问题上不能。
升级
git pull && ./install.sh已经用 Skill 管理器的机器改用:
git pull && ./install.sh --runtime-only