From cb70daf35ceea120090215ab329ab4d25daf0327 Mon Sep 17 00:00:00 2001 From: cyq <15000851237@163.com> Date: Sun, 24 May 2026 02:20:09 +0800 Subject: [PATCH] fix: include configured and workspace skills in slash menu discovery --- crates/tui/src/tui/app.rs | 43 ++++++++++++++++++++++--- crates/tui/src/tui/command_palette.rs | 46 +++++++++++++++++++++++++-- 2 files changed, 83 insertions(+), 6 deletions(-) diff --git a/crates/tui/src/tui/app.rs b/crates/tui/src/tui/app.rs index 7e5479d2..85b018b8 100644 --- a/crates/tui/src/tui/app.rs +++ b/crates/tui/src/tui/app.rs @@ -1705,7 +1705,7 @@ impl App { let plan_state = new_shared_plan_state(); let skills_dir = resolve_skills_dir(&workspace, &global_skills_dir, config); - let cached_skills = Self::discover_cached_skills(&workspace); + let cached_skills = Self::discover_cached_skills(&workspace, &skills_dir); let input_history = crate::composer_history::load_history(); let (initial_input_text, initial_input_cursor) = match initial_input { @@ -1922,8 +1922,11 @@ impl App { } } - fn discover_cached_skills(workspace: &std::path::Path) -> Vec<(String, String)> { - crate::skills::discover_in_workspace(workspace) + fn discover_cached_skills( + workspace: &std::path::Path, + skills_dir: &std::path::Path, + ) -> Vec<(String, String)> { + crate::skills::discover_for_workspace_and_dir(workspace, skills_dir) .list() .iter() .map(|s| (s.name.clone(), s.description.clone())) @@ -1931,7 +1934,8 @@ impl App { } pub fn refresh_skill_cache(&mut self) { - self.cached_skills = Self::discover_cached_skills(&self.workspace); + let skills_dir = self.skills_dir.clone(); + self.cached_skills = Self::discover_cached_skills(&self.workspace, &skills_dir); } pub fn submit_api_key(&mut self) -> Result { @@ -5109,6 +5113,37 @@ mod tests { ); } + #[test] + fn cached_skills_include_configured_directory() { + let tmp = tempfile::TempDir::new().expect("tempdir"); + let workspace = tmp.path().join("workspace"); + + let configured_dir = tmp.path().join("configured-skills"); + let configured_skill_dir = configured_dir.join("configured-skill"); + std::fs::create_dir_all(&configured_skill_dir).expect("configured skill dir"); + std::fs::write( + configured_skill_dir.join("SKILL.md"), + "---\nname: configured-skill\ndescription: Configured skill\n---\nbody\n", + ) + .expect("write configured skill"); + + let mut options = test_options(false); + options.workspace = workspace.clone(); + options.skills_dir = configured_dir.clone(); + let mut config = Config::default(); + config.skills_dir = Some(configured_dir.to_string_lossy().into_owned()); + let app = App::new(options, &config); + + assert!( + app.cached_skills + .iter() + .any(|(name, description)| name == "configured-skill" + && description == "Configured skill"), + "configured skill dir should be merged: {:?}", + app.cached_skills + ); + } + #[test] fn paste_consolidates_oversized_text_into_paste_file_visibly() { // Visible-before-submit consolidation (paste UX): when a single diff --git a/crates/tui/src/tui/command_palette.rs b/crates/tui/src/tui/command_palette.rs index de072c81..e5de7416 100644 --- a/crates/tui/src/tui/command_palette.rs +++ b/crates/tui/src/tui/command_palette.rs @@ -15,7 +15,7 @@ use unicode_width::UnicodeWidthStr; use crate::commands; use crate::localization::Locale; use crate::palette; -use crate::skills::SkillRegistry; +use crate::skills; use crate::tools::spec::ApprovalRequirement; use crate::tools::spec::ToolCapability; use crate::tools::{ToolContext, ToolRegistryBuilder}; @@ -78,7 +78,7 @@ pub fn build_entries( }); } - let skills = SkillRegistry::discover(skills_dir); + let skills = skills::discover_for_workspace_and_dir(workspace, skills_dir); for skill in skills.list() { entries.push(CommandPaletteEntry { section: PaletteSection::Skill, @@ -798,6 +798,7 @@ impl ModalView for CommandPaletteView { mod tests { use super::*; use std::path::Path; + use tempfile::TempDir; fn palette_entry( section: PaletteSection, @@ -920,6 +921,47 @@ mod tests { assert_eq!(view.entries[view.filtered[0]].label, "skill:search"); } + #[test] + fn command_palette_skills_use_workspace_and_configured_directories() { + let tmp = TempDir::new().expect("tempdir"); + let workspace = tmp.path().join("workspace"); + let workspace_skill_dir = workspace + .join(".agents") + .join("skills") + .join("workspace-skill"); + std::fs::create_dir_all(&workspace_skill_dir).expect("create workspace skill dir"); + std::fs::write( + workspace_skill_dir.join("SKILL.md"), + "---\nname: workspace-skill\ndescription: Workspace skill\ngithub: https://example.com\n---\nbody", + ) + .expect("write workspace skill"); + + let configured_dir = tmp.path().join("configured-skills"); + let configured_skill_dir = configured_dir.join("configured-skill"); + std::fs::create_dir_all(&configured_skill_dir).expect("create configured skill dir"); + std::fs::write( + configured_skill_dir.join("SKILL.md"), + "---\nname: configured-skill\ndescription: Configured skill\n---\nbody", + ) + .expect("write configured skill"); + + let entries = build_entries( + Locale::En, + configured_dir.as_path(), + workspace.as_path(), + Path::new("mcp.json"), + None, + ); + let skill_labels = entries + .iter() + .filter(|entry| entry.section == PaletteSection::Skill) + .map(|entry| entry.label.as_str()) + .collect::>(); + + assert!(skill_labels.contains(&"skill:workspace-skill")); + assert!(skill_labels.contains(&"skill:configured-skill")); + } + #[test] fn command_palette_command_entries_include_links_and_config_but_not_removed_commands() { let entries = build_entries(