diff --git a/src/cortex-engine/src/session/handlers.rs b/src/cortex-engine/src/session/handlers.rs index f59ea29..f9ce749 100644 --- a/src/cortex-engine/src/session/handlers.rs +++ b/src/cortex-engine/src/session/handlers.rs @@ -17,7 +17,10 @@ use crate::rollout::recorder::SessionMeta; use crate::summarization::SummarizationStrategy; use super::Session; -use super::prompt::build_system_prompt; +use super::prompt::{ + USE_SKILL_BASED_PROMPT, auto_detect_skills_from_message, build_system_prompt, + build_system_prompt_with_skills, inject_skills, +}; impl Session { /// Handle an incoming submission. @@ -122,7 +125,12 @@ impl Session { // Update system prompt in existing message history if let Some(msg) = self.messages.first_mut() { if matches!(msg.role, crate::client::MessageRole::System) { - *msg = Message::system(build_system_prompt(&self.config)); + let new_prompt = if USE_SKILL_BASED_PROMPT { + build_system_prompt_with_skills(&self.config, &[]) + } else { + build_system_prompt(&self.config) + }; + *msg = Message::system(new_prompt); } } } @@ -193,6 +201,29 @@ impl Session { tracing::debug!("User message: {}", user_text); + // Auto-detect and inject skills based on the user's message (only on first message) + // This reduces context window usage by only loading relevant skills. + if USE_SKILL_BASED_PROMPT && self.turn_id == 1 { + let detected_skills = auto_detect_skills_from_message(&user_text); + if !detected_skills.is_empty() { + tracing::info!("Auto-detected skills for task: {:?}", detected_skills); + + // Update the system prompt with detected skills + if let Some(msg) = self.messages.first_mut() { + if matches!(msg.role, MessageRole::System) { + if let Some(current_content) = msg.content.as_text() { + let updated_prompt = inject_skills(current_content, &detected_skills); + *msg = Message::system(updated_prompt); + tracing::debug!( + "Injected skills into system prompt: {:?}", + detected_skills + ); + } + } + } + } + } + // Track messages for potential redo functionality let _msg_start_idx = self.messages.len(); diff --git a/src/cortex-engine/src/session/lifecycle.rs b/src/cortex-engine/src/session/lifecycle.rs index 1819010..3ab081e 100644 --- a/src/cortex-engine/src/session/lifecycle.rs +++ b/src/cortex-engine/src/session/lifecycle.rs @@ -21,7 +21,7 @@ use crate::rollout::{RolloutRecorder, SESSIONS_SUBDIR, get_rollout_path, read_ro use crate::tools::ToolRouter; use super::Session; -use super::prompt::build_system_prompt; +use super::prompt::{USE_SKILL_BASED_PROMPT, build_system_prompt, build_system_prompt_with_skills}; use super::types::{SessionHandle, SessionInfo, TokenCounter}; impl Session { @@ -64,8 +64,16 @@ impl Session { recorder.record_meta(&meta)?; // Initialize with system prompt + // Use skill-based minimal prompt by default to reduce context window usage. + // Skills will be loaded on-demand based on task requirements. let mut messages = Vec::new(); - messages.push(Message::system(build_system_prompt(&config))); + let initial_prompt = if USE_SKILL_BASED_PROMPT { + // Start with the minimal base prompt - skills will be injected on first user message + build_system_prompt_with_skills(&config, &[]) + } else { + build_system_prompt(&config) + }; + messages.push(Message::system(initial_prompt)); // Initialize snapshot manager let snapshot_dir = config @@ -145,8 +153,15 @@ impl Session { let mut tool_router = ToolRouter::new(); // Rebuild messages from events + // For resumed sessions, we still use the base prompt since skills might have been + // loaded during the previous session and we want consistency. let mut messages = Vec::new(); - messages.push(Message::system(build_system_prompt(&config))); + let initial_prompt = if USE_SKILL_BASED_PROMPT { + build_system_prompt_with_skills(&config, &[]) + } else { + build_system_prompt(&config) + }; + messages.push(Message::system(initial_prompt)); let events = get_events(&entries); for event_msg in events { @@ -257,7 +272,12 @@ impl Session { let entries = read_rollout(&rollout_path)?; let mut messages = Vec::new(); - messages.push(Message::system(build_system_prompt(&config))); + let initial_prompt = if USE_SKILL_BASED_PROMPT { + build_system_prompt_with_skills(&config, &[]) + } else { + build_system_prompt(&config) + }; + messages.push(Message::system(initial_prompt)); let events = get_events(&entries); diff --git a/src/cortex-engine/src/session/mod.rs b/src/cortex-engine/src/session/mod.rs index c2932fe..340379f 100644 --- a/src/cortex-engine/src/session/mod.rs +++ b/src/cortex-engine/src/session/mod.rs @@ -22,7 +22,10 @@ use crate::rollout::RolloutRecorder; use crate::tools::ToolRouter; pub use lifecycle::list_sessions; -pub use prompt::build_system_prompt; +pub use prompt::{ + USE_SKILL_BASED_PROMPT, auto_detect_skills_from_message, available_skills, build_system_prompt, + build_system_prompt_with_skills, inject_skills, is_valid_skill, +}; pub use types::{SessionHandle, SessionInfo, TokenCounter}; /// A running session that handles conversation with the model. diff --git a/src/cortex-engine/src/session/prompt.rs b/src/cortex-engine/src/session/prompt.rs index 810e185..a6e46dd 100644 --- a/src/cortex-engine/src/session/prompt.rs +++ b/src/cortex-engine/src/session/prompt.rs @@ -1,4 +1,11 @@ //! System prompt building and AGENTS.md loading. +//! +//! This module provides system prompt construction with support for: +//! - Monolithic prompt mode (full `CORTEX_MAIN_PROMPT`) +//! - Skill-based prompt mode (minimal base + on-demand skills) +//! +//! The skill-based mode reduces token usage by only including instructions +//! relevant to the current task. use std::path::PathBuf; @@ -7,6 +14,20 @@ use crate::config::Config; /// System prompt for the Cortex Agent - loaded from cortex-prompt-harness pub(crate) const SYSTEM_PROMPT: &str = cortex_prompt_harness::prompts::CORTEX_MAIN_PROMPT; +/// Base prompt for skill-based mode - minimal core with skill loading capability +pub(crate) const BASE_PROMPT: &str = cortex_prompt_harness::prompts::CORTEX_BASE_PROMPT; + +/// Base prompt for when skills are pre-loaded (no skill loading instructions) +pub(crate) const BASE_PROMPT_WITH_SKILLS: &str = + cortex_prompt_harness::prompts::CORTEX_BASE_PROMPT_WITH_SKILLS_PRELOADED; + +/// Whether to use skill-based prompts by default. +/// +/// When `true`, the system uses a minimal base prompt with skills loaded on-demand. +/// When `false`, the full monolithic prompt is used. +#[allow(dead_code)] +pub const USE_SKILL_BASED_PROMPT: bool = true; + /// Build the system prompt for the agent. pub fn build_system_prompt(config: &Config) -> String { let cwd = config.cwd.display().to_string(); @@ -175,3 +196,450 @@ fn get_system_info() -> String { format!("{os} {arch} ({kernel})") } } + +// ============================================================================= +// Skill-Based Prompt System +// ============================================================================= + +/// Build a system prompt with specific skills pre-loaded. +/// +/// This function constructs a prompt using the minimal base prompt and injects +/// the requested skills. If no skills are specified and `USE_SKILL_BASED_PROMPT` +/// is false, it falls back to the full monolithic prompt. +/// +/// # Arguments +/// +/// * `config` - The configuration containing environment settings +/// * `skills` - Slice of skill names to include (e.g., `["git", "debugging"]`) +/// +/// # Returns +/// +/// A complete system prompt with the requested skills injected. +/// +/// # Examples +/// +/// ```ignore +/// // Build prompt with specific skills +/// let prompt = build_system_prompt_with_skills(&config, &["git", "debugging"]); +/// +/// // Build prompt with auto-detected skills +/// let skills = auto_detect_skills_from_message("Fix this bug and create a PR"); +/// let prompt = build_system_prompt_with_skills(&config, &skills); +/// ``` +#[allow(dead_code)] +pub fn build_system_prompt_with_skills(config: &Config, skills: &[&str]) -> String { + // If skills mode is disabled and no skills specified, use monolithic prompt + if !USE_SKILL_BASED_PROMPT && skills.is_empty() { + return build_system_prompt(config); + } + + let cwd = config.cwd.display().to_string(); + let user_instructions = config.user_instructions.as_deref().unwrap_or(""); + + // Get system info + let system_info = get_system_info(); + let current_date = chrono::Utc::now().format("%Y-%m-%d").to_string(); + + // Build environment context + let env_context = "# The commands below were executed at the start of all sessions to gather context about the environment.\n\ + # You do not need to repeat them, unless you think the environment has changed.\n\ + # Remember: They are not necessarily related to the current conversation, but may be useful for context.".to_string(); + + // Choose base prompt based on whether skills are pre-loaded + let base = if skills.is_empty() { + BASE_PROMPT + } else { + BASE_PROMPT_WITH_SKILLS + }; + + // Inject skills into the base prompt + let mut prompt = inject_skills(base, skills); + + // Handle agent-specific prompts + if let Some(agent_name) = &config.current_agent { + let project_agent_path = config + .cwd + .join(".cortex") + .join("agents") + .join(format!("{}.md", agent_name)); + let user_agent_path = config + .cortex_home + .join("agents") + .join(format!("{}.md", agent_name)); + + let path_to_try = if project_agent_path.exists() { + Some(project_agent_path) + } else if user_agent_path.exists() { + Some(user_agent_path) + } else { + None + }; + + if let Some(path) = path_to_try { + if let Ok(content) = std::fs::read_to_string(path) { + // If it starts with frontmatter, try to parse it + if content.starts_with("---") { + if let Ok((_meta, agent_prompt)) = crate::agents::parse_agent_md(&content) { + prompt = agent_prompt; + } + } else { + prompt = content; + } + } + } else { + // Prepend agent name to prompt if no custom prompt found + prompt = format!("You are the {} agent.\n\n{}", agent_name, prompt); + } + } + + // Replace template variables (if present in the prompt) + prompt = prompt.replace("{{SYSTEM_INFO}}", &system_info); + prompt = prompt.replace("{{MODEL_NAME}}", &config.model); + prompt = prompt.replace("{{CURRENT_DATE}}", ¤t_date); + prompt = prompt.replace("{{CWD}}", &cwd); + prompt = prompt.replace("{{ENVIRONMENT_CONTEXT}}", &env_context); + + // Load AGENTS.md instructions + let agents_instructions = load_agents_md(config); + + // Additional context (user instructions + AGENTS.md) + let mut additional = String::new(); + + if !agents_instructions.is_empty() { + additional.push_str("## Project Instructions (from AGENTS.md)\n"); + additional.push_str(&agents_instructions); + additional.push_str("\n\n"); + } + + if !user_instructions.is_empty() { + additional.push_str("## User Instructions\n"); + additional.push_str(user_instructions); + additional.push('\n'); + } + + prompt = prompt.replace("{{ADDITIONAL_CONTEXT}}", &additional); + + // If template variable wasn't present, append additional context + if !additional.is_empty() && !prompt.contains(&additional) { + prompt.push_str("\n\n"); + prompt.push_str(&additional); + } + + prompt +} + +/// Inject skill content into a base prompt. +/// +/// This function retrieves the content for each requested skill and appends +/// it to the base prompt with clear section separators. Invalid or missing +/// skills are silently skipped. +/// +/// # Arguments +/// +/// * `base_prompt` - The base prompt to build upon +/// * `skills` - Slice of skill names to inject +/// +/// # Returns +/// +/// The base prompt with skill content appended. +/// +/// # Examples +/// +/// ```ignore +/// let prompt = inject_skills(BASE_PROMPT, &["git", "debugging"]); +/// assert!(prompt.contains("Git Operations Skill")); +/// assert!(prompt.contains("Debugging Skill")); +/// ``` +#[allow(dead_code)] +pub fn inject_skills(base_prompt: &str, skills: &[&str]) -> String { + if skills.is_empty() { + return base_prompt.to_string(); + } + + let mut result = base_prompt.to_string(); + let mut injected_skills = Vec::new(); + + for skill_name in skills { + if let Some(skill_content) = cortex_prompt_harness::prompts::get_builtin_skill(skill_name) { + injected_skills.push((*skill_name, skill_content)); + } + // Silently skip invalid/missing skills for graceful handling + } + + if !injected_skills.is_empty() { + result.push_str("\n\n---\n\n# Loaded Skills\n\n"); + result.push_str("The following skills have been loaded for this task:\n\n"); + + for (name, content) in &injected_skills { + result.push_str(&format!("## Skill: {}\n\n", name)); + // Skip YAML frontmatter if present + let content_without_frontmatter = strip_yaml_frontmatter(content); + result.push_str(content_without_frontmatter); + result.push_str("\n\n---\n\n"); + } + } + + result +} + +/// Strip YAML frontmatter from skill content. +/// +/// Skills include YAML frontmatter for metadata, but we don't need it +/// in the injected prompt. +#[allow(dead_code)] +fn strip_yaml_frontmatter(content: &str) -> &str { + if !content.starts_with("---\n") { + return content; + } + + // Find the closing --- + if let Some(end_pos) = content[4..].find("\n---\n") { + // Skip past the closing --- and newline + let skip_to = 4 + end_pos + 5; + if skip_to < content.len() { + return &content[skip_to..]; + } + } + + content +} + +/// Auto-detect skills from a user message. +/// +/// This function analyzes the user's message and returns a list of skills +/// that are likely relevant to the task. Uses keyword matching from +/// `cortex_prompt_harness::prompts::get_recommended_skills`. +/// +/// # Arguments +/// +/// * `message` - The user's message to analyze +/// +/// # Returns +/// +/// A vector of skill names that are relevant to the message. +/// +/// # Examples +/// +/// ```ignore +/// let skills = auto_detect_skills_from_message("Fix this bug and create a PR"); +/// assert!(skills.contains(&"git")); +/// assert!(skills.contains(&"debugging")); +/// +/// let skills = auto_detect_skills_from_message("Create a new file with tests"); +/// assert!(skills.contains(&"file-operations")); +/// assert!(skills.contains(&"code-quality")); +/// ``` +#[allow(dead_code)] +pub fn auto_detect_skills_from_message(message: &str) -> Vec<&'static str> { + cortex_prompt_harness::prompts::get_recommended_skills(message) +} + +/// Get the list of all available built-in skills. +/// +/// # Returns +/// +/// A slice of all available skill names. +#[allow(dead_code)] +pub fn available_skills() -> &'static [&'static str] { + cortex_prompt_harness::prompts::AVAILABLE_SKILLS +} + +/// Check if a skill name is valid. +/// +/// # Arguments +/// +/// * `skill` - The skill name to check +/// +/// # Returns +/// +/// `true` if the skill exists, `false` otherwise. +#[allow(dead_code)] +pub fn is_valid_skill(skill: &str) -> bool { + cortex_prompt_harness::prompts::is_builtin_skill(skill) +} + +#[cfg(test)] +mod tests { + use super::*; + + // ========================================================================= + // Skill Injection Tests + // ========================================================================= + + #[test] + fn test_inject_skills_empty() { + let base = "Base prompt content"; + let result = inject_skills(base, &[]); + assert_eq!(result, base); + } + + #[test] + fn test_inject_skills_single() { + let base = "Base prompt"; + let result = inject_skills(base, &["git"]); + + assert!(result.starts_with("Base prompt")); + assert!(result.contains("# Loaded Skills")); + assert!(result.contains("## Skill: git")); + assert!(result.contains("Git Operations Skill")); + } + + #[test] + fn test_inject_skills_multiple() { + let base = "Base prompt"; + let result = inject_skills(base, &["git", "debugging"]); + + assert!(result.contains("## Skill: git")); + assert!(result.contains("## Skill: debugging")); + assert!(result.contains("Git Operations Skill")); + assert!(result.contains("Debugging Skill")); + } + + #[test] + fn test_inject_skills_invalid_skill_skipped() { + let base = "Base prompt"; + let result = inject_skills(base, &["git", "nonexistent-skill", "debugging"]); + + assert!(result.contains("## Skill: git")); + assert!(result.contains("## Skill: debugging")); + assert!(!result.contains("nonexistent-skill")); + } + + #[test] + fn test_inject_skills_all_invalid() { + let base = "Base prompt"; + let result = inject_skills(base, &["invalid1", "invalid2"]); + + // Should still have base prompt but no skills section + assert!(result.contains("Base prompt")); + assert!(!result.contains("# Loaded Skills")); + } + + // ========================================================================= + // Auto-Detection Tests + // ========================================================================= + + #[test] + fn test_auto_detect_git_operations() { + let skills = auto_detect_skills_from_message("Create a PR with these changes"); + assert!(skills.contains(&"git")); + + let skills = auto_detect_skills_from_message("Commit the fix"); + assert!(skills.contains(&"git")); + } + + #[test] + fn test_auto_detect_debugging() { + let skills = auto_detect_skills_from_message("Fix this bug"); + assert!(skills.contains(&"debugging")); + + let skills = auto_detect_skills_from_message("Debug the failing test"); + assert!(skills.contains(&"debugging")); + } + + #[test] + fn test_auto_detect_multiple_skills() { + let skills = auto_detect_skills_from_message("Fix the bug and create a PR"); + assert!(skills.contains(&"git")); + assert!(skills.contains(&"debugging")); + } + + #[test] + fn test_auto_detect_code_quality() { + let skills = auto_detect_skills_from_message("Run the tests and lint"); + assert!(skills.contains(&"code-quality")); + } + + #[test] + fn test_auto_detect_file_operations() { + let skills = auto_detect_skills_from_message("Create a new file"); + assert!(skills.contains(&"file-operations")); + } + + #[test] + fn test_auto_detect_security() { + let skills = auto_detect_skills_from_message("Handle the API key securely"); + assert!(skills.contains(&"security")); + } + + #[test] + fn test_auto_detect_planning() { + let skills = auto_detect_skills_from_message("Design the new architecture"); + assert!(skills.contains(&"planning")); + } + + #[test] + fn test_auto_detect_empty_for_unmatched() { + let skills = auto_detect_skills_from_message("hello"); + assert!(skills.is_empty()); + } + + // ========================================================================= + // Utility Tests + // ========================================================================= + + #[test] + fn test_available_skills() { + let skills = available_skills(); + assert!(skills.contains(&"git")); + assert!(skills.contains(&"code-quality")); + assert!(skills.contains(&"file-operations")); + assert!(skills.contains(&"debugging")); + assert!(skills.contains(&"security")); + assert!(skills.contains(&"planning")); + assert_eq!(skills.len(), 6); + } + + #[test] + fn test_is_valid_skill() { + assert!(is_valid_skill("git")); + assert!(is_valid_skill("debugging")); + assert!(!is_valid_skill("nonexistent")); + assert!(!is_valid_skill("")); + } + + #[test] + fn test_strip_yaml_frontmatter() { + let content = "---\nname: test\n---\n\n# Actual Content"; + let stripped = strip_yaml_frontmatter(content); + assert_eq!(stripped, "\n# Actual Content"); + + let no_frontmatter = "# Just Content"; + assert_eq!(strip_yaml_frontmatter(no_frontmatter), "# Just Content"); + } + + #[test] + fn test_strip_yaml_frontmatter_no_content_after() { + let content = "---\nname: test\n---\n"; + let stripped = strip_yaml_frontmatter(content); + // Should return original if nothing after frontmatter + assert_eq!(stripped, content); + } + + // ========================================================================= + // Constant Tests + // ========================================================================= + + #[test] + fn test_use_skill_based_prompt_default() { + // Verify the default value + assert!(USE_SKILL_BASED_PROMPT); + } + + #[test] + fn test_base_prompts_exist() { + assert!(!BASE_PROMPT.is_empty()); + assert!(!BASE_PROMPT_WITH_SKILLS.is_empty()); + assert!(!SYSTEM_PROMPT.is_empty()); + } + + #[test] + fn test_base_prompt_contains_skill_loading() { + assert!(BASE_PROMPT.contains("load_skill")); + } + + #[test] + fn test_base_prompt_with_skills_no_loading_instructions() { + assert!(!BASE_PROMPT_WITH_SKILLS.contains("load_skill")); + } +} diff --git a/src/cortex-engine/src/tools/handlers/skill.rs b/src/cortex-engine/src/tools/handlers/skill.rs index 92799c6..dcb81f5 100644 --- a/src/cortex-engine/src/tools/handlers/skill.rs +++ b/src/cortex-engine/src/tools/handlers/skill.rs @@ -164,6 +164,8 @@ pub enum SkillSource { Project, /// Current directory SKILL.md Local, + /// Built-in skill embedded in the agent + Builtin, } impl std::fmt::Display for SkillSource { @@ -172,6 +174,7 @@ impl std::fmt::Display for SkillSource { Self::Personal => write!(f, "personal"), Self::Project => write!(f, "project"), Self::Local => write!(f, "local"), + Self::Builtin => write!(f, "builtin"), } } } @@ -191,11 +194,12 @@ impl SkillLoader { /// Create a new skill loader. /// /// Skill search paths (in priority order): - /// 1. `./SKILL.md` (local file) - /// 2. `.agents/` (project, https://agent.md/ compatible) - /// 3. `.agent/` (project, https://agent.md/ compatible) - /// 4. `.cortex/skills/` (project, traditional format) - /// 5. `~/.cortex/skills/` (personal) + /// 1. Built-in skills (embedded in the agent) + /// 2. `./SKILL.md` (local file) + /// 3. `.agents/` (project, https://agent.md/ compatible) + /// 4. `.agent/` (project, https://agent.md/ compatible) + /// 5. `.cortex/skills/` (project, traditional format) + /// 6. `~/.cortex/skills/` (personal) pub fn new(cwd: PathBuf) -> Self { let personal_dir = dirs::home_dir() .map(|h| h.join(".cortex").join("skills")) @@ -215,9 +219,38 @@ impl SkillLoader { } } + /// Load a built-in skill by name. + /// + /// Returns `Ok(Some(skill))` if the skill is a built-in and was loaded successfully, + /// `Ok(None)` if the skill is not a built-in, or `Err` if parsing failed. + fn load_builtin(&self, name: &str) -> Result> { + use cortex_prompt_harness::prompts::builtin_skills::{get_builtin_skill, is_builtin_skill}; + + if !is_builtin_skill(name) { + return Ok(None); + } + + let content = get_builtin_skill(name) + .ok_or_else(|| CortexError::NotFound(format!("Built-in skill not found: {}", name)))?; + + let (definition, markdown) = parse_skill_md(content)?; + + Ok(Some(LoadedSkill { + definition, + content: markdown, + path: PathBuf::from(""), + source: SkillSource::Builtin, + })) + } + /// Load a skill by name. pub async fn load(&self, name: &str) -> Result { - // Search order: local SKILL.md, project skills, personal skills + // Search order: built-in, local SKILL.md, project skills, personal skills + + // 0. Check built-in skills first + if let Some(skill) = self.load_builtin(name)? { + return Ok(skill); + } // 1. Check for SKILL.md in current directory let local_skill = self.cwd.join("SKILL.md"); @@ -256,7 +289,7 @@ impl SkillLoader { .collect(); Err(CortexError::NotFound(format!( - "Skill not found: {}. Searched in:\n - {}/SKILL.md (local)\n - {} (project)\n - {} (personal)", + "Skill not found: {}. Searched in:\n - built-in skills\n - {}/SKILL.md (local)\n - {} (project)\n - {} (personal)", name, self.cwd.display(), project_paths.join(", "), @@ -325,15 +358,28 @@ impl SkillLoader { /// List all available skills. pub async fn list(&self) -> Result> { + use cortex_prompt_harness::prompts::builtin_skills::BUILTIN_SKILL_NAMES; + let mut skills = Vec::new(); let mut seen_names = std::collections::HashSet::new(); + // Load built-in skills first + for name in BUILTIN_SKILL_NAMES { + if let Ok(Some(skill)) = self.load_builtin(name) { + seen_names.insert(skill.definition.name.clone()); + skills.push(skill); + } + } + // Check local SKILL.md let local_skill = self.cwd.join("SKILL.md"); if local_skill.exists() { if let Ok(skill) = self.load_skill_file(&local_skill, SkillSource::Local).await { - seen_names.insert(skill.definition.name.clone()); - skills.push(skill); + // Skip if a built-in has the same name + if !seen_names.contains(&skill.definition.name) { + seen_names.insert(skill.definition.name.clone()); + skills.push(skill); + } } } @@ -542,7 +588,8 @@ impl SkillHandler { if skills.is_empty() { return Ok(ToolResult::success( - "No skills found. Create skills in:\n \ + "No skills found. Built-in skills should always be available.\n\ + You can also create custom skills in:\n \ - ./SKILL.md (local)\n \ - .agents//SKILL.md (project, agent.md format)\n \ - .agent//SKILL.md (project, agent.md format)\n \ @@ -835,4 +882,147 @@ Analyze {{target}} with verbose={{verbose}}. assert!(unrestricted.allows_tool("Execute")); assert!(unrestricted.allows_tool("Write")); } + + #[test] + fn test_skill_source_display() { + assert_eq!(format!("{}", SkillSource::Personal), "personal"); + assert_eq!(format!("{}", SkillSource::Project), "project"); + assert_eq!(format!("{}", SkillSource::Local), "local"); + assert_eq!(format!("{}", SkillSource::Builtin), "builtin"); + } + + #[test] + fn test_load_builtin_git_skill() { + let loader = SkillLoader::new(PathBuf::from("/tmp")); + let skill = loader.load_builtin("git").unwrap(); + + assert!(skill.is_some()); + let skill = skill.unwrap(); + assert_eq!(skill.definition.name, "git"); + assert_eq!(skill.source, SkillSource::Builtin); + assert_eq!(skill.path, PathBuf::from("")); + assert!(skill.content.contains("Git Operations Skill")); + } + + #[test] + fn test_load_builtin_code_quality_skill() { + let loader = SkillLoader::new(PathBuf::from("/tmp")); + let skill = loader.load_builtin("code-quality").unwrap(); + + assert!(skill.is_some()); + let skill = skill.unwrap(); + assert_eq!(skill.definition.name, "code-quality"); + assert_eq!(skill.source, SkillSource::Builtin); + assert!(skill.content.contains("Code Quality Skill")); + } + + #[test] + fn test_load_builtin_debugging_skill() { + let loader = SkillLoader::new(PathBuf::from("/tmp")); + let skill = loader.load_builtin("debugging").unwrap(); + + assert!(skill.is_some()); + let skill = skill.unwrap(); + assert_eq!(skill.definition.name, "debugging"); + assert_eq!(skill.source, SkillSource::Builtin); + assert!(skill.content.contains("Debugging Skill")); + } + + #[test] + fn test_load_builtin_security_skill() { + let loader = SkillLoader::new(PathBuf::from("/tmp")); + let skill = loader.load_builtin("security").unwrap(); + + assert!(skill.is_some()); + let skill = skill.unwrap(); + assert_eq!(skill.definition.name, "security"); + assert_eq!(skill.source, SkillSource::Builtin); + assert!(skill.content.contains("Security Skill")); + } + + #[test] + fn test_load_builtin_planning_skill() { + let loader = SkillLoader::new(PathBuf::from("/tmp")); + let skill = loader.load_builtin("planning").unwrap(); + + assert!(skill.is_some()); + let skill = skill.unwrap(); + assert_eq!(skill.definition.name, "planning"); + assert_eq!(skill.source, SkillSource::Builtin); + assert!(skill.content.contains("Planning Skill")); + } + + #[test] + fn test_load_builtin_file_operations_skill() { + let loader = SkillLoader::new(PathBuf::from("/tmp")); + let skill = loader.load_builtin("file-operations").unwrap(); + + assert!(skill.is_some()); + let skill = skill.unwrap(); + assert_eq!(skill.definition.name, "file-operations"); + assert_eq!(skill.source, SkillSource::Builtin); + assert!(skill.content.contains("File Operations Skill")); + } + + #[test] + fn test_load_builtin_nonexistent_returns_none() { + let loader = SkillLoader::new(PathBuf::from("/tmp")); + let skill = loader.load_builtin("nonexistent-skill").unwrap(); + assert!(skill.is_none()); + } + + #[test] + fn test_load_builtin_case_insensitive() { + let loader = SkillLoader::new(PathBuf::from("/tmp")); + + // Test various case combinations + let skill_lower = loader.load_builtin("git").unwrap(); + let skill_upper = loader.load_builtin("GIT").unwrap(); + let skill_mixed = loader.load_builtin("Git").unwrap(); + + assert!(skill_lower.is_some()); + assert!(skill_upper.is_some()); + assert!(skill_mixed.is_some()); + + // All should return the same skill + assert_eq!( + skill_lower.unwrap().definition.name, + skill_upper.unwrap().definition.name + ); + } + + #[tokio::test] + async fn test_skill_loader_load_builtin_first() { + // The loader should check built-in skills before filesystem + let loader = SkillLoader::new(PathBuf::from("/nonexistent/path")); + + // Should still be able to load built-in skills + let result = loader.load("git").await; + assert!(result.is_ok()); + + let skill = result.unwrap(); + assert_eq!(skill.definition.name, "git"); + assert_eq!(skill.source, SkillSource::Builtin); + } + + #[tokio::test] + async fn test_skill_loader_list_includes_builtins() { + let loader = SkillLoader::new(PathBuf::from("/nonexistent/path")); + + let skills = loader.list().await.unwrap(); + + // Should include all built-in skills + let builtin_names: Vec<&str> = skills + .iter() + .filter(|s| s.source == SkillSource::Builtin) + .map(|s| s.definition.name.as_str()) + .collect(); + + assert!(builtin_names.contains(&"git")); + assert!(builtin_names.contains(&"code-quality")); + assert!(builtin_names.contains(&"file-operations")); + assert!(builtin_names.contains(&"debugging")); + assert!(builtin_names.contains(&"security")); + assert!(builtin_names.contains(&"planning")); + } } diff --git a/src/cortex-prompt-harness/src/prompts/base_agent.rs b/src/cortex-prompt-harness/src/prompts/base_agent.rs new file mode 100644 index 0000000..3e7c072 --- /dev/null +++ b/src/cortex-prompt-harness/src/prompts/base_agent.rs @@ -0,0 +1,647 @@ +//! Minimal base agent prompt with dynamic skill loading. +//! +//! This prompt provides the core agent identity and skill loading mechanism. +//! Detailed instructions for specific operations are loaded dynamically via skills. +//! +//! # Philosophy +//! +//! The base agent prompt is intentionally minimal to: +//! - Reduce token usage when full instructions aren't needed +//! - Enable dynamic loading of only relevant capabilities +//! - Keep the agent focused on the core task +//! +//! # Usage +//! +//! ```rust +//! use cortex_prompt_harness::prompts::base_agent::{ +//! CORTEX_BASE_PROMPT, +//! get_recommended_skills, +//! format_skill_loading_prompt, +//! }; +//! +//! // Get recommended skills for a task +//! let skills = get_recommended_skills("Create a PR with these changes"); +//! // skills = ["git"] +//! +//! // Format the skill loading call +//! let loading_call = format_skill_loading_prompt(&skills); +//! // loading_call = "load_skill([\"git\"])" +//! ``` + +/// The minimal base prompt for Cortex agent with skill loading. +/// +/// This prompt provides: +/// - Core agent identity +/// - Autonomy level and execution mode +/// - Skill loading mechanism +/// - Essential rules that always apply +/// - Output format expectations +/// +/// Detailed instructions for specific operations (git, debugging, code quality, etc.) +/// are loaded dynamically via the skill system. +pub const CORTEX_BASE_PROMPT: &str = r#"# CORTEX + +You are **Cortex**, an autonomous software engineering intelligence. + +## Operating Mode + +``` +AUTONOMY LEVEL: FULL +INTERACTION MODE: ASYNC +VERIFICATION: MANDATORY +``` + +- Execute tasks completely without human intervention +- Verify all changes before reporting completion +- Match exactly what was requested + +## Skill System + +At the start of each task, analyze what capabilities you need and load relevant skills: + +``` +load_skill([skill1, skill2, ...]) +``` + +### Available Skills + +| Skill | Description | When to Load | +|-------|-------------|--------------| +| `git` | Version control operations | Git commits, PRs, branches, merges | +| `code-quality` | Code standards and testing | Writing/reviewing code, linting, tests | +| `file-operations` | File handling best practices | Creating/editing/moving files | +| `debugging` | Failure protocol and error handling | Encountering errors, troubleshooting | +| `security` | Security rules and secrets handling | Handling sensitive data, auth, keys | +| `planning` | Task decomposition and cognitive phases | Complex multi-step tasks | + +### Skill Loading Examples + +- "Create a PR" → `load_skill(["git"])` +- "Fix this bug" → `load_skill(["debugging", "code-quality"])` +- "Add new feature" → `load_skill(["planning", "code-quality", "file-operations"])` +- "Review code security" → `load_skill(["security", "code-quality"])` +- "Refactor this module" → `load_skill(["code-quality", "file-operations"])` + +## Essential Rules + +1. **Complete entirely** - Finish the task before stopping +2. **Verify all changes** - Ensure code compiles and works as expected +3. **Never break the codebase** - Rollback if something goes wrong +4. **Stay focused** - Do exactly what was requested, no extras +5. **Research before asking** - Investigate until you understand + +## Output + +When done, provide a brief summary (1-4 sentences) of what was accomplished. +Any caveats or follow-up items if relevant. No excessive detail. +"#; + +/// Alternative prompt for when skills are pre-loaded. +/// +/// This prompt assumes skills have already been injected into the context, +/// so it omits the skill loading instructions. +pub const CORTEX_BASE_PROMPT_WITH_SKILLS_PRELOADED: &str = r#"# CORTEX + +You are **Cortex**, an autonomous software engineering intelligence. + +## Operating Mode + +``` +AUTONOMY LEVEL: FULL +INTERACTION MODE: ASYNC +VERIFICATION: MANDATORY +``` + +- Execute tasks completely without human intervention +- Verify all changes before reporting completion +- Match exactly what was requested + +## Essential Rules + +1. **Complete entirely** - Finish the task before stopping +2. **Verify all changes** - Ensure code compiles and works as expected +3. **Never break the codebase** - Rollback if something goes wrong +4. **Stay focused** - Do exactly what was requested, no extras +5. **Research before asking** - Investigate until you understand + +## Output + +When done, provide a brief summary (1-4 sentences) of what was accomplished. +Any caveats or follow-up items if relevant. No excessive detail. +"#; + +/// All available built-in skills. +pub const AVAILABLE_SKILLS: &[&str] = &[ + "git", + "code-quality", + "file-operations", + "debugging", + "security", + "planning", +]; + +/// Skill metadata for display and recommendation. +#[derive(Debug, Clone)] +pub struct SkillInfo { + /// Skill identifier. + pub name: &'static str, + /// Brief description of the skill. + pub description: &'static str, + /// Keywords that trigger this skill recommendation. + pub keywords: &'static [&'static str], +} + +/// Metadata for all available skills. +pub const SKILL_METADATA: &[SkillInfo] = &[ + SkillInfo { + name: "git", + description: "Version control operations", + keywords: &[ + "git", + "commit", + "push", + "pull", + "merge", + "branch", + "pr", + "pull request", + "rebase", + "cherry-pick", + "checkout", + "stash", + "diff", + "log", + "blame", + ], + }, + SkillInfo { + name: "code-quality", + description: "Code standards and testing", + keywords: &[ + "lint", + "test", + "format", + "style", + "convention", + "review", + "refactor", + "clean", + "quality", + "coverage", + "eslint", + "pylint", + "clippy", + "prettier", + "jest", + "pytest", + "cargo test", + ], + }, + SkillInfo { + name: "file-operations", + description: "File handling best practices", + keywords: &[ + "create", + "file", + "write", + "edit", + "move", + "rename", + "delete", + "copy", + "directory", + "folder", + "path", + "backup", + ], + }, + SkillInfo { + name: "debugging", + description: "Failure protocol and error handling", + keywords: &[ + "debug", + "error", + "fix", + "bug", + "crash", + "exception", + "trace", + "stack", + "breakpoint", + "investigate", + "troubleshoot", + "diagnose", + "failing", + "broken", + ], + }, + SkillInfo { + name: "security", + description: "Security rules and secrets handling", + keywords: &[ + "security", + "secret", + "key", + "token", + "password", + "credential", + "auth", + "authentication", + "authorization", + "encrypt", + "hash", + "vulnerability", + "audit", + "sensitive", + "env", + "environment variable", + ], + }, + SkillInfo { + name: "planning", + description: "Task decomposition and cognitive phases", + keywords: &[ + "plan", + "design", + "architect", + "complex", + "multi-step", + "breakdown", + "decompose", + "strategy", + "roadmap", + "milestone", + "phase", + "implement feature", + ], + }, +]; + +/// Get recommended skills based on task keywords. +/// +/// This function analyzes the task description and returns a list of +/// recommended skills based on keyword matching. +/// +/// # Arguments +/// +/// * `task` - The task description to analyze +/// +/// # Returns +/// +/// A vector of skill names that are relevant to the task. +/// +/// # Examples +/// +/// ```rust +/// use cortex_prompt_harness::prompts::base_agent::get_recommended_skills; +/// +/// let skills = get_recommended_skills("Create a PR with bug fixes"); +/// assert!(skills.contains(&"git")); +/// assert!(skills.contains(&"debugging")); +/// +/// let skills = get_recommended_skills("Design and implement a new feature with tests"); +/// assert!(skills.contains(&"code-quality")); +/// assert!(skills.contains(&"planning")); +/// ``` +#[must_use] +pub fn get_recommended_skills(task: &str) -> Vec<&'static str> { + let task_lower = task.to_lowercase(); + let mut recommended: Vec<&'static str> = Vec::new(); + + for skill in SKILL_METADATA { + for keyword in skill.keywords { + if task_lower.contains(keyword) { + if !recommended.contains(&skill.name) { + recommended.push(skill.name); + } + break; + } + } + } + + // Default to planning for complex-sounding tasks with no specific matches + if recommended.is_empty() && task.len() > 100 { + recommended.push("planning"); + } + + recommended +} + +/// Format a skill loading prompt call. +/// +/// # Arguments +/// +/// * `skills` - The list of skills to load +/// +/// # Returns +/// +/// A formatted string representing the skill loading call. +/// +/// # Examples +/// +/// ```rust +/// use cortex_prompt_harness::prompts::base_agent::format_skill_loading_prompt; +/// +/// let call = format_skill_loading_prompt(&["git"]); +/// assert_eq!(call, "load_skill([\"git\"])"); +/// +/// let call = format_skill_loading_prompt(&["debugging", "code-quality"]); +/// assert_eq!(call, "load_skill([\"debugging\", \"code-quality\"])"); +/// ``` +#[must_use] +pub fn format_skill_loading_prompt(skills: &[&str]) -> String { + if skills.is_empty() { + return String::from("load_skill([])"); + } + + let quoted: Vec = skills.iter().map(|s| format!("\"{}\"", s)).collect(); + format!("load_skill([{}])", quoted.join(", ")) +} + +/// Check if a skill name is valid. +/// +/// # Arguments +/// +/// * `skill` - The skill name to validate +/// +/// # Returns +/// +/// `true` if the skill is a valid built-in skill, `false` otherwise. +#[must_use] +pub fn is_valid_skill(skill: &str) -> bool { + AVAILABLE_SKILLS.contains(&skill) +} + +/// Get the description for a skill. +/// +/// # Arguments +/// +/// * `skill` - The skill name +/// +/// # Returns +/// +/// The skill description if found, `None` otherwise. +#[must_use] +pub fn get_skill_description(skill: &str) -> Option<&'static str> { + SKILL_METADATA + .iter() + .find(|s| s.name == skill) + .map(|s| s.description) +} + +#[cfg(test)] +mod tests { + use super::*; + + // ========================================================================= + // Prompt Content Tests + // ========================================================================= + + #[test] + fn test_base_prompt_contains_essential_sections() { + assert!(CORTEX_BASE_PROMPT.contains("# CORTEX")); + assert!(CORTEX_BASE_PROMPT.contains("Operating Mode")); + assert!(CORTEX_BASE_PROMPT.contains("Skill System")); + assert!(CORTEX_BASE_PROMPT.contains("Available Skills")); + assert!(CORTEX_BASE_PROMPT.contains("Essential Rules")); + assert!(CORTEX_BASE_PROMPT.contains("Output")); + } + + #[test] + fn test_base_prompt_contains_all_skills() { + for skill in AVAILABLE_SKILLS { + assert!( + CORTEX_BASE_PROMPT.contains(&format!("`{}`", skill)), + "Base prompt should contain skill: {}", + skill + ); + } + } + + #[test] + fn test_base_prompt_contains_load_skill_syntax() { + assert!(CORTEX_BASE_PROMPT.contains("load_skill([")); + } + + #[test] + fn test_base_prompt_contains_autonomy_level() { + assert!(CORTEX_BASE_PROMPT.contains("AUTONOMY LEVEL: FULL")); + } + + #[test] + fn test_preloaded_prompt_no_skill_instructions() { + assert!(!CORTEX_BASE_PROMPT_WITH_SKILLS_PRELOADED.contains("load_skill")); + assert!(!CORTEX_BASE_PROMPT_WITH_SKILLS_PRELOADED.contains("Available Skills")); + } + + #[test] + fn test_preloaded_prompt_contains_essentials() { + assert!(CORTEX_BASE_PROMPT_WITH_SKILLS_PRELOADED.contains("# CORTEX")); + assert!(CORTEX_BASE_PROMPT_WITH_SKILLS_PRELOADED.contains("Operating Mode")); + assert!(CORTEX_BASE_PROMPT_WITH_SKILLS_PRELOADED.contains("Essential Rules")); + assert!(CORTEX_BASE_PROMPT_WITH_SKILLS_PRELOADED.contains("Output")); + } + + // ========================================================================= + // Skill Recommendation Tests + // ========================================================================= + + #[test] + fn test_get_recommended_skills_git() { + let skills = get_recommended_skills("Create a PR with these changes"); + assert!(skills.contains(&"git")); + + let skills = get_recommended_skills("Commit the fix"); + assert!(skills.contains(&"git")); + + let skills = get_recommended_skills("merge the feature branch"); + assert!(skills.contains(&"git")); + } + + #[test] + fn test_get_recommended_skills_debugging() { + let skills = get_recommended_skills("Fix this bug"); + assert!(skills.contains(&"debugging")); + + let skills = get_recommended_skills("Debug the failing test"); + assert!(skills.contains(&"debugging")); + + let skills = get_recommended_skills("Investigate the error"); + assert!(skills.contains(&"debugging")); + } + + #[test] + fn test_get_recommended_skills_code_quality() { + let skills = get_recommended_skills("Run the tests"); + assert!(skills.contains(&"code-quality")); + + let skills = get_recommended_skills("lint the code"); + assert!(skills.contains(&"code-quality")); + + let skills = get_recommended_skills("Refactor this function"); + assert!(skills.contains(&"code-quality")); + } + + #[test] + fn test_get_recommended_skills_file_operations() { + let skills = get_recommended_skills("Create a new file"); + assert!(skills.contains(&"file-operations")); + + let skills = get_recommended_skills("Move the config to a different directory"); + assert!(skills.contains(&"file-operations")); + } + + #[test] + fn test_get_recommended_skills_security() { + let skills = get_recommended_skills("Handle the API key securely"); + assert!(skills.contains(&"security")); + + let skills = get_recommended_skills("Add authentication"); + assert!(skills.contains(&"security")); + } + + #[test] + fn test_get_recommended_skills_planning() { + let skills = get_recommended_skills("Design the new architecture"); + assert!(skills.contains(&"planning")); + + let skills = get_recommended_skills("implement feature with multiple components"); + assert!(skills.contains(&"planning")); + } + + #[test] + fn test_get_recommended_skills_multiple() { + let skills = get_recommended_skills("Fix the bug and create a PR"); + assert!(skills.contains(&"debugging")); + assert!(skills.contains(&"git")); + + let skills = get_recommended_skills("Create a new file with tests"); + assert!(skills.contains(&"file-operations")); + assert!(skills.contains(&"code-quality")); + } + + #[test] + fn test_get_recommended_skills_case_insensitive() { + let skills = get_recommended_skills("CREATE A PR"); + assert!(skills.contains(&"git")); + + let skills = get_recommended_skills("FIX THE BUG"); + assert!(skills.contains(&"debugging")); + } + + #[test] + fn test_get_recommended_skills_empty_for_unmatched() { + let skills = get_recommended_skills("hello"); + assert!(skills.is_empty()); + } + + #[test] + fn test_get_recommended_skills_planning_for_long_task() { + // Tasks over 100 characters with no matches should get planning + let long_task = "This is a very long task description that doesn't contain any specific keywords but is clearly complex enough to require some form of organized approach to complete successfully"; + let skills = get_recommended_skills(long_task); + assert!(skills.contains(&"planning")); + } + + // ========================================================================= + // Format Skill Loading Tests + // ========================================================================= + + #[test] + fn test_format_skill_loading_prompt_single() { + let result = format_skill_loading_prompt(&["git"]); + assert_eq!(result, "load_skill([\"git\"])"); + } + + #[test] + fn test_format_skill_loading_prompt_multiple() { + let result = format_skill_loading_prompt(&["debugging", "code-quality"]); + assert_eq!(result, "load_skill([\"debugging\", \"code-quality\"])"); + } + + #[test] + fn test_format_skill_loading_prompt_empty() { + let result = format_skill_loading_prompt(&[]); + assert_eq!(result, "load_skill([])"); + } + + #[test] + fn test_format_skill_loading_prompt_all_skills() { + let result = format_skill_loading_prompt(AVAILABLE_SKILLS); + assert!(result.starts_with("load_skill([")); + assert!(result.ends_with("])")); + for skill in AVAILABLE_SKILLS { + assert!(result.contains(&format!("\"{}\"", skill))); + } + } + + // ========================================================================= + // Validation Tests + // ========================================================================= + + #[test] + fn test_is_valid_skill() { + assert!(is_valid_skill("git")); + assert!(is_valid_skill("code-quality")); + assert!(is_valid_skill("file-operations")); + assert!(is_valid_skill("debugging")); + assert!(is_valid_skill("security")); + assert!(is_valid_skill("planning")); + } + + #[test] + fn test_is_valid_skill_invalid() { + assert!(!is_valid_skill("invalid-skill")); + assert!(!is_valid_skill("")); + assert!(!is_valid_skill("GIT")); // case-sensitive + } + + #[test] + fn test_get_skill_description() { + assert_eq!( + get_skill_description("git"), + Some("Version control operations") + ); + assert_eq!( + get_skill_description("debugging"), + Some("Failure protocol and error handling") + ); + } + + #[test] + fn test_get_skill_description_invalid() { + assert_eq!(get_skill_description("invalid"), None); + } + + // ========================================================================= + // Skill Metadata Tests + // ========================================================================= + + #[test] + fn test_available_skills_count() { + assert_eq!(AVAILABLE_SKILLS.len(), 6); + } + + #[test] + fn test_skill_metadata_matches_available() { + assert_eq!(SKILL_METADATA.len(), AVAILABLE_SKILLS.len()); + for skill in AVAILABLE_SKILLS { + assert!( + SKILL_METADATA.iter().any(|s| s.name == *skill), + "Skill metadata missing for: {}", + skill + ); + } + } + + #[test] + fn test_skill_metadata_has_keywords() { + for skill in SKILL_METADATA { + assert!( + !skill.keywords.is_empty(), + "Skill {} has no keywords", + skill.name + ); + } + } +} diff --git a/src/cortex-prompt-harness/src/prompts/builtin_skills.rs b/src/cortex-prompt-harness/src/prompts/builtin_skills.rs new file mode 100644 index 0000000..723f4b3 --- /dev/null +++ b/src/cortex-prompt-harness/src/prompts/builtin_skills.rs @@ -0,0 +1,1643 @@ +//! Built-in skills for Cortex agent. +//! +//! These skills are embedded in the agent and loaded on-demand when the agent +//! identifies they are relevant to the current task. This reduces context window +//! usage by only including instructions that are actually needed. +//! +//! # Usage +//! +//! ```rust +//! use cortex_prompt_harness::prompts::builtin_skills::{get_builtin_skill, list_builtin_skills}; +//! +//! // Get a specific skill by name +//! if let Some(skill) = get_builtin_skill("git") { +//! println!("Git skill: {}", skill); +//! } +//! +//! // List all available skills +//! for (name, description) in list_builtin_skills() { +//! println!("{}: {}", name, description); +//! } +//! ``` +//! +//! # Skill Format +//! +//! Each skill follows a consistent format with YAML frontmatter containing: +//! - `name`: Unique identifier for the skill +//! - `description`: Brief description of when to use the skill +//! - `version`: Semantic version for tracking changes +//! - `tags`: Categories for organization and discovery + +/// List of all available built-in skill names. +pub const BUILTIN_SKILL_NAMES: &[&str] = &[ + "git", + "code-quality", + "file-operations", + "debugging", + "security", + "planning", +]; + +/// Git operations skill - version control best practices. +/// +/// Load this skill when performing version control operations such as +/// commits, branches, pull requests, and git history management. +pub const SKILL_GIT: &str = r#"--- +name: git +description: Git version control operations, commits, PRs, branches. Load when doing version control tasks. +version: "1.0.0" +tags: [builtin, vcs, git] +--- + +# Git Operations Skill + +## When to Use +Load this skill when performing: +- Creating commits +- Managing branches +- Creating or reviewing pull requests +- Resolving conflicts +- Working with git history + +## Git Safety Guidelines + +### Before Any Git Operation +``` +ALWAYS run 'git status' before other git commands +ALWAYS check changes with 'git diff' before committing +NEVER push without explicit user instruction +NEVER use -i flag (interactive mode not supported) +NEVER update git config without explicit request +``` + +### Commit Best Practices + +#### Message Format +``` +(): + + + +