using Pgvector; using TeamUp.SharedKernel.Domain; namespace TeamUp.Modules.Skills.Domain; /// /// An indexed skill atom: the projection of a SKILL.md (Git is the source of truth) into a /// queryable Postgres + pgvector row. Identified by (SkillKey, Version). /// internal sealed class Skill : Entity { public string SkillKey { get; private set; } = null!; public string Name { get; private set; } = null!; public string Version { get; private set; } = null!; public string? Summary { get; private set; } public List Roles { get; private set; } = []; public string? Inputs { get; private set; } public string? Outputs { get; private set; } public List Actions { get; private set; } = []; public List Tools { get; private set; } = []; public List Context { get; private set; } = []; public List GoldenTests { get; private set; } = []; public SkillVisibility Visibility { get; private set; } public SkillTier MinTier { get; private set; } public SkillStatus Status { get; private set; } public string Body { get; private set; } = null!; public string ContentHash { get; private set; } = null!; public string? SourceRepo { get; private set; } public string? SourcePath { get; private set; } public string? SourceCommit { get; private set; } public Vector? Embedding { get; private set; } public DateTimeOffset IndexedAtUtc { get; private set; } public DateTimeOffset UpdatedAtUtc { get; private set; } private Skill() { } public static Skill Create(string skillKey, string version, DateTimeOffset nowUtc) => new() { SkillKey = skillKey, Version = version, IndexedAtUtc = nowUtc }; /// (Re)projects a parsed manifest + body onto this row. Used for both insert and update. public void Index( SkillManifest manifest, string body, string contentHash, string? sourceRepo, string? sourcePath, string? sourceCommit, Vector? embedding, SkillStatus status, DateTimeOffset nowUtc) { Name = string.IsNullOrWhiteSpace(manifest.Name) ? manifest.Id : manifest.Name; Version = manifest.Version; Summary = manifest.Summary; Roles = manifest.Roles; Inputs = manifest.Inputs; Outputs = manifest.Outputs; Actions = manifest.Actions .Select(a => new SkillAction { Name = a.Name, Risk = ParseRisk(a.Risk), Description = a.Description }) .ToList(); Tools = manifest.Tools; Context = manifest.Context; GoldenTests = manifest.GoldenTests; Visibility = ParseVisibility(manifest.Visibility); MinTier = ParseTier(manifest.MinTier); Status = status; Body = body; ContentHash = contentHash; SourceRepo = sourceRepo; SourcePath = sourcePath; SourceCommit = sourceCommit; Embedding = embedding; UpdatedAtUtc = nowUtc; } private static string Normalize(string value) => value.Trim().Replace("-", string.Empty).Replace("_", string.Empty); private static ActionRisk ParseRisk(string value) => Normalize(value).ToLowerInvariant() switch { "draft" => ActionRisk.Draft, "publish" => ActionRisk.Publish, "destructive" => ActionRisk.Destructive, _ => ActionRisk.Read, }; private static SkillVisibility ParseVisibility(string value) => Normalize(value).ToLowerInvariant() is "privatetoorg" or "private" ? SkillVisibility.PrivateToOrg : SkillVisibility.Public; private static SkillTier ParseTier(string value) => Normalize(value).ToLowerInvariant() switch { "team" => SkillTier.Team, "scale" => SkillTier.Scale, "enterprise" => SkillTier.Enterprise, _ => SkillTier.Free, }; }