mirror of
https://github.com/openai/codex.git
synced 2026-11-13 22:47:20 +00:00
Move skill policy resolution into codex-skills (#36913)
## What changed - Move `resolve_disabled_skill_paths` into `codex-skills` and expose it from the crate. - Re-export the resolver from `core-skills` to preserve existing callers. - Split config-layer and policy-resolution coverage into focused unit tests, including ordered path and name overrides. GitOrigin-RevId: 6528ff99285925132bf80eb33eafb1b62118e033
This commit is contained in:
1 parent
6a828ca26f
commit
2999b8e831
6 files changed
+298
-213
No files matched your search
@@ -1,5 +1,3 @@
|
|||||||
use std::collections::HashSet;
|
|
||||||
|
|
||||||
use codex_config::ConfigLayerSource;
|
use codex_config::ConfigLayerSource;
|
||||||
use codex_config::ConfigLayerStack;
|
use codex_config::ConfigLayerStack;
|
||||||
use codex_config::SkillConfig;
|
use codex_config::SkillConfig;
|
||||||
@@ -7,11 +5,9 @@ use codex_config::SkillsConfig;
|
|||||||
pub use codex_skills::SkillConfigRule;
|
pub use codex_skills::SkillConfigRule;
|
||||||
pub use codex_skills::SkillConfigRuleSelector;
|
pub use codex_skills::SkillConfigRuleSelector;
|
||||||
pub use codex_skills::SkillConfigRules;
|
pub use codex_skills::SkillConfigRules;
|
||||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
pub use codex_skills::resolve_disabled_skill_paths;
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
|
|
||||||
use crate::SkillMetadata;
|
|
||||||
|
|
||||||
pub fn skill_config_rules_from_stack(config_layer_stack: &ConfigLayerStack) -> SkillConfigRules {
|
pub fn skill_config_rules_from_stack(config_layer_stack: &ConfigLayerStack) -> SkillConfigRules {
|
||||||
let mut entries = Vec::new();
|
let mut entries = Vec::new();
|
||||||
for layer in config_layer_stack.all_layers_low_to_high() {
|
for layer in config_layer_stack.all_layers_low_to_high() {
|
||||||
@@ -50,40 +46,6 @@ pub fn skill_config_rules_from_stack(config_layer_stack: &ConfigLayerStack) -> S
|
|||||||
SkillConfigRules { entries }
|
SkillConfigRules { entries }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resolve_disabled_skill_paths(
|
|
||||||
skills: &[SkillMetadata],
|
|
||||||
rules: &SkillConfigRules,
|
|
||||||
) -> HashSet<AbsolutePathBuf> {
|
|
||||||
let mut disabled_paths = HashSet::new();
|
|
||||||
|
|
||||||
for entry in &rules.entries {
|
|
||||||
match &entry.selector {
|
|
||||||
SkillConfigRuleSelector::Path(path) => {
|
|
||||||
if entry.enabled {
|
|
||||||
disabled_paths.remove(path);
|
|
||||||
} else {
|
|
||||||
disabled_paths.insert(path.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SkillConfigRuleSelector::Name(name) => {
|
|
||||||
for path in skills
|
|
||||||
.iter()
|
|
||||||
.filter(|skill| skill.name == *name)
|
|
||||||
.map(|skill| skill.path_to_skills_md.clone())
|
|
||||||
{
|
|
||||||
if entry.enabled {
|
|
||||||
disabled_paths.remove(&path);
|
|
||||||
} else {
|
|
||||||
disabled_paths.insert(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
disabled_paths
|
|
||||||
}
|
|
||||||
|
|
||||||
fn skill_config_rule_selector(entry: &SkillConfig) -> Option<SkillConfigRuleSelector> {
|
fn skill_config_rule_selector(entry: &SkillConfig) -> Option<SkillConfigRuleSelector> {
|
||||||
match (entry.path.as_ref(), entry.name.as_deref()) {
|
match (entry.path.as_ref(), entry.name.as_deref()) {
|
||||||
(Some(path), None) => Some(SkillConfigRuleSelector::Path(
|
(Some(path), None) => Some(SkillConfigRuleSelector::Path(
|
||||||
@@ -108,3 +70,7 @@ fn skill_config_rule_selector(entry: &SkillConfig) -> Option<SkillConfigRuleSele
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "config_rules_tests.rs"]
|
||||||
|
mod tests;
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
use codex_config::CONFIG_TOML_FILE;
|
||||||
|
use codex_config::ConfigLayerEntry;
|
||||||
|
use codex_config::ConfigLayerSource;
|
||||||
|
use codex_config::ConfigLayerStack;
|
||||||
|
use codex_config::ConfigRequirementsToml;
|
||||||
|
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||||
|
use codex_utils_absolute_path::test_support::PathBufExt;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
use tempfile::TempDir;
|
||||||
|
|
||||||
|
use super::SkillConfigRule;
|
||||||
|
use super::SkillConfigRuleSelector;
|
||||||
|
use super::SkillConfigRules;
|
||||||
|
use super::skill_config_rules_from_stack;
|
||||||
|
|
||||||
|
fn user_layer(codex_home: &TempDir, config: &str) -> ConfigLayerEntry {
|
||||||
|
let config_path = AbsolutePathBuf::try_from(codex_home.path().join(CONFIG_TOML_FILE))
|
||||||
|
.expect("absolute config path");
|
||||||
|
ConfigLayerEntry::new(
|
||||||
|
ConfigLayerSource::User {
|
||||||
|
file: config_path,
|
||||||
|
profile: None,
|
||||||
|
},
|
||||||
|
toml::from_str(config).expect("valid user config"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stack(codex_home: &TempDir, user: &str, session: &str) -> ConfigLayerStack {
|
||||||
|
ConfigLayerStack::new(
|
||||||
|
vec![
|
||||||
|
user_layer(codex_home, user),
|
||||||
|
ConfigLayerEntry::new(
|
||||||
|
ConfigLayerSource::SessionFlags,
|
||||||
|
toml::from_str(session).expect("valid session config"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
Default::default(),
|
||||||
|
ConfigRequirementsToml::default(),
|
||||||
|
)
|
||||||
|
.expect("valid config stack")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn path_toggle_config(path: &std::path::Path, enabled: bool) -> String {
|
||||||
|
format!(
|
||||||
|
r#"[[skills.config]]
|
||||||
|
path = "{}"
|
||||||
|
enabled = {enabled}
|
||||||
|
"#,
|
||||||
|
path.display()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(windows, ignore)]
|
||||||
|
#[test]
|
||||||
|
fn session_flags_can_reenable_user_disabled_path() {
|
||||||
|
let codex_home = TempDir::new().expect("temp dir");
|
||||||
|
let skill_path = codex_home.path().join("skills/demo/SKILL.md");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
skill_config_rules_from_stack(&stack(
|
||||||
|
&codex_home,
|
||||||
|
&path_toggle_config(&skill_path, /*enabled*/ false),
|
||||||
|
&path_toggle_config(&skill_path, /*enabled*/ true),
|
||||||
|
)),
|
||||||
|
SkillConfigRules {
|
||||||
|
entries: vec![SkillConfigRule {
|
||||||
|
selector: SkillConfigRuleSelector::Path(skill_path.abs()),
|
||||||
|
enabled: true,
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(windows, ignore)]
|
||||||
|
#[test]
|
||||||
|
fn session_flags_can_disable_user_enabled_path() {
|
||||||
|
let codex_home = TempDir::new().expect("temp dir");
|
||||||
|
let skill_path = codex_home.path().join("skills/demo/SKILL.md");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
skill_config_rules_from_stack(&stack(
|
||||||
|
&codex_home,
|
||||||
|
&path_toggle_config(&skill_path, /*enabled*/ true),
|
||||||
|
&path_toggle_config(&skill_path, /*enabled*/ false),
|
||||||
|
)),
|
||||||
|
SkillConfigRules {
|
||||||
|
entries: vec![SkillConfigRule {
|
||||||
|
selector: SkillConfigRuleSelector::Path(skill_path.abs()),
|
||||||
|
enabled: false,
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn preserves_name_selectors() {
|
||||||
|
let codex_home = TempDir::new().expect("temp dir");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
skill_config_rules_from_stack(&stack(
|
||||||
|
&codex_home,
|
||||||
|
r#"
|
||||||
|
[[skills.config]]
|
||||||
|
name = "github:yeet"
|
||||||
|
enabled = false
|
||||||
|
"#,
|
||||||
|
"",
|
||||||
|
)),
|
||||||
|
SkillConfigRules {
|
||||||
|
entries: vec![SkillConfigRule {
|
||||||
|
selector: SkillConfigRuleSelector::Name("github:yeet".to_string()),
|
||||||
|
enabled: false,
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(windows, ignore)]
|
||||||
|
#[test]
|
||||||
|
fn preserves_order_across_path_and_name_selectors() {
|
||||||
|
let codex_home = TempDir::new().expect("temp dir");
|
||||||
|
let skill_path = codex_home.path().join("skills/demo/SKILL.md");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
skill_config_rules_from_stack(&stack(
|
||||||
|
&codex_home,
|
||||||
|
&path_toggle_config(&skill_path, /*enabled*/ false),
|
||||||
|
r#"
|
||||||
|
[[skills.config]]
|
||||||
|
name = "github:yeet"
|
||||||
|
enabled = true
|
||||||
|
"#,
|
||||||
|
)),
|
||||||
|
SkillConfigRules {
|
||||||
|
entries: vec![
|
||||||
|
SkillConfigRule {
|
||||||
|
selector: SkillConfigRuleSelector::Path(skill_path.abs()),
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
SkillConfigRule {
|
||||||
|
selector: SkillConfigRuleSelector::Name("github:yeet".to_string()),
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,7 +1,4 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::SkillMetadata;
|
|
||||||
use crate::config_rules::resolve_disabled_skill_paths;
|
|
||||||
use crate::config_rules::skill_config_rules_from_stack;
|
|
||||||
use codex_config::CONFIG_TOML_FILE;
|
use codex_config::CONFIG_TOML_FILE;
|
||||||
use codex_config::ConfigLayerEntry;
|
use codex_config::ConfigLayerEntry;
|
||||||
use codex_config::ConfigLayerSource;
|
use codex_config::ConfigLayerSource;
|
||||||
@@ -81,36 +78,6 @@ fn plugin_skill_root_for_skill_path(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_skill(name: &str, path: PathBuf) -> SkillMetadata {
|
|
||||||
SkillMetadata {
|
|
||||||
name: name.to_string(),
|
|
||||||
description: "test".to_string(),
|
|
||||||
short_description: None,
|
|
||||||
interface: None,
|
|
||||||
dependencies: None,
|
|
||||||
policy: None,
|
|
||||||
path_to_skills_md: path
|
|
||||||
.abs()
|
|
||||||
.canonicalize()
|
|
||||||
.expect("skill path should canonicalize"),
|
|
||||||
scope: SkillScope::User,
|
|
||||||
plugin_id: None,
|
|
||||||
remote_plugin_id: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_demo_skill(tempdir: &TempDir) -> PathBuf {
|
|
||||||
let skill_path = tempdir.path().join("skills").join("demo").join("SKILL.md");
|
|
||||||
fs::create_dir_all(skill_path.parent().expect("skill path should have parent"))
|
|
||||||
.expect("create skill dir");
|
|
||||||
fs::write(
|
|
||||||
&skill_path,
|
|
||||||
"---\nname: demo-skill\ndescription: demo description\n---\n\n# Body\n",
|
|
||||||
)
|
|
||||||
.expect("write skill");
|
|
||||||
skill_path
|
|
||||||
}
|
|
||||||
|
|
||||||
fn user_config_layer(codex_home: &TempDir, config_toml: &str) -> ConfigLayerEntry {
|
fn user_config_layer(codex_home: &TempDir, config_toml: &str) -> ConfigLayerEntry {
|
||||||
let config_path = AbsolutePathBuf::try_from(codex_home.path().join(CONFIG_TOML_FILE))
|
let config_path = AbsolutePathBuf::try_from(codex_home.path().join(CONFIG_TOML_FILE))
|
||||||
.expect("user config path should be absolute");
|
.expect("user config path should be absolute");
|
||||||
@@ -686,147 +653,6 @@ async fn skills_for_cwd_uses_cached_result_until_force_reload() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(windows, ignore)]
|
|
||||||
#[test]
|
|
||||||
fn disabled_paths_for_skills_allows_session_flags_to_override_user_layer() {
|
|
||||||
let tempdir = tempfile::tempdir().expect("tempdir");
|
|
||||||
let skill_path = write_demo_skill(&tempdir);
|
|
||||||
let skill = test_skill("demo-skill", skill_path.clone());
|
|
||||||
let user_file = AbsolutePathBuf::try_from(tempdir.path().join("config.toml"))
|
|
||||||
.expect("user config path should be absolute");
|
|
||||||
let user_layer = ConfigLayerEntry::new(
|
|
||||||
ConfigLayerSource::User {
|
|
||||||
file: user_file,
|
|
||||||
profile: None,
|
|
||||||
},
|
|
||||||
toml::from_str(&path_toggle_config(&skill_path, /*enabled*/ false))
|
|
||||||
.expect("user layer toml"),
|
|
||||||
);
|
|
||||||
let session_layer = ConfigLayerEntry::new(
|
|
||||||
ConfigLayerSource::SessionFlags,
|
|
||||||
toml::from_str(&path_toggle_config(&skill_path, /*enabled*/ true))
|
|
||||||
.expect("session layer toml"),
|
|
||||||
);
|
|
||||||
let stack = ConfigLayerStack::new(
|
|
||||||
vec![user_layer, session_layer],
|
|
||||||
Default::default(),
|
|
||||||
ConfigRequirementsToml::default(),
|
|
||||||
)
|
|
||||||
.expect("valid config layer stack");
|
|
||||||
|
|
||||||
let skill_config_rules = skill_config_rules_from_stack(&stack);
|
|
||||||
assert_eq!(
|
|
||||||
resolve_disabled_skill_paths(&[skill], &skill_config_rules),
|
|
||||||
HashSet::new()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(windows, ignore)]
|
|
||||||
#[test]
|
|
||||||
fn disabled_paths_for_skills_allows_session_flags_to_disable_user_enabled_skill() {
|
|
||||||
let tempdir = tempfile::tempdir().expect("tempdir");
|
|
||||||
let skill_path = write_demo_skill(&tempdir);
|
|
||||||
let skill = test_skill("demo-skill", skill_path.clone());
|
|
||||||
let user_file = AbsolutePathBuf::try_from(tempdir.path().join("config.toml"))
|
|
||||||
.expect("user config path should be absolute");
|
|
||||||
let user_layer = ConfigLayerEntry::new(
|
|
||||||
ConfigLayerSource::User {
|
|
||||||
file: user_file,
|
|
||||||
profile: None,
|
|
||||||
},
|
|
||||||
toml::from_str(&path_toggle_config(&skill_path, /*enabled*/ true))
|
|
||||||
.expect("user layer toml"),
|
|
||||||
);
|
|
||||||
let session_layer = ConfigLayerEntry::new(
|
|
||||||
ConfigLayerSource::SessionFlags,
|
|
||||||
toml::from_str(&path_toggle_config(&skill_path, /*enabled*/ false))
|
|
||||||
.expect("session layer toml"),
|
|
||||||
);
|
|
||||||
let stack = ConfigLayerStack::new(
|
|
||||||
vec![user_layer, session_layer],
|
|
||||||
Default::default(),
|
|
||||||
ConfigRequirementsToml::default(),
|
|
||||||
)
|
|
||||||
.expect("valid config layer stack");
|
|
||||||
|
|
||||||
let skill_config_rules = skill_config_rules_from_stack(&stack);
|
|
||||||
assert_eq!(
|
|
||||||
resolve_disabled_skill_paths(&[skill], &skill_config_rules),
|
|
||||||
HashSet::from([skill_path
|
|
||||||
.abs()
|
|
||||||
.canonicalize()
|
|
||||||
.expect("skill path should canonicalize")])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(windows, ignore)]
|
|
||||||
#[test]
|
|
||||||
fn disabled_paths_for_skills_disables_matching_name_selectors() {
|
|
||||||
let tempdir = tempfile::tempdir().expect("tempdir");
|
|
||||||
let skill_path = write_demo_skill(&tempdir);
|
|
||||||
let skill = test_skill("github:yeet", skill_path.clone());
|
|
||||||
let user_file = AbsolutePathBuf::try_from(tempdir.path().join("config.toml"))
|
|
||||||
.expect("user config path should be absolute");
|
|
||||||
let user_layer = ConfigLayerEntry::new(
|
|
||||||
ConfigLayerSource::User {
|
|
||||||
file: user_file,
|
|
||||||
profile: None,
|
|
||||||
},
|
|
||||||
toml::from_str(&name_toggle_config("github:yeet", /*enabled*/ false))
|
|
||||||
.expect("user layer toml"),
|
|
||||||
);
|
|
||||||
let stack = ConfigLayerStack::new(
|
|
||||||
vec![user_layer],
|
|
||||||
Default::default(),
|
|
||||||
ConfigRequirementsToml::default(),
|
|
||||||
)
|
|
||||||
.expect("valid config layer stack");
|
|
||||||
|
|
||||||
let skill_config_rules = skill_config_rules_from_stack(&stack);
|
|
||||||
assert_eq!(
|
|
||||||
resolve_disabled_skill_paths(&[skill], &skill_config_rules),
|
|
||||||
HashSet::from([skill_path
|
|
||||||
.abs()
|
|
||||||
.canonicalize()
|
|
||||||
.expect("skill path should canonicalize")])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(windows, ignore)]
|
|
||||||
#[test]
|
|
||||||
fn disabled_paths_for_skills_allows_name_selector_to_override_path_selector() {
|
|
||||||
let tempdir = tempfile::tempdir().expect("tempdir");
|
|
||||||
let skill_path = write_demo_skill(&tempdir);
|
|
||||||
let skill = test_skill("github:yeet", skill_path.clone());
|
|
||||||
let user_file = AbsolutePathBuf::try_from(tempdir.path().join("config.toml"))
|
|
||||||
.expect("user config path should be absolute");
|
|
||||||
let user_layer = ConfigLayerEntry::new(
|
|
||||||
ConfigLayerSource::User {
|
|
||||||
file: user_file,
|
|
||||||
profile: None,
|
|
||||||
},
|
|
||||||
toml::from_str(&path_toggle_config(&skill_path, /*enabled*/ false))
|
|
||||||
.expect("user layer toml"),
|
|
||||||
);
|
|
||||||
let session_layer = ConfigLayerEntry::new(
|
|
||||||
ConfigLayerSource::SessionFlags,
|
|
||||||
toml::from_str(&name_toggle_config("github:yeet", /*enabled*/ true))
|
|
||||||
.expect("session layer toml"),
|
|
||||||
);
|
|
||||||
let stack = ConfigLayerStack::new(
|
|
||||||
vec![user_layer, session_layer],
|
|
||||||
Default::default(),
|
|
||||||
ConfigRequirementsToml::default(),
|
|
||||||
)
|
|
||||||
.expect("valid config layer stack");
|
|
||||||
|
|
||||||
let skill_config_rules = skill_config_rules_from_stack(&stack);
|
|
||||||
assert_eq!(
|
|
||||||
resolve_disabled_skill_paths(&[skill], &skill_config_rules),
|
|
||||||
HashSet::new()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(windows, ignore)]
|
#[cfg_attr(windows, ignore)]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn skills_for_config_ignores_cwd_cache_when_session_flags_reenable_skill() {
|
async fn skills_for_config_ignores_cwd_cache_when_session_flags_reenable_skill() {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
mod interface;
|
mod interface;
|
||||||
mod model;
|
mod model;
|
||||||
mod parser;
|
mod parser;
|
||||||
|
mod policy;
|
||||||
|
|
||||||
pub use interface::SkillInterfaceAssetPolicy;
|
pub use interface::SkillInterfaceAssetPolicy;
|
||||||
pub use interface::SkillInterfaceFile;
|
pub use interface::SkillInterfaceFile;
|
||||||
@@ -17,6 +18,7 @@ pub use model::SkillToolDependency;
|
|||||||
pub use parser::ParsedSkillFrontmatter;
|
pub use parser::ParsedSkillFrontmatter;
|
||||||
pub use parser::SkillParseError;
|
pub use parser::SkillParseError;
|
||||||
pub use parser::parse_skill_frontmatter_metadata;
|
pub use parser::parse_skill_frontmatter_metadata;
|
||||||
|
pub use policy::resolve_disabled_skill_paths;
|
||||||
|
|
||||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||||
use include_dir::Dir;
|
use include_dir::Dir;
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||||
|
|
||||||
|
use crate::SkillConfigRuleSelector;
|
||||||
|
use crate::SkillConfigRules;
|
||||||
|
use crate::SkillMetadata;
|
||||||
|
|
||||||
|
/// Resolves disabled `SKILL.md` paths by applying configuration rules in order.
|
||||||
|
///
|
||||||
|
/// Later rules override earlier rules for every skill path they select.
|
||||||
|
pub fn resolve_disabled_skill_paths(
|
||||||
|
skills: &[SkillMetadata],
|
||||||
|
rules: &SkillConfigRules,
|
||||||
|
) -> HashSet<AbsolutePathBuf> {
|
||||||
|
let mut disabled_paths = HashSet::new();
|
||||||
|
|
||||||
|
for entry in &rules.entries {
|
||||||
|
match &entry.selector {
|
||||||
|
SkillConfigRuleSelector::Path(path) => {
|
||||||
|
if entry.enabled {
|
||||||
|
disabled_paths.remove(path);
|
||||||
|
} else {
|
||||||
|
disabled_paths.insert(path.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SkillConfigRuleSelector::Name(name) => {
|
||||||
|
for path in skills
|
||||||
|
.iter()
|
||||||
|
.filter(|skill| skill.name == *name)
|
||||||
|
.map(|skill| skill.path_to_skills_md.clone())
|
||||||
|
{
|
||||||
|
if entry.enabled {
|
||||||
|
disabled_paths.remove(&path);
|
||||||
|
} else {
|
||||||
|
disabled_paths.insert(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
disabled_paths
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "policy_tests.rs"]
|
||||||
|
mod tests;
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
use codex_protocol::protocol::SkillScope;
|
||||||
|
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
use super::resolve_disabled_skill_paths;
|
||||||
|
use crate::SkillConfigRule;
|
||||||
|
use crate::SkillConfigRuleSelector;
|
||||||
|
use crate::SkillConfigRules;
|
||||||
|
use crate::SkillMetadata;
|
||||||
|
|
||||||
|
fn skill(name: &str, path: AbsolutePathBuf) -> SkillMetadata {
|
||||||
|
SkillMetadata {
|
||||||
|
name: name.to_string(),
|
||||||
|
description: "test".to_string(),
|
||||||
|
short_description: None,
|
||||||
|
interface: None,
|
||||||
|
dependencies: None,
|
||||||
|
policy: None,
|
||||||
|
path_to_skills_md: path,
|
||||||
|
scope: SkillScope::User,
|
||||||
|
plugin_id: None,
|
||||||
|
remote_plugin_id: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_root() -> AbsolutePathBuf {
|
||||||
|
AbsolutePathBuf::try_from(std::env::current_dir().expect("current directory"))
|
||||||
|
.expect("absolute current directory")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn path_rule_disables_selected_path() {
|
||||||
|
let path = test_root().join("disable-by-path/SKILL.md");
|
||||||
|
let rules = SkillConfigRules {
|
||||||
|
entries: vec![SkillConfigRule {
|
||||||
|
selector: SkillConfigRuleSelector::Path(path.clone()),
|
||||||
|
enabled: false,
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
resolve_disabled_skill_paths(&[], &rules),
|
||||||
|
[path].into_iter().collect()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn later_name_rule_reenables_path_disabled_skill() {
|
||||||
|
let path = test_root().join("reenable-by-name/SKILL.md");
|
||||||
|
let skills = vec![skill("demo", path.clone())];
|
||||||
|
let rules = SkillConfigRules {
|
||||||
|
entries: vec![
|
||||||
|
SkillConfigRule {
|
||||||
|
selector: SkillConfigRuleSelector::Path(path),
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
SkillConfigRule {
|
||||||
|
selector: SkillConfigRuleSelector::Name("demo".to_string()),
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
resolve_disabled_skill_paths(&skills, &rules),
|
||||||
|
Default::default()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn later_path_rule_reenables_one_skill_disabled_by_name() {
|
||||||
|
let root = test_root().join("reenable-by-path");
|
||||||
|
let first_path = root.join("first/SKILL.md");
|
||||||
|
let second_path = root.join("second/SKILL.md");
|
||||||
|
let skills = vec![
|
||||||
|
skill("demo", first_path.clone()),
|
||||||
|
skill("demo", second_path.clone()),
|
||||||
|
];
|
||||||
|
let rules = SkillConfigRules {
|
||||||
|
entries: vec![
|
||||||
|
SkillConfigRule {
|
||||||
|
selector: SkillConfigRuleSelector::Name("demo".to_string()),
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
SkillConfigRule {
|
||||||
|
selector: SkillConfigRuleSelector::Path(first_path),
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
resolve_disabled_skill_paths(&skills, &rules),
|
||||||
|
[second_path].into_iter().collect()
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in new issue
Block a user