refactor(core): move embedded system skills into codex-skills crate (#12435)
## Why `codex-core` was carrying the embedded system-skill sample assets (and a `build.rs` that walks those files to register rerun triggers). Those assets change infrequently, but any change under `codex-core` still ties them to `codex-core`'s build/cache lifecycle. This change moves the embedded system-skills packaging into a dedicated `codex-skills` crate so it can be cached independently. That reduces unnecessary invalidation/rebuild pressure on `codex-core` when the skills bundle is the only thing that changes. ## What Changed - Added a new `codex-rs/skills` crate (`codex-skills`) with: - `Cargo.toml` - `BUILD.bazel` - `build.rs` to track skill asset file changes for Cargo rebuilds - `src/lib.rs` containing the embedded system-skills install/cache logic previously in `codex-core` - Moved the embedded sample skill assets from `codex-rs/core/src/skills/assets/samples` to `codex-rs/skills/src/assets/samples`. - Updated `codex-rs/core/Cargo.toml` to depend on `codex-skills` and removed `codex-core`'s direct `include_dir` dependency. - Removed `codex-core`'s `build.rs`. - Replaced `codex-rs/core/src/skills/system.rs` implementation with a thin re-export wrapper to keep existing `codex-core` call sites unchanged. - Updated workspace manifests/lockfile (`codex-rs/Cargo.toml`, `codex-rs/Cargo.lock`) for the new crate.
11
codex-rs/Cargo.lock
generated
|
|
@ -1644,6 +1644,7 @@ dependencies = [
|
|||
"codex-rmcp-client",
|
||||
"codex-secrets",
|
||||
"codex-shell-command",
|
||||
"codex-skills",
|
||||
"codex-state",
|
||||
"codex-utils-absolute-path",
|
||||
"codex-utils-cargo-bin",
|
||||
|
|
@ -1663,7 +1664,6 @@ dependencies = [
|
|||
"futures",
|
||||
"http 1.4.0",
|
||||
"image",
|
||||
"include_dir",
|
||||
"indexmap 2.13.0",
|
||||
"indoc",
|
||||
"insta",
|
||||
|
|
@ -2189,6 +2189,15 @@ dependencies = [
|
|||
"which",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-skills"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"codex-utils-absolute-path",
|
||||
"include_dir",
|
||||
"thiserror 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-state"
|
||||
version = "0.0.0"
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ members = [
|
|||
"cli",
|
||||
"config",
|
||||
"shell-command",
|
||||
"skills",
|
||||
"core",
|
||||
"hooks",
|
||||
"secrets",
|
||||
|
|
@ -112,6 +113,7 @@ codex-responses-api-proxy = { path = "responses-api-proxy" }
|
|||
codex-rmcp-client = { path = "rmcp-client" }
|
||||
codex-secrets = { path = "secrets" }
|
||||
codex-shell-command = { path = "shell-command" }
|
||||
codex-skills = { path = "skills" }
|
||||
codex-state = { path = "state" }
|
||||
codex-stdio-to-uds = { path = "stdio-to-uds" }
|
||||
codex-tui = { path = "tui" }
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ edition.workspace = true
|
|||
license.workspace = true
|
||||
name = "codex-core"
|
||||
version.workspace = true
|
||||
build = "build.rs"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
|
@ -34,6 +33,7 @@ codex-async-utils = { workspace = true }
|
|||
codex-client = { workspace = true }
|
||||
codex-config = { workspace = true }
|
||||
codex-shell-command = { workspace = true }
|
||||
codex-skills = { workspace = true }
|
||||
codex-execpolicy = { workspace = true }
|
||||
codex-file-search = { workspace = true }
|
||||
codex-git = { workspace = true }
|
||||
|
|
@ -58,7 +58,6 @@ env-flags = { workspace = true }
|
|||
eventsource-stream = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
http = { workspace = true }
|
||||
include_dir = { workspace = true }
|
||||
indexmap = { workspace = true }
|
||||
indoc = { workspace = true }
|
||||
keyring = { workspace = true, features = ["crypto-rust"] }
|
||||
|
|
|
|||
|
|
@ -1,196 +1,2 @@
|
|||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use include_dir::Dir;
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::fs;
|
||||
use std::hash::Hash;
|
||||
use std::hash::Hasher;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
const SYSTEM_SKILLS_DIR: Dir =
|
||||
include_dir::include_dir!("$CARGO_MANIFEST_DIR/src/skills/assets/samples");
|
||||
|
||||
const SYSTEM_SKILLS_DIR_NAME: &str = ".system";
|
||||
const SKILLS_DIR_NAME: &str = "skills";
|
||||
const SYSTEM_SKILLS_MARKER_FILENAME: &str = ".codex-system-skills.marker";
|
||||
const SYSTEM_SKILLS_MARKER_SALT: &str = "v1";
|
||||
|
||||
/// Returns the on-disk cache location for embedded system skills.
|
||||
///
|
||||
/// This is typically located at `CODEX_HOME/skills/.system`.
|
||||
pub(crate) fn system_cache_root_dir(codex_home: &Path) -> PathBuf {
|
||||
AbsolutePathBuf::try_from(codex_home)
|
||||
.and_then(|codex_home| system_cache_root_dir_abs(&codex_home))
|
||||
.map(AbsolutePathBuf::into_path_buf)
|
||||
.unwrap_or_else(|_| {
|
||||
codex_home
|
||||
.join(SKILLS_DIR_NAME)
|
||||
.join(SYSTEM_SKILLS_DIR_NAME)
|
||||
})
|
||||
}
|
||||
|
||||
fn system_cache_root_dir_abs(codex_home: &AbsolutePathBuf) -> std::io::Result<AbsolutePathBuf> {
|
||||
codex_home
|
||||
.join(SKILLS_DIR_NAME)?
|
||||
.join(SYSTEM_SKILLS_DIR_NAME)
|
||||
}
|
||||
|
||||
/// Installs embedded system skills into `CODEX_HOME/skills/.system`.
|
||||
///
|
||||
/// Clears any existing system skills directory first and then writes the embedded
|
||||
/// skills directory into place.
|
||||
///
|
||||
/// To avoid doing unnecessary work on every startup, a marker file is written
|
||||
/// with a fingerprint of the embedded directory. When the marker matches, the
|
||||
/// install is skipped.
|
||||
pub(crate) fn install_system_skills(codex_home: &Path) -> Result<(), SystemSkillsError> {
|
||||
let codex_home = AbsolutePathBuf::try_from(codex_home)
|
||||
.map_err(|source| SystemSkillsError::io("normalize codex home dir", source))?;
|
||||
let skills_root_dir = codex_home
|
||||
.join(SKILLS_DIR_NAME)
|
||||
.map_err(|source| SystemSkillsError::io("resolve skills root dir", source))?;
|
||||
fs::create_dir_all(skills_root_dir.as_path())
|
||||
.map_err(|source| SystemSkillsError::io("create skills root dir", source))?;
|
||||
|
||||
let dest_system = system_cache_root_dir_abs(&codex_home)
|
||||
.map_err(|source| SystemSkillsError::io("resolve system skills cache root dir", source))?;
|
||||
|
||||
let marker_path = dest_system
|
||||
.join(SYSTEM_SKILLS_MARKER_FILENAME)
|
||||
.map_err(|source| SystemSkillsError::io("resolve system skills marker path", source))?;
|
||||
let expected_fingerprint = embedded_system_skills_fingerprint();
|
||||
if dest_system.as_path().is_dir()
|
||||
&& read_marker(&marker_path).is_ok_and(|marker| marker == expected_fingerprint)
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if dest_system.as_path().exists() {
|
||||
fs::remove_dir_all(dest_system.as_path())
|
||||
.map_err(|source| SystemSkillsError::io("remove existing system skills dir", source))?;
|
||||
}
|
||||
|
||||
write_embedded_dir(&SYSTEM_SKILLS_DIR, &dest_system)?;
|
||||
fs::write(marker_path.as_path(), format!("{expected_fingerprint}\n"))
|
||||
.map_err(|source| SystemSkillsError::io("write system skills marker", source))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_marker(path: &AbsolutePathBuf) -> Result<String, SystemSkillsError> {
|
||||
Ok(fs::read_to_string(path.as_path())
|
||||
.map_err(|source| SystemSkillsError::io("read system skills marker", source))?
|
||||
.trim()
|
||||
.to_string())
|
||||
}
|
||||
|
||||
fn embedded_system_skills_fingerprint() -> String {
|
||||
let mut items = Vec::new();
|
||||
collect_fingerprint_items(&SYSTEM_SKILLS_DIR, &mut items);
|
||||
items.sort_unstable_by(|(a, _), (b, _)| a.cmp(b));
|
||||
|
||||
let mut hasher = DefaultHasher::new();
|
||||
SYSTEM_SKILLS_MARKER_SALT.hash(&mut hasher);
|
||||
for (path, contents_hash) in items {
|
||||
path.hash(&mut hasher);
|
||||
contents_hash.hash(&mut hasher);
|
||||
}
|
||||
format!("{:x}", hasher.finish())
|
||||
}
|
||||
|
||||
fn collect_fingerprint_items(dir: &Dir<'_>, items: &mut Vec<(String, Option<u64>)>) {
|
||||
for entry in dir.entries() {
|
||||
match entry {
|
||||
include_dir::DirEntry::Dir(subdir) => {
|
||||
items.push((subdir.path().to_string_lossy().to_string(), None));
|
||||
collect_fingerprint_items(subdir, items);
|
||||
}
|
||||
include_dir::DirEntry::File(file) => {
|
||||
let mut file_hasher = DefaultHasher::new();
|
||||
file.contents().hash(&mut file_hasher);
|
||||
items.push((
|
||||
file.path().to_string_lossy().to_string(),
|
||||
Some(file_hasher.finish()),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes the embedded `include_dir::Dir` to disk under `dest`.
|
||||
///
|
||||
/// Preserves the embedded directory structure.
|
||||
fn write_embedded_dir(dir: &Dir<'_>, dest: &AbsolutePathBuf) -> Result<(), SystemSkillsError> {
|
||||
fs::create_dir_all(dest.as_path())
|
||||
.map_err(|source| SystemSkillsError::io("create system skills dir", source))?;
|
||||
|
||||
for entry in dir.entries() {
|
||||
match entry {
|
||||
include_dir::DirEntry::Dir(subdir) => {
|
||||
let subdir_dest = dest.join(subdir.path()).map_err(|source| {
|
||||
SystemSkillsError::io("resolve system skills subdir", source)
|
||||
})?;
|
||||
fs::create_dir_all(subdir_dest.as_path()).map_err(|source| {
|
||||
SystemSkillsError::io("create system skills subdir", source)
|
||||
})?;
|
||||
write_embedded_dir(subdir, dest)?;
|
||||
}
|
||||
include_dir::DirEntry::File(file) => {
|
||||
let path = dest.join(file.path()).map_err(|source| {
|
||||
SystemSkillsError::io("resolve system skills file", source)
|
||||
})?;
|
||||
if let Some(parent) = path.as_path().parent() {
|
||||
fs::create_dir_all(parent).map_err(|source| {
|
||||
SystemSkillsError::io("create system skills file parent", source)
|
||||
})?;
|
||||
}
|
||||
fs::write(path.as_path(), file.contents())
|
||||
.map_err(|source| SystemSkillsError::io("write system skill file", source))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub(crate) enum SystemSkillsError {
|
||||
#[error("io error while {action}: {source}")]
|
||||
Io {
|
||||
action: &'static str,
|
||||
#[source]
|
||||
source: std::io::Error,
|
||||
},
|
||||
}
|
||||
|
||||
impl SystemSkillsError {
|
||||
fn io(action: &'static str, source: std::io::Error) -> Self {
|
||||
Self::Io { action, source }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::SYSTEM_SKILLS_DIR;
|
||||
use super::collect_fingerprint_items;
|
||||
|
||||
#[test]
|
||||
fn fingerprint_traverses_nested_entries() {
|
||||
let mut items = Vec::new();
|
||||
collect_fingerprint_items(&SYSTEM_SKILLS_DIR, &mut items);
|
||||
let mut paths: Vec<String> = items.into_iter().map(|(path, _)| path).collect();
|
||||
paths.sort_unstable();
|
||||
|
||||
assert!(
|
||||
paths
|
||||
.binary_search_by(|probe| probe.as_str().cmp("skill-creator/SKILL.md"))
|
||||
.is_ok()
|
||||
);
|
||||
assert!(
|
||||
paths
|
||||
.binary_search_by(|probe| probe.as_str().cmp("skill-creator/scripts/init_skill.py"))
|
||||
.is_ok()
|
||||
);
|
||||
}
|
||||
}
|
||||
pub(crate) use codex_skills::install_system_skills;
|
||||
pub(crate) use codex_skills::system_cache_root_dir;
|
||||
|
|
|
|||
15
codex-rs/skills/BUILD.bazel
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
load("//:defs.bzl", "codex_rust_crate")
|
||||
|
||||
codex_rust_crate(
|
||||
name = "skills",
|
||||
crate_name = "codex_skills",
|
||||
compile_data = glob(
|
||||
include = ["**"],
|
||||
exclude = [
|
||||
"**/* *",
|
||||
"BUILD.bazel",
|
||||
"Cargo.toml",
|
||||
],
|
||||
allow_empty = True,
|
||||
),
|
||||
)
|
||||
19
codex-rs/skills/Cargo.toml
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
[package]
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
name = "codex-skills"
|
||||
version.workspace = true
|
||||
build = "build.rs"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
name = "codex_skills"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
codex-utils-absolute-path = { workspace = true }
|
||||
include_dir = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
|
|
@ -2,7 +2,7 @@ use std::fs;
|
|||
use std::path::Path;
|
||||
|
||||
fn main() {
|
||||
let samples_dir = Path::new("src/skills/assets/samples");
|
||||
let samples_dir = Path::new("src/assets/samples");
|
||||
if !samples_dir.exists() {
|
||||
return;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 923 B After Width: | Height: | Size: 923 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
195
codex-rs/skills/src/lib.rs
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use include_dir::Dir;
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::fs;
|
||||
use std::hash::Hash;
|
||||
use std::hash::Hasher;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
const SYSTEM_SKILLS_DIR: Dir = include_dir::include_dir!("$CARGO_MANIFEST_DIR/src/assets/samples");
|
||||
|
||||
const SYSTEM_SKILLS_DIR_NAME: &str = ".system";
|
||||
const SKILLS_DIR_NAME: &str = "skills";
|
||||
const SYSTEM_SKILLS_MARKER_FILENAME: &str = ".codex-system-skills.marker";
|
||||
const SYSTEM_SKILLS_MARKER_SALT: &str = "v1";
|
||||
|
||||
/// Returns the on-disk cache location for embedded system skills.
|
||||
///
|
||||
/// This is typically located at `CODEX_HOME/skills/.system`.
|
||||
pub fn system_cache_root_dir(codex_home: &Path) -> PathBuf {
|
||||
AbsolutePathBuf::try_from(codex_home)
|
||||
.and_then(|codex_home| system_cache_root_dir_abs(&codex_home))
|
||||
.map(AbsolutePathBuf::into_path_buf)
|
||||
.unwrap_or_else(|_| {
|
||||
codex_home
|
||||
.join(SKILLS_DIR_NAME)
|
||||
.join(SYSTEM_SKILLS_DIR_NAME)
|
||||
})
|
||||
}
|
||||
|
||||
fn system_cache_root_dir_abs(codex_home: &AbsolutePathBuf) -> std::io::Result<AbsolutePathBuf> {
|
||||
codex_home
|
||||
.join(SKILLS_DIR_NAME)?
|
||||
.join(SYSTEM_SKILLS_DIR_NAME)
|
||||
}
|
||||
|
||||
/// Installs embedded system skills into `CODEX_HOME/skills/.system`.
|
||||
///
|
||||
/// Clears any existing system skills directory first and then writes the embedded
|
||||
/// skills directory into place.
|
||||
///
|
||||
/// To avoid doing unnecessary work on every startup, a marker file is written
|
||||
/// with a fingerprint of the embedded directory. When the marker matches, the
|
||||
/// install is skipped.
|
||||
pub fn install_system_skills(codex_home: &Path) -> Result<(), SystemSkillsError> {
|
||||
let codex_home = AbsolutePathBuf::try_from(codex_home)
|
||||
.map_err(|source| SystemSkillsError::io("normalize codex home dir", source))?;
|
||||
let skills_root_dir = codex_home
|
||||
.join(SKILLS_DIR_NAME)
|
||||
.map_err(|source| SystemSkillsError::io("resolve skills root dir", source))?;
|
||||
fs::create_dir_all(skills_root_dir.as_path())
|
||||
.map_err(|source| SystemSkillsError::io("create skills root dir", source))?;
|
||||
|
||||
let dest_system = system_cache_root_dir_abs(&codex_home)
|
||||
.map_err(|source| SystemSkillsError::io("resolve system skills cache root dir", source))?;
|
||||
|
||||
let marker_path = dest_system
|
||||
.join(SYSTEM_SKILLS_MARKER_FILENAME)
|
||||
.map_err(|source| SystemSkillsError::io("resolve system skills marker path", source))?;
|
||||
let expected_fingerprint = embedded_system_skills_fingerprint();
|
||||
if dest_system.as_path().is_dir()
|
||||
&& read_marker(&marker_path).is_ok_and(|marker| marker == expected_fingerprint)
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if dest_system.as_path().exists() {
|
||||
fs::remove_dir_all(dest_system.as_path())
|
||||
.map_err(|source| SystemSkillsError::io("remove existing system skills dir", source))?;
|
||||
}
|
||||
|
||||
write_embedded_dir(&SYSTEM_SKILLS_DIR, &dest_system)?;
|
||||
fs::write(marker_path.as_path(), format!("{expected_fingerprint}\n"))
|
||||
.map_err(|source| SystemSkillsError::io("write system skills marker", source))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_marker(path: &AbsolutePathBuf) -> Result<String, SystemSkillsError> {
|
||||
Ok(fs::read_to_string(path.as_path())
|
||||
.map_err(|source| SystemSkillsError::io("read system skills marker", source))?
|
||||
.trim()
|
||||
.to_string())
|
||||
}
|
||||
|
||||
fn embedded_system_skills_fingerprint() -> String {
|
||||
let mut items = Vec::new();
|
||||
collect_fingerprint_items(&SYSTEM_SKILLS_DIR, &mut items);
|
||||
items.sort_unstable_by(|(a, _), (b, _)| a.cmp(b));
|
||||
|
||||
let mut hasher = DefaultHasher::new();
|
||||
SYSTEM_SKILLS_MARKER_SALT.hash(&mut hasher);
|
||||
for (path, contents_hash) in items {
|
||||
path.hash(&mut hasher);
|
||||
contents_hash.hash(&mut hasher);
|
||||
}
|
||||
format!("{:x}", hasher.finish())
|
||||
}
|
||||
|
||||
fn collect_fingerprint_items(dir: &Dir<'_>, items: &mut Vec<(String, Option<u64>)>) {
|
||||
for entry in dir.entries() {
|
||||
match entry {
|
||||
include_dir::DirEntry::Dir(subdir) => {
|
||||
items.push((subdir.path().to_string_lossy().to_string(), None));
|
||||
collect_fingerprint_items(subdir, items);
|
||||
}
|
||||
include_dir::DirEntry::File(file) => {
|
||||
let mut file_hasher = DefaultHasher::new();
|
||||
file.contents().hash(&mut file_hasher);
|
||||
items.push((
|
||||
file.path().to_string_lossy().to_string(),
|
||||
Some(file_hasher.finish()),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes the embedded `include_dir::Dir` to disk under `dest`.
|
||||
///
|
||||
/// Preserves the embedded directory structure.
|
||||
fn write_embedded_dir(dir: &Dir<'_>, dest: &AbsolutePathBuf) -> Result<(), SystemSkillsError> {
|
||||
fs::create_dir_all(dest.as_path())
|
||||
.map_err(|source| SystemSkillsError::io("create system skills dir", source))?;
|
||||
|
||||
for entry in dir.entries() {
|
||||
match entry {
|
||||
include_dir::DirEntry::Dir(subdir) => {
|
||||
let subdir_dest = dest.join(subdir.path()).map_err(|source| {
|
||||
SystemSkillsError::io("resolve system skills subdir", source)
|
||||
})?;
|
||||
fs::create_dir_all(subdir_dest.as_path()).map_err(|source| {
|
||||
SystemSkillsError::io("create system skills subdir", source)
|
||||
})?;
|
||||
write_embedded_dir(subdir, dest)?;
|
||||
}
|
||||
include_dir::DirEntry::File(file) => {
|
||||
let path = dest.join(file.path()).map_err(|source| {
|
||||
SystemSkillsError::io("resolve system skills file", source)
|
||||
})?;
|
||||
if let Some(parent) = path.as_path().parent() {
|
||||
fs::create_dir_all(parent).map_err(|source| {
|
||||
SystemSkillsError::io("create system skills file parent", source)
|
||||
})?;
|
||||
}
|
||||
fs::write(path.as_path(), file.contents())
|
||||
.map_err(|source| SystemSkillsError::io("write system skill file", source))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum SystemSkillsError {
|
||||
#[error("io error while {action}: {source}")]
|
||||
Io {
|
||||
action: &'static str,
|
||||
#[source]
|
||||
source: std::io::Error,
|
||||
},
|
||||
}
|
||||
|
||||
impl SystemSkillsError {
|
||||
fn io(action: &'static str, source: std::io::Error) -> Self {
|
||||
Self::Io { action, source }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::SYSTEM_SKILLS_DIR;
|
||||
use super::collect_fingerprint_items;
|
||||
|
||||
#[test]
|
||||
fn fingerprint_traverses_nested_entries() {
|
||||
let mut items = Vec::new();
|
||||
collect_fingerprint_items(&SYSTEM_SKILLS_DIR, &mut items);
|
||||
let mut paths: Vec<String> = items.into_iter().map(|(path, _)| path).collect();
|
||||
paths.sort_unstable();
|
||||
|
||||
assert!(
|
||||
paths
|
||||
.binary_search_by(|probe| probe.as_str().cmp("skill-creator/SKILL.md"))
|
||||
.is_ok()
|
||||
);
|
||||
assert!(
|
||||
paths
|
||||
.binary_search_by(|probe| probe.as_str().cmp("skill-creator/scripts/init_skill.py"))
|
||||
.is_ok()
|
||||
);
|
||||
}
|
||||
}
|
||||