Add Phase 4 knowledge graph: nodes, cells, facts, edges, gRPC service
Build the knowledge graph pillar with the kg package: - Node: hierarchical notes with parent/children, C2 wiki-style naming, shared tag/category pool with artifacts - Cell: content units (markdown, code, plain) with ordinal ordering - Fact: EAV tuples with transaction timestamps and retraction support - Edge: directed graph links (child, parent, related, artifact_link) Includes schema migration (002_knowledge_graph.sql), protobuf definitions (kg.proto), full gRPC KnowledgeGraphService implementation, CLI commands (node create/get), and comprehensive test coverage. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -29,6 +29,8 @@ Commands:
|
||||
cat add <category> [...] Create categories
|
||||
cat list List all categories
|
||||
search tag <tag> Search artifacts by tag
|
||||
node create <name> Create a knowledge graph node
|
||||
node get <id> Get a node with its cells
|
||||
version Print version
|
||||
|
||||
Environment:
|
||||
@@ -53,13 +55,15 @@ func main() {
|
||||
runCat(os.Args[2:])
|
||||
case "search":
|
||||
runSearch(os.Args[2:])
|
||||
case "node":
|
||||
runNode(os.Args[2:])
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "unknown command: %s\n", os.Args[1])
|
||||
usage()
|
||||
}
|
||||
}
|
||||
|
||||
func dial() pb.ArtifactServiceClient {
|
||||
func dialConn() *grpc.ClientConn {
|
||||
addr := os.Getenv("EXO_ADDR")
|
||||
if addr == "" {
|
||||
addr = "localhost:9090"
|
||||
@@ -70,8 +74,15 @@ func dial() pb.ArtifactServiceClient {
|
||||
if err != nil {
|
||||
log.Fatalf("exo: failed to connect to %s: %v", addr, err)
|
||||
}
|
||||
// Connection will be closed when the process exits.
|
||||
return pb.NewArtifactServiceClient(conn)
|
||||
return conn
|
||||
}
|
||||
|
||||
func dial() pb.ArtifactServiceClient {
|
||||
return pb.NewArtifactServiceClient(dialConn())
|
||||
}
|
||||
|
||||
func dialKG() pb.KnowledgeGraphServiceClient {
|
||||
return pb.NewKnowledgeGraphServiceClient(dialConn())
|
||||
}
|
||||
|
||||
func runImport(args []string) {
|
||||
@@ -203,3 +214,62 @@ func runSearch(args []string) {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func runNode(args []string) {
|
||||
if len(args) == 0 {
|
||||
fmt.Fprintln(os.Stderr, "usage: exo node <create|get> [...]")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
client := dialKG()
|
||||
ctx := context.Background()
|
||||
|
||||
switch args[0] {
|
||||
case "create":
|
||||
if len(args) < 2 {
|
||||
fmt.Fprintln(os.Stderr, "usage: exo node create <name>")
|
||||
os.Exit(1)
|
||||
}
|
||||
resp, err := client.CreateNode(ctx, &pb.CreateNodeRequest{
|
||||
Name: args[1],
|
||||
Type: "note",
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("exo: failed to create node: %v", err)
|
||||
}
|
||||
fmt.Printf("created node: %s\n", resp.Id)
|
||||
|
||||
case "get":
|
||||
if len(args) < 2 {
|
||||
fmt.Fprintln(os.Stderr, "usage: exo node get <id>")
|
||||
os.Exit(1)
|
||||
}
|
||||
resp, err := client.GetNode(ctx, &pb.GetNodeRequest{Id: args[1]})
|
||||
if err != nil {
|
||||
log.Fatalf("exo: failed to get node: %v", err)
|
||||
}
|
||||
n := resp.Node
|
||||
fmt.Printf("ID: %s\n", n.Id)
|
||||
fmt.Printf("Name: %s\n", n.Name)
|
||||
fmt.Printf("Type: %s\n", n.Type)
|
||||
fmt.Printf("Parent: %s\n", n.ParentId)
|
||||
fmt.Printf("Created: %s\n", n.Created)
|
||||
fmt.Printf("Modified: %s\n", n.Modified)
|
||||
if len(n.Children) > 0 {
|
||||
fmt.Printf("Children: %v\n", n.Children)
|
||||
}
|
||||
if len(n.Tags) > 0 {
|
||||
fmt.Printf("Tags: %v\n", n.Tags)
|
||||
}
|
||||
if len(resp.Cells) > 0 {
|
||||
fmt.Printf("Cells: %d\n", len(resp.Cells))
|
||||
for _, c := range resp.Cells {
|
||||
fmt.Printf(" [%d] %s (%s): %s\n", c.Ordinal, c.Id, c.Type, string(c.Contents))
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "unknown node subcommand: %s\n", args[0])
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,6 +71,7 @@ func main() {
|
||||
|
||||
grpcServer := grpc.NewServer()
|
||||
pb.RegisterArtifactServiceServer(grpcServer, server.NewArtifactServer(database, blobStore))
|
||||
pb.RegisterKnowledgeGraphServiceServer(grpcServer, server.NewKGServer(database))
|
||||
|
||||
// Graceful shutdown on SIGINT/SIGTERM.
|
||||
sigCh := make(chan os.Signal, 1)
|
||||
|
||||
Reference in New Issue
Block a user