mirror of
https://github.com/instructkr/claw-code.git
synced 2026-04-07 00:24:50 +08:00
Fix slash skill invoke normalization
This commit is contained in:
@@ -2293,10 +2293,53 @@ pub fn classify_skills_slash_command(args: Option<&str>) -> SkillSlashDispatch {
|
||||
Some(args) if args == "install" || args.starts_with("install ") => {
|
||||
SkillSlashDispatch::Local
|
||||
}
|
||||
Some(args) => SkillSlashDispatch::Invoke(format!("${args}")),
|
||||
Some(args) => SkillSlashDispatch::Invoke(format!("${}", args.trim_start_matches('/'))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolve a skill invocation by validating the skill exists on disk before
|
||||
/// returning the dispatch. When the skill is not found, returns `Err` with a
|
||||
/// human-readable message that lists nearby skill names.
|
||||
pub fn resolve_skill_invocation(
|
||||
cwd: &Path,
|
||||
args: Option<&str>,
|
||||
) -> Result<SkillSlashDispatch, String> {
|
||||
let dispatch = classify_skills_slash_command(args);
|
||||
if let SkillSlashDispatch::Invoke(ref prompt) = dispatch {
|
||||
// Extract the skill name from the "$skill [args]" prompt.
|
||||
let skill_token = prompt
|
||||
.trim_start_matches('$')
|
||||
.split_whitespace()
|
||||
.next()
|
||||
.unwrap_or_default();
|
||||
if !skill_token.is_empty() {
|
||||
if let Err(error) = resolve_skill_path(cwd, skill_token) {
|
||||
let mut message =
|
||||
format!("Unknown skill: {skill_token} ({error})");
|
||||
let roots = discover_skill_roots(cwd);
|
||||
if let Ok(available) = load_skills_from_roots(&roots) {
|
||||
let names: Vec<String> = available
|
||||
.iter()
|
||||
.filter(|s| s.shadowed_by.is_none())
|
||||
.map(|s| s.name.clone())
|
||||
.collect();
|
||||
if !names.is_empty() {
|
||||
message.push_str(&format!(
|
||||
"\n Available skills: {}",
|
||||
names.join(", ")
|
||||
));
|
||||
}
|
||||
}
|
||||
message.push_str(
|
||||
"\n Usage: /skills [list|install <path>|help|<skill> [args]]",
|
||||
);
|
||||
return Err(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(dispatch)
|
||||
}
|
||||
|
||||
pub fn resolve_skill_path(cwd: &Path, skill: &str) -> std::io::Result<PathBuf> {
|
||||
let requested = skill.trim().trim_start_matches('/').trim_start_matches('$');
|
||||
if requested.is_empty() {
|
||||
@@ -4301,6 +4344,10 @@ mod tests {
|
||||
classify_skills_slash_command(Some("help overview")),
|
||||
SkillSlashDispatch::Invoke("$help overview".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
classify_skills_slash_command(Some("/test")),
|
||||
SkillSlashDispatch::Invoke("$test".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
classify_skills_slash_command(Some("install ./skill-pack")),
|
||||
SkillSlashDispatch::Local
|
||||
|
||||
@@ -7778,6 +7778,17 @@ mod tests {
|
||||
output_format: CliOutputFormat::Text,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
parse_args(&["/skills".to_string(), "/test".to_string()])
|
||||
.expect("/skills /test should normalize to a single skill prompt prefix"),
|
||||
CliAction::Prompt {
|
||||
prompt: "$test".to_string(),
|
||||
model: DEFAULT_MODEL.to_string(),
|
||||
output_format: CliOutputFormat::Text,
|
||||
allowed_tools: None,
|
||||
permission_mode: crate::default_permission_mode(),
|
||||
}
|
||||
);
|
||||
let error = parse_args(&["/status".to_string()])
|
||||
.expect_err("/status should remain REPL-only when invoked directly");
|
||||
assert!(error.contains("interactive-only"));
|
||||
|
||||
Reference in New Issue
Block a user