1use std::str::FromStr;
2
3const SYSTEM_PROMPT_IDENTITY: &str = r#"你是一个谨慎、务实、高效的代码代理,可以使用工具完成任务。"#;
4
5const SYSTEM_PROMPT_MISSION: &str = r#"核心目标:
6- 安全、正确地完成用户提出的编码任务。
7- 优先依据仓库内容、工具输出和可验证事实,而不是猜测。
8- 以尽可能小的改动完整解决问题。
9- 除非用户明确要求,否则尽量保持现有行为不变。"#;
10
11const SYSTEM_PROMPT_TOOLS: &str = r#"可用工具:
12- read / write / edit / multi_edit:文件读写。修改已有文件前先 read;修改已有文件时优先使用 edit 或 multi_edit,而不是 write。
13- ls:列出目录下的一级内容(非递归)。
14- glob:按模式查找文件。
15- search:按正则搜索文件内容。
16- bash:执行构建、测试、lint、git 检查等 shell 命令。
17- ask:向用户提问澄清歧义或选择方案。遇到不确定的情况时使用此工具,而非自行猜测。
18- todo_write:用于维护非简单任务的待办列表;始终保持且仅保持一个 in_progress。
19- websearch:客户端网页搜索工具,使用 DuckDuckGo 搜索并返回结果列表。
20- web_search:服务端网页搜索工具(仅 Anthropic),由 API 直接执行搜索,结果更精准。
21- webfetch:获取指定 URL 的页面内容。
22- skill:当任务匹配某项技能时,优先加载技能说明,而不是自行猜测。
23
24工具选择建议:
25- 需要搜索网页信息时,优先使用 web_search(服务端搜索,结果更精准)。
26- 如果 web_search 不可用或需要更多控制,可使用 websearch(客户端搜索)。
27- 要获取具体网页内容时,使用 webfetch。
28- 需要用户澄清或决策时,使用 ask 工具。"#;
29
30const SYSTEM_PROMPT_WORKFLOW: &str = r#"工作方式:
311. 先理解需求,再查看相关代码和文件。
322. 对于非简单任务,使用 todo_write 创建并持续更新待办列表。
333. 每次调用工具前,先简短说明接下来要做什么。
344. 优先基于证据做判断;如果不确定,就继续检查。
355. 保持改动聚焦、最小,并与现有代码风格一致。
366. 除非为安全完成任务所必需,否则避免无关重构。
377. 修改完成后,执行最小且相关的验证。
388. 如果无法验证,要明确说明原因和剩余风险。"#;
39
40const SYSTEM_PROMPT_BEHAVIOR: &str = r#"行为约束:
41- 如果需求存在歧义,且该歧义会阻碍安全推进,先使用 `ask` 工具向用户提问澄清。
42- 当存在多种可行方案时,不要猜测或自选;使用 `ask` 工具列出选项、提供推荐方案及理由,等待用户决定。
43- 不要臆造文件、符号、API、测试或运行结果;必须用工具验证。
44- 在没有检查相关文件或命令输出前,不要宣称已经成功。
45- 未经用户明确要求,不要覆盖、回滚或丢弃你未创建的用户改动。
46- 在可行时,优先修复根因,而不是只做表面补丁。
47- 执行具有破坏性、高风险或高成本的命令前,先提醒用户。
48- 如果用户要求的操作不安全或当前不支持,要说明原因,并给出最接近的安全替代方案。"#;
49
50const SYSTEM_PROMPT_EDITING: &str = r#"编辑规则:
51- 修改文件前,先读取目标文件或相关片段。
52- 遵循周边代码的命名、格式和架构约定。
53- 除非任务明确要求,否则不要修改生成文件。
54- 除非确有必要,否则不要新增依赖。
55- 尽量只改动完成任务所需的最少文件。"#;
56
57const SYSTEM_PROMPT_EXECUTION: &str = r#"执行策略:
58- 当用户请求实现、调试或修改时,优先直接使用工具推进,而不是只停留在高层建议。
59- 只要可以安全地检查、编辑或验证,就不要停在纯分析阶段。
60- 当下一步明显且风险较低时,无需额外确认即可继续。
61- 当遇到不确定的决策点或多种方案可选时,必须使用 `ask` 工具询问用户,不要自行假设。
62- `ask` 工具必须包含:问题描述、可选方案列表、你的推荐方案及推荐理由。"#;
63
64const SYSTEM_PROMPT_LANGUAGE: &str = r#"语言规则:
65- 默认使用中文回复,除非用户明确要求使用其他语言。
66- 代码、命令、路径、报错信息和标识符在合适时保持原文。
67- 表达应简洁、清晰、面向执行。"#;
68
69const SYSTEM_PROMPT_COMPLETION: &str = r#"完成要求:
70- 结束时提供:
71 1. 改动摘要;
72 2. 已执行的验证;
73 3. 剩余风险或后续建议。"#;
74
75const DEFAULT_SYSTEM_PROMPT_MODULES: &[&str] = &[
76 SYSTEM_PROMPT_IDENTITY,
77 SYSTEM_PROMPT_MISSION,
78 SYSTEM_PROMPT_TOOLS,
79 SYSTEM_PROMPT_WORKFLOW,
80 SYSTEM_PROMPT_BEHAVIOR,
81 SYSTEM_PROMPT_EDITING,
82 SYSTEM_PROMPT_EXECUTION,
83 SYSTEM_PROMPT_LANGUAGE,
84 SYSTEM_PROMPT_COMPLETION,
85];
86
87const SAFE_SYSTEM_PROMPT_MODULES: &[&str] = &[
88 SYSTEM_PROMPT_IDENTITY,
89 SYSTEM_PROMPT_MISSION,
90 SYSTEM_PROMPT_TOOLS,
91 SYSTEM_PROMPT_WORKFLOW,
92 SYSTEM_PROMPT_BEHAVIOR,
93 SYSTEM_PROMPT_EDITING,
94 SYSTEM_PROMPT_LANGUAGE,
95 SYSTEM_PROMPT_COMPLETION,
96];
97
98const FAST_SYSTEM_PROMPT_MODULES: &[&str] = &[
99 SYSTEM_PROMPT_IDENTITY,
100 SYSTEM_PROMPT_MISSION,
101 SYSTEM_PROMPT_TOOLS,
102 SYSTEM_PROMPT_WORKFLOW,
103 SYSTEM_PROMPT_EXECUTION,
104 SYSTEM_PROMPT_LANGUAGE,
105 SYSTEM_PROMPT_COMPLETION,
106];
107
108const REVIEW_SYSTEM_PROMPT_MODULES: &[&str] = &[
109 SYSTEM_PROMPT_IDENTITY,
110 SYSTEM_PROMPT_MISSION,
111 SYSTEM_PROMPT_TOOLS,
112 SYSTEM_PROMPT_WORKFLOW,
113 SYSTEM_PROMPT_BEHAVIOR,
114 SYSTEM_PROMPT_LANGUAGE,
115 SYSTEM_PROMPT_COMPLETION,
116];
117
118#[derive(Debug, Clone, Copy, PartialEq, Eq)]
119#[derive(Default)]
120pub enum PromptProfile {
121 #[default]
122 Default,
123 Safe,
124 Fast,
125 Review,
126}
127
128impl PromptProfile {
129 pub const fn as_str(self) -> &'static str {
130 match self {
131 Self::Default => "default",
132 Self::Safe => "safe",
133 Self::Fast => "fast",
134 Self::Review => "review",
135 }
136 }
137
138 const fn static_modules(self) -> &'static [&'static str] {
139 match self {
140 Self::Default => DEFAULT_SYSTEM_PROMPT_MODULES,
141 Self::Safe => SAFE_SYSTEM_PROMPT_MODULES,
142 Self::Fast => FAST_SYSTEM_PROMPT_MODULES,
143 Self::Review => REVIEW_SYSTEM_PROMPT_MODULES,
144 }
145 }
146}
147
148
149impl FromStr for PromptProfile {
150 type Err = String;
151
152 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
153 match s.trim().to_ascii_lowercase().as_str() {
154 "default" => Ok(Self::Default),
155 "safe" => Ok(Self::Safe),
156 "fast" => Ok(Self::Fast),
157 "review" => Ok(Self::Review),
158 other => Err(format!(
159 "unknown prompt profile '{other}'. expected one of: default, safe, fast, review"
160 )),
161 }
162 }
163}
164
165pub fn build_static_system_prompt(profile: PromptProfile) -> String {
166 profile.static_modules().join("\n\n")
167}
168
169pub const SECTION_PROJECT_CONTEXT: &str = "PROJECT CONTEXT";
170pub const SECTION_TASK_CONTEXT: &str = "TASK CONTEXT";
171pub const SECTION_AVAILABLE_SKILLS: &str = "AVAILABLE SKILLS";
172pub const SECTION_ACCUMULATED_MEMORY: &str = "ACCUMULATED MEMORY";
173
174pub const MEMORY_SUMMARY_HEADER: &str = r#"【跨会话记忆摘要】
176以下是从过往对话中积累的关键知识,请在回答时参考这些信息以保持一致性:"#;
177
178pub const MEMORY_ENTRY_TEMPLATE: &str = "{icon} {category}: {content}";
180
181const OVERVIEW_PROMPT_HEADER: &str = "请分析以下项目并生成一份详细的项目概览文档 MATRIX.md。\n\n";
186
187const OVERVIEW_PROMPT_REQUIREMENTS: &[&str] = &[
188 "1. 分析项目的架构和核心功能",
189 "2. 说明关键目录的作用",
190 "3. 提供常用开发命令(构建、测试、运行等)",
191 "4. 总结项目的关键模式和约定",
192 "5. 提供开发注意事项",
193 "6. 如果有业务逻辑(如订单流程、用户系统等),请详细说明",
194];
195
196const OVERVIEW_PROMPT_FORMAT: &str = "输出格式:直接输出 markdown 内容,不要加代码块包裹。";
197
198const OVERVIEW_PROMPT_FOOTER: &str = "请基于以上信息,生成一份详细的项目概览文档 MATRIX.md。";
199
200pub struct OverviewContext {
202 pub project_name: String,
203 pub project_type: String,
204 pub directory_structure: String,
205 pub config_files: Vec<(String, String)>,
206 pub readme: Option<String>,
207 pub source_files: Vec<(String, String)>,
208}
209
210pub fn build_overview_prompt(context: &OverviewContext) -> String {
212 let mut prompt = String::new();
213
214 prompt.push_str(OVERVIEW_PROMPT_HEADER);
215 prompt.push_str("要求:\n");
216 for req in OVERVIEW_PROMPT_REQUIREMENTS {
217 prompt.push_str(req);
218 prompt.push('\n');
219 }
220 prompt.push('\n');
221 prompt.push_str(OVERVIEW_PROMPT_FORMAT);
222 prompt.push_str("\n\n---\n\n");
223
224 prompt.push_str(&format!("项目名称: {}\n", context.project_name));
226 prompt.push_str(&format!("项目类型: {}\n\n", context.project_type));
227
228 prompt.push_str("## 目录结构\n\n");
230 prompt.push_str("```\n");
231 prompt.push_str(&context.directory_structure);
232 prompt.push_str("```\n\n");
233
234 if !context.config_files.is_empty() {
236 prompt.push_str("## 配置文件\n\n");
237 for (filename, content) in &context.config_files {
238 prompt.push_str(&format!("### {}\n\n", filename));
239 prompt.push_str("```\n");
240 prompt.push_str(content);
241 prompt.push_str("\n```\n\n");
242 }
243 }
244
245 if let Some(readme) = &context.readme {
247 prompt.push_str("## README.md (开头部分)\n\n");
248 prompt.push_str(readme);
249 prompt.push_str("\n\n");
250 }
251
252 if !context.source_files.is_empty() {
254 prompt.push_str("## 关键源文件\n\n");
255 for (filename, content) in &context.source_files {
256 prompt.push_str(&format!("### {}\n\n", filename));
257 prompt.push_str("```\n");
258 prompt.push_str(content);
259 prompt.push_str("\n```\n\n");
260 }
261 }
262
263 prompt.push_str("---\n\n");
264 prompt.push_str(OVERVIEW_PROMPT_FOOTER);
265 prompt.push('\n');
266
267 prompt
268}
269
270#[derive(Debug, Clone, PartialEq, Eq)]
271pub struct PromptSection {
272 title: String,
273 body: String,
274}
275
276impl PromptSection {
277 pub fn new(title: impl Into<String>, body: impl Into<String>) -> Option<Self> {
278 let title = title.into().trim().to_string();
279 let body = body.into().trim().to_string();
280 if title.is_empty() || body.is_empty() {
281 return None;
282 }
283 Some(Self { title, body })
284 }
285
286 pub fn render(&self) -> String {
287 format!("[{}]\n{}", self.title, self.body)
288 }
289}
290
291#[derive(Debug, Clone, Default, PartialEq, Eq)]
292pub struct PromptContext {
293 sections: Vec<PromptSection>,
294}
295
296impl PromptContext {
297 pub fn new() -> Self {
298 Self::default()
299 }
300
301 pub fn push_section(&mut self, title: impl Into<String>, body: impl Into<String>) {
302 if let Some(section) = PromptSection::new(title, body) {
303 self.sections.push(section);
304 }
305 }
306
307 pub fn with_section(mut self, title: impl Into<String>, body: impl Into<String>) -> Self {
308 self.push_section(title, body);
309 self
310 }
311
312 pub fn push_available_skills(&mut self, body: impl Into<String>) {
313 self.push_section(SECTION_AVAILABLE_SKILLS, body);
314 }
315
316 pub fn with_available_skills(mut self, body: impl Into<String>) -> Self {
317 self.push_available_skills(body);
318 self
319 }
320
321 pub fn extend(&mut self, other: PromptContext) {
322 self.sections.extend(other.sections);
323 }
324
325 pub fn is_empty(&self) -> bool {
326 self.sections.is_empty()
327 }
328
329 pub fn render_sections(&self) -> Vec<String> {
330 self.sections.iter().map(PromptSection::render).collect()
331 }
332}
333
334#[derive(Debug, Clone)]
335pub struct SystemPromptBuilder {
336 profile: PromptProfile,
337 context: PromptContext,
338}
339
340impl SystemPromptBuilder {
341 pub fn new(profile: PromptProfile) -> Self {
342 Self {
343 profile,
344 context: PromptContext::new(),
345 }
346 }
347
348 pub fn push_section(&mut self, title: impl Into<String>, body: impl Into<String>) {
349 self.context.push_section(title, body);
350 }
351
352 pub fn with_section(mut self, title: impl Into<String>, body: impl Into<String>) -> Self {
353 self.push_section(title, body);
354 self
355 }
356
357 pub fn push_context(&mut self, context: PromptContext) {
358 self.context.extend(context);
359 }
360
361 pub fn with_context(mut self, context: PromptContext) -> Self {
362 self.push_context(context);
363 self
364 }
365
366 pub fn push_available_skills(&mut self, body: impl Into<String>) {
367 self.context.push_available_skills(body);
368 }
369
370 pub fn with_available_skills(mut self, body: impl Into<String>) -> Self {
371 self.push_available_skills(body);
372 self
373 }
374
375 pub fn build(&self) -> String {
376 let mut parts = vec![build_static_system_prompt(self.profile)];
377 parts.extend(self.context.render_sections());
378 parts.join("\n\n")
379 }
380}
381
382pub fn build_system_prompt(
384 profile: &PromptProfile,
385 skills: &[crate::skills::Skill],
386 project_overview: Option<&str>,
387 memory_summary: Option<&str>,
388) -> String {
389 let builder = SystemPromptBuilder::new(*profile);
390
391 let mut result = builder.build();
392
393 if let Some(overview) = project_overview {
395 result.push_str("\n\n[PROJECT CONTEXT]\n");
396 result.push_str(overview);
397 }
398
399 if let Some(memory) = memory_summary {
401 result.push_str("\n\n[ACCUMULATED MEMORY]\n");
402 result.push_str(memory);
403 }
404
405 if !skills.is_empty() {
407 result.push_str("\n\n[AVAILABLE SKILLS]\n");
408 for skill in skills {
409 result.push_str(&format!("- {}: {}\n", skill.name, skill.description));
410 }
411 }
412
413 result
414}