using MeetingAssistant.MeetingNotes; using YamlDotNet.Serialization; namespace MeetingAssistant.Summary; public sealed record BoundMeetingProject(string Name, string Path); public sealed class BoundMeetingProjectResolver { private static readonly IDeserializer YamlDeserializer = new DeserializerBuilder() .IgnoreUnmatchedProperties() .Build(); private readonly MeetingAssistantOptions options; public BoundMeetingProjectResolver(MeetingAssistantOptions options) { this.options = options; } public string ProjectsRoot => VaultPath.Resolve(options.Vault, options.Vault.ProjectsFolder); public async Task> GetBoundProjectsAsync( MeetingSessionArtifacts artifacts, CancellationToken cancellationToken = default) { if (!Directory.Exists(ProjectsRoot)) { return []; } var projectNames = await ReadMeetingProjectNamesAsync(artifacts.MeetingNotePath, cancellationToken); if (projectNames.Count == 0) { return []; } return Directory.EnumerateDirectories(ProjectsRoot) .Select(path => new BoundMeetingProject(Path.GetFileName(path), path)) .Where(project => projectNames.Contains(project.Name)) .OrderBy(project => project.Name, StringComparer.OrdinalIgnoreCase) .ToList(); } private static async Task> ReadMeetingProjectNamesAsync( string meetingNotePath, CancellationToken cancellationToken) { if (!File.Exists(meetingNotePath)) { return []; } var content = await File.ReadAllTextAsync(meetingNotePath, cancellationToken); var document = MarkdownDocumentParser.SplitOptional(content); if (!document.HasFrontmatter) { return []; } var frontmatter = YamlDeserializer.Deserialize(document.Frontmatter) ?? new ProjectFrontmatter(); return (frontmatter.Projects ?? []) .Where(project => !string.IsNullOrWhiteSpace(project)) .Select(project => project.Trim()) .ToHashSet(StringComparer.OrdinalIgnoreCase); } private sealed class ProjectFrontmatter { [YamlMember(Alias = "projects")] public List? Projects { get; set; } } }