using MeetingAssistant; using MeetingAssistant.MeetingNotes; using MeetingAssistant.Summary; using Microsoft.Extensions.Options; namespace MeetingAssistant.Tests; public sealed class MeetingSummaryInstructionBuilderTests { [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.Equal("Custom summarizer instructions.", 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); var expected = """ Base. --- projects: # Alpha Alpha instructions. # Beta Beta instructions. """; Assert.Equal(NormalizeLineEndings(expected), NormalizeLineEndings(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); } }