//! Skill registered to a project. //! //! Skills can be sourced manually (by project admin) or auto-discovered from //! repositories within the project (via `.claude/skills/` directory scanning). use crate::{DateTimeUtc, ProjectId, RepoId, UserId}; use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; /// Skill source: `"manual"` or `"repo"`. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum SkillSource { Manual, Repo, } impl std::fmt::Display for SkillSource { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { SkillSource::Manual => write!(f, "manual"), SkillSource::Repo => write!(f, "repo"), } } } impl std::str::FromStr for SkillSource { type Err = &'static str; fn from_str(s: &str) -> Result { match s { "manual" => Ok(SkillSource::Manual), "repo" => Ok(SkillSource::Repo), _ => Err("unknown skill source"), } } } /// Parsed frontmatter from a skill's SKILL.md file. #[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct SkillMetadata { /// Human-readable name (falls back to slug/folder name). #[serde(default)] pub name: Option, /// Short description of what the skill does. #[serde(default)] pub description: Option, /// SPDX license identifier. #[serde(default)] pub license: Option, /// Compatibility notes (e.g. "Requires openspec CLI"). #[serde(default)] pub compatibility: Option, /// Free-form metadata from the frontmatter. #[serde(default)] pub metadata: serde_json::Value, } impl From for SkillMetadata { fn from(v: serde_json::Value) -> Self { serde_json::from_value(v).unwrap_or_default() } } /// Skill record persisted in the `project_skill` table. #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)] #[sea_orm(table_name = "project_skill")] pub struct Model { #[sea_orm(primary_key)] pub id: i64, /// Project this skill belongs to. pub project_uuid: ProjectId, /// URL-safe identifier, unique within a project. pub slug: String, /// Display name (extracted from frontmatter or folder name). pub name: String, /// Optional short description. pub description: Option, /// `"manual"` or `"repo"`. pub source: String, /// If source=repo, the repo this skill was discovered from. #[sea_orm(nullable)] pub repo_id: Option, /// If source=repo, the commit SHA where the skill was found. #[sea_orm(nullable)] pub commit_sha: Option, /// If source=repo, the blob SHA of the SKILL.md file. #[sea_orm(nullable)] pub blob_hash: Option, /// Raw markdown content (SKILL.md body after frontmatter). pub content: String, /// Full frontmatter as JSON. #[sea_orm(column_type = "JsonBinary")] pub metadata: serde_json::Value, /// Whether this skill is currently active. pub enabled: bool, /// Who added this skill (null for repo-sourced skills). #[sea_orm(nullable)] pub created_by: Option, pub created_at: DateTimeUtc, pub updated_at: DateTimeUtc, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation {} impl ActiveModelBehavior for ActiveModel {} impl Model { pub fn source_enum(&self) -> Result { self.source.parse() } pub fn metadata_parsed(&self) -> SkillMetadata { SkillMetadata::from(self.metadata.clone()) } }