@@ -2293,10 +2293,53 @@ pub fn classify_skills_slash_command(args: Option<&str>) -> SkillSlashDispatch {
22932293 Some ( args) if args == "install" || args. starts_with ( "install " ) => {
22942294 SkillSlashDispatch :: Local
22952295 }
2296- Some ( args) => SkillSlashDispatch :: Invoke ( format ! ( "${args}" ) ) ,
2296+ Some ( args) => SkillSlashDispatch :: Invoke ( format ! ( "${}" , args . trim_start_matches ( '/' ) ) ) ,
22972297 }
22982298}
22992299
2300+ /// Resolve a skill invocation by validating the skill exists on disk before
2301+ /// returning the dispatch. When the skill is not found, returns `Err` with a
2302+ /// human-readable message that lists nearby skill names.
2303+ pub fn resolve_skill_invocation (
2304+ cwd : & Path ,
2305+ args : Option < & str > ,
2306+ ) -> Result < SkillSlashDispatch , String > {
2307+ let dispatch = classify_skills_slash_command ( args) ;
2308+ if let SkillSlashDispatch :: Invoke ( ref prompt) = dispatch {
2309+ // Extract the skill name from the "$skill [args]" prompt.
2310+ let skill_token = prompt
2311+ . trim_start_matches ( '$' )
2312+ . split_whitespace ( )
2313+ . next ( )
2314+ . unwrap_or_default ( ) ;
2315+ if !skill_token. is_empty ( ) {
2316+ if let Err ( error) = resolve_skill_path ( cwd, skill_token) {
2317+ let mut message =
2318+ format ! ( "Unknown skill: {skill_token} ({error})" ) ;
2319+ let roots = discover_skill_roots ( cwd) ;
2320+ if let Ok ( available) = load_skills_from_roots ( & roots) {
2321+ let names: Vec < String > = available
2322+ . iter ( )
2323+ . filter ( |s| s. shadowed_by . is_none ( ) )
2324+ . map ( |s| s. name . clone ( ) )
2325+ . collect ( ) ;
2326+ if !names. is_empty ( ) {
2327+ message. push_str ( & format ! (
2328+ "\n Available skills: {}" ,
2329+ names. join( ", " )
2330+ ) ) ;
2331+ }
2332+ }
2333+ message. push_str (
2334+ "\n Usage: /skills [list|install <path>|help|<skill> [args]]" ,
2335+ ) ;
2336+ return Err ( message) ;
2337+ }
2338+ }
2339+ }
2340+ Ok ( dispatch)
2341+ }
2342+
23002343pub fn resolve_skill_path ( cwd : & Path , skill : & str ) -> std:: io:: Result < PathBuf > {
23012344 let requested = skill. trim ( ) . trim_start_matches ( '/' ) . trim_start_matches ( '$' ) ;
23022345 if requested. is_empty ( ) {
@@ -4301,6 +4344,10 @@ mod tests {
43014344 classify_skills_slash_command( Some ( "help overview" ) ) ,
43024345 SkillSlashDispatch :: Invoke ( "$help overview" . to_string( ) )
43034346 ) ;
4347+ assert_eq ! (
4348+ classify_skills_slash_command( Some ( "/test" ) ) ,
4349+ SkillSlashDispatch :: Invoke ( "$test" . to_string( ) )
4350+ ) ;
43044351 assert_eq ! (
43054352 classify_skills_slash_command( Some ( "install ./skill-pack" ) ) ,
43064353 SkillSlashDispatch :: Local
0 commit comments