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
19 changes: 19 additions & 0 deletions internal/boot/boot.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ func Build(ctx context.Context, opts Options) (*control.Controller, error) {
case ok:
spec := plugin.Spec{
Name: "codegraph",
StripRawPrefix: "codegraph_",
Command: bin,
Args: []string{"serve", "--mcp"},
Dir: root,
Expand Down Expand Up @@ -345,6 +346,24 @@ func Build(ctx context.Context, opts Options) (*control.Controller, error) {
registerDeferred(lazySpecs, false)
registerDeferred(bgSpecs, true)

// Inject codegraph steering into the system prompt when symbol-graph tools
// are available, so the model knows to prefer them for architecture / call-graph
// questions over grep/read_file. Also register codegraph tool names in the
// subagent allowed-tools list so explore/research/review can use them.
if cfg.Codegraph.Enabled {
prefix := plugin.ToolPrefix("codegraph")
var cgTools []string
for _, name := range reg.Names() {
if strings.HasPrefix(name, prefix) {
cgTools = append(cgTools, name)
}
}
if len(cgTools) > 0 {
sysPrompt += "\n\n" + codegraph.SteerText
skill.SetExtraReadTools(cgTools)
}
}

for _, msg := range demoteMessages {
sink.Emit(event.Event{Kind: event.Notice, Level: event.LevelInfo, Text: msg})
}
Expand Down
12 changes: 12 additions & 0 deletions internal/codegraph/codegraph.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,18 @@ import (

const initTimeout = 30 * time.Second

// SteerText is injected into the system prompt when CodeGraph tools are
// available, so the model knows to prefer them for symbol-level questions.
const SteerText = `## Code Intelligence (codegraph)
You have codegraph tools for symbol-level code intelligence. For architecture questions, "how does X work", call graphs, symbol search, and impact analysis, prefer codegraph tools over grep/read_file:
- codegraph_context — entry points + related symbols + key code in one call (USE THIS FIRST for "how does X work")
- codegraph_search — find symbols by name (functions, types, interfaces)
- codegraph_callers / codegraph_callees — trace call chains
- codegraph_impact — what breaks if I change X
- codegraph_trace — full call path between two symbols
- codegraph_files — project file tree with symbol counts
Use grep/read_file for content search (comments, strings, config values) and when codegraph is not available.`

// BundleDirName is the directory, beside the reasonix executable, that the release
// archive unpacks the CodeGraph bundle into. Its launcher lives at
// <BundleDirName>/bin/codegraph, with the bundled node runtime and lib/ beside it;
Expand Down
7 changes: 6 additions & 1 deletion internal/plugin/lazy.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"context"
"encoding/json"
"fmt"
"strings"
"sync"
"time"

Expand Down Expand Up @@ -241,9 +242,13 @@ func LazyToolset(spec Spec, cs *CachedSchema, host *Host, reg *tool.Registry, se
} else {
out = make([]tool.Tool, 0, len(cs.Tools))
for _, ct := range cs.Tools {
visibleName := ct.Name
if spec.StripRawPrefix != "" {
visibleName = strings.TrimPrefix(visibleName, spec.StripRawPrefix)
}
out = append(out, &lazyTool{
shared: shared,
name: toolName(spec.Name, ct.Name),
name: toolName(spec.Name, visibleName),
desc: ct.Description,
schema: ct.Schema,
readOnly: spec.toolReadOnly(ct.Name, ct.ReadOnly),
Expand Down
12 changes: 11 additions & 1 deletion internal/plugin/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ type Spec struct {
// the server omits annotations.readOnlyHint. It is for first-party adapters
// with known semantics; user-configured plugins should rely on MCP metadata.
ReadOnlyToolNames map[string]bool
// StripRawPrefix, when non-empty, removes this prefix from each MCP tool's
// raw name before namespacing. For example, StripRawPrefix="codegraph_" turns
// "codegraph_context" into "context", yielding "mcp__codegraph__context"
// instead of the redundant "mcp__codegraph__codegraph_context". The original
// raw name is preserved for MCP protocol calls.
StripRawPrefix string
}

// transport carries JSON-RPC messages to and from one MCP server. call sends a
Expand Down Expand Up @@ -744,10 +750,14 @@ func (c *Client) listTools(ctx context.Context) ([]tool.Tool, error) {
tools := make([]tool.Tool, 0, len(out.Tools))
for _, t := range out.Tools {
hinted := t.Annotations != nil && t.Annotations.ReadOnlyHint
visibleName := t.Name
if c.spec.StripRawPrefix != "" {
visibleName = strings.TrimPrefix(visibleName, c.spec.StripRawPrefix)
}
toolInfos = append(toolInfos, ToolInfo{Name: t.Name, Description: t.Description})
tools = append(tools, &remoteTool{
client: c,
name: toolName(c.name, t.Name),
name: toolName(c.name, visibleName),
rawName: t.Name,
desc: t.Description,
schema: canonicalizeSchema(t.InputSchema),
Expand Down
29 changes: 20 additions & 9 deletions internal/skill/builtins.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ const tuiFormatting = `Keep the final answer compact and terminal-friendly: shor
const builtinExploreBody = `You are running as an exploration subagent. Investigate the codebase the parent pointed you at, then return one focused, distilled answer.

How to operate:
- Use read_file, grep, glob, ls as your primary tools. Stay read-only.
- For "find all places that call / reference / use X" questions, use ` + "`grep`" + ` (content search) — NOT ` + "`glob`" + ` (which only matches file names). Using the wrong one gives empty results and wastes your budget.
- Cast a wide net first (grep for symbol references, ls/glob for structure) to map the territory; then read the 3-10 most relevant files in full.
- Use codegraph tools (codegraph_context, codegraph_search, codegraph_callers, codegraph_callees, codegraph_trace) as your PRIMARY tools for symbol/code-structure questions. Fall back to read_file, grep, glob, ls for content search (comments, strings, config) or when codegraph tools are not available. Stay read-only.
- codegraph_context is the best starting point for "how does X work" / architecture questions — it returns entry points + related symbols + key code in one call.
- For "find all places that call / reference / use X" questions: use codegraph_callers (preferred) or ` + "`grep`" + ` (content search) — NOT ` + "`glob`" + ` (which only matches file names). Using the wrong one gives empty results and wastes your budget.
- Cast a wide net first (codegraph_search for symbols, grep for content references, ls/glob for structure) to map the territory; then read the 3-10 most relevant files in full.
- Don't read every file — be selective. Breadth on the first pass, depth only where the question demands it.
- Stop exploring as soon as you can answer. The parent doesn't see your tool calls, so over-exploration is pure waste.

Expand All @@ -34,8 +35,9 @@ The 'task' the parent gave you is the question you must answer. Treat any other
const builtinResearchBody = `You are running as a research subagent. Gather information from code AND the web, synthesize it, and return one focused conclusion.

How to operate:
- Combine code reading (read_file, grep, glob) with web_fetch as appropriate. (There is no dedicated web-search tool — fetch the canonical doc/spec URL directly when you know it.)
- For "how does X work" / "is Y supported" questions: fetch the canonical reference, then verify against the local code.
- Combine code reading (codegraph tools + read_file, grep, glob) with web_fetch as appropriate. (There is no dedicated web-search tool — fetch the canonical doc/spec URL directly when you know it.)
- For "how does X work" questions: use codegraph_context first for symbol-level understanding, then read_file for full context.
- For "is Y supported" questions: fetch the canonical reference, then verify against the local code.
- For "what's our policy on Z" / "where do we use Q": local code first, web only to compare against external standards.
- Cap yourself at ~10 tool calls. If you can't converge, return what you have plus a note on what's missing.

Expand All @@ -57,7 +59,7 @@ How to operate:
- Default scope: the current branch's diff vs the default branch. If the task names a specific commit range or files, honor that instead.
- Discover scope first: ` + "`bash git status`" + `, ` + "`git diff --stat`" + `, ` + "`git log --oneline`" + `. Then ` + "`git diff`" + ` (or ` + "`git diff <base>...HEAD`" + `) for the hunks.
- Read touched files (read_file) when the diff alone lacks context — signatures, surrounding invariants, callers.
- For "any callers depending on this?" questions: grep the symbol BEFORE asserting impact.
- For "any callers depending on this?" questions: use codegraph_callers or codegraph_impact (preferred) or grep the symbol BEFORE asserting impact.
- Stay read-only. Never commit, never write files, never propose edits as applied changes. The parent decides whether to act.
- Cap yourself at ~12 tool calls. If the diff is too big, pick the riskiest 2-3 files and say so.

Expand Down Expand Up @@ -85,7 +87,7 @@ const builtinSecurityReviewBody = `You are running as a security-review subagent
How to operate:
- Default scope: the current branch's diff vs the default branch. Honor a named range or directory if given.
- Discover scope first: ` + "`bash git status`" + `, ` + "`git diff --stat`" + `, ` + "`git diff <base>...HEAD`" + `. Read touched files (read_file) when the diff lacks security context — auth checks, input validation, the handler that calls the changed code.
- Use grep to verify "is this user-controlled input ever sanitized later?" / "what other call sites depend on this validation?" before asserting impact.
- Use codegraph_callers or codegraph_impact (preferred) or grep to verify "is this user-controlled input ever sanitized later?" / "what other call sites depend on this validation?" before asserting impact.
- Stay read-only. Never write, never run destructive commands. The parent decides what to act on.
- Cap yourself at ~12 tool calls. If the diff is too big, focus on the riskiest 2-3 files and say so.

Expand Down Expand Up @@ -148,11 +150,20 @@ Rules:
- Don't fabricate conventions the code doesn't demonstrate.
- After writing, summarize in one or two lines what you captured and tell the user to review and edit it.`

// extraReadTools holds additional tool names (e.g. codegraph tools) injected at
// boot time so subagent skills can use them without hardcoding MCP-prefixed names.
var extraReadTools []string

// SetExtraReadTools registers additional read-only tool names that subagent
// skills (explore, research, review, security-review) are allowed to use. Call
// from boot after plugin tools are registered.
func SetExtraReadTools(names []string) { extraReadTools = names }

// builtinSkills returns the shipped skills. A fresh slice each call so callers
// can't mutate the shared set.
func builtinSkills() []Skill {
readCodeTools := []string{"read_file", "ls", "glob", "grep"}
reviewTools := []string{"read_file", "ls", "glob", "grep", "bash"}
readCodeTools := append([]string{"read_file", "ls", "glob", "grep"}, extraReadTools...)
reviewTools := append(append([]string(nil), readCodeTools...), "bash")
return []Skill{
{
Name: "init",
Expand Down
2 changes: 1 addition & 1 deletion internal/tool/builtin/bash.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func (b bash) Description() string {
// bashToolSteer points the model at the cross-platform built-in tools instead of
// shell utilities, so it doesn't reach for grep/cat/ls/find (absent or different
// on native Windows) when a native tool already does the job everywhere.
const bashToolSteer = " Use for builds, tests, git, package managers, etc. To search/read/list/edit files, prefer the dedicated tools (grep, read_file, ls, glob, edit_file) over shell grep/cat/ls/find/sed — they behave identically on every OS."
const bashToolSteer = " Use for builds, tests, git, package managers, etc. To search/read/list/edit files, prefer the dedicated tools (grep, read_file, ls, glob, edit_file) over shell grep/cat/ls/find/sed — they behave identically on every OS. For symbol search, call graphs, or architecture questions, use codegraph tools instead of grep."

// resolved returns the bound shell, resolving lazily for the zero-value instance
// (e.g. a registry that never went through ConfineBash).
Expand Down
Loading