From cfac6be1d006da9aedf6869d86cab94eec45a324 Mon Sep 17 00:00:00 2001 From: xingshining Date: Thu, 13 Aug 2026 14:19:03 +0800 Subject: [PATCH] feat(skills): support isolated bootstrap directories --- crates/aion-agent/src/bootstrap.rs | 14 +++++++++++- crates/aion-agent/src/bootstrap_test.rs | 29 +++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/crates/aion-agent/src/bootstrap.rs b/crates/aion-agent/src/bootstrap.rs index 5d7d3c24..0564353f 100644 --- a/crates/aion-agent/src/bootstrap.rs +++ b/crates/aion-agent/src/bootstrap.rs @@ -63,6 +63,7 @@ pub struct AgentBootstrap { config: Config, workspace: PathBuf, extra_skill_dirs: Vec, + isolate_skill_dirs: bool, // Output integration. output: Arc, @@ -104,6 +105,7 @@ impl AgentBootstrap { config, workspace: PathBuf::from(workspace.into()), extra_skill_dirs: Vec::new(), + isolate_skill_dirs: false, output, provider: None, resume_session: None, @@ -142,6 +144,16 @@ impl AgentBootstrap { self } + /// Discover skills only from the supplied directories and bundled skills. + /// + /// Each directory is interpreted as a project root containing + /// `.aionrs/skills`. + pub fn isolated_skill_dirs(mut self, dirs: Vec) -> Self { + self.extra_skill_dirs = dirs; + self.isolate_skill_dirs = true; + self + } + /// Read-only access to the config (for session management before build). pub fn config(&self) -> &Config { &self.config @@ -269,7 +281,7 @@ impl AgentBootstrap { } async fn load_skills(&self, workspace: &Path, mcp_manager: Option<&McpManager>) -> Vec { - load_all_skills(workspace, &self.extra_skill_dirs, false, mcp_manager).await + load_all_skills(workspace, &self.extra_skill_dirs, self.isolate_skill_dirs, mcp_manager).await } fn configure_system_prompt(&mut self, environment: &BootstrapEnvironment, skills: &[SkillMetadata]) -> PromptUsage { diff --git a/crates/aion-agent/src/bootstrap_test.rs b/crates/aion-agent/src/bootstrap_test.rs index d6ce0369..7aae2923 100644 --- a/crates/aion-agent/src/bootstrap_test.rs +++ b/crates/aion-agent/src/bootstrap_test.rs @@ -3,6 +3,7 @@ use super::*; #[cfg(test)] mod tests { use std::collections::HashMap; + use std::fs; use std::sync::Arc; use aion_config::config::{CliArgs, McpServerConfig, TransportType}; @@ -74,6 +75,34 @@ mod tests { .unwrap() } + fn write_skill(project_root: &std::path::Path, name: &str) { + let skill_dir = project_root.join(".aionrs").join("skills").join(name); + fs::create_dir_all(&skill_dir).unwrap(); + fs::write(skill_dir.join("SKILL.md"), "---\ndescription: test skill\n---\n").unwrap(); + } + + #[tokio::test] + async fn isolated_skill_dirs_excludes_workspace_skills() { + let workspace = tempfile::TempDir::new().unwrap(); + let isolated_root = tempfile::TempDir::new().unwrap(); + write_skill(workspace.path(), "workspace-only"); + write_skill(isolated_root.path(), "isolated-only"); + + let output: Arc = Arc::new(NullSink); + let bootstrap = AgentBootstrap::new(test_config(), workspace.path().to_string_lossy(), output) + .isolated_skill_dirs(vec![isolated_root.path().to_path_buf()]); + + let names = bootstrap + .load_skills(workspace.path(), None) + .await + .into_iter() + .map(|skill| skill.name) + .collect::>(); + + assert!(names.iter().any(|name| name == "isolated-only")); + assert!(!names.iter().any(|name| name == "workspace-only")); + } + #[test] fn mcp_servers_with_runtime_env_uses_server_env_as_override() { let mut config = test_config();