diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 2e1d8707a..5eebcc0d6 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -2782,6 +2782,7 @@ dependencies = [ "codex-model-provider", "codex-otel", "codex-protocol", + "codex-shell-command", "codex-skills", "codex-utils-absolute-path", "codex-utils-output-truncation", diff --git a/codex-rs/core-skills/Cargo.toml b/codex-rs/core-skills/Cargo.toml index 9473fa8e4..7748540ac 100644 --- a/codex-rs/core-skills/Cargo.toml +++ b/codex-rs/core-skills/Cargo.toml @@ -23,6 +23,7 @@ codex-login = { workspace = true } codex-model-provider = { workspace = true } codex-otel = { workspace = true } codex-protocol = { workspace = true } +codex-shell-command = { workspace = true } codex-skills = { workspace = true } codex-utils-absolute-path = { workspace = true } codex-utils-output-truncation = { workspace = true } diff --git a/codex-rs/core-skills/src/invocation_utils.rs b/codex-rs/core-skills/src/invocation_utils.rs index 4c9d0a411..50864a834 100644 --- a/codex-rs/core-skills/src/invocation_utils.rs +++ b/codex-rs/core-skills/src/invocation_utils.rs @@ -3,6 +3,8 @@ use std::path::Path; use crate::SkillLoadOutcome; use crate::SkillMetadata; +use codex_protocol::parse_command::ParsedCommand; +use codex_shell_command::parse_command::parse_command_impl; use codex_utils_absolute_path::AbsolutePathBuf; pub(crate) fn build_implicit_skill_path_indexes( @@ -101,33 +103,18 @@ fn detect_skill_doc_read( tokens: &[String], workdir: &AbsolutePathBuf, ) -> Option { - if !command_reads_file(tokens) { - return None; - } - - for token in tokens.iter().skip(1) { - if token.starts_with('-') { - continue; - } - let path = Path::new(token); - let candidate_path = canonicalize_if_exists(&workdir.join(path)); - if let Some(candidate) = outcome.implicit_skills_by_doc_path.get(&candidate_path) { - return Some(candidate.clone()); + for command in parse_command_impl(tokens) { + if let ParsedCommand::Read { path, .. } = command { + let candidate_path = canonicalize_if_exists(&workdir.join(path.as_path())); + if let Some(candidate) = outcome.implicit_skills_by_doc_path.get(&candidate_path) { + return Some(candidate.clone()); + } } } None } -fn command_reads_file(tokens: &[String]) -> bool { - const READERS: [&str; 8] = ["cat", "sed", "head", "tail", "less", "more", "bat", "awk"]; - let Some(program) = tokens.first() else { - return false; - }; - let program = command_basename(program).to_ascii_lowercase(); - READERS.contains(&program.as_str()) -} - fn command_basename(command: &str) -> String { Path::new(command) .file_name() diff --git a/codex-rs/core-skills/src/invocation_utils_tests.rs b/codex-rs/core-skills/src/invocation_utils_tests.rs index f6e3883c1..30f978118 100644 --- a/codex-rs/core-skills/src/invocation_utils_tests.rs +++ b/codex-rs/core-skills/src/invocation_utils_tests.rs @@ -76,6 +76,30 @@ fn skill_doc_read_detection_matches_absolute_path() { ); } +#[test] +fn skill_doc_read_detection_matches_shared_read_parser() { + let skill_doc_path = test_path_buf("/tmp/skill-test/SKILL.md").abs(); + let normalized_skill_doc_path = canonicalize_if_exists(&skill_doc_path); + let skill = test_skill_metadata(skill_doc_path); + let outcome = SkillLoadOutcome { + implicit_skills_by_scripts_dir: Arc::new(HashMap::new()), + implicit_skills_by_doc_path: Arc::new(HashMap::from([(normalized_skill_doc_path, skill)])), + ..Default::default() + }; + + let tokens = vec![ + "nl".to_string(), + "-ba".to_string(), + test_path_display("/tmp/skill-test/SKILL.md"), + ]; + let found = detect_skill_doc_read(&outcome, &tokens, &test_path_buf("/tmp").abs()); + + assert_eq!( + found.map(|value| value.name), + Some("test-skill".to_string()) + ); +} + #[test] fn skill_script_run_detection_matches_relative_path_from_skill_root() { let skill_doc_path = test_path_buf("/tmp/skill-test/SKILL.md").abs();