use std::fs;
use std::io::Write;
use std::path::Path;
const BUNDLED_SKILL_VERSION: &str = "8";
const SKILL_CREATOR_BODY: &str = include_str!("../../assets/skills/skill-creator/SKILL.md");
const DELEGATE_BODY: &str = include_str!("../../assets/skills/delegate/SKILL.md");
const PLUGIN_CREATOR_BODY: &str = include_str!("../../assets/skills/plugin-creator/SKILL.md");
const SKILL_INSTALLER_BODY: &str = include_str!("../../assets/skills/skill-installer/SKILL.md");
const MCP_BUILDER_BODY: &str = include_str!("../../assets/skills/mcp-builder/SKILL.md");
const FLEET_MANAGER_BODY: &str = include_str!("../../assets/skills/fleet-manager/SKILL.md");
const HELP_BODY: &str = include_str!("../../assets/skills/help/SKILL.md");
const BEST_OF_N_BODY: &str = include_str!("../../assets/skills/best-of-n/SKILL.md");
const INTERVIEW_BODY: &str = include_str!("../../assets/skills/interview/SKILL.md");
const PLAN_BODY: &str = include_str!("../../assets/skills/plan/SKILL.md");
const IMPLEMENT_BODY: &str = include_str!("../../assets/skills/implement/SKILL.md");
const DEBUG_BODY: &str = include_str!("../../assets/skills/debug/SKILL.md");
const TEST_BODY: &str = include_str!("../../assets/skills/test/SKILL.md");
const REVIEW_BODY: &str = include_str!("../../assets/skills/review/SKILL.md");
const SECURITY_REVIEW_BODY: &str = include_str!("../../assets/skills/security-review/SKILL.md");
const SIMPLIFY_BODY: &str = include_str!("../../assets/skills/simplify/SKILL.md");
const VERIFY_BODY: &str = include_str!("../../assets/skills/verify/SKILL.md");
const RESEARCH_BODY: &str = include_str!("../../assets/skills/research/SKILL.md");
const FRONTEND_DESIGN_BODY: &str = include_str!("../../assets/skills/frontend-design/SKILL.md");
const WEBAPP_TESTING_BODY: &str = include_str!("../../assets/skills/webapp-testing/SKILL.md");
const DOCUMENT_BODY: &str = include_str!("../../assets/skills/document/SKILL.md");
const DATAVIZ_BODY: &str = include_str!("../../assets/skills/dataviz/SKILL.md");
const DOCX_BODY: &str = include_str!("../../assets/skills/docx/SKILL.md");
const PDF_BODY: &str = include_str!("../../assets/skills/pdf/SKILL.md");
const PPTX_BODY: &str = include_str!("../../assets/skills/pptx/SKILL.md");
const XLSX_BODY: &str = include_str!("../../assets/skills/xlsx/SKILL.md");
const DOCUMENTS_ALIAS_BODY: &str = include_str!("../../assets/skills/documents/SKILL.md");
const PRESENTATIONS_ALIAS_BODY: &str = include_str!("../../assets/skills/presentations/SKILL.md");
const SPREADSHEETS_ALIAS_BODY: &str = include_str!("../../assets/skills/spreadsheets/SKILL.md");
const BATCH_BODY: &str = include_str!("../../assets/skills/batch/SKILL.md");
const DEPENDENCY_UPDATE_BODY: &str = include_str!("../../assets/skills/dependency-update/SKILL.md");
const RELEASE_BODY: &str = include_str!("../../assets/skills/release/SKILL.md");
const CONTRIBUTOR_ONBOARDING_BODY: &str =
include_str!("../../assets/skills/contributor-onboarding/SKILL.md");
const FEISHU_BODY: &str = include_str!("../../assets/skills/feishu/SKILL.md");
const V4_BEST_PRACTICES_BODY: &str = include_str!("../../assets/skills/v4-best-practices/SKILL.md");
struct BundledSkill {
name: &'static str,
body: &'static str,
introduced_in: u32,
}
const BUNDLED_SKILLS: &[BundledSkill] = &[
BundledSkill {
name: "skill-creator",
body: SKILL_CREATOR_BODY,
introduced_in: 1,
},
BundledSkill {
name: "delegate",
body: DELEGATE_BODY,
introduced_in: 2,
},
BundledSkill {
name: "plugin-creator",
body: PLUGIN_CREATOR_BODY,
introduced_in: 3,
},
BundledSkill {
name: "skill-installer",
body: SKILL_INSTALLER_BODY,
introduced_in: 3,
},
BundledSkill {
name: "mcp-builder",
body: MCP_BUILDER_BODY,
introduced_in: 3,
},
BundledSkill {
name: "fleet-manager",
body: FLEET_MANAGER_BODY,
introduced_in: 4,
},
BundledSkill {
name: "help",
body: HELP_BODY,
introduced_in: 7,
},
BundledSkill {
name: "best-of-n",
body: BEST_OF_N_BODY,
introduced_in: 6,
},
BundledSkill {
name: "interview",
body: INTERVIEW_BODY,
introduced_in: 5,
},
BundledSkill {
name: "plan",
body: PLAN_BODY,
introduced_in: 5,
},
BundledSkill {
name: "implement",
body: IMPLEMENT_BODY,
introduced_in: 5,
},
BundledSkill {
name: "debug",
body: DEBUG_BODY,
introduced_in: 5,
},
BundledSkill {
name: "test",
body: TEST_BODY,
introduced_in: 5,
},
BundledSkill {
name: "review",
body: REVIEW_BODY,
introduced_in: 5,
},
BundledSkill {
name: "security-review",
body: SECURITY_REVIEW_BODY,
introduced_in: 5,
},
BundledSkill {
name: "simplify",
body: SIMPLIFY_BODY,
introduced_in: 5,
},
BundledSkill {
name: "verify",
body: VERIFY_BODY,
introduced_in: 5,
},
BundledSkill {
name: "research",
body: RESEARCH_BODY,
introduced_in: 5,
},
BundledSkill {
name: "frontend-design",
body: FRONTEND_DESIGN_BODY,
introduced_in: 5,
},
BundledSkill {
name: "webapp-testing",
body: WEBAPP_TESTING_BODY,
introduced_in: 5,
},
BundledSkill {
name: "document",
body: DOCUMENT_BODY,
introduced_in: 5,
},
BundledSkill {
name: "dataviz",
body: DATAVIZ_BODY,
introduced_in: 5,
},
BundledSkill {
name: "docx",
body: DOCX_BODY,
introduced_in: 5,
},
BundledSkill {
name: "pdf",
body: PDF_BODY,
introduced_in: 3,
},
BundledSkill {
name: "pptx",
body: PPTX_BODY,
introduced_in: 5,
},
BundledSkill {
name: "xlsx",
body: XLSX_BODY,
introduced_in: 5,
},
BundledSkill {
name: "documents",
body: DOCUMENTS_ALIAS_BODY,
introduced_in: 3,
},
BundledSkill {
name: "presentations",
body: PRESENTATIONS_ALIAS_BODY,
introduced_in: 3,
},
BundledSkill {
name: "spreadsheets",
body: SPREADSHEETS_ALIAS_BODY,
introduced_in: 3,
},
BundledSkill {
name: "batch",
body: BATCH_BODY,
introduced_in: 5,
},
BundledSkill {
name: "dependency-update",
body: DEPENDENCY_UPDATE_BODY,
introduced_in: 5,
},
BundledSkill {
name: "release",
body: RELEASE_BODY,
introduced_in: 5,
},
BundledSkill {
name: "contributor-onboarding",
body: CONTRIBUTOR_ONBOARDING_BODY,
introduced_in: 8,
},
];
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum BundledSkillTier {
CoreAgentic,
FormatTooling,
}
impl BundledSkillTier {
#[must_use]
pub const fn label(self) -> &'static str {
match self {
Self::CoreAgentic => "core",
Self::FormatTooling => "tools",
}
}
#[must_use]
pub const fn heading(self) -> &'static str {
match self {
Self::CoreAgentic => "Core agentic",
Self::FormatTooling => "Format & tooling",
}
}
}
#[must_use]
pub fn bundled_skill_tier(name: &str) -> Option<BundledSkillTier> {
if !is_bundled_skill_name(name) {
return None;
}
let tier = match name {
"skill-creator" | "plugin-creator" | "skill-installer" | "mcp-builder" | "help"
| "frontend-design" | "webapp-testing" | "document" | "dataviz" | "docx" | "pdf"
| "pptx" | "xlsx" | "documents" | "presentations" | "spreadsheets" => {
BundledSkillTier::FormatTooling
}
_ => BundledSkillTier::CoreAgentic,
};
Some(tier)
}
#[must_use]
#[cfg(test)]
pub fn bundled_skill_names() -> Vec<&'static str> {
BUNDLED_SKILLS.iter().map(|skill| skill.name).collect()
}
#[must_use]
#[cfg(test)]
pub fn bundled_skill_generation() -> &'static str {
BUNDLED_SKILL_VERSION
}
fn v4_best_practices_body() -> &'static str {
V4_BEST_PRACTICES_BODY
}
fn feishu_body() -> &'static str {
FEISHU_BODY
}
#[must_use]
pub fn is_bundled_skill_name(name: &str) -> bool {
BUNDLED_SKILLS.iter().any(|s| s.name == name)
}
#[must_use]
pub fn is_exact_bundled_skill(name: &str, skill_md_content: &str) -> bool {
BUNDLED_SKILLS
.iter()
.any(|s| s.name == name && s.body == skill_md_content)
}
#[must_use]
#[allow(dead_code)] pub fn bundled_skill_body_sha256(name: &str) -> Option<String> {
use sha2::{Digest, Sha256};
BUNDLED_SKILLS.iter().find(|s| s.name == name).map(|s| {
let digest = Sha256::digest(s.body.as_bytes());
let mut out = String::with_capacity(digest.len() * 2);
for byte in digest {
use std::fmt::Write as _;
let _ = write!(&mut out, "{byte:02x}");
}
out
})
}
fn install_one(
skills_dir: &Path,
skill: &BundledSkill,
installed_version: Option<&str>,
) -> std::io::Result<bool> {
let target_dir = skills_dir.join(skill.name);
let target_file = target_dir.join("SKILL.md");
let dir_exists = target_dir.exists();
let installed_number = installed_version.and_then(|value| value.parse::<u32>().ok());
let should_install = match (installed_version, installed_number, dir_exists) {
(None, _, false) => true,
(Some(_), Some(version), _) if version < skill.introduced_in => true,
(Some(version), _, true) if version != BUNDLED_SKILL_VERSION => true,
_ => false,
};
if should_install {
if target_file.exists() {
let existing = fs::read_to_string(&target_file).unwrap_or_default();
if !existing.is_empty() && existing != skill.body {
return Ok(false);
}
}
fs::create_dir_all(&target_dir)?;
fs::write(&target_file, skill.body)?;
}
Ok(should_install)
}
pub fn install_system_skills(skills_dir: &Path) -> std::io::Result<()> {
let marker = skills_dir.join(".system-installed-version");
let (installed_version, repair_marker) = match fs::read_to_string(&marker) {
Ok(contents) => match contents.trim().parse::<u32>() {
Ok(_) => (Some(contents.trim().to_string()), false),
Err(_) => (None, true),
},
Err(error) if error.kind() == std::io::ErrorKind::NotFound => (None, false),
Err(_) => (None, true),
};
let mut changed = false;
for skill in BUNDLED_SKILLS {
changed |= install_one(skills_dir, skill, installed_version.as_deref())?;
}
changed |= retire_unchanged_v4_best_practices(skills_dir)?;
let _ = feishu_body();
if changed || repair_marker {
fs::create_dir_all(skills_dir)?;
if marker.exists() && !marker.is_file() {
if marker.is_dir() {
fs::remove_dir_all(&marker)?;
} else {
fs::remove_file(&marker)?;
}
}
write_marker_atomically(&marker, BUNDLED_SKILL_VERSION)?;
}
Ok(())
}
fn retire_unchanged_v4_best_practices(skills_dir: &Path) -> std::io::Result<bool> {
let dir = skills_dir.join("v4-best-practices");
let file = dir.join("SKILL.md");
if !file.exists() {
return Ok(false);
}
let existing = fs::read_to_string(&file)?;
if existing != v4_best_practices_body() {
return Ok(false);
}
fs::remove_dir_all(&dir)?;
Ok(true)
}
fn write_marker_atomically(marker: &Path, version: &str) -> std::io::Result<()> {
let parent = marker
.parent()
.expect("skill version marker should have a parent directory");
let mut temporary = tempfile::NamedTempFile::new_in(parent)?;
temporary.write_all(version.as_bytes())?;
temporary.as_file().sync_all()?;
#[cfg(windows)]
if marker.exists() {
fs::remove_file(marker)?;
}
fs::rename(temporary.path(), marker)
}
#[allow(dead_code)]
pub fn uninstall_system_skills(skills_dir: &Path) -> std::io::Result<()> {
let marker = skills_dir.join(".system-installed-version");
for skill in BUNDLED_SKILLS {
let dir = skills_dir.join(skill.name);
if dir.exists() {
fs::remove_dir_all(&dir)?;
}
}
if marker.exists() {
fs::remove_file(&marker)?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
fn skill_file(tmp: &TempDir, name: &str) -> std::path::PathBuf {
tmp.path().join(name).join("SKILL.md")
}
fn skill_dir(tmp: &TempDir, name: &str) -> std::path::PathBuf {
tmp.path().join(name)
}
fn marker_file(tmp: &TempDir) -> std::path::PathBuf {
tmp.path().join(".system-installed-version")
}
#[test]
fn fresh_install_creates_bundled_skills_and_marker() {
let tmp = TempDir::new().unwrap();
install_system_skills(tmp.path()).unwrap();
for skill in BUNDLED_SKILLS {
assert!(
skill_file(&tmp, skill.name).exists(),
"{} SKILL.md should be created",
skill.name
);
}
assert!(marker_file(&tmp).exists(), "marker should be created");
let ver = fs::read_to_string(marker_file(&tmp)).unwrap();
assert_eq!(ver.trim(), BUNDLED_SKILL_VERSION);
}
#[test]
fn contributor_onboarding_ships_at_generation_8_and_keeps_its_refusals() {
let skill = BUNDLED_SKILLS
.iter()
.find(|skill| skill.name == "contributor-onboarding")
.expect("contributor-onboarding must be bundled");
assert_eq!(skill.introduced_in, 8);
assert_eq!(BUNDLED_SKILL_VERSION, "8");
let body = skill.body;
assert!(body.contains("invocation: explicit-only"));
assert!(body.contains("Do not run `git fetch`, `git pull`, `git rebase`"));
assert!(body.contains("Do not stash, discard, reset, or commit a dirty tree"));
assert!(body.contains("cargo clippy --workspace --all-features --locked"));
assert!(body.contains(".github/workflows/ci.yml"));
assert!(body.contains("Do not call a model provider"));
assert!(body.contains("./target/release/codewhale exec --help"));
assert!(body.contains("Never select a provider for them"));
assert!(body.contains("@JayBeest"));
}
#[test]
fn fresh_install_skills_parse_for_discovery() {
let tmp = TempDir::new().unwrap();
install_system_skills(tmp.path()).unwrap();
let registry = crate::skills::SkillRegistry::discover(tmp.path());
assert!(
registry.warnings().is_empty(),
"bundled skills should parse cleanly: {:?}",
registry.warnings()
);
for skill in BUNDLED_SKILLS {
let parsed = registry
.get(skill.name)
.unwrap_or_else(|| panic!("{} should be discoverable", skill.name));
assert!(
!parsed.description.is_empty(),
"{} should include model-visible description",
skill.name
);
}
}
#[test]
fn corrupt_marker_is_repaired_without_overwriting_user_skill_body() {
let tmp = TempDir::new().unwrap();
install_system_skills(tmp.path()).unwrap();
let user_body = "user-edited body";
fs::write(skill_file(&tmp, "delegate"), user_body).unwrap();
fs::remove_dir_all(skill_dir(&tmp, "skill-creator")).unwrap();
fs::write(marker_file(&tmp), "not-a-version").unwrap();
install_system_skills(tmp.path()).unwrap();
assert_eq!(
fs::read_to_string(skill_file(&tmp, "delegate")).unwrap(),
user_body
);
assert!(skill_file(&tmp, "skill-creator").exists());
assert_eq!(
fs::read_to_string(marker_file(&tmp)).unwrap().trim(),
BUNDLED_SKILL_VERSION
);
}
#[test]
fn directory_marker_is_replaced_and_user_skills_are_preserved() {
let tmp = TempDir::new().unwrap();
install_system_skills(tmp.path()).unwrap();
let user_body = "user-edited body";
fs::write(skill_file(&tmp, "delegate"), user_body).unwrap();
fs::remove_dir_all(skill_dir(&tmp, "skill-creator")).unwrap();
fs::remove_file(marker_file(&tmp)).unwrap();
fs::create_dir(marker_file(&tmp)).unwrap();
fs::write(marker_file(&tmp).join("stale-entry"), "stale").unwrap();
install_system_skills(tmp.path()).unwrap();
assert_eq!(
fs::read_to_string(skill_file(&tmp, "delegate")).unwrap(),
user_body
);
assert!(skill_file(&tmp, "skill-creator").exists());
assert!(marker_file(&tmp).is_file());
assert_eq!(
fs::read_to_string(marker_file(&tmp)).unwrap().trim(),
BUNDLED_SKILL_VERSION
);
}
#[test]
fn invalid_marker_is_repaired_even_when_no_skill_body_changes() {
let tmp = TempDir::new().unwrap();
install_system_skills(tmp.path()).unwrap();
fs::write(marker_file(&tmp), "").unwrap();
install_system_skills(tmp.path()).unwrap();
assert_eq!(
fs::read_to_string(marker_file(&tmp)).unwrap().trim(),
BUNDLED_SKILL_VERSION
);
}
#[test]
fn bundled_catalog_has_two_complete_truthful_tiers() {
for skill in BUNDLED_SKILLS {
assert!(
bundled_skill_tier(skill.name).is_some(),
"{} must have a picker tier",
skill.name
);
}
assert_eq!(
bundled_skill_tier("best-of-n"),
Some(BundledSkillTier::CoreAgentic)
);
assert_eq!(
bundled_skill_tier("pdf"),
Some(BundledSkillTier::FormatTooling)
);
assert_eq!(bundled_skill_tier("user-created"), None);
assert!(
!is_bundled_skill_name("imagine"),
"do not advertise image generation without an image-generation tool"
);
}
#[test]
fn calling_twice_is_idempotent() {
let tmp = TempDir::new().unwrap();
install_system_skills(tmp.path()).unwrap();
for skill in BUNDLED_SKILLS {
fs::write(
skill_file(&tmp, skill.name),
format!("{}-sentinel", skill.name),
)
.unwrap();
}
install_system_skills(tmp.path()).unwrap();
for skill in BUNDLED_SKILLS {
let body = fs::read_to_string(skill_file(&tmp, skill.name)).unwrap();
assert_eq!(
body,
format!("{}-sentinel", skill.name),
"second install should not overwrite {}",
skill.name
);
}
}
#[test]
fn user_deleted_dir_is_not_recreated() {
let tmp = TempDir::new().unwrap();
install_system_skills(tmp.path()).unwrap();
fs::remove_dir_all(skill_dir(&tmp, "delegate")).unwrap();
install_system_skills(tmp.path()).unwrap();
assert!(
!skill_file(&tmp, "delegate").exists(),
"delegate must not be recreated after user deleted it"
);
assert!(
skill_file(&tmp, "skill-creator").exists(),
"skill-creator should still be present (not deleted by user)"
);
}
#[test]
fn user_deleted_all_dirs_are_not_recreated() {
let tmp = TempDir::new().unwrap();
install_system_skills(tmp.path()).unwrap();
for skill in BUNDLED_SKILLS {
fs::remove_dir_all(skill_dir(&tmp, skill.name)).unwrap();
}
install_system_skills(tmp.path()).unwrap();
for skill in BUNDLED_SKILLS {
assert!(
!skill_file(&tmp, skill.name).exists(),
"{} must not be recreated after user deletion",
skill.name
);
}
}
#[test]
fn outdated_marker_triggers_reinstall_of_existing_skills() {
let tmp = TempDir::new().unwrap();
for skill in BUNDLED_SKILLS.iter().filter(|s| s.introduced_in <= 4) {
fs::create_dir_all(skill_dir(&tmp, skill.name)).unwrap();
fs::write(skill_file(&tmp, skill.name), skill.body).unwrap();
}
fs::write(marker_file(&tmp), "0").unwrap();
install_system_skills(tmp.path()).unwrap();
for skill in BUNDLED_SKILLS {
assert!(
skill_file(&tmp, skill.name).exists(),
"{} should be installed after marker upgrade",
skill.name
);
let content = fs::read_to_string(skill_file(&tmp, skill.name)).unwrap();
assert_eq!(
content, skill.body,
"{} body should match shipped",
skill.name
);
}
let ver = fs::read_to_string(marker_file(&tmp)).unwrap();
assert_eq!(ver.trim(), BUNDLED_SKILL_VERSION);
}
#[test]
fn version_bump_adds_skills_introduced_after_marker() {
let tmp = TempDir::new().unwrap();
for skill in BUNDLED_SKILLS.iter().filter(|s| s.introduced_in <= 4) {
fs::create_dir_all(skill_dir(&tmp, skill.name)).unwrap();
fs::write(skill_file(&tmp, skill.name), skill.body).unwrap();
}
fs::write(marker_file(&tmp), "4").unwrap();
install_system_skills(tmp.path()).unwrap();
for skill in BUNDLED_SKILLS.iter().filter(|s| s.introduced_in == 5) {
assert!(
skill_file(&tmp, skill.name).exists(),
"v5 skill {} should be installed on upgrade",
skill.name
);
}
for skill in BUNDLED_SKILLS.iter().filter(|s| s.introduced_in <= 4) {
let content = fs::read_to_string(skill_file(&tmp, skill.name)).unwrap();
assert_eq!(content, skill.body);
}
let ver = fs::read_to_string(marker_file(&tmp)).unwrap();
assert_eq!(ver.trim(), BUNDLED_SKILL_VERSION);
}
#[test]
fn version_bump_from_v5_adds_best_of_n_without_recreating_deleted_skills() {
let tmp = TempDir::new().unwrap();
fs::write(marker_file(&tmp), "5").unwrap();
install_system_skills(tmp.path()).unwrap();
assert!(skill_file(&tmp, "best-of-n").is_file());
assert!(
!skill_file(&tmp, "delegate").exists(),
"an intentionally absent older skill must stay absent"
);
assert_eq!(
fs::read_to_string(marker_file(&tmp)).unwrap().trim(),
BUNDLED_SKILL_VERSION
);
}
#[test]
fn version_bump_respects_deleted_existing_skill_while_adding_new_skill() {
let tmp = TempDir::new().unwrap();
fs::write(marker_file(&tmp), "2").unwrap();
install_system_skills(tmp.path()).unwrap();
assert!(
!skill_file(&tmp, "skill-creator").exists(),
"version bump should not recreate deleted skill-creator"
);
assert!(
!skill_file(&tmp, "delegate").exists(),
"version bump should not recreate deleted delegate"
);
for skill in BUNDLED_SKILLS
.iter()
.filter(|skill| skill.introduced_in > 2)
{
assert!(
skill_file(&tmp, skill.name).exists(),
"version bump should install newly introduced {}",
skill.name
);
}
let ver = fs::read_to_string(marker_file(&tmp)).unwrap();
assert_eq!(ver.trim(), BUNDLED_SKILL_VERSION);
}
#[test]
fn uninstall_removes_bundled_skills_and_marker() {
let tmp = TempDir::new().unwrap();
install_system_skills(tmp.path()).unwrap();
uninstall_system_skills(tmp.path()).unwrap();
for skill in BUNDLED_SKILLS {
assert!(
!skill_file(&tmp, skill.name).exists(),
"{} should be removed",
skill.name
);
}
assert!(!marker_file(&tmp).exists(), "marker should be removed");
}
#[test]
fn uninstall_on_clean_dir_is_a_noop() {
let tmp = TempDir::new().unwrap();
uninstall_system_skills(tmp.path()).unwrap();
}
#[test]
fn upgrade_from_v4_installs_pack_and_retires_unchanged_v4_best_practices() {
let tmp = TempDir::new().unwrap();
fs::create_dir_all(skill_dir(&tmp, "v4-best-practices")).unwrap();
fs::write(
skill_file(&tmp, "v4-best-practices"),
V4_BEST_PRACTICES_BODY,
)
.unwrap();
fs::write(marker_file(&tmp), "4").unwrap();
install_system_skills(tmp.path()).unwrap();
assert!(
!skill_dir(&tmp, "v4-best-practices").exists(),
"unchanged v4-best-practices must be retired"
);
assert!(skill_file(&tmp, "debug").exists());
assert!(skill_file(&tmp, "docx").exists());
assert!(skill_file(&tmp, "release").exists());
assert!(
!skill_dir(&tmp, "feishu").exists(),
"feishu must not be universally installed"
);
let ver = fs::read_to_string(marker_file(&tmp)).unwrap();
assert_eq!(ver.trim(), BUNDLED_SKILL_VERSION);
}
#[test]
fn upgrade_preserves_modified_v4_best_practices() {
let tmp = TempDir::new().unwrap();
fs::create_dir_all(skill_dir(&tmp, "v4-best-practices")).unwrap();
fs::write(
skill_file(&tmp, "v4-best-practices"),
"---\nname: v4-best-practices\ndescription: user-owned\n---\n\n# mine\n",
)
.unwrap();
fs::write(marker_file(&tmp), "4").unwrap();
install_system_skills(tmp.path()).unwrap();
assert!(skill_dir(&tmp, "v4-best-practices").exists());
let body = fs::read_to_string(skill_file(&tmp, "v4-best-practices")).unwrap();
assert!(
body.contains("user-owned"),
"modified body must be preserved"
);
}
#[test]
fn upgrade_preserves_user_modified_bundled_skill_body() {
let tmp = TempDir::new().unwrap();
install_system_skills(tmp.path()).unwrap();
let path = skill_file(&tmp, "debug");
fs::write(
&path,
"---\nname: debug\ndescription: customized\n---\n\n# custom\n",
)
.unwrap();
fs::write(marker_file(&tmp), "4").unwrap();
install_system_skills(tmp.path()).unwrap();
let body = fs::read_to_string(path).unwrap();
assert!(
body.contains("customized"),
"user edit must not be overwritten by name alone"
);
}
#[test]
fn end_user_pack_skills_parse_for_discovery() {
let tmp = TempDir::new().unwrap();
install_system_skills(tmp.path()).unwrap();
let registry = crate::skills::SkillRegistry::discover(tmp.path());
assert!(
registry.warnings().is_empty(),
"bundled skills should parse cleanly: {:?}",
registry.warnings()
);
for name in [
"debug", "test", "review", "document", "docx", "release", "plan", "verify",
] {
assert!(registry.get(name).is_some(), "{name} must be discoverable");
}
}
}