Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions docs/agent-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -645,3 +645,56 @@ If `Edit` or `Write` (or another tool an execution agent needs) is missing from
the target project's allowlist, the agent will report `TASK_BLOCKED` with an
actionable error message explaining what permissions are needed. The user must
update the project's permissions config before retrying.

## Knowledge Base Tools

### `get_knowledge_base`

Returns all knowledge-base docs for a project in a single call. Used by
planning, brainstorming, and debugging skills to load architectural context.

**Input:**

| Field | Required | Description |
| --------- | -------- | ----------------------------------------- |
| `project` | yes | Project name |
| `repo` | no | Repo name; defaults to the primary repo |

**Response:**

```json
{
"project": "my-project",
"repo": "primary",
"docs": {
"architecture.md": "...",
"code-structure.md": "...",
"api-documentation.md": "...",
"glossary.md": "..."
},
"summaries": {
"architecture.md": "Short summary extracted from the doc's ## Summary section.",
"code-structure.md": "Short summary...",
"api-documentation.md": "",
"glossary.md": "Short summary..."
},
"meta": { "...": "..." }
}
```

**`summaries` field:** a map from doc name to the text of its first `## Summary`
section. Empty string when a doc has no `## Summary` section. Always present
(never `null`) — an empty object `{}` means no docs have summaries yet.

**Usage pattern for agents:** when `summaries` is non-empty, read each doc's
summary to judge relevance to the current task, then retain full content from
`docs` only for the relevant docs. When `summaries` is empty or a doc has no
entry, load all docs as before (current fallback behaviour).

### `read_knowledge_doc`

Read a single KB doc by name. Use when you need only one doc and want to avoid
loading the full payload.

**Input:** `project` (required), `repo` (optional), `doc` (required — one of
`architecture.md`, `code-structure.md`, `api-documentation.md`, `glossary.md`).
22 changes: 14 additions & 8 deletions internal/mcp/tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -1206,10 +1206,11 @@ type getKnowledgeBaseInput struct {
}

type getKnowledgeBaseOutput struct {
Project string `json:"project"`
Repo string `json:"repo"`
Docs map[string]string `json:"docs"`
Meta board.KnowledgeRepoMeta `json:"meta"`
Project string `json:"project"`
Repo string `json:"repo"`
Docs map[string]string `json:"docs"`
Summaries map[string]string `json:"summaries"`
Meta board.KnowledgeRepoMeta `json:"meta"`
}

func registerGetKnowledgeBase(server *mcp.Server, svc *service.CardService) {
Expand All @@ -1227,15 +1228,20 @@ func registerGetKnowledgeBase(server *mcp.Server, svc *service.CardService) {
out.Docs = map[string]string{}
}

if out.Summaries == nil {
out.Summaries = map[string]string{}
}

if out.Meta.Docs == nil {
out.Meta.Docs = map[string]board.KnowledgeDocMeta{}
}

return nil, getKnowledgeBaseOutput{
Project: out.Project,
Repo: out.Repo,
Docs: out.Docs,
Meta: out.Meta,
Project: out.Project,
Repo: out.Repo,
Docs: out.Docs,
Summaries: out.Summaries,
Meta: out.Meta,
}, nil
})
}
Expand Down
54 changes: 45 additions & 9 deletions internal/service/service_knowledge.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,10 +237,43 @@ func (s *CardService) WriteKnowledgeDocs(ctx context.Context, in WriteKnowledgeD

// KnowledgeBaseRead is returned by ReadKnowledgeBase.
type KnowledgeBaseRead struct {
Project string `json:"project"`
Repo string `json:"repo"`
Docs map[string]string `json:"docs"`
Meta board.KnowledgeRepoMeta `json:"meta"`
Project string `json:"project"`
Repo string `json:"repo"`
Docs map[string]string `json:"docs"`
Summaries map[string]string `json:"summaries"`
Meta board.KnowledgeRepoMeta `json:"meta"`
}

// extractSummary returns the text content of the first ## Summary section in
// the given markdown content. It scans for the first line that is exactly
// "## Summary", collects all following lines until the next ##-level heading
// or EOF, then returns the trimmed result. Returns an empty string if no
// ## Summary section is found.
func extractSummary(content string) string {
lines := strings.Split(content, "\n")

inSummary := false

var summaryLines []string

for _, line := range lines {
if !inSummary {
if line == "## Summary" {
inSummary = true
}

continue
}

// Stop at the next ##-level heading.
if strings.HasPrefix(line, "## ") {
break
}

summaryLines = append(summaryLines, line)
}

return strings.TrimSpace(strings.Join(summaryLines, "\n"))
}

// KnowledgeDocRead is returned by ReadKnowledgeDoc.
Expand Down Expand Up @@ -319,7 +352,7 @@ func (s *CardService) ReadKnowledgeBase(ctx context.Context, project, repo strin
// primary, store I/O) must propagate so callers see the real
// failure.
if errors.Is(err, errNoReposConfigured) {
return &KnowledgeBaseRead{Project: project, Docs: map[string]string{}}, nil
return &KnowledgeBaseRead{Project: project, Docs: map[string]string{}, Summaries: map[string]string{}}, nil
}

return nil, err
Expand All @@ -331,6 +364,7 @@ func (s *CardService) ReadKnowledgeBase(ctx context.Context, project, repo strin
}

docs := map[string]string{}
summaries := map[string]string{}

for _, name := range board.KnowledgeDocNames {
exists, err := s.store.KnowledgeDocExists(ctx, project, resolvedRepo, name)
Expand All @@ -348,13 +382,15 @@ func (s *CardService) ReadKnowledgeBase(ctx context.Context, project, repo strin
}

docs[name] = string(data)
summaries[name] = extractSummary(string(data))
}

return &KnowledgeBaseRead{
Project: project,
Repo: resolvedRepo,
Docs: docs,
Meta: meta.Repos[resolvedRepo],
Project: project,
Repo: resolvedRepo,
Docs: docs,
Summaries: summaries,
Meta: meta.Repos[resolvedRepo],
}, nil
}

Expand Down
87 changes: 87 additions & 0 deletions internal/service/service_knowledge_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,93 @@ func TestWriteKnowledgeDocs_InvalidDocNameReturnsSentinel(t *testing.T) {
assert.ErrorIs(t, err, storage.ErrInvalidKnowledgeDoc)
}

func TestExtractSummary(t *testing.T) {
tests := []struct {
name string
content string
expected string
}{
{
name: "well-formed doc with summary section",
content: `# Architecture

## Summary
This document describes the system architecture.
It covers components and data flow.

## Components
Details here.
`,
expected: "This document describes the system architecture.\nIt covers components and data flow.",
},
{
name: "doc missing summary section",
content: "# No Summary Here\n\n## Components\nDetails.\n",
expected: "",
},
{
name: "summary followed by next section stops at heading",
content: `## Summary
First summary line.
Second summary line.
## NextSection
Should not be included.
`,
expected: "First summary line.\nSecond summary line.",
},
{
name: "two summary headings returns first one only",
content: `## Summary
First summary content.

## Other
## Summary
Second summary content should be ignored.
`,
expected: "First summary content.",
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got := extractSummary(tc.content)
assert.Equal(t, tc.expected, got)
})
}
}

func TestReadKnowledgeBase_PopulatesSummaries(t *testing.T) {
svc, _, cleanup := setupTest(t)
defer cleanup()

ctx := context.Background()
content := "# Architecture\n\n## Summary\nBrief description of the architecture.\n\n## Details\nMore here.\n"

_, err := svc.WriteKnowledgeDocs(ctx, WriteKnowledgeDocsInput{
Project: "test-project",
Repo: "core",
Docs: map[string]string{"architecture.md": content},
Source: KnowledgeWriteSourceRefresh,
HeadCommit: "abc",
AgentID: "human:t",
})
require.NoError(t, err)

out, err := svc.ReadKnowledgeBase(ctx, "test-project", "core")
require.NoError(t, err)
assert.NotNil(t, out.Summaries)
assert.Equal(t, "Brief description of the architecture.", out.Summaries["architecture.md"])
}

func TestReadKnowledgeBase_SummariesNonNilWhenEmpty(t *testing.T) {
svc, _, cleanup := setupTest(t)
defer cleanup()

out, err := svc.ReadKnowledgeBase(context.Background(), "test-project", "")
require.NoError(t, err)
assert.NotNil(t, out.Summaries, "Summaries must be non-nil even when no docs exist")
}

func TestBuildRefreshPlan_ReasonsOnlyMissingOrScheduled(t *testing.T) {
svc, _, cleanup := setupTest(t)
defer cleanup()
Expand Down
6 changes: 6 additions & 0 deletions workflow-skills/brainstorming.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,12 @@ You MUST complete each of these in order:
`glossary.md` to use the project's vocabulary correctly. Reference
them when discussing architecture, decomposition, or naming. If
empty, note that to the user when relevant and proceed.

If `summaries` is non-empty, use each doc's summary to judge
relevance to the current task before loading its full content from
`docs`. Retain in active context only the docs whose summary
indicates relevance. If `summaries` is empty or a doc has no entry,
load all docs.
2. **Explore project context** — read files referenced in the card and
anything the KB doesn't cover (recent commits, files mentioned in
the body). Don't re-derive what the KB already states.
Expand Down
6 changes: 6 additions & 0 deletions workflow-skills/create-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,12 @@ Hold this claim through Phase 5.
`glossary.md` to use the project's vocabulary correctly. If empty,
proceed.

If `summaries` is non-empty, use each doc's summary to judge
relevance to the current task before loading its full content from
`docs`. Retain in active context only the docs whose summary
indicates relevance. If `summaries` is empty or a doc has no entry,
load all docs.

2. **Review card details.** Read the card details provided above. If
the card body already contains a `## Plan` section, use it as a
starting point — do not discard previous planning work. Only call
Expand Down
18 changes: 18 additions & 0 deletions workflow-skills/refresh-knowledge.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,8 @@ via the Task tool. Pass:

Collect each sub-agent's output as the new doc content.

Every generated doc MUST begin with `## Summary` as its first level-2 heading (150–300 tokens, prose describing the doc's purpose and major sections).

After each sub-agent returns, call `update_refresh_progress` so the UI
shows progress:

Expand Down Expand Up @@ -241,6 +243,10 @@ in local mode (no UI-side job to update) — proceed regardless.
```markdown
# System Architecture

## Summary

[150–300 token prose paragraph: what this doc covers, what questions it answers, and the major sections inside — written for an agent deciding which full docs to load into context.]

## System Overview

[2-4 paragraph high-level description of what the system does, who calls
Expand Down Expand Up @@ -296,6 +302,10 @@ where it is enforced so the agent can verify before changing nearby code.]
```markdown
# Code Structure

## Summary

[150–300 token prose paragraph: what this doc covers, what questions it answers, and the major sections inside — written for an agent deciding which full docs to load into context.]

## Build System

- **Type**: [go modules / npm / pyproject / cargo / make-driven / etc.]
Expand Down Expand Up @@ -357,6 +367,10 @@ without REST/MCP/CLI). Note the skip in your output to the user.
```markdown
# API Documentation

## Summary

[150–300 token prose paragraph: what this doc covers, what questions it answers, and the major sections inside — written for an agent deciding which full docs to load into context.]

## Overview

[Which surfaces this repo exposes: REST, MCP, CLI, gRPC, webhooks. Where
Expand Down Expand Up @@ -412,6 +426,10 @@ each is registered in the codebase.]
```markdown
# Glossary

## Summary

[150–300 token prose paragraph: what this doc covers, what questions it answers, and the major sections inside — written for an agent deciding which full docs to load into context.]

## Domain terms

### [Term]
Expand Down
5 changes: 5 additions & 0 deletions workflow-skills/run-autonomous.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ If docs are returned, hold them in your reasoning context: use
boundaries, `api-documentation.md` to avoid breaking public surfaces, and
`glossary.md` to use the project's vocabulary correctly. If empty, proceed.

If `summaries` is non-empty, use each doc's summary to judge relevance to the
current task before loading its full content from `docs`. Retain in active
context only the docs whose summary indicates relevance. If `summaries` is
empty or a doc has no entry, load all docs.

Immediately after the KB call, log the outcome:

```
Expand Down
5 changes: 5 additions & 0 deletions workflow-skills/systematic-debugging.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ You MUST complete each phase before proceeding to the next.
- Translate user-facing names to internal names (`glossary.md`).
- If empty, proceed without — fall back to grepping for symbols and
reading `CLAUDE.md`.
- If `summaries` is non-empty, use each doc's summary to judge
relevance to the current bug before loading its full content from
`docs`. Retain in active context only the docs whose summary
indicates relevance. If `summaries` is empty or a doc has no
entry, load all docs.

2. **Read the card body carefully.**
- Quote any stack traces, error messages, error codes, or log lines the
Expand Down
Loading