mirror of
https://github.com/instructkr/claw-code.git
synced 2026-04-07 16:44: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 ") => {
|
Some(args) if args == "install" || args.starts_with("install ") => {
|
||||||
SkillSlashDispatch::Local
|
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> {
|
pub fn resolve_skill_path(cwd: &Path, skill: &str) -> std::io::Result<PathBuf> {
|
||||||
let requested = skill.trim().trim_start_matches('/').trim_start_matches('$');
|
let requested = skill.trim().trim_start_matches('/').trim_start_matches('$');
|
||||||
if requested.is_empty() {
|
if requested.is_empty() {
|
||||||
@@ -4301,6 +4344,10 @@ mod tests {
|
|||||||
classify_skills_slash_command(Some("help overview")),
|
classify_skills_slash_command(Some("help overview")),
|
||||||
SkillSlashDispatch::Invoke("$help overview".to_string())
|
SkillSlashDispatch::Invoke("$help overview".to_string())
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
classify_skills_slash_command(Some("/test")),
|
||||||
|
SkillSlashDispatch::Invoke("$test".to_string())
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
classify_skills_slash_command(Some("install ./skill-pack")),
|
classify_skills_slash_command(Some("install ./skill-pack")),
|
||||||
SkillSlashDispatch::Local
|
SkillSlashDispatch::Local
|
||||||
|
|||||||
@@ -7778,6 +7778,17 @@ mod tests {
|
|||||||
output_format: CliOutputFormat::Text,
|
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()])
|
let error = parse_args(&["/status".to_string()])
|
||||||
.expect_err("/status should remain REPL-only when invoked directly");
|
.expect_err("/status should remain REPL-only when invoked directly");
|
||||||
assert!(error.contains("interactive-only"));
|
assert!(error.contains("interactive-only"));
|
||||||
|
|||||||
Reference in New Issue
Block a user