From 9df9ff6ad9ec7d15cdae440a810c9b806711153f Mon Sep 17 00:00:00 2001 From: ryanleeoai Date: Wed, 12 Aug 2026 17:44:14 +0000 Subject: [PATCH] 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 --- codex-rs/skills/src/invocation.rs | 39 ++++++++++++++++++++++ codex-rs/skills/src/invocation_tests.rs | 44 +++++++++++++++++++++++++ 2 files changed, 83 insertions(+) diff --git a/codex-rs/skills/src/invocation.rs b/codex-rs/skills/src/invocation.rs index b762fa24d5..dbfa9a8332 100644 --- a/codex-rs/skills/src/invocation.rs +++ b/codex-rs/skills/src/invocation.rs @@ -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 { + 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('"')?; + ( + "ed_path[..closing_quote], + "ed_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() diff --git a/codex-rs/skills/src/invocation_tests.rs b/codex-rs/skills/src/invocation_tests.rs index 0daf84b162..02109f092a 100644 --- a/codex-rs/skills/src/invocation_tests.rs +++ b/codex-rs/skills/src/invocation_tests.rs @@ -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();