From efd6a8d9902e79df852012bf4a3fa94e1f8889cb Mon Sep 17 00:00:00 2001
From: ai_xiaopei <xiaopei@aisim.cn>
Date: Fri, 21 Aug 2026 19:05:17 +0800
Subject: [PATCH] feat(graph): tag/entity 虚拟节点落库为真实节点(合成 path 命名空间)
---
internal/graph/builder.go | 13 ++++++++++---
internal/graph/model.go | 1 +
internal/graph/builder_test.go | 30 ++++++++++++++++++++++++++++--
3 files changed, 39 insertions(+), 5 deletions(-)
diff --git a/internal/graph/builder.go b/internal/graph/builder.go
index 428535b..ce5f0ea 100644
--- a/internal/graph/builder.go
+++ b/internal/graph/builder.go
@@ -15,13 +15,19 @@
labelToID := make(map[string]int64)
nextVirtualID := int64(1000000) // 虚拟节点从 1000000 开始
- getOrCreateVirtualNode := func(label string) int64 {
+ getOrCreateVirtualNode := func(label, nodeType string) int64 {
if id, ok := labelToID[label]; ok {
return id
}
id := nextVirtualID
nextVirtualID++
labelToID[label] = id
+ g.Nodes = append(g.Nodes, &Node{
+ ID: id,
+ Path: label, // label 已含 "tag:"/"entity:" 前缀
+ Title: strings.TrimPrefix(strings.TrimPrefix(label, "tag:"), "entity:"),
+ NodeType: nodeType,
+ })
return id
}
@@ -31,6 +37,7 @@
Path: f.Path,
Title: f.Title,
Section: f.Section,
+ NodeType: "file",
Tags: f.Tags,
Entities: f.Entities,
Wikilinks: f.Wikilinks,
@@ -40,7 +47,7 @@
// 创建 tag 边
for _, tag := range f.Tags {
- virtualID := getOrCreateVirtualNode("tag:" + tag)
+ virtualID := getOrCreateVirtualNode("tag:"+tag, "tag")
g.Edges = append(g.Edges, &Edge{
FromNode: node.ID,
ToNode: virtualID,
@@ -51,7 +58,7 @@
// 创建 entity 边
for _, entity := range f.Entities {
- virtualID := getOrCreateVirtualNode("entity:" + entity)
+ virtualID := getOrCreateVirtualNode("entity:"+entity, "entity")
g.Edges = append(g.Edges, &Edge{
FromNode: node.ID,
ToNode: virtualID,
diff --git a/internal/graph/builder_test.go b/internal/graph/builder_test.go
index 8b66be5..a966c86 100644
--- a/internal/graph/builder_test.go
+++ b/internal/graph/builder_test.go
@@ -1,6 +1,7 @@
package graph
import (
+ "strings"
"testing"
"github.com/aisim/kb-cli/internal/vault"
@@ -30,8 +31,33 @@
g := BuildGraph(files)
- if len(g.Nodes) != 2 {
- t.Errorf("node count = %d, want 2", len(g.Nodes))
+ // 2 个文件节点 + 2 个 tag 虚拟节点(充装、智能枪)+ 1 个 entity 虚拟节点
+ if len(g.Nodes) != 5 {
+ t.Errorf("node count = %d, want 5", len(g.Nodes))
+ }
+
+ // 验证虚拟节点 path 命名空间与 NodeType
+ tagCount, entityCount, fileCount := 0, 0, 0
+ for _, n := range g.Nodes {
+ switch n.NodeType {
+ case "tag":
+ tagCount++
+ if !strings.HasPrefix(n.Path, "tag:") {
+ t.Errorf("tag node path = %q, want prefix tag:", n.Path)
+ }
+ case "entity":
+ entityCount++
+ if !strings.HasPrefix(n.Path, "entity:") {
+ t.Errorf("entity node path = %q, want prefix entity:", n.Path)
+ }
+ case "file":
+ fileCount++
+ default:
+ t.Errorf("unexpected node_type %q for %s", n.NodeType, n.Path)
+ }
+ }
+ if tagCount != 2 || entityCount != 1 || fileCount != 2 {
+ t.Errorf("file/tag/entity = %d/%d/%d, want 2/2/1", fileCount, tagCount, entityCount)
}
// 应该有 3 条边:2条 tag + 1条 entity
diff --git a/internal/graph/model.go b/internal/graph/model.go
index 0702912..bcad193 100644
--- a/internal/graph/model.go
+++ b/internal/graph/model.go
@@ -6,6 +6,7 @@
Path string `json:"path"`
Title string `json:"title"`
Section string `json:"section"`
+ NodeType string `json:"node_type"` // "file" | "tag" | "entity"
Tags []string `json:"tags"`
Entities []string `json:"entities"`
Wikilinks []string `json:"wikilinks"`
--
Gitblit v1.10.0