| New file |
| | |
| | | # kb-cli 增强功能实现计划 |
| | | |
| | | > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. |
| | | |
| | | **Goal:** 为 kb-cli 添加 `--with-content` 和 `--with-links` 功能,并调整 kb-search 技能优先使用 kb-cli |
| | | |
| | | **Architecture:** 在现有 SearchResult 结构体上添加可选字段 Content 和 Links,通过 SearchOptions 控制是否填充这些字段。修改输出格式化器支持新字段的展示。 |
| | | |
| | | **Tech Stack:** Go 1.22, SQLite FTS5, Cobra CLI |
| | | |
| | | ## Global Constraints |
| | | |
| | | - 所有新功能必须有单元测试 |
| | | - JSON 输出使用 `omitempty` 标签 |
| | | - 保持向后兼容(不破坏现有命令) |
| | | - 性能目标:< 100ms(包含内容和链接获取) |
| | | |
| | | --- |
| | | |
| | | ## Task 1: 添加 GetNodeLinks 数据库方法 |
| | | |
| | | **Files:** |
| | | - Modify: `internal/index/sqlite.go:120-140` |
| | | - Test: `internal/index/sqlite_test.go` |
| | | |
| | | **Interfaces:** |
| | | - Consumes: `Store.db` (SQLite 连接) |
| | | - Produces: `GetNodeLinks(nodeID int64) ([]string, error)` 方法 |
| | | |
| | | - [ ] **Step 1: 编写失败测试** |
| | | |
| | | 在 `internal/index/sqlite_test.go` 添加: |
| | | |
| | | ```go |
| | | func TestGetNodeLinks(t *testing.T) { |
| | | store := setupTestStore(t) |
| | | defer store.Close() |
| | | |
| | | // 插入测试节点 |
| | | node1 := &graph.Node{ |
| | | Path: "FAQ/001-测试.md", |
| | | Title: "测试文档", |
| | | Section: "FAQ", |
| | | Content: "# 测试\n\n内容", |
| | | } |
| | | node2 := &graph.Node{ |
| | | Path: "FAQ/002-相关.md", |
| | | Title: "相关文档", |
| | | Section: "FAQ", |
| | | Content: "# 相关\n\n内容", |
| | | } |
| | | |
| | | id1, err := store.InsertNode(node1) |
| | | require.NoError(t, err) |
| | | |
| | | id2, err := store.InsertNode(node2) |
| | | require.NoError(t, err) |
| | | |
| | | // 插入 wikilink 边 |
| | | edge := &graph.Edge{ |
| | | FromNode: id1, |
| | | ToNode: id2, |
| | | Relation: "wikilink", |
| | | Label: "相关文档", |
| | | } |
| | | err = store.InsertEdge(edge) |
| | | require.NoError(t, err) |
| | | |
| | | // 测试获取链接 |
| | | links, err := store.GetNodeLinks(id1) |
| | | require.NoError(t, err) |
| | | assert.Len(t, links, 1) |
| | | assert.Equal(t, "FAQ/002-相关.md", links[0]) |
| | | |
| | | // 测试无链接的节点 |
| | | links, err = store.GetNodeLinks(id2) |
| | | require.NoError(t, err) |
| | | assert.Len(t, links, 0) |
| | | } |
| | | ``` |
| | | |
| | | - [ ] **Step 2: 运行测试确认失败** |
| | | |
| | | ```bash |
| | | cd ~/workspace/kb-cli |
| | | go test ./internal/index -run TestGetNodeLinks -v |
| | | ``` |
| | | |
| | | Expected: FAIL - "store.GetNodeLinks undefined" |
| | | |
| | | - [ ] **Step 3: 实现 GetNodeLinks 方法** |
| | | |
| | | 在 `internal/index/sqlite.go` 的 `GetNodeContent` 方法后添加: |
| | | |
| | | ```go |
| | | // GetNodeLinks 获取节点的关联链接(wikilink 目标) |
| | | func (s *Store) GetNodeLinks(nodeID int64) ([]string, error) { |
| | | query := ` |
| | | SELECT n.path |
| | | FROM edges e |
| | | JOIN nodes n ON n.id = e.to_node |
| | | WHERE e.from_node = ? AND e.relation = 'wikilink' |
| | | ` |
| | | rows, err := s.db.Query(query, nodeID) |
| | | if err != nil { |
| | | return nil, fmt.Errorf("查询链接失败: %w", err) |
| | | } |
| | | defer rows.Close() |
| | | |
| | | var links []string |
| | | for rows.Next() { |
| | | var path string |
| | | if err := rows.Scan(&path); err != nil { |
| | | return nil, fmt.Errorf("扫描链接失败: %w", err) |
| | | } |
| | | links = append(links, path) |
| | | } |
| | | |
| | | if err := rows.Err(); err != nil { |
| | | return nil, fmt.Errorf("遍历链接失败: %w", err) |
| | | } |
| | | |
| | | return links, nil |
| | | } |
| | | ``` |
| | | |
| | | - [ ] **Step 4: 运行测试确认通过** |
| | | |
| | | ```bash |
| | | cd ~/workspace/kb-cli |
| | | go test ./internal/index -run TestGetNodeLinks -v |
| | | ``` |
| | | |
| | | Expected: PASS |
| | | |
| | | - [ ] **Step 5: 提交** |
| | | |
| | | ```bash |
| | | cd ~/workspace/kb-cli |
| | | git add internal/index/sqlite.go internal/index/sqlite_test.go |
| | | git commit -m "feat: add GetNodeLinks method for wikilink retrieval" |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## Task 2: 扩展 SearchResult 结构体 |
| | | |
| | | **Files:** |
| | | - Modify: `internal/search/engine.go:15-25` |
| | | - Test: `internal/search/engine_test.go` |
| | | |
| | | **Interfaces:** |
| | | - Consumes: 无 |
| | | - Produces: 扩展的 `SearchResult` 结构体(Content, Links 字段) |
| | | |
| | | - [ ] **Step 1: 编写失败测试** |
| | | |
| | | 在 `internal/search/engine_test.go` 添加: |
| | | |
| | | ```go |
| | | func TestSearchResultJSON_OmitEmpty(t *testing.T) { |
| | | // 测试无内容和链接时,JSON 不包含这些字段 |
| | | result := SearchResult{ |
| | | ID: 1, |
| | | Path: "test.md", |
| | | Title: "测试", |
| | | Section: "FAQ", |
| | | Score: 10, |
| | | } |
| | | |
| | | jsonBytes, err := json.Marshal(result) |
| | | require.NoError(t, err) |
| | | |
| | | jsonStr := string(jsonBytes) |
| | | assert.NotContains(t, jsonStr, "content") |
| | | assert.NotContains(t, jsonStr, "links") |
| | | |
| | | // 测试有内容时,JSON 包含 content 字段 |
| | | result.Content = "# 测试内容" |
| | | jsonBytes, err = json.Marshal(result) |
| | | require.NoError(t, err) |
| | | |
| | | jsonStr = string(jsonBytes) |
| | | assert.Contains(t, jsonStr, `"content":"# 测试内容"`) |
| | | |
| | | // 测试有链接时,JSON 包含 links 字段 |
| | | result.Links = []string{"related.md"} |
| | | jsonBytes, err = json.Marshal(result) |
| | | require.NoError(t, err) |
| | | |
| | | jsonStr = string(jsonBytes) |
| | | assert.Contains(t, jsonStr, `"links":["related.md"]`) |
| | | } |
| | | ``` |
| | | |
| | | - [ ] **Step 2: 运行测试确认失败** |
| | | |
| | | ```bash |
| | | cd ~/workspace/kb-cli |
| | | go test ./internal/search -run TestSearchResultJSON_OmitEmpty -v |
| | | ``` |
| | | |
| | | Expected: FAIL - "Content undefined" 或测试失败(因为字段不存在) |
| | | |
| | | - [ ] **Step 3: 扩展 SearchResult 结构体** |
| | | |
| | | 修改 `internal/search/engine.go` 中的 `SearchResult` 结构体: |
| | | |
| | | ```go |
| | | // SearchResult 搜索结果 |
| | | type SearchResult struct { |
| | | ID int64 `json:"id"` |
| | | Path string `json:"path"` |
| | | Title string `json:"title"` |
| | | Section string `json:"section"` |
| | | Score int `json:"score"` |
| | | |
| | | // 可选字段(omitempty) |
| | | Content string `json:"content,omitempty"` // --with-content 时填充 |
| | | Links []string `json:"links,omitempty"` // --with-links 时填充 |
| | | } |
| | | ``` |
| | | |
| | | - [ ] **Step 4: 运行测试确认通过** |
| | | |
| | | ```bash |
| | | cd ~/workspace/kb-cli |
| | | go test ./internal/search -run TestSearchResultJSON_OmitEmpty -v |
| | | ``` |
| | | |
| | | Expected: PASS |
| | | |
| | | - [ ] **Step 5: 提交** |
| | | |
| | | ```bash |
| | | cd ~/workspace/kb-cli |
| | | git add internal/search/engine.go internal/search/engine_test.go |
| | | git commit -m "feat: extend SearchResult with Content and Links fields" |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## Task 3: 扩展 SearchOptions 并修改 Search 函数 |
| | | |
| | | **Files:** |
| | | - Modify: `internal/search/engine.go:30-40, 80-120` |
| | | - Test: `internal/search/engine_test.go` |
| | | |
| | | **Interfaces:** |
| | | - Consumes: `Store.GetNodeContent`, `Store.GetNodeLinks` (Task 1) |
| | | - Produces: 扩展的 `SearchOptions` 和增强的 `Search` 函数 |
| | | |
| | | - [ ] **Step 1: 编写失败测试** |
| | | |
| | | 在 `internal/search/engine_test.go` 添加: |
| | | |
| | | ```go |
| | | func TestSearch_WithContent(t *testing.T) { |
| | | store := setupTestStore(t) |
| | | defer store.Close() |
| | | |
| | | // 插入测试节点 |
| | | node := &graph.Node{ |
| | | Path: "FAQ/001-测试.md", |
| | | Title: "测试文档", |
| | | Section: "FAQ", |
| | | Content: "# 测试\n\n这是测试内容", |
| | | } |
| | | id, err := store.InsertNode(node) |
| | | require.NoError(t, err) |
| | | |
| | | err = store.CreateFTS() |
| | | require.NoError(t, err) |
| | | err = store.PopulateFTS() |
| | | require.NoError(t, err) |
| | | |
| | | // 测试带内容的搜索 |
| | | opts := SearchOptions{ |
| | | TopN: 5, |
| | | WithContent: true, |
| | | } |
| | | results, err := Search(store, []string{"测试"}, opts) |
| | | require.NoError(t, err) |
| | | require.Len(t, results, 1) |
| | | assert.Equal(t, id, results[0].ID) |
| | | assert.Equal(t, "# 测试\n\n这是测试内容", results[0].Content) |
| | | |
| | | // 测试不带内容的搜索 |
| | | opts.WithContent = false |
| | | results, err = Search(store, []string{"测试"}, opts) |
| | | require.NoError(t, err) |
| | | require.Len(t, results, 1) |
| | | assert.Empty(t, results[0].Content) |
| | | } |
| | | |
| | | func TestSearch_WithLinks(t *testing.T) { |
| | | store := setupTestStore(t) |
| | | defer store.Close() |
| | | |
| | | // 插入两个节点 |
| | | node1 := &graph.Node{ |
| | | Path: "FAQ/001-测试.md", |
| | | Title: "测试文档", |
| | | Section: "FAQ", |
| | | Content: "# 测试", |
| | | } |
| | | node2 := &graph.Node{ |
| | | Path: "FAQ/002-相关.md", |
| | | Title: "相关文档", |
| | | Section: "FAQ", |
| | | Content: "# 相关", |
| | | } |
| | | |
| | | id1, err := store.InsertNode(node1) |
| | | require.NoError(t, err) |
| | | id2, err := store.InsertNode(node2) |
| | | require.NoError(t, err) |
| | | |
| | | // 插入 wikilink 边 |
| | | edge := &graph.Edge{ |
| | | FromNode: id1, |
| | | ToNode: id2, |
| | | Relation: "wikilink", |
| | | Label: "相关文档", |
| | | } |
| | | err = store.InsertEdge(edge) |
| | | require.NoError(t, err) |
| | | |
| | | err = store.CreateFTS() |
| | | require.NoError(t, err) |
| | | err = store.PopulateFTS() |
| | | require.NoError(t, err) |
| | | |
| | | // 测试带链接的搜索 |
| | | opts := SearchOptions{ |
| | | TopN: 5, |
| | | WithLinks: true, |
| | | } |
| | | results, err := Search(store, []string{"测试"}, opts) |
| | | require.NoError(t, err) |
| | | require.Len(t, results, 1) |
| | | assert.Equal(t, id1, results[0].ID) |
| | | assert.Len(t, results[0].Links, 1) |
| | | assert.Equal(t, "FAQ/002-相关.md", results[0].Links[0]) |
| | | |
| | | // 测试不带链接的搜索 |
| | | opts.WithLinks = false |
| | | results, err = Search(store, []string{"测试"}, opts) |
| | | require.NoError(t, err) |
| | | require.Len(t, results, 1) |
| | | assert.Empty(t, results[0].Links) |
| | | } |
| | | ``` |
| | | |
| | | - [ ] **Step 2: 运行测试确认失败** |
| | | |
| | | ```bash |
| | | cd ~/workspace/kb-cli |
| | | go test ./internal/search -run "TestSearch_WithContent|TestSearch_WithLinks" -v |
| | | ``` |
| | | |
| | | Expected: FAIL - "WithContent undefined" 或测试失败 |
| | | |
| | | - [ ] **Step 3: 扩展 SearchOptions 并修改 Search 函数** |
| | | |
| | | 修改 `internal/search/engine.go`: |
| | | |
| | | ```go |
| | | // SearchOptions 搜索选项 |
| | | type SearchOptions struct { |
| | | Expanded []string |
| | | Symptom []string |
| | | TopN int |
| | | WithContent bool // 新增:是否返回内容 |
| | | WithLinks bool // 新增:是否返回链接 |
| | | } |
| | | |
| | | // Search 执行搜索 |
| | | func Search(store *index.Store, keywords []string, opts SearchOptions) ([]SearchResult, error) { |
| | | // 合并所有关键词 |
| | | allKeywords := append(keywords, opts.Expanded...) |
| | | allKeywords = append(allKeywords, opts.Symptom...) |
| | | |
| | | // FTS5 搜索 |
| | | ftsResults, err := store.FTSSearch(allKeywords, 100) |
| | | if err != nil { |
| | | return nil, err |
| | | } |
| | | |
| | | // 评分 |
| | | scoreMap := make(map[int64]int) |
| | | for _, r := range ftsResults { |
| | | score := 0 |
| | | for _, kw := range keywords { |
| | | score += scoreResult(r, kw, ScoreNormal) |
| | | } |
| | | for _, kw := range opts.Expanded { |
| | | score += scoreResult(r, kw, ScoreExpanded) |
| | | } |
| | | for _, kw := range opts.Symptom { |
| | | score += scoreResult(r, kw, ScoreSymptom) |
| | | } |
| | | scoreMap[r.ID] = score |
| | | } |
| | | |
| | | // 转换为结果列表 |
| | | var results []SearchResult |
| | | for _, r := range ftsResults { |
| | | results = append(results, SearchResult{ |
| | | ID: r.ID, |
| | | Path: r.Path, |
| | | Title: r.Title, |
| | | Section: r.Section, |
| | | Score: scoreMap[r.ID], |
| | | }) |
| | | } |
| | | |
| | | // 按分数排序 |
| | | sort.Slice(results, func(i, j int) bool { |
| | | return results[i].Score > results[j].Score |
| | | }) |
| | | |
| | | // 限制返回数量 |
| | | if opts.TopN > 0 && len(results) > opts.TopN { |
| | | results = results[:opts.TopN] |
| | | } |
| | | |
| | | // 增强结果(新增) |
| | | for i := range results { |
| | | if opts.WithContent { |
| | | content, _, _, err := store.GetNodeContent(results[i].ID) |
| | | if err == nil { |
| | | results[i].Content = content |
| | | } |
| | | } |
| | | if opts.WithLinks { |
| | | links, err := store.GetNodeLinks(results[i].ID) |
| | | if err == nil { |
| | | results[i].Links = links |
| | | } |
| | | } |
| | | } |
| | | |
| | | return results, nil |
| | | } |
| | | ``` |
| | | |
| | | - [ ] **Step 4: 运行测试确认通过** |
| | | |
| | | ```bash |
| | | cd ~/workspace/kb-cli |
| | | go test ./internal/search -run "TestSearch_WithContent|TestSearch_WithLinks" -v |
| | | ``` |
| | | |
| | | Expected: PASS |
| | | |
| | | - [ ] **Step 5: 运行所有测试确认无回归** |
| | | |
| | | ```bash |
| | | cd ~/workspace/kb-cli |
| | | go test ./... -v |
| | | ``` |
| | | |
| | | Expected: 所有测试通过 |
| | | |
| | | - [ ] **Step 6: 提交** |
| | | |
| | | ```bash |
| | | cd ~/workspace/kb-cli |
| | | git add internal/search/engine.go internal/search/engine_test.go |
| | | git commit -m "feat: add WithContent and WithLinks options to Search" |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## Task 4: 添加 CLI flags 并传递给 SearchOptions |
| | | |
| | | **Files:** |
| | | - Modify: `cmd/search.go:20-50` |
| | | |
| | | **Interfaces:** |
| | | - Consumes: `SearchOptions.WithContent`, `SearchOptions.WithLinks` (Task 3) |
| | | - Produces: 新增 `--with-content` 和 `--with-links` flags |
| | | |
| | | - [ ] **Step 1: 添加 flag 变量** |
| | | |
| | | 在 `cmd/search.go` 的变量声明区域添加: |
| | | |
| | | ```go |
| | | var ( |
| | | withContent bool |
| | | withLinks bool |
| | | ) |
| | | ``` |
| | | |
| | | - [ ] **Step 2: 注册 flags** |
| | | |
| | | 在 `init()` 函数中添加: |
| | | |
| | | ```go |
| | | func init() { |
| | | searchCmd.Flags().StringSliceVar(&expanded, "expanded", nil, "扩展词(提升相关实体权重)") |
| | | searchCmd.Flags().StringSliceVar(&symptom, "symptom", nil, "症状词(针对具体症状)") |
| | | searchCmd.Flags().IntVar(&topN, "top", 10, "返回前 N 条结果") |
| | | searchCmd.Flags().BoolVar(&jsonOut, "json", false, "JSON 格式输出") |
| | | |
| | | // 新增 flags |
| | | searchCmd.Flags().BoolVar(&withContent, "with-content", false, "返回完整文件内容") |
| | | searchCmd.Flags().BoolVar(&withLinks, "with-links", false, "显示关联文档链接") |
| | | } |
| | | ``` |
| | | |
| | | - [ ] **Step 3: 传递 flags 到 SearchOptions** |
| | | |
| | | 修改 `runSearch` 函数: |
| | | |
| | | ```go |
| | | func runSearch(cmd *cobra.Command, args []string) error { |
| | | // ... 现有代码 ... |
| | | |
| | | opts := search.SearchOptions{ |
| | | Expanded: expanded, |
| | | Symptom: symptom, |
| | | TopN: topN, |
| | | WithContent: withContent, |
| | | WithLinks: withLinks, |
| | | } |
| | | |
| | | results, err := search.Search(store, args, opts) |
| | | // ... 现有代码 ... |
| | | } |
| | | ``` |
| | | |
| | | - [ ] **Step 4: 构建并测试 help 输出** |
| | | |
| | | ```bash |
| | | cd ~/workspace/kb-cli |
| | | go build -o bin/kb . |
| | | ./bin/kb search --help |
| | | ``` |
| | | |
| | | Expected: 显示 `--with-content` 和 `--with-links` flags |
| | | |
| | | - [ ] **Step 5: 提交** |
| | | |
| | | ```bash |
| | | cd ~/workspace/kb-cli |
| | | git add cmd/search.go |
| | | git commit -m "feat: add --with-content and --with-links CLI flags" |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## Task 5: 修改输出格式化器支持新字段 |
| | | |
| | | **Files:** |
| | | - Modify: `internal/output/formatter.go` |
| | | - Test: `internal/output/formatter_test.go` |
| | | |
| | | **Interfaces:** |
| | | - Consumes: `SearchResult.Content`, `SearchResult.Links` (Task 2) |
| | | - Produces: 增强的 `FormatTable` 和 `FormatJSON` 方法 |
| | | |
| | | - [ ] **Step 1: 编写失败测试** |
| | | |
| | | 在 `internal/output/formatter_test.go` 添加: |
| | | |
| | | ```go |
| | | func TestFormatTable_WithContent(t *testing.T) { |
| | | results := []search.SearchResult{ |
| | | { |
| | | ID: 1, |
| | | Path: "FAQ/001-测试.md", |
| | | Title: "测试文档", |
| | | Section: "FAQ", |
| | | Score: 10, |
| | | Content: "# 测试\n\n这是内容", |
| | | }, |
| | | } |
| | | |
| | | output := FormatTable(results) |
| | | assert.Contains(t, output, "FAQ/001-测试.md") |
| | | assert.Contains(t, output, "--- 内容 ---") |
| | | assert.Contains(t, output, "# 测试") |
| | | } |
| | | |
| | | func TestFormatTable_WithLinks(t *testing.T) { |
| | | results := []search.SearchResult{ |
| | | { |
| | | ID: 1, |
| | | Path: "FAQ/001-测试.md", |
| | | Title: "测试文档", |
| | | Section: "FAQ", |
| | | Score: 10, |
| | | Links: []string{"FAQ/002-相关.md"}, |
| | | }, |
| | | } |
| | | |
| | | output := FormatTable(results) |
| | | assert.Contains(t, output, "--- 关联文档 ---") |
| | | assert.Contains(t, output, "FAQ/002-相关.md") |
| | | } |
| | | |
| | | func TestFormatTable_WithBoth(t *testing.T) { |
| | | results := []search.SearchResult{ |
| | | { |
| | | ID: 1, |
| | | Path: "FAQ/001-测试.md", |
| | | Title: "测试文档", |
| | | Section: "FAQ", |
| | | Score: 10, |
| | | Content: "# 测试", |
| | | Links: []string{"FAQ/002-相关.md"}, |
| | | }, |
| | | } |
| | | |
| | | output := FormatTable(results) |
| | | assert.Contains(t, output, "--- 内容 ---") |
| | | assert.Contains(t, output, "--- 关联文档 ---") |
| | | } |
| | | |
| | | func TestFormatJSON_WithContentAndLinks(t *testing.T) { |
| | | results := []search.SearchResult{ |
| | | { |
| | | ID: 1, |
| | | Path: "FAQ/001-测试.md", |
| | | Title: "测试文档", |
| | | Section: "FAQ", |
| | | Score: 10, |
| | | Content: "# 测试", |
| | | Links: []string{"FAQ/002-相关.md"}, |
| | | }, |
| | | } |
| | | |
| | | output, err := FormatJSON(results) |
| | | require.NoError(t, err) |
| | | assert.Contains(t, output, `"content":"# 测试"`) |
| | | assert.Contains(t, output, `"links":["FAQ/002-相关.md"]`) |
| | | } |
| | | ``` |
| | | |
| | | - [ ] **Step 2: 运行测试确认失败** |
| | | |
| | | ```bash |
| | | cd ~/workspace/kb-cli |
| | | go test ./internal/output -run "TestFormatTable_With|TestFormatJSON_With" -v |
| | | ``` |
| | | |
| | | Expected: FAIL - 测试失败(输出不包含新字段) |
| | | |
| | | - [ ] **Step 3: 修改 FormatTable 方法** |
| | | |
| | | 修改 `internal/output/formatter.go` 中的 `FormatTable` 函数: |
| | | |
| | | ```go |
| | | // FormatTable 格式化表格输出 |
| | | func FormatTable(results []search.SearchResult) string { |
| | | if len(results) == 0 { |
| | | return "未找到匹配结果\n" |
| | | } |
| | | |
| | | var buf bytes.Buffer |
| | | |
| | | // 表头 |
| | | buf.WriteString(fmt.Sprintf("%-50s %-30s %-10s %s\n", "路径", "标题", "板块", "得分")) |
| | | buf.WriteString(strings.Repeat("-", 100) + "\n") |
| | | |
| | | // 每行结果 |
| | | for _, r := range results { |
| | | title := r.Title |
| | | if len(title) > 28 { |
| | | title = title[:28] + ".." |
| | | } |
| | | buf.WriteString(fmt.Sprintf("%-50s %-30s %-10s %d\n", r.Path, title, r.Section, r.Score)) |
| | | |
| | | // 输出内容(如果有) |
| | | if r.Content != "" { |
| | | buf.WriteString("\n--- 内容 ---\n") |
| | | buf.WriteString(r.Content) |
| | | buf.WriteString("\n") |
| | | } |
| | | |
| | | // 输出关联文档(如果有) |
| | | if len(r.Links) > 0 { |
| | | buf.WriteString("\n--- 关联文档 ---\n") |
| | | for _, link := range r.Links { |
| | | buf.WriteString(fmt.Sprintf("- %s\n", link)) |
| | | } |
| | | } |
| | | } |
| | | |
| | | buf.WriteString(fmt.Sprintf("\n共 %d 条结果\n", len(results))) |
| | | return buf.String() |
| | | } |
| | | ``` |
| | | |
| | | - [ ] **Step 4: 运行测试确认通过** |
| | | |
| | | ```bash |
| | | cd ~/workspace/kb-cli |
| | | go test ./internal/output -run "TestFormatTable_With|TestFormatJSON_With" -v |
| | | ``` |
| | | |
| | | Expected: PASS |
| | | |
| | | - [ ] **Step 5: 运行所有测试确认无回归** |
| | | |
| | | ```bash |
| | | cd ~/workspace/kb-cli |
| | | go test ./... -v |
| | | ``` |
| | | |
| | | Expected: 所有测试通过 |
| | | |
| | | - [ ] **Step 6: 提交** |
| | | |
| | | ```bash |
| | | cd ~/workspace/kb-cli |
| | | git add internal/output/formatter.go internal/output/formatter_test.go |
| | | git commit -m "feat: enhance output formatter with content and links support" |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## Task 6: 集成测试与性能验证 |
| | | |
| | | **Files:** |
| | | - 无新文件(使用现有测试数据) |
| | | |
| | | **Interfaces:** |
| | | - Consumes: 所有之前的任务 |
| | | - Produces: 验证完整功能链 |
| | | |
| | | - [ ] **Step 1: 构建最新版本** |
| | | |
| | | ```bash |
| | | cd ~/workspace/kb-cli |
| | | go build -o bin/kb . |
| | | ``` |
| | | |
| | | - [ ] **Step 2: 测试基础搜索(无增强)** |
| | | |
| | | ```bash |
| | | ./bin/kb search 充装 --top 3 |
| | | ``` |
| | | |
| | | Expected: 显示 3 条结果,不包含内容和链接 |
| | | |
| | | - [ ] **Step 3: 测试带内容搜索** |
| | | |
| | | ```bash |
| | | ./bin/kb search 充装 --with-content --top 3 |
| | | ``` |
| | | |
| | | Expected: 显示 3 条结果,每条后跟 `--- 内容 ---` 和文件内容 |
| | | |
| | | - [ ] **Step 4: 测试带链接搜索** |
| | | |
| | | ```bash |
| | | ./bin/kb search 充装 --with-links --top 3 |
| | | ``` |
| | | |
| | | Expected: 显示 3 条结果,有链接的显示 `--- 关联文档 ---` |
| | | |
| | | - [ ] **Step 5: 测试两者都有** |
| | | |
| | | ```bash |
| | | ./bin/kb search 充装 --with-content --with-links --top 3 |
| | | ``` |
| | | |
| | | Expected: 显示 3 条结果,同时包含内容和链接 |
| | | |
| | | - [ ] **Step 6: 测试 JSON 输出** |
| | | |
| | | ```bash |
| | | ./bin/kb search 充装 --with-content --with-links --json --top 3 |
| | | ``` |
| | | |
| | | Expected: JSON 格式输出,包含 `content` 和 `links` 字段 |
| | | |
| | | - [ ] **Step 7: 性能测试** |
| | | |
| | | ```bash |
| | | time ./bin/kb search 充装 --with-content --with-links --top 10 |
| | | ``` |
| | | |
| | | Expected: 总耗时 < 100ms |
| | | |
| | | - [ ] **Step 8: 提交(如有修复)** |
| | | |
| | | ```bash |
| | | cd ~/workspace/kb-cli |
| | | git add -A |
| | | git commit -m "test: integration tests for enhanced search" |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## Task 7: 更新 kb-search 技能文档 |
| | | |
| | | **Files:** |
| | | - Modify: `~/.hermes/skills/kb-knowledge/kb-search/SKILL.md` |
| | | |
| | | **Interfaces:** |
| | | - Consumes: kb-cli 新功能 |
| | | - Produces: 更新的技能文档 |
| | | |
| | | - [ ] **Step 1: 备份现有文档** |
| | | |
| | | ```bash |
| | | cp ~/.hermes/skills/kb-knowledge/kb-search/SKILL.md ~/.hermes/skills/kb-knowledge/kb-search/SKILL.md.bak |
| | | ``` |
| | | |
| | | - [ ] **Step 2: 修改核心工具部分** |
| | | |
| | | 将 `## 核心工具` 部分从: |
| | | |
| | | ```markdown |
| | | ## 核心工具 |
| | | |
| | | **kb-search.py**:知识库搜索 |
| | | ``` |
| | | |
| | | 改为: |
| | | |
| | | ```markdown |
| | | ## 核心工具 |
| | | |
| | | **kb-cli**:知识库搜索(优先使用) |
| | | |
| | | **完整路径**:`~/go/bin/kb` |
| | | |
| | | **基本用法**: |
| | | ```bash |
| | | # 基础搜索 |
| | | kb search "关键词" --top 5 |
| | | |
| | | # 带内容 |
| | | kb search "关键词" --with-content --top 3 |
| | | |
| | | # 带链接 |
| | | kb search "关键词" --with-links --top 3 |
| | | |
| | | # 带扩展词和症状词 |
| | | kb search "充装" --expanded "重量 规格" --symptom "报错" --with-content --top 3 |
| | | |
| | | # JSON 输出 |
| | | kb search "关键词" --json --top 5 |
| | | ``` |
| | | |
| | | **kb-search.py**:知识库搜索(降级方案,仅当 kb-cli 不可用时) |
| | | ``` |
| | | |
| | | - [ ] **Step 3: 修改执行流程部分** |
| | | |
| | | 将 `## 执行流程` 部分的命令示例从 `python3 kb-search.py` 改为 `kb`: |
| | | |
| | | ```bash |
| | | # 方式1: 基础搜索 |
| | | kb search "用户问题关键词" --top 3 |
| | | |
| | | # 方式2: 带内容(推荐) |
| | | kb search "用户问题关键词" --with-content --top 3 |
| | | |
| | | # 方式3: 带内容和链接 |
| | | kb search "用户问题关键词" --with-content --with-links --top 3 |
| | | |
| | | # 方式4: 带扩展词和症状词 |
| | | kb search "关键词" --expanded "扩展词" --symptom "症状词" --with-content --top 3 |
| | | ``` |
| | | |
| | | - [ ] **Step 4: 添加降级方案章节** |
| | | |
| | | 在文档末尾添加: |
| | | |
| | | ```markdown |
| | | ## 降级方案 |
| | | |
| | | **仅当 kb-cli 不可用时**,使用 kb-search.py: |
| | | |
| | | ```bash |
| | | export KB_VAULT=/home/aisim-p/aisim/note/001/笔记001 |
| | | python3 ~/.hermes/skills/kb-knowledge/kb-search/scripts/kb-search.py search "关键词" --with-content --top 3 |
| | | ``` |
| | | |
| | | **注意**:kb-search.py 当前有索引问题,可能返回空结果。优先使用 kb-cli。 |
| | | ``` |
| | | |
| | | - [ ] **Step 5: 验证文档格式** |
| | | |
| | | ```bash |
| | | cat ~/.hermes/skills/kb-knowledge/kb-search/SKILL.md | head -50 |
| | | ``` |
| | | |
| | | Expected: 显示更新后的文档,核心工具为 kb-cli |
| | | |
| | | - [ ] **Step 6: 提交到知识库** |
| | | |
| | | ```bash |
| | | cd ~/aisim/note/001/笔记001 |
| | | git add . |
| | | git commit -m "docs: update kb-search skill to prioritize kb-cli" |
| | | git push |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## Task 8: 最终验证与文档更新 |
| | | |
| | | **Files:** |
| | | - Modify: `~/workspace/kb-cli/README.md` |
| | | |
| | | **Interfaces:** |
| | | - Consumes: 所有功能 |
| | | - Produces: 完整的文档和验证 |
| | | |
| | | - [ ] **Step 1: 更新 README** |
| | | |
| | | 在 `~/workspace/kb-cli/README.md` 的 `## 使用方法` 部分添加新功能示例: |
| | | |
| | | ```markdown |
| | | ### 高级搜索 |
| | | |
| | | ```bash |
| | | # 带文件内容 |
| | | kb search 充装规格 --with-content --top 3 |
| | | |
| | | # 带关联文档 |
| | | kb search 充装规格 --with-links --top 3 |
| | | |
| | | # 带内容和关联 |
| | | kb search 充装规格 --with-content --with-links --top 3 |
| | | |
| | | # 带扩展词和症状词 |
| | | kb search 充装 --expanded "重量 规格" --symptom "报错" --with-content --top 3 |
| | | ``` |
| | | ``` |
| | | |
| | | - [ ] **Step 2: 运行完整测试套件** |
| | | |
| | | ```bash |
| | | cd ~/workspace/kb-cli |
| | | go test ./... -v |
| | | ``` |
| | | |
| | | Expected: 所有测试通过 |
| | | |
| | | - [ ] **Step 3: 构建并安装** |
| | | |
| | | ```bash |
| | | cd ~/workspace/kb-cli |
| | | go build -o bin/kb . |
| | | cp bin/kb ~/go/bin/ |
| | | ``` |
| | | |
| | | - [ ] **Step 4: 最终功能验证** |
| | | |
| | | ```bash |
| | | # 测试所有功能 |
| | | kb search 充装 --top 3 |
| | | kb search 充装 --with-content --top 3 |
| | | kb search 充装 --with-links --top 3 |
| | | kb search 充装 --with-content --with-links --top 3 |
| | | kb search 充装 --with-content --json --top 3 |
| | | ``` |
| | | |
| | | Expected: 所有命令正常工作 |
| | | |
| | | - [ ] **Step 5: 提交所有更改** |
| | | |
| | | ```bash |
| | | cd ~/workspace/kb-cli |
| | | git add README.md |
| | | git commit -m "docs: update README with new features" |
| | | git push |
| | | ``` |
| | | |
| | | - [ ] **Step 6: 创建发布说明** |
| | | |
| | | ```bash |
| | | cat > RELEASE_NOTES.md << 'EOF' |
| | | # kb-cli v1.1.0 发布说明 |
| | | |
| | | ## 新功能 |
| | | |
| | | - `--with-content`: 搜索时返回完整文件内容 |
| | | - `--with-links`: 搜索时显示关联文档链接 |
| | | |
| | | ## 使用示例 |
| | | |
| | | ```bash |
| | | # 带内容搜索 |
| | | kb search 充装规格 --with-content --top 3 |
| | | |
| | | # 带关联文档 |
| | | kb search 充装规格 --with-links --top 3 |
| | | |
| | | # 两者都有 |
| | | kb search 充装规格 --with-content --with-links --top 3 |
| | | ``` |
| | | |
| | | ## 性能 |
| | | |
| | | - 搜索 + 内容 + 链接:< 100ms |
| | | - 向后兼容:现有命令不受影响 |
| | | |
| | | ## 技能调整 |
| | | |
| | | - kb-search 技能现在优先使用 kb-cli |
| | | - kb-search.py 降级为备选方案 |
| | | EOF |
| | | ``` |
| | | |
| | | - [ ] **Step 7: 提交发布说明** |
| | | |
| | | ```bash |
| | | cd ~/workspace/kb-cli |
| | | git add RELEASE_NOTES.md |
| | | git commit -m "docs: add release notes for v1.1.0" |
| | | git push |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## 完成标准 |
| | | |
| | | 所有任务完成后,必须满足: |
| | | |
| | | 1. ✅ 所有单元测试通过 |
| | | 2. ✅ 集成测试通过 |
| | | 3. ✅ 性能 < 100ms |
| | | 4. ✅ kb-search 技能文档已更新 |
| | | 5. ✅ README 已更新 |
| | | 6. ✅ 发布说明已创建 |
| | | 7. ✅ 代码已推送到远程仓库 |
| | | |
| | | --- |
| | | |
| | | ## 执行选项 |
| | | |
| | | **Plan complete and saved to `docs/superpowers/plans/2026-07-26-kb-cli-enhancement.md`. Two execution options:** |
| | | |
| | | **1. Subagent-Driven (recommended)** - I dispatch a fresh subagent per task, review between tasks, fast iteration |
| | | |
| | | **2. Inline Execution** - Execute tasks in this session using executing-plans, batch execution with checkpoints |
| | | |
| | | **Which approach?** |