//! read_skill AI 工具声明式注册(按名读本机 Claude 技能规程正文,只读)。 //! //! 工具职责:LLM 主动调 read_skill(name) → 查进程内技能缓存(含项目级,PROJECT_SKILLS_ROOT) //! 命中后读 SKILL.md 剥 frontmatter 正文返回(含 source 来源标注);未命中返回明确错误文案 //! (LLM 可据此向用户澄清技能名,不静默空转)。 //! //! 风险:Low(纯只读,读 ~/.claude 与项目 .claude 下 SKILL.md,无写副作用)。 //! handler 直调 skills.rs 的 `read_skill_content_stripped` / `skills_cached`(注入链同一 //! 真相源,与 / 技能 SkillResolver 读到相同内容)。 use std::sync::Arc; use df_ai::ai_tools::{object_schema, AiToolRegistry, RiskLevel}; use df_ai::declare_tool; /// 注册 read_skill 工具到 `$registry`(无外部捕获,读 skills.rs 进程内缓存 + 单文件)。 pub fn register(registry: &mut AiToolRegistry) { // 无捕获:占位 Arc<()>(handler 只调 skills.rs 缓存/读文件,不持外部状态)。 let dummy: Arc<()> = Arc::new(()); // schema:name 必填(技能名,如 "review" / "swarm")。 let schema = object_schema(vec![("name", "string", true)]); declare_tool!( registry, dummy: Arc<()>, "read_skill", "按名称读取本机已安装的 Claude 技能规程(SKILL.md)完整正文,只读查阅,不执行技能。参数 name 为技能名。命中返回 {name, source, content}(source 为来源 skill/project/command/plugin,content 已剥离 frontmatter);未命中返回明确错误。当用户 / 技能调用失败、用户提到某技能名、或你判断某技能与本任务相关时调用本工具读取其完整规程内容", RiskLevel::Low, schema: schema, args => { let name = args["name"].as_str().unwrap_or("").trim().to_string(); if name.is_empty() { return Err(anyhow::anyhow!("参数 name 不能为空:请传入技能名,如 {{\"name\": \"review\"}}")); } let content = crate::commands::ai::skills::read_skill_content_stripped(name.clone()) .await .ok_or_else(|| anyhow::anyhow!( "技能 \"{name}\" 未找到(未安装/名称不匹配/user_invocable:false)。请核对技能名后重试,或请用户确认该技能是否已安装", ))?; let source = crate::commands::ai::skills::skills_cached() .await .into_iter() .find(|s| s.name == name) .map(|s| s.source) .unwrap_or_else(|| "skill".to_string()); Ok(serde_json::json!({ "name": name, "source": source, "content": content })) } ); }