日期:2026-08-21
状态:草案(待用户审阅)
当前 kb-cli index build 后:814 个文件节点,仅 138 条边(全部是 wikilink)。
根因(已在源码中确认):
internal/graph/builder.go 为每个文件的 tags/entities 生成了指向虚拟节点(ID 从 1000000 起)的边,但虚拟节点从不插入 SQLite。cmd/rebuild.go 写边时遇到 idMap 中不存在的节点直接跳过,导致全库 1100+ 个文件 frontmatter 中的 tags 一条边都没落库。matchesWikilink 要求链接文本等于文件标题或去编号后的文件名。库内 3334 个 wikilink 中大量是实体名([[电子秤]] 401 次、[[运营管理平台]] 315 次等),无同名文件,匹配失败。实际影响:
- graph query / graph related 的关联遍历基本失效(每节点平均 0.17 条边)
- search 排序不依赖边(FTS + 板块匹配),日常搜索未受影响
- "相关文档推荐"(共同标签/共同实体)能力缺失
graph related 能通过共同标签/实体找到真正相关的文档search 行为:tag/entity 节点不能污染 FTS 搜索结果和关键词匹配CREATE TABLE nodes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
path TEXT NOT NULL UNIQUE,
node_type TEXT NOT NULL DEFAULT 'file', -- 'file' | 'tag' | 'entity'
title TEXT,
section TEXT,
tags TEXT,
entities TEXT,
wikilinks TEXT,
content_fts TEXT,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now'))
);
迁移策略:initTables 建表时包含新列;对已存在的旧库执行ALTER TABLE nodes ADD COLUMN node_type TEXT NOT NULL DEFAULT 'file'
(SQLite 支持 ADD COLUMN,幂等处理:先 PRAGMA table_info 检查列是否存在)。
由于 index build 本来就是全量 ClearData + 重建,旧数据会被清掉,
迁移只影响"旧库直接跑非 build 命令"的场景,DEFAULT 'file' 已兜底。
builder.go:虚拟节点不再用内存 ID,改为生成带命名空间的合成 path:
path = "tag:<标签名>",node_type = "tag",title = 标签名path = "entity:<实体名>",node_type = "entity",title = 实体名node_type = "file"Graph 结构增加 NodeType 字段。虚拟节点也进入 g.Nodes 切片(保证 idMap 完整),
边生成逻辑不变(file → 虚拟节点的 tag/entity 边 + wikilink 边)。
rebuild.go:写入逻辑不变(先节点后边,idMap 映射),虚拟节点现在有真实 ID,
tag/entity 边不再被跳过。
path 命名空间安全性:vault 内实际文件路径均不含 tag:/entity: 前缀
(已验证),UNIQUE 约束不会冲突。
以下查询增加 WHERE node_type = 'file'(或等效过滤):
| 函数 | 位置 | 过滤方式 |
|---|---|---|
CreateFTS / PopulateFTS |
fts.go | PopulateFTS 的 INSERT...SELECT 增加 WHERE node_type='file',tag 节点不进 FTS |
FTSSearch |
fts.go | FTS 表只含 file 节点,天然隔离,无需改动 |
FindNodesByKeyword |
sqlite.go | SQL 增加 AND node_type='file' |
FindRelatedNodes |
sqlite.go | 种子节点查询增加 AND node_type='file';共同标签/实体遍历逻辑不变(tag 边落库后此逻辑才真正生效) |
GetAllNodes(gc 用) |
sqlite.go | gc 的 os.Stat 检查只对 node_type='file' 的节点执行,tag/entity 节点跳过(它们本就不是磁盘文件) |
GetNodeEdges 图遍历不过滤 node_type——tag 节点正是遍历要经过的中间节点。
graph stats 的关系分布自然包含 tag/entity/wikilink 三类,无需改动;index status 的边数会如实反映新数量。
vault 扫描(814 files)
→ 构建图: 814 文件节点 + N 个 tag 节点 + M 个 entity 节点
→ 边: file→tag, file→entity, file→file(wikilink)
→ SQLite: 全部节点落库(node_type 区分), 全部边落库
→ FTS: 仅 file 节点
→ search: FTS+板块(行为不变)
→ graph related: 共同 tag/entity 关联真正可用
ALTER TABLE 迁移失败 → 报错退出(build 前保证 schema 就绪)go test ./internal/graph/:go test ./internal/index/:kb-cli index build 后 index status 边数 ≥ 2000;graph related "电磁阀" 返回结果包含共享 tag 的文档;search 结果与改动前一致(不出现 tag 节点)。