Skip to main content

vtcode_core/skills/
enhanced_harness.rs

1//! # Enhanced Skill Harness
2//!
3//! Provides an improved skill execution harness that automatically tracks and reports
4//! generated files, eliminating the "where is it?" problem identified in session logs.
5
6use crate::tools::generation_helpers::GenerationHelper;
7use anyhow::Result;
8use serde_json::Value;
9use std::path::PathBuf;
10
11/// Enhanced skill result with automatic file tracking
12pub struct EnhancedSkillResult {
13    pub message: String,
14    pub generated_files: Vec<String>,
15    pub metadata: Value,
16}
17
18/// Enhanced skill harness that provides automatic file verification
19pub struct EnhancedSkillHarness {
20    workspace_root: PathBuf,
21    helper: GenerationHelper,
22}
23
24impl EnhancedSkillHarness {
25    pub fn new(workspace_root: PathBuf) -> Self {
26        Self {
27            workspace_root: workspace_root.clone(),
28            helper: GenerationHelper::new(workspace_root),
29        }
30    }
31
32    /// Execute a skill and automatically verify generated files
33    pub async fn execute_skill_with_verification(
34        &self,
35        skill_name: &str,
36        output_filename: Option<&str>,
37        skill_logic: impl FnOnce() -> Result<()>,
38    ) -> Result<EnhancedSkillResult> {
39        // Execute the skill logic
40        skill_logic()?;
41
42        // If output filename is provided, verify it
43        let mut verification_messages = Vec::new();
44        let mut generated_files = Vec::new();
45
46        if let Some(filename) = output_filename {
47            let verification = self.helper.verify_and_report(filename).await?;
48            verification_messages.push(verification.clone());
49            generated_files.push(filename.to_string());
50        }
51
52        // Create result message
53        let message = if verification_messages.is_empty() {
54            format!("✓ Skill '{skill_name}' executed successfully")
55        } else {
56            format!("✓ Skill '{}' executed successfully\n\n{}", skill_name, verification_messages.join("\n"))
57        };
58
59        Ok(EnhancedSkillResult {
60            message,
61            generated_files,
62            metadata: serde_json::json!({
63                "skill_name": skill_name,
64                "workspace_root": self.workspace_root.display().to_string(),
65            }),
66        })
67    }
68
69    /// Creates a standardized response for file generation skills
70    pub async fn create_file_generation_response(
71        &self,
72        file_type: &str,
73        filename: &str,
74        generation_details: Option<&str>,
75    ) -> Result<String> {
76        let verification = self.helper.verify_and_report(filename).await?;
77
78        let details_section = if let Some(details) = generation_details {
79            format!("\n\n{details}")
80        } else {
81            String::new()
82        };
83
84        Ok(format!("✓ Generated {file_type}: {filename}\n\n{verification}{details_section}"))
85    }
86
87    /// Creates a quick sample response with immediate file verification
88    pub async fn create_sample_response(
89        &self,
90        file_type: &str,
91        generator_code: impl FnOnce(&PathBuf) -> Result<()>,
92    ) -> Result<String> {
93        // Generate a timestamped filename
94        let timestamp = chrono::Local::now().format("%Y%m%d_%H%M%S");
95        let filename = format!("sample_{}_{}", file_type.to_lowercase(), timestamp);
96        let filepath = self.workspace_root.join(&filename);
97
98        // Execute the generator
99        generator_code(&filepath)?;
100
101        // Verify and report
102        self.create_file_generation_response(
103            file_type,
104            &filename,
105            Some("This is a sample file. You can modify the content as needed."),
106        )
107        .await
108    }
109}
110
111/// Quick execution helper for common patterns
112pub async fn execute_file_generation_skill(
113    workspace_root: PathBuf,
114    skill_name: &str,
115    filename: &str,
116    success_message: Option<&str>,
117) -> Result<String> {
118    let harness = EnhancedSkillHarness::new(workspace_root);
119
120    let verification = harness.helper.verify_and_report(filename).await?;
121
122    let message = if let Some(custom_msg) = success_message {
123        format!("{custom_msg}\n\n{verification}")
124    } else {
125        format!("✓ Skill '{skill_name}' completed.\n\n{verification}")
126    };
127
128    Ok(message)
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134    use tempfile::TempDir;
135
136    #[tokio::test]
137    async fn test_enhanced_harness_creation() {
138        let temp_dir = TempDir::new().unwrap();
139        let harness = EnhancedSkillHarness::new(temp_dir.path().to_path_buf());
140
141        // Test basic creation
142        assert_eq!(harness.workspace_root, temp_dir.path().to_path_buf());
143    }
144
145    #[tokio::test]
146    async fn test_execute_skill_with_verification() {
147        let temp_dir = TempDir::new().unwrap();
148        let harness = EnhancedSkillHarness::new(temp_dir.path().to_path_buf());
149
150        // Test with a skill that doesn't generate files
151        let result = harness
152            .execute_skill_with_verification("test-skill", None, || Ok(()))
153            .await
154            .unwrap();
155
156        assert!(result.message.contains("test-skill"));
157        assert!(result.message.contains("executed successfully"));
158        assert!(result.generated_files.is_empty());
159    }
160
161    #[tokio::test]
162    async fn test_create_file_generation_response() {
163        let temp_dir = TempDir::new().unwrap();
164        let harness = EnhancedSkillHarness::new(temp_dir.path().to_path_buf());
165
166        let response = harness
167            .create_file_generation_response("PDF", "nonexistent.pdf", None)
168            .await
169            .unwrap();
170
171        assert!(response.contains("Generated PDF"));
172        assert!(response.contains("nonexistent.pdf"));
173    }
174
175    #[tokio::test]
176    async fn test_quick_execution_helper() {
177        let temp_dir = TempDir::new().unwrap();
178        let result = execute_file_generation_skill(
179            temp_dir.path().to_path_buf(),
180            "test-skill",
181            "test.pdf",
182            Some("Custom success message"),
183        )
184        .await
185        .unwrap();
186
187        assert!(result.contains("Custom success message"));
188    }
189}