Detect implicit skill invocations from PowerShell reads (#38228)

## What changed

- Recognize `Get-Content` reads of indexed `SKILL.md` files as implicit skill invocations.
- Support the optional `-Raw` flag, quoted paths containing spaces, and Windows paths without stripping backslashes.

## Testing

- Add unit coverage for the supported `Get-Content` forms and Windows path parsing.

GitOrigin-RevId: 681f2e2fff9a597326bc94b97a31e311ef21b949
This commit is contained in:
ryanleeoai
2026-08-12 18:04:20 +00:00
committed by copyberry
parent 1ad4397821
commit 9df9ff6ad9
2 changed files with 83 additions and 0 deletions
+39
View File
@@ -35,6 +35,7 @@ pub fn detect_implicit_skill_invocation_for_command(
}
detect_skill_doc_read(outcome, tokens.as_slice(), &workdir)
.or_else(|| detect_powershell_skill_doc_read(outcome, command, &workdir))
}
/// Resolves statically recognizable skill accesses without consulting the host filesystem.
@@ -150,6 +151,44 @@ fn detect_skill_doc_read(
None
}
fn detect_powershell_skill_doc_read(
outcome: &impl ImplicitSkillLookup,
command: &str,
workdir: &AbsolutePathBuf,
) -> Option<SkillMetadata> {
let path = powershell_get_content_path(command)?;
let candidate_path = canonicalize_if_exists(&workdir.join(Path::new(path)));
outcome
.implicit_skill_for_doc_path(&candidate_path)
.cloned()
}
fn powershell_get_content_path(command: &str) -> Option<&str> {
let mut arguments = command.trim().strip_prefix("Get-Content ")?;
if let Some(remaining_arguments) = arguments.strip_prefix("-Raw ") {
arguments = remaining_arguments;
}
let (path, trailing) = if let Some(quoted_path) = arguments.strip_prefix('"') {
let closing_quote = quoted_path.find('"')?;
(
&quoted_path[..closing_quote],
&quoted_path[closing_quote + 1..],
)
} else {
let path_end = arguments
.char_indices()
.find_map(|(index, character)| character.is_whitespace().then_some(index))
.unwrap_or(arguments.len());
(&arguments[..path_end], &arguments[path_end..])
};
if path.is_empty() || path.starts_with('-') || !trailing.trim().is_empty() {
return None;
}
Some(path)
}
fn command_basename(command: &str) -> String {
Path::new(command)
.file_name()
+44
View File
@@ -64,6 +64,50 @@ fn script_run_detection_excludes_python_c() {
assert!(script_run_token(&tokens).is_none());
}
#[test]
fn powershell_skill_doc_read_matches_common_forms() {
let skill_doc_path = test_path_buf("/tmp/skill-test/SKILL.md").abs();
let spaced_skill_doc_path = test_path_buf("/tmp/skill test/SKILL.md").abs();
let skill = test_skill_metadata(skill_doc_path.clone());
let spaced_skill = test_skill_metadata(spaced_skill_doc_path.clone());
let outcome = TestLookup {
by_doc_path: HashMap::from([
(canonicalize_if_exists(&skill_doc_path), skill),
(canonicalize_if_exists(&spaced_skill_doc_path), spaced_skill),
]),
..Default::default()
};
let path = skill_doc_path.display();
let spaced_path = spaced_skill_doc_path.display();
for command in [
format!("Get-Content {path}"),
format!("Get-Content -Raw {path}"),
format!("Get-Content \"{spaced_path}\""),
format!("Get-Content -Raw \"{spaced_path}\""),
] {
let found = detect_implicit_skill_invocation_for_command(
&outcome,
&command,
&test_path_buf("/tmp").abs(),
);
assert_eq!(
found.map(|value| value.name),
Some("test-skill".to_string()),
"command: {command}"
);
}
}
#[test]
fn powershell_get_content_path_preserves_windows_backslashes() {
assert_eq!(
powershell_get_content_path(r#"Get-Content C:\skills\example\SKILL.md"#),
Some(r#"C:\skills\example\SKILL.md"#)
);
}
#[test]
fn skill_doc_read_detection_matches_absolute_path() {
let skill_doc_path = test_path_buf("/tmp/skill-test/SKILL.md").abs();