返回 DeepSeek-Reasonix
builtins.go
根目录 / internal / skill / builtins.go
1 package skill
2
3 import (
4 "sort"
5 "strings"
6
7 "reasonix/internal/tool"
8 )
9
10 // Built-in skills ship with Reasonix and back the dedicated subagent tools
11 // (explore / research / review / security_review) plus inline playbooks such as
12 // test. A user/project file with the same name overrides the
13 // built-in (see Store.List / Store.Read). Tool names in the bodies match
14 // internal/tool/builtin.
15
16 // negativeClaimRule keeps subagents honest about "found nothing" answers.
17 const negativeClaimRule = `When you claim something does NOT exist (no caller, no usage, not implemented), say which searches you ran to reach that conclusion — a negative claim is only as trustworthy as the search behind it.`
18
19 const optionalCodeGraphHint = `Optional installed code graph MCP tools are available in this session. Choose the semantic tool that fits the task: use LSP for language semantics (definitions, references, hover, diagnostics), use code graph tools first for call graph, impact analysis, and architecture relationships, use code_index only as the built-in outline/definition-candidate fallback, and verify textual or negative claims with read_file or grep.`
20
21 const builtinExploreBody = `You are a read-only exploration subagent. Answer the parent's question from the relevant code and report file:line evidence.
22
23 Use LSP for definitions, references, and call hierarchy when available; code_index supplies outlines/definition candidates, not proof of all callers. Use content search for textual references and read the context needed to verify the mechanism. Let the question determine breadth and depth.
24
25 Finish when the requested scope is supported by evidence. If an essential source is unavailable or further searches add no evidence, report the uncovered scope and what would resolve it.
26
27 ` + negativeClaimRule
28
29 const builtinResearchBody = `You are a read-only research subagent. Resolve the parent's question using repository code and primary external sources as needed.
30
31 Verify external claims against the relevant version and local implementation. Use available search/fetch tools rather than assuming a fixed tool set. Distinguish source documentation from observed code behavior and cite both where relevant.
32
33 Continue until the question is answered with evidence, or identify the concrete missing source or access that prevents an answer. Respect the task's time, cost, and permission limits.
34
35 ` + negativeClaimRule
36
37 const builtinInstallCapabilityBody = `This skill is INLINED. Use it when the user asks to install a Reasonix MCP server or skill from a URL, local file, local folder, .mcp.json, or package name. For removing a previously installed skill or MCP server, follow the "Uninstall" rules at the bottom — same tool, different op.
38
39 Operate as an installer, not as a shell-script guesser:
40 1. Extract the source string exactly from the user's request. It may be an https URL, GitHub URL, local path, .mcp.json, executable path, or npm package name.
41 2. Decide kind only when it is explicit. Use kind="auto" when unsure.
42 3. First call install_source with apply=false. Include scope when the user says project/global. Include mode when they say copy/link/register; otherwise leave mode="auto".
43 4. Read the returned plan. If status is blocked or failed, report the concrete next step. Do not invent a command from a README when the tool could not identify a manifest.
44 5. Inspect the plan's actions. Each one carries a riskLevel:
45 - low → safe to apply without asking.
46 - medium → safe to apply, but mention what was written.
47 - high → ask the user to confirm in one short question before apply=true. High actions include MCP installs that send auth headers, eager-tier servers, link targets that are absolute paths outside the project/home root, and any replace=true on an existing entry.
48 6. If the plan is acceptable and any needed user confirmation has happened, call install_source again with apply=true and echo back the same planId you got from the planning call. The tool refuses to apply when the planId does not match, so always re-fetch by running apply=false again if the user changed their mind about the source. Host permissions may still deny the apply call.
49 7. After apply=true, report what was installed, where it was persisted, and whether it is usable in the current session. For skills, prefer actions[].canonicalPath, actions[].installRoot, actions[].discoverable, and actions[].indexed over guessing from the source path. The plan's kinds field tells you how many skills vs MCP servers were touched.
50
51 Defaults:
52 - MCP installs default to global so the server is available in every project; use scope="project" only for project-specific servers, tokens, or commands. A project-root .mcp.json import stays project-scoped by default.
53 - A folder containing many skills should be registered as a skill root, not copied.
54 - A single SKILL.md, <name>.md, or <name>/SKILL.md should be copied unless the user asked to link/register. The installer writes canonical <skill-name>/SKILL.md paths by default; flat <name>.md is compatibility input, not the preferred output.
55 - A local SKILL.md source may have references/, scripts/, assets/, or other sibling files. Treat its parent directory as the skill package so those files remain available after install.
56 - Local skill folders may contain grouped skills up to a bounded depth. Let install_source decide which roots to register instead of telling the user to manually split every nested folder first.
57 - Remote MCP URLs should use http unless the endpoint is explicitly SSE.
58 - Package-name MCP installs should default to npx -y <package>.
59 - Never put raw tokens in headers or config. Prefer ${VAR} placeholders and tell the user which env var to set.
60
61 Uninstall (op=uninstall):
62 - Use op=uninstall with the same name and scope as the original install. Source is ignored.
63 - Skill and MCP server matching happen in the chosen scope's active config; if you don't know where the entry lives, ask the user. Removal is destructive but symmetric with a previously approved install, so it is applied directly (no approval step).
64
65 Stop rather than guessing when the source is only a documentation page, README without a manifest, or a repo whose install command cannot be determined.`
66
67 const builtinReviewBody = `You are a read-only code-review subagent. Assess the working-tree diff or the range and files named by the parent. Verify supplied context against current code; inspect history only when needed to establish a regression or requested explicitly.
68
69 Trace correctness, security, behavior changes, and missing regression coverage through affected callers and state owners. Use references/content search to establish impact; code_index alone cannot prove absence of callers. Report material findings, not cosmetic preferences.
70
71 Cover the requested scope. If a concrete access or execution limit prevents coverage, name what remains unreviewed. Do not write files, commit, or present proposed fixes as applied.
72
73 Return verdict, blocking_findings, non_blocking, and required_changes with file:line evidence and concise fix directions.
74
75 ` + negativeClaimRule
76
77 const builtinSecurityReviewBody = `You are a read-only security-review subagent. Review the named diff/range, or the current branch against its verified base, for exploitable regressions.
78
79 Trace untrusted input and privileges through callers, validation, and sinks. Examine auth/authz, secrets, command execution, file paths, network egress, dependencies, and persistence where the diff affects them. Establish the threat, preconditions, impact, and existing mitigations before assigning severity.
80
81 Cover the requested scope; report any concrete access or execution limit and the remaining coverage. Do not modify files or run destructive commands. General style and unrelated performance work are outside this review.
82
83 Lead with the verdict and group findings by severity. Each finding needs file:line evidence, an exploitation scenario, and a repair direction. A clean report states what was checked without manufacturing findings.
84
85 ` + negativeClaimRule
86
87 const builtinTestBody = `Run and repair the tests requested by the user in the parent loop. Discover the project's actual test commands and relevant modules from its manifests, scripts, and instructions. Use isolated fixtures and honor existing session authorization.
88
89 Diagnose each failure against the tested contract. Fix production defects in production code; correct invalid tests with an explanation. Never skip, delete, or weaken valid checks to manufacture green results.
90
91 Re-run affected checks after changes and broaden for shared impact. Continue while new evidence supports a next repair; passing one package does not complete a broader requested suite. When repeated attempts produce no new evidence, revisit the root cause instead of blindly retrying. Finish independent work before reporting a concrete remaining blocker.
92
93 Resolve environment issues within the authorized reversible scope. Ask only when a necessary dependency, configuration, credential, or external action falls outside that scope. Report commands, results, repairs, and any checks still blocked.`
94
95 const builtinInitBody = `The user invoked /init to create or improve durable project instructions.
96
97 Find existing AGENTS.md, REASONIX.md, or CLAUDE.md and follow their imports to the authoritative document. Preserve thin wrappers and unrelated user guidance; update the owning file rather than creating a competing instruction source. Use AGENTS.md for a new project with no existing instruction document.
98
99 Inspect enough code, manifests, and scripts to establish non-obvious project contracts and verified commands. Keep always-loaded guidance concise: ownership boundaries, important defaults, safe local workflows, and concrete completion criteria. Link task-specific procedures only where they are needed.
100
101 Do not prescribe a fixed section list, restate ordinary coding advice, invent conventions, copy secrets, or turn one past failure into a universal rule. Remove stale or duplicate instructions when supported by current evidence. Validate paths and commands that the document recommends, then summarize what changed.`
102
103 // CodeGraphReadTools returns read-only tool names that look like an installed
104 // codegraph MCP surface. Writable or untrusted tools stay out of subagents.
105 func CodeGraphReadTools(reg *tool.Registry) []string {
106 if reg == nil {
107 return nil
108 }
109 var names []string
110 for _, name := range reg.Names() {
111 if !looksLikeCodeGraphTool(name) {
112 continue
113 }
114 tl, ok := reg.Get(name)
115 if !ok || !tl.ReadOnly() {
116 continue
117 }
118 names = append(names, name)
119 }
120 return normalizeExtraToolNames(names)
121 }
122
123 func looksLikeCodeGraphTool(name string) bool {
124 return strings.HasPrefix(name, "codegraph_") ||
125 strings.HasPrefix(name, tool.MCPNamePrefix+"codegraph__")
126 }
127
128 func normalizeExtraToolNames(names []string) []string {
129 seen := map[string]bool{}
130 out := make([]string, 0, len(names))
131 for _, name := range names {
132 name = strings.TrimSpace(name)
133 if name == "" || seen[name] {
134 continue
135 }
136 seen[name] = true
137 out = append(out, name)
138 }
139 sort.Strings(out)
140 return out
141 }
142
143 func withOptionalCodeGraphHint(body string, enabled bool) string {
144 if !enabled {
145 return body
146 }
147 if strings.Contains(body, optionalCodeGraphHint) {
148 return body
149 }
150 return body + "\n\n" + optionalCodeGraphHint
151 }
152
153 // WithCodeGraphTools enables user-installed codegraph MCP tools for built-in
154 // code-reading subagent skills. The caller passes names discovered from its live
155 // registry so desktop tabs/sessions never share mutable skill state.
156 func WithCodeGraphTools(sk Skill, names []string) Skill {
157 names = normalizeExtraToolNames(names)
158 if len(names) == 0 || sk.Scope != ScopeBuiltin || !codeReadingBuiltin(sk.Name) {
159 return sk
160 }
161 sk.AllowedTools = appendUniqueToolNames(sk.AllowedTools, names...)
162 sk.Body = withOptionalCodeGraphHint(sk.Body, true)
163 return sk
164 }
165
166 func codeReadingBuiltin(name string) bool {
167 switch name {
168 case "explore", "research", "review", "security-review":
169 return true
170 default:
171 return false
172 }
173 }
174
175 func appendUniqueToolNames(base []string, extra ...string) []string {
176 out := append([]string(nil), base...)
177 seen := make(map[string]bool, len(out)+len(extra))
178 for _, name := range out {
179 seen[name] = true
180 }
181 for _, name := range extra {
182 if seen[name] {
183 continue
184 }
185 seen[name] = true
186 out = append(out, name)
187 }
188 return out
189 }
190
191 // builtinSkills returns the shipped skills. A fresh slice each call so callers
192 // can't mutate the shared set.
193 func builtinSkills() []Skill {
194 readCodeTools := []string{"read_file", "ls", "glob", "grep", "code_index"}
195 // use_capability is the stable MCP proxy for strict review children: they
196 // never inherit direct mcp__* schemas, so the proxy must be allowlisted or
197 // review/security-review cannot discover authorized read-only MCP readers.
198 reviewTools := append(append([]string(nil), readCodeTools...), "bash", "use_capability")
199 base := []Skill{
200 {
201 Name: "init",
202 Description: "Create or update concise project instructions from verified repository contracts and commands.",
203 Body: builtinInitBody,
204 Scope: ScopeBuiltin,
205 Path: "(builtin)",
206 RunAs: RunInline,
207 Triggers: []string{"agents.md", "initialize project", "bootstrap project", "初始化项目", "项目记忆", "生成 agents.md"},
208 AutoUse: "suggest",
209 },
210 {
211 Name: "explore",
212 Description: "Investigate repository structure or behavior in a read-only subagent.",
213 Body: builtinExploreBody,
214 Scope: ScopeBuiltin,
215 Path: "(builtin)",
216 RunAs: RunSubagent,
217 AllowedTools: append([]string(nil), readCodeTools...),
218 Triggers: []string{"how does", "find all", "architecture", "callers", "references", "impact analysis", "代码架构", "怎么实现", "如何实现", "调用链", "所有引用", "影响范围", "分析代码"},
219 AutoUse: "suggest",
220 },
221 {
222 Name: "research",
223 Description: "Research a technical question using code and primary sources in a read-only subagent.",
224 Body: builtinResearchBody,
225 Scope: ScopeBuiltin,
226 Path: "(builtin)",
227 RunAs: RunSubagent,
228 AllowedTools: append(append([]string(nil), readCodeTools...), "web_fetch"),
229 Triggers: []string{"canonical", "documentation", "specification", "compare against", "is supported", "official docs", "官方文档", "规范", "是否支持", "对比实现", "外部资料", "最新文档"},
230 AutoUse: "suggest",
231 NeedsFreshData: true,
232 },
233 {
234 Name: "install-capability",
235 Description: "Install or uninstall Reasonix skills and MCP servers through install_source plans.",
236 Body: builtinInstallCapabilityBody,
237 Scope: ScopeBuiltin,
238 Path: "(builtin)",
239 RunAs: RunInline,
240 Triggers: []string{"install skill", "install mcp", "install plugin", "安装 skill", "安装 mcp", "安装插件"},
241 AutoUse: "suggest",
242 },
243 {
244 Name: "review",
245 Description: "Review the requested code changes for correctness, security, and regression risks in a read-only subagent.",
246 Body: builtinReviewBody,
247 Scope: ScopeBuiltin,
248 Path: "(builtin)",
249 RunAs: RunSubagent,
250 AllowedTools: append([]string(nil), reviewTools...),
251 ReadOnly: true,
252 Triggers: []string{"review changes", "review diff", "code review", "评审变更", "审查代码", "检查改动"},
253 AutoUse: "suggest",
254 },
255 {
256 Name: "security-review",
257 Description: "Assess code changes for exploitable security regressions in a read-only subagent.",
258 Body: builtinSecurityReviewBody,
259 Scope: ScopeBuiltin,
260 Path: "(builtin)",
261 RunAs: RunSubagent,
262 AllowedTools: append([]string(nil), reviewTools...),
263 ReadOnly: true,
264 Triggers: []string{"security review", "authentication", "authorization", "token handling", "injection", "安全评审", "安全审查", "鉴权", "权限", "令牌", "注入", "漏洞"},
265 AutoUse: "suggest",
266 },
267 {
268 Name: "test",
269 Description: "Run requested tests, diagnose failures, and complete authorized repairs.",
270 Body: builtinTestBody,
271 Scope: ScopeBuiltin,
272 Path: "(builtin)",
273 RunAs: RunInline,
274 Triggers: []string{"run tests", "test failure", "failing test", "ci failure", "运行测试", "测试失败", "修复测试", "ci 失败"},
275 AutoUse: "suggest",
276 },
277 }
278 // Embedded directory skills (reasonix-guide, …) append after the const
279 // playbooks so the index order stays deterministic and bodies stay on-demand.
280 return append(base, loadEmbeddedBuiltins()...)
281 }
282
283 // BuiltinNames returns the built-in skill names, used by callers that wire
284 // dedicated subagent tools for the subagent built-ins.
285 func BuiltinNames() []string {
286 skills := builtinSkills()
287 names := make([]string, len(skills))
288 for i, s := range skills {
289 names[i] = s.Name
290 }
291 return names
292 }
293
293 lines GO