返回 DeepSeek-TUI-2026
review.rs
根目录 / crates / tui / src / commands / review.rs
1 //! Review command: activate review skill and send a target immediately.
2
3 use crate::skills::{SkillRegistry, default_skills_dir};
4 use crate::tui::app::{App, AppAction};
5 use crate::tui::history::HistoryCell;
6
7 use super::CommandResult;
8
9 fn warnings_suffix(registry: &SkillRegistry) -> String {
10 if registry.warnings().is_empty() {
11 return String::new();
12 }
13
14 format!("\n\nWarnings:\n- {}", registry.warnings().join("\n- "))
15 }
16
17 pub fn review(app: &mut App, args: Option<&str>) -> CommandResult {
18 let target = args.unwrap_or("").trim();
19 if target.is_empty() {
20 return CommandResult::error("Usage: /review <target>");
21 }
22
23 let skills_dir = app.skills_dir.clone();
24 let registry = SkillRegistry::discover(&skills_dir);
25 let mut warnings = warnings_suffix(&registry);
26 let mut skill = registry.get("review").cloned();
27
28 let global_dir = default_skills_dir();
29 if skill.is_none() && global_dir != skills_dir {
30 let registry = SkillRegistry::discover(&global_dir);
31 if warnings.is_empty() {
32 warnings = warnings_suffix(&registry);
33 } else if !registry.warnings().is_empty() {
34 warnings.push_str(&format!("\n- {}", registry.warnings().join("\n- ")));
35 }
36 skill = registry.get("review").cloned();
37 }
38
39 let skill = match skill {
40 Some(skill) => skill,
41 None => {
42 let global_display = global_dir.display();
43 return CommandResult::error(format!(
44 "Review skill not found in {} or {}. Create ~/.deepseek/skills/review/SKILL.md.{}",
45 skills_dir.display(),
46 global_display,
47 warnings
48 ));
49 }
50 };
51
52 let instruction = format!(
53 "You are now using a skill. Follow these instructions:\n\n# Skill: {}\n\n{}\n\n---\n\nNow respond to the user's request following the above skill instructions.",
54 skill.name, skill.body
55 );
56
57 app.add_message(HistoryCell::System {
58 content: format!("Activated skill: {}\n\n{}", skill.name, skill.description),
59 });
60 app.active_skill = Some(instruction);
61
62 CommandResult::action(AppAction::SendMessage(target.to_string()))
63 }
64
65 #[cfg(test)]
66 mod tests {
67 use super::*;
68 use crate::config::Config;
69 use crate::tui::app::{App, TuiOptions};
70 use tempfile::TempDir;
71
72 fn create_test_app_with_tmpdir(tmpdir: &TempDir) -> App {
73 let options = TuiOptions {
74 model: "deepseek-v4-pro".to_string(),
75 workspace: tmpdir.path().to_path_buf(),
76 config_path: None,
77 config_profile: None,
78 allow_shell: false,
79 use_alt_screen: true,
80 use_mouse_capture: false,
81 use_bracketed_paste: true,
82 max_subagents: 1,
83 skills_dir: tmpdir.path().join("skills"),
84 memory_path: tmpdir.path().join("memory.md"),
85 notes_path: tmpdir.path().join("notes.txt"),
86 mcp_config_path: tmpdir.path().join("mcp.json"),
87 use_memory: false,
88 start_in_agent_mode: false,
89 skip_onboarding: true,
90 yolo: false,
91 resume_session_id: None,
92 initial_input: None,
93 };
94 App::new(options, &Config::default())
95 }
96
97 fn create_review_skill_dir(tmpdir: &TempDir) {
98 let skill_dir = tmpdir.path().join("skills").join("review");
99 std::fs::create_dir_all(&skill_dir).unwrap();
100 std::fs::write(
101 skill_dir.join("SKILL.md"),
102 "---\nname: review\ndescription: Code review skill\n---\nReview the code",
103 )
104 .unwrap();
105 }
106
107 #[test]
108 fn test_review_without_target() {
109 let tmpdir = TempDir::new().unwrap();
110 let mut app = create_test_app_with_tmpdir(&tmpdir);
111 let result = review(&mut app, None);
112 assert!(result.message.is_some());
113 assert!(result.message.unwrap().contains("Usage: /review"));
114 }
115
116 #[test]
117 fn test_review_without_skill_installed() {
118 let tmpdir = TempDir::new().unwrap();
119 let mut app = create_test_app_with_tmpdir(&tmpdir);
120 // Set skills dir to empty temp dir
121 app.skills_dir = tmpdir.path().join("nonexistent_skills");
122 let result = review(&mut app, Some("file.rs"));
123 // The command should either error about missing skill or work if global skill exists
124 assert!(result.message.is_some() || result.action.is_some());
125 }
126
127 #[test]
128 fn test_review_with_skill_activates_and_sends() {
129 let tmpdir = TempDir::new().unwrap();
130 create_review_skill_dir(&tmpdir);
131 let mut app = create_test_app_with_tmpdir(&tmpdir);
132 let result = review(&mut app, Some("file.rs"));
133 assert!(result.message.is_none());
134 assert!(matches!(result.action, Some(AppAction::SendMessage(_))));
135 assert!(app.active_skill.is_some());
136 assert!(!app.history.is_empty());
137 }
138 }
139
139 lines RUST