using MeetingAssistant; using MeetingAssistant.MeetingNotes; using MeetingAssistant.Summary; using Microsoft.Extensions.Options; namespace MeetingAssistant.Tests; public sealed class MeetingSummaryInstructionBuilderTests { [Fact] public async Task BuilderAddsBoundMetadataWithoutAgentsAndRequestGuidanceToCustomPrompt() { var root = Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N")); var projectsRoot = Path.Combine(root, "Projects"); var artifacts = CreateArtifacts(root); await WriteMeetingNoteAsync(artifacts.MeetingNotePath, ["Alpha"]); Directory.CreateDirectory(Path.Combine(projectsRoot, "Alpha")); await File.WriteAllTextAsync(Path.Combine(projectsRoot, "Alpha", "PROJECT.md"), "---\nname: Alpha Platform\ndescription: Builds meeting automation\n---\nInternal notes"); await WriteProjectAgentsAsync(projectsRoot, "Unbound", "Do not expose these instructions"); var builder = new MeetingSummaryInstructionBuilder(Options.Create(new MeetingAssistantOptions { Agent = new AgentOptions { InitialPrompt = "Custom base." }, Vault = new VaultOptions { ProjectsFolder = projectsRoot } })); var instructions = await builder.BuildAsync(artifacts, CancellationToken.None); Assert.StartsWith("Custom base.", instructions); Assert.Contains("# Alpha", instructions); Assert.Contains("Alpha Platform", instructions); Assert.Contains("Builds meeting automation", instructions); Assert.Contains("PROJECT.md", instructions); Assert.Contains("256", instructions); Assert.Contains("request_project_association", instructions); Assert.Contains("request_workflow_change", instructions); Assert.DoesNotContain("Internal notes", instructions); Assert.DoesNotContain("Do not expose", instructions); } [Fact] public async Task BuilderUsesDefaultPromptWhenConfiguredPromptIsBlank() { var root = Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N")); var artifacts = CreateArtifacts(root); await WriteMeetingNoteAsync(artifacts.MeetingNotePath, []); var builder = new MeetingSummaryInstructionBuilder(Options.Create(new MeetingAssistantOptions { Agent = new AgentOptions { InitialPrompt = " " }, Vault = new VaultOptions { ProjectsFolder = Path.Combine(root, "Projects") } })); var instructions = await builder.BuildAsync(artifacts, CancellationToken.None); Assert.Contains("You are the Meeting Assistant summary agent.", instructions); Assert.Contains("include only the most relevant cropped screenshots", instructions); Assert.Contains("Do not include every cropped screenshot", instructions); Assert.Contains("encode spaces in image-link targets as `%20`", instructions); Assert.Contains("Use add_attendee and remove_attendee", instructions); Assert.Contains("partial screenshot", instructions); Assert.Contains("override_speaker", instructions); Assert.Contains("very certain", instructions); Assert.Contains("merge=true", instructions); Assert.Contains("delete_identity", instructions); Assert.Contains("wrongfully matched", instructions); Assert.Contains("oneliner", instructions); Assert.Contains("very short", instructions); Assert.Contains("conciseness is more important", instructions); Assert.Contains("generated default title", instructions); Assert.Contains("purpose of the meeting is clear", instructions); Assert.Contains("title parameter", instructions); } [Fact] public async Task DefaultPromptTreatsAssistantContextAsMeetingMemoryForUncertainty() { var root = Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N")); var artifacts = CreateArtifacts(root); await WriteMeetingNoteAsync(artifacts.MeetingNotePath, []); var builder = new MeetingSummaryInstructionBuilder(Options.Create(new MeetingAssistantOptions { Agent = new AgentOptions { InitialPrompt = " " }, Vault = new VaultOptions { ProjectsFolder = Path.Combine(root, "Projects") } })); var instructions = await builder.BuildAsync(artifacts, CancellationToken.None); Assert.Contains("meeting-specific memory", instructions); Assert.Contains("unexpected problems", instructions); Assert.Contains("missing information", instructions); Assert.Contains("assumptions", instructions); Assert.Contains("write_context", instructions); } [Fact] public async Task BuilderUsesConfiguredPrompt() { var root = Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N")); var artifacts = CreateArtifacts(root); await WriteMeetingNoteAsync(artifacts.MeetingNotePath, []); var builder = new MeetingSummaryInstructionBuilder(Options.Create(new MeetingAssistantOptions { Agent = new AgentOptions { InitialPrompt = "Custom summarizer instructions." }, Vault = new VaultOptions { ProjectsFolder = Path.Combine(root, "Projects") } })); var instructions = await builder.BuildAsync(artifacts, CancellationToken.None); Assert.StartsWith("Custom summarizer instructions.", instructions); Assert.Contains("request_project_association", instructions); } [Fact] public async Task BuilderAppendsAgentsFilesForBoundProjects() { var root = Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N")); var projectsRoot = Path.Combine(root, "Projects"); var artifacts = CreateArtifacts(root); await WriteMeetingNoteAsync(artifacts.MeetingNotePath, ["Beta", "Alpha", "Missing"]); await WriteProjectAgentsAsync(projectsRoot, "Alpha", "Alpha instructions."); await WriteProjectAgentsAsync(projectsRoot, "Beta", "Beta instructions."); Directory.CreateDirectory(Path.Combine(projectsRoot, "Missing")); var builder = new MeetingSummaryInstructionBuilder(Options.Create(new MeetingAssistantOptions { Agent = new AgentOptions { InitialPrompt = "Base." }, Vault = new VaultOptions { ProjectsFolder = projectsRoot } })); var instructions = await builder.BuildAsync(artifacts, CancellationToken.None); Assert.StartsWith("Base.", instructions); Assert.Contains("---\nprojects:", NormalizeLineEndings(instructions)); Assert.Contains("# Alpha", instructions); Assert.Contains("Alpha instructions.", instructions); Assert.Contains("# Beta", instructions); Assert.Contains("Beta instructions.", instructions); } private static MeetingSessionArtifacts CreateArtifacts(string root) { return new MeetingSessionArtifacts( Path.Combine(root, "Meetings", "Notes", "meeting.md"), Path.Combine(root, "Meetings", "Transcripts", "transcript.md"), Path.Combine(root, "Meetings", "Assistant Context", "context.md"), Path.Combine(root, "Meetings", "Summaries", "summary.md")); } private static async Task WriteMeetingNoteAsync(string path, IReadOnlyList projects) { Directory.CreateDirectory(Path.GetDirectoryName(path)!); var projectLines = projects.Count == 0 ? "projects: []" : "projects:\n" + string.Join('\n', projects.Select(project => $"- {project}")); await File.WriteAllTextAsync( path, $""" --- title: Meeting {projectLines} --- """); } private static async Task WriteProjectAgentsAsync(string projectsRoot, string project, string content) { var folder = Path.Combine(projectsRoot, project); Directory.CreateDirectory(folder); await File.WriteAllTextAsync(Path.Combine(folder, "AGENTS.md"), content); } private static string NormalizeLineEndings(string value) { return value.Replace("\r\n", "\n", StringComparison.Ordinal); } }