Skip to main content

matrixcode_core/
prompt.rs

1use std::str::FromStr;
2
3const SYSTEM_PROMPT_IDENTITY: &str =
4    r#"你是一个谨慎、务实、高效的代码代理,可以使用工具完成任务。"#;
5
6const SYSTEM_PROMPT_THINKING: &str = r#"任务决策:
7- 简单(单文件、<10行):直接执行
8- 中等(多文件、需规划):快速规划后执行
9- 复杂(架构影响、风险不确定):先确认方案再执行
10
11Skill 工具:
12遇到需要专业方法的场景时,用 Skill 工具传入意图关键词(如调试、规划、重构),系统会匹配最适合的能力。不确定就求助。"#;
13
14const SYSTEM_PROMPT_MISSION: &str = r#"核心目标:
15- 安全正确完成编码任务
16- 依据仓库内容和工具输出,不猜测
17- 最小改动完整解决问题
18- 保持现有行为不变(除非明确要求)"#;
19
20const SYSTEM_PROMPT_WORKFLOW: &str = r#"工作方式:
211. 先理解需求,再查看相关代码
222. 非简单任务使用 todo_write 创建待办列表
233. 调用工具前简短说明意图
244. 基于证据判断;不确定就继续检查
255. 改动聚焦、最小,与现有风格一致
266. 完成后执行最小且相关的验证"#;
27
28const SYSTEM_PROMPT_BEHAVIOR: &str = r#"行为约束:
29- 不臆造文件、符号、API、测试或运行结果;必须用工具验证
30- 未检查前不宣称成功
31- 未授权不覆盖、回滚或丢弃用户改动
32- 优先修复根因,而非表面补丁
33- 高风险/高成本操作前先提醒用户
34- 不安全或不支持的操作要说明原因,给出安全替代方案"#;
35
36const SYSTEM_PROMPT_AMBIGUITY: &str = r#"歧义确认:
37- 需求模糊时必须用 `ask` 工具确认,不要自行解读
38- 需确认的情况:目标不明、范围不清、方案有分歧、影响不确定
39- 确认时提供:具体选项 + 你的推荐 + 推荐理由
40- 小决策可跳过:明显最优、低风险、可逆的改动"#;
41
42const SYSTEM_PROMPT_QUALITY: &str = r#"代码质量:
43- 命名清晰表达意图,避免无意义缩写(id/url/idx 可接受)
44- 单一职责,函数不超过 30 行,嵌套不超过 3 层
45- 注释只写"为什么",复杂逻辑和边界条件必须注释
46- 优先强类型,避免 any/dynamic
47- 外部调用必须有错误处理,禁止静默失败"#;
48
49const SYSTEM_PROMPT_TESTING: &str = r#"测试验证:
50- 修改后运行相关测试确认未破坏现有功能
51- 新增功能评估是否需要测试(简单改动可跳过)
52- 测试失败先分析原因再修改,不盲目猜测
53- 无测试覆盖的改动需说明风险"#;
54
55const SYSTEM_PROMPT_DEBUGGING: &str = r#"调试策略:
56- 先复现:理解错误信息、失败场景、触发条件
57- 定位代码:grep/read 查找相关文件,分析逻辑流程
58- 不猜测根因:用工具(日志、调试器)验证假设
59- 修复后确认:运行测试或验证步骤
60- 无法定位时:说明已尝试方法、排查范围、剩余可能性"#;
61
62const SYSTEM_PROMPT_SECURITY: &str = r#"安全意识:
63- 用户输入必须验证,不信任外部数据
64- 拼接敏感字符串使用参数化方式,避免注入风险
65- 密钥/Token/密码不硬编码,使用环境变量或安全配置
66- 文件路径操作需验证,避免路径穿越
67- 发现潜在安全问题要提醒用户"#;
68
69const SYSTEM_PROMPT_EDITING: &str = r#"编辑规则:
70- 修改前先读取目标文件,理解上下文和依赖关系
71- 遵循项目约定:命名风格、文件结构、导入顺序
72- 改动最小化,只改必要部分
73- 修改公共代码时评估对其他模块的影响
74- 生成代码优先可读性,其次性能
75- 新增依赖需谨慎评估:必要性、维护状态、许可证"#;
76
77const SYSTEM_PROMPT_EXECUTION: &str = r#"执行策略:
78- 思考优先:动手前先建立完整理解
79- 分层执行:理解现状 → 规划方案 → 执行修改 → 验证效果
80- 渐进式推进:每次一个明确小步骤,验证后继续
81- 明显且低风险的下一步无需确认即可继续
82- 不确定或多方案可选时必须用 `ask` 工具询问用户
83
84【高风险操作 - 必须强制确认】
85- 删除文件/目录/分支
86- 修改数据库 schema 或数据迁移
87- 修改公共 API、接口签名
88- 修改配置文件
89- 可能造成数据丢失的命令"#;
90
91const SYSTEM_PROMPT_LANGUAGE: &str = r#"语言规则:
92- 使用中文回复,除非用户明确要求其他语言
93- 代码、命令、路径、错误信息保持原文
94- 技术术语保留英文(Promise、Hook、Middleware 等)
95- 表达简洁,每段不超过 3 行
96- 回答先给结论再给解释
97- 引用代码标注文件路径和行号"#;
98
99const SYSTEM_PROMPT_COMPLETION: &str = r#"完成要求:
100结束时提供:
1011. 改动摘要(改了什么、为什么改)
1022. 已执行的验证
1033. 剩余风险或后续建议(如有)"#;
104
105const SYSTEM_PROMPT_OUTPUT_CONTROL: &str = r#"输出控制:
106- 回复简洁明了,直接给结论和关键信息
107- 读取大文件使用 offset/limit 分批读取
108- 执行大输出命令使用 head_limit 或管道限制
109- 工具结果超过 50KB 会自动截断,主动控制避免信息丢失
110- 输出代码只展示关键部分,用注释标注省略内容"#;
111
112const SYSTEM_PROMPT_TASK_TRACKING: &str = r#"任务追踪:
113- 多步骤任务必须先用 todo_write 列出所有子任务
114- 每完成一个子任务立即标记为 completed
115- 返回纯文本前必须检查:所有子任务都已完成?
116- 有未完成项继续执行工具调用,不要停止
117- 遇阻塞时说明原因和剩余任务,不静默结束"#;
118
119const DEFAULT_SYSTEM_PROMPT_MODULES: &[&str] = &[
120    SYSTEM_PROMPT_IDENTITY,
121    SYSTEM_PROMPT_THINKING,
122    SYSTEM_PROMPT_MISSION,
123    SYSTEM_PROMPT_WORKFLOW,
124    SYSTEM_PROMPT_AMBIGUITY,
125    SYSTEM_PROMPT_BEHAVIOR,
126    SYSTEM_PROMPT_QUALITY,
127    SYSTEM_PROMPT_TESTING,
128    SYSTEM_PROMPT_DEBUGGING,
129    SYSTEM_PROMPT_SECURITY,
130    SYSTEM_PROMPT_EDITING,
131    SYSTEM_PROMPT_EXECUTION,
132    SYSTEM_PROMPT_LANGUAGE,
133    SYSTEM_PROMPT_OUTPUT_CONTROL,
134    SYSTEM_PROMPT_COMPLETION,
135    SYSTEM_PROMPT_TASK_TRACKING,
136];
137
138const SAFE_SYSTEM_PROMPT_MODULES: &[&str] = &[
139    SYSTEM_PROMPT_IDENTITY,
140    SYSTEM_PROMPT_THINKING,
141    SYSTEM_PROMPT_MISSION,
142    SYSTEM_PROMPT_WORKFLOW,
143    SYSTEM_PROMPT_AMBIGUITY,
144    SYSTEM_PROMPT_BEHAVIOR,
145    SYSTEM_PROMPT_QUALITY,
146    SYSTEM_PROMPT_SECURITY,
147    SYSTEM_PROMPT_EDITING,
148    SYSTEM_PROMPT_LANGUAGE,
149    SYSTEM_PROMPT_OUTPUT_CONTROL,
150    SYSTEM_PROMPT_COMPLETION,
151    SYSTEM_PROMPT_TASK_TRACKING,
152];
153
154const FAST_SYSTEM_PROMPT_MODULES: &[&str] = &[
155    SYSTEM_PROMPT_IDENTITY,
156    SYSTEM_PROMPT_MISSION,
157    SYSTEM_PROMPT_OUTPUT_CONTROL,
158    SYSTEM_PROMPT_EXECUTION,
159    SYSTEM_PROMPT_LANGUAGE,
160    SYSTEM_PROMPT_COMPLETION,
161];
162
163const REVIEW_SYSTEM_PROMPT_MODULES: &[&str] = &[
164    SYSTEM_PROMPT_IDENTITY,
165    SYSTEM_PROMPT_THINKING,
166    SYSTEM_PROMPT_MISSION,
167    SYSTEM_PROMPT_WORKFLOW,
168    SYSTEM_PROMPT_AMBIGUITY,
169    SYSTEM_PROMPT_BEHAVIOR,
170    SYSTEM_PROMPT_QUALITY,
171    SYSTEM_PROMPT_TESTING,
172    SYSTEM_PROMPT_SECURITY,
173    SYSTEM_PROMPT_LANGUAGE,
174    SYSTEM_PROMPT_OUTPUT_CONTROL,
175    SYSTEM_PROMPT_COMPLETION,
176    SYSTEM_PROMPT_TASK_TRACKING,
177];
178
179#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
180pub enum PromptProfile {
181    #[default]
182    Default,
183    Safe,
184    Fast,
185    Review,
186}
187
188impl PromptProfile {
189    pub const fn as_str(self) -> &'static str {
190        match self {
191            Self::Default => "default",
192            Self::Safe => "safe",
193            Self::Fast => "fast",
194            Self::Review => "review",
195        }
196    }
197
198    const fn static_modules(self) -> &'static [&'static str] {
199        match self {
200            Self::Default => DEFAULT_SYSTEM_PROMPT_MODULES,
201            Self::Safe => SAFE_SYSTEM_PROMPT_MODULES,
202            Self::Fast => FAST_SYSTEM_PROMPT_MODULES,
203            Self::Review => REVIEW_SYSTEM_PROMPT_MODULES,
204        }
205    }
206}
207
208impl FromStr for PromptProfile {
209    type Err = String;
210
211    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
212        match s.trim().to_ascii_lowercase().as_str() {
213            "default" => Ok(Self::Default),
214            "safe" => Ok(Self::Safe),
215            "fast" => Ok(Self::Fast),
216            "review" => Ok(Self::Review),
217            other => Err(format!(
218                "unknown prompt profile '{other}'. expected one of: default, safe, fast, review"
219            )),
220        }
221    }
222}
223
224pub fn build_static_system_prompt(profile: PromptProfile) -> String {
225    profile.static_modules().join("\n\n")
226}
227
228pub const SECTION_PROJECT_CONTEXT: &str = "PROJECT CONTEXT";
229pub const SECTION_TASK_CONTEXT: &str = "TASK CONTEXT";
230pub const SECTION_AVAILABLE_SKILLS: &str = "AVAILABLE SKILLS";
231pub const SECTION_ACCUMULATED_MEMORY: &str = "ACCUMULATED MEMORY";
232
233/// Memory summary section header for system prompt.
234pub const MEMORY_SUMMARY_HEADER: &str = r#"【跨会话记忆摘要】
235以下是从过往对话中积累的关键知识,请在回答时参考这些信息以保持一致性:"#;
236
237/// Memory entry format template.
238pub const MEMORY_ENTRY_TEMPLATE: &str = "{icon} {category}: {content}";
239
240// =============================================================================
241// Overview Generation Prompt Constants
242// =============================================================================
243
244const OVERVIEW_PROMPT_HEADER: &str = "请分析以下项目并生成一份详细的项目概览文档 MATRIX.md。\n\n";
245
246const OVERVIEW_PROMPT_REQUIREMENTS: &[&str] = &[
247    "1. 分析项目的架构和核心功能",
248    "2. 说明关键目录的作用",
249    "3. 提供常用开发命令(构建、测试、运行等)",
250    "4. 总结项目的关键模式和约定",
251    "5. 提供开发注意事项",
252    "6. 如果有业务逻辑(如订单流程、用户系统等),请详细说明",
253];
254
255const OVERVIEW_PROMPT_FORMAT: &str = "输出格式:直接输出 markdown 内容,不要加代码块包裹。";
256
257const OVERVIEW_PROMPT_FOOTER: &str = "请基于以上信息,生成一份详细的项目概览文档 MATRIX.md。";
258
259/// Project context for overview generation.
260pub struct OverviewContext {
261    pub project_name: String,
262    pub project_type: String,
263    pub directory_structure: String,
264    pub config_files: Vec<(String, String)>,
265    pub readme: Option<String>,
266    pub source_files: Vec<(String, String)>,
267}
268
269/// Build the AI prompt for generating project overview (MATRIX.md).
270pub fn build_overview_prompt(context: &OverviewContext) -> String {
271    let mut prompt = String::new();
272
273    prompt.push_str(OVERVIEW_PROMPT_HEADER);
274    prompt.push_str("要求:\n");
275    for req in OVERVIEW_PROMPT_REQUIREMENTS {
276        prompt.push_str(req);
277        prompt.push('\n');
278    }
279    prompt.push('\n');
280    prompt.push_str(OVERVIEW_PROMPT_FORMAT);
281    prompt.push_str("\n\n---\n\n");
282
283    // Add project info
284    prompt.push_str(&format!("项目名称: {}\n", context.project_name));
285    prompt.push_str(&format!("项目类型: {}\n\n", context.project_type));
286
287    // Add directory structure
288    prompt.push_str("## 目录结构\n\n");
289    prompt.push_str("```\n");
290    prompt.push_str(&context.directory_structure);
291    prompt.push_str("```\n\n");
292
293    // Add config files
294    if !context.config_files.is_empty() {
295        prompt.push_str("## 配置文件\n\n");
296        for (filename, content) in &context.config_files {
297            prompt.push_str(&format!("### {}\n\n", filename));
298            prompt.push_str("```\n");
299            prompt.push_str(content);
300            prompt.push_str("\n```\n\n");
301        }
302    }
303
304    // Add README
305    if let Some(readme) = &context.readme {
306        prompt.push_str("## README.md (开头部分)\n\n");
307        prompt.push_str(readme);
308        prompt.push_str("\n\n");
309    }
310
311    // Add key source files
312    if !context.source_files.is_empty() {
313        prompt.push_str("## 关键源文件\n\n");
314        for (filename, content) in &context.source_files {
315            prompt.push_str(&format!("### {}\n\n", filename));
316            prompt.push_str("```\n");
317            prompt.push_str(content);
318            prompt.push_str("\n```\n\n");
319        }
320    }
321
322    prompt.push_str("---\n\n");
323    prompt.push_str(OVERVIEW_PROMPT_FOOTER);
324    prompt.push('\n');
325
326    prompt
327}
328
329#[derive(Debug, Clone, PartialEq, Eq)]
330pub struct PromptSection {
331    title: String,
332    body: String,
333}
334
335impl PromptSection {
336    pub fn new(title: impl Into<String>, body: impl Into<String>) -> Option<Self> {
337        let title = title.into().trim().to_string();
338        let body = body.into().trim().to_string();
339        if title.is_empty() || body.is_empty() {
340            return None;
341        }
342        Some(Self { title, body })
343    }
344
345    pub fn render(&self) -> String {
346        format!("[{}]\n{}", self.title, self.body)
347    }
348}
349
350#[derive(Debug, Clone, Default, PartialEq, Eq)]
351pub struct PromptContext {
352    sections: Vec<PromptSection>,
353}
354
355impl PromptContext {
356    pub fn new() -> Self {
357        Self::default()
358    }
359
360    pub fn push_section(&mut self, title: impl Into<String>, body: impl Into<String>) {
361        if let Some(section) = PromptSection::new(title, body) {
362            self.sections.push(section);
363        }
364    }
365
366    pub fn with_section(mut self, title: impl Into<String>, body: impl Into<String>) -> Self {
367        self.push_section(title, body);
368        self
369    }
370
371    pub fn push_available_skills(&mut self, body: impl Into<String>) {
372        self.push_section(SECTION_AVAILABLE_SKILLS, body);
373    }
374
375    pub fn with_available_skills(mut self, body: impl Into<String>) -> Self {
376        self.push_available_skills(body);
377        self
378    }
379
380    pub fn extend(&mut self, other: PromptContext) {
381        self.sections.extend(other.sections);
382    }
383
384    pub fn is_empty(&self) -> bool {
385        self.sections.is_empty()
386    }
387
388    pub fn render_sections(&self) -> Vec<String> {
389        self.sections.iter().map(PromptSection::render).collect()
390    }
391}
392
393#[derive(Debug, Clone)]
394pub struct SystemPromptBuilder {
395    profile: PromptProfile,
396    context: PromptContext,
397}
398
399impl SystemPromptBuilder {
400    pub fn new(profile: PromptProfile) -> Self {
401        Self {
402            profile,
403            context: PromptContext::new(),
404        }
405    }
406
407    pub fn push_section(&mut self, title: impl Into<String>, body: impl Into<String>) {
408        self.context.push_section(title, body);
409    }
410
411    pub fn with_section(mut self, title: impl Into<String>, body: impl Into<String>) -> Self {
412        self.push_section(title, body);
413        self
414    }
415
416    pub fn push_context(&mut self, context: PromptContext) {
417        self.context.extend(context);
418    }
419
420    pub fn with_context(mut self, context: PromptContext) -> Self {
421        self.push_context(context);
422        self
423    }
424
425    pub fn push_available_skills(&mut self, body: impl Into<String>) {
426        self.context.push_available_skills(body);
427    }
428
429    pub fn with_available_skills(mut self, body: impl Into<String>) -> Self {
430        self.push_available_skills(body);
431        self
432    }
433
434    pub fn build(&self) -> String {
435        let mut parts = vec![build_static_system_prompt(self.profile)];
436        parts.extend(self.context.render_sections());
437        parts.join("\n\n")
438    }
439}
440
441/// Convenience function to build full system prompt
442pub fn build_system_prompt(
443    profile: &PromptProfile,
444    skills: &[crate::skills::Skill],
445    project_overview: Option<&str>,
446    memory_summary: Option<&str>,
447) -> String {
448    let builder = SystemPromptBuilder::new(*profile);
449
450    // Get static prompt parts
451    let static_prompt = build_static_system_prompt(*profile);
452
453    // Dynamically generate tools description
454    let tools_prompt = crate::tools::generate_tools_prompt();
455
456    // Combine: static prompt + tools + sections
457    let mut parts = vec![static_prompt, tools_prompt];
458    parts.extend(builder.context.render_sections());
459    let mut result = parts.join("\n\n");
460
461    // Add project overview if provided
462    if let Some(overview) = project_overview {
463        result.push_str("\n\n[PROJECT CONTEXT]\n");
464        result.push_str(overview);
465    }
466
467    // Add memory summary if provided
468    if let Some(memory) = memory_summary {
469        result.push_str("\n\n[ACCUMULATED MEMORY]\n");
470        result.push_str(memory);
471    }
472
473    // Add available skills
474    if !skills.is_empty() {
475        result.push_str("\n\n[AVAILABLE SKILLS]\n");
476        for skill in skills {
477            result.push_str(&format!("- {}: {}\n", skill.name, skill.description));
478        }
479    }
480
481    result
482}
483
484// =============================================================================
485// Runtime User Message Prompt Constants
486// =============================================================================
487
488/// Warning message when approaching iteration limit.
489pub const MSG_ITERATION_WARNING: &str = "⚠️ 接近最大迭代次数限制(当前 {iterations}/{max_iterations})。\n\
490    请检查任务进度:\n\
491    1. 如果有未完成的子任务,优先完成最关键的项\n\
492    2. 使用 todo_write 查看和更新任务状态\n\
493    3. 确保在限制内完成或在最后输出剩余任务摘要";
494
495/// Message when pending todos detected.
496pub const MSG_PENDING_TODOS: &str = "📋 检测到未完成的待办任务。请继续执行剩余任务,或在 todo_write 中将已完成的任务标记为 completed。\n\
497    注意:只有所有任务都完成后才能结束。如果遇到阻塞,请说明原因。";
498
499/// Error message when operation is cancelled.
500pub const MSG_OPERATION_CANCELLED: &str = "操作已取消";
501
502/// Progress message during context compression.
503pub const MSG_COMPRESSING_CONTEXT: &str = "正在压缩上下文...";
504
505/// Error message prefix when compression fails.
506pub const MSG_COMPRESSION_FAILED: &str = "压缩失败:";
507
508/// Error message when maximum iterations reached.
509pub const MSG_MAX_ITERATIONS_REACHED: &str = "⚠️ 已达到最大迭代次数限制({max_iterations} 次)。\n\n\
510    **任务状态**:任务可能未完全完成。\n\n\
511    **发生了什么**:代理在执行 {iterations} 次迭代后停止,以防止无限循环。\n\n\
512    **下一步操作**:\n\
513    1. 检查任务是否已完成\n\
514    2. 如未完成,您可以:\n\
515       - 提供更具体的指令继续执行\n\
516       - 将任务拆分为更小的子任务\n\
517       - 使用 '/resume' 从当前状态继续\n\n\
518    **限制原因**:防止失控操作和资源耗尽。\n\
519    **可调整**:未来版本将支持自定义迭代次数限制。";