ai_xiaopei
9 days ago 4bb2313aadcd0644b6ad515406b4f8842e024a55
cmd/index.go
@@ -3,6 +3,10 @@
import (
   "fmt"
   "os"
   "os/exec"
   "path/filepath"
   "strings"
   "time"
   "github.com/aisim/kb-cli/internal/index"
   "github.com/spf13/cobra"
@@ -11,27 +15,96 @@
var indexCmd = &cobra.Command{
   Use:   "index",
   Short: "索引管理",
   Long:  `管理知识库索引`,
}
var indexBuildCmd = &cobra.Command{
   Use:   "build",
   Short: "构建/重建索引",
   Long:  `构建或重建知识库索引`,
   RunE:  runIndexBuild,
   Short: "构建或重建知识库索引",
   Long: `# index build - 构建或重建知识库索引
kb-cli index build [--vault=<路径>]
操作说明:
  1. 扫描知识库目录下的所有 Markdown 文件
  2. 解析 frontmatter、标签、实体、wikilinks
  3. 构建知识图谱并写入 SQLite 数据库
  4. 创建 FTS5 全文索引
参数:
  --vault <路径>    知识库根目录(默认:~/aisim/note/001/笔记001)
  --db <路径>       索引数据库路径(默认:~/.cache/kb-cli/kb.db)`,
   RunE: runIndexBuild,
}
var indexStatusCmd = &cobra.Command{
   Use:   "status",
   Short: "查看索引状态",
   Long:  `查看索引状态信息`,
   RunE:  runIndexStatus,
   Short: "查看索引状态信息",
   Long: `# index status - 查看索引状态信息
kb-cli index status
输出信息:
  - 知识库路径
  - 索引文件路径
  - 节点数量
  - 边数量
  - 索引 commit
  - 当前 commit
  - 构建时间
  - 是否需要更新
参数:
  --vault <路径>    知识库根目录(默认:~/aisim/note/001/笔记001)
  --db <路径>       索引数据库路径(默认:~/.cache/kb-cli/kb.db)`,
   RunE: runIndexStatus,
}
var indexGcCmd = &cobra.Command{
   Use:   "gc",
   Short: "清理孤立节点和边",
   Long: `# index gc - 清理孤立节点和边
kb-cli index gc
操作说明:
  1. 扫描索引中的所有节点
  2. 检查对应的 Markdown 文件是否存在
  3. 删除不存在的文件对应的节点和边
  4. 输出清理的节点数量
参数:
  --vault <路径>    知识库根目录(默认:~/aisim/note/001/笔记001)
  --db <路径>       索引数据库路径(默认:~/.cache/kb-cli/kb.db)`,
   RunE: runIndexGc,
}
var gitCmd = &cobra.Command{
   Use:   "git",
   Short: "Git 同步操作",
}
var gitSyncCmd = &cobra.Command{
   Use:   "sync",
   Short: "同步知识库到 Git 仓库",
   Long: `# git sync - 同步知识库到 Git 仓库
kb-cli git sync
操作说明:
  1. 检查知识库目录是否是 Git 仓库
  2. 执行 git add -A 添加所有变更
  3. 如果有变更,执行 git commit(自动生成提交信息)
  4. 执行 git push 推送到远程仓库
参数:
  --vault <路径>    知识库根目录(默认:~/aisim/note/001/笔记001)`,
   RunE: runGitSync,
}
func init() {
   rootCmd.AddCommand(indexCmd)
   indexCmd.AddCommand(indexBuildCmd)
   indexCmd.AddCommand(indexStatusCmd)
   indexCmd.AddCommand(indexGcCmd)
   rootCmd.AddCommand(gitCmd)
   gitCmd.AddCommand(gitSyncCmd)
}
func runIndexBuild(cmd *cobra.Command, args []string) error {
@@ -115,3 +188,109 @@
   return nil
}
func runIndexGc(cmd *cobra.Command, args []string) error {
   // 展开 ~ 为实际路径
   expandedVaultPath := vaultPath
   if strings.HasPrefix(vaultPath, "~/") {
      home, err := os.UserHomeDir()
      if err != nil {
         return fmt.Errorf("获取用户目录失败: %w", err)
      }
      expandedVaultPath = filepath.Join(home, vaultPath[2:])
   }
   // 打开索引
   store, err := index.Open(dbPath)
   if err != nil {
      return fmt.Errorf("打开索引失败: %w", err)
   }
   defer store.Close()
   fmt.Fprintln(os.Stderr, "正在清理孤立节点和边...")
   // 获取所有节点路径
   nodes, err := store.GetAllNodes()
   if err != nil {
      return fmt.Errorf("获取节点失败: %w", err)
   }
   // 检查文件是否存在
   var orphanPaths []string
   for _, node := range nodes {
      fullPath := filepath.Join(expandedVaultPath, node.Path)
      if _, err := os.Stat(fullPath); os.IsNotExist(err) {
         orphanPaths = append(orphanPaths, node.Path)
      }
   }
   if len(orphanPaths) == 0 {
      fmt.Fprintln(os.Stderr, "没有发现孤立节点")
      return nil
   }
   fmt.Fprintf(os.Stderr, "发现 %d 个孤立节点,正在清理...\n", len(orphanPaths))
   // 删除孤立节点
   deletedCount, err := store.DeleteNodesByPaths(orphanPaths)
   if err != nil {
      return fmt.Errorf("删除节点失败: %w", err)
   }
   fmt.Fprintf(os.Stderr, "已清理 %d 个孤立节点\n", deletedCount)
   return nil
}
func runGitSync(cmd *cobra.Command, args []string) error {
   // 展开 ~ 为实际路径
   expandedVaultPath := vaultPath
   if strings.HasPrefix(vaultPath, "~/") {
      home, err := os.UserHomeDir()
      if err != nil {
         return fmt.Errorf("获取用户目录失败: %w", err)
      }
      expandedVaultPath = filepath.Join(home, vaultPath[2:])
   }
   fmt.Fprintln(os.Stderr, "正在同步知识库到 Git 仓库...")
   // 检查是否是 git 仓库
   gitDir := filepath.Join(expandedVaultPath, ".git")
   if _, err := os.Stat(gitDir); os.IsNotExist(err) {
      return fmt.Errorf("知识库目录不是 Git 仓库: %s", expandedVaultPath)
   }
   // 执行 git add -A
   addCmd := exec.Command("git", "-C", expandedVaultPath, "add", "-A")
   if output, err := addCmd.CombinedOutput(); err != nil {
      return fmt.Errorf("git add 失败: %w\n%s", err, output)
   }
   // 检查是否有变更
   statusCmd := exec.Command("git", "-C", expandedVaultPath, "status", "--porcelain")
   statusOutput, err := statusCmd.Output()
   if err != nil {
      return fmt.Errorf("git status 失败: %w", err)
   }
   if len(statusOutput) == 0 {
      fmt.Fprintln(os.Stderr, "没有变更需要提交")
      return nil
   }
   // 生成 commit 信息
   commitMsg := fmt.Sprintf("kb-cli: 自动同步 %s", time.Now().Format("2006-01-02 15:04:05"))
   commitCmd := exec.Command("git", "-C", expandedVaultPath, "commit", "-m", commitMsg)
   if output, err := commitCmd.CombinedOutput(); err != nil {
      return fmt.Errorf("git commit 失败: %w\n%s", err, output)
   }
   // 执行 git push
   pushCmd := exec.Command("git", "-C", expandedVaultPath, "push")
   if output, err := pushCmd.CombinedOutput(); err != nil {
      return fmt.Errorf("git push 失败: %w\n%s", err, output)
   }
   fmt.Fprintln(os.Stderr, "Git 同步完成")
   return nil
}