Public Access
PR and Push Build/Test / build-and-test (push) Successful in 12m0s
Append successful offline recovery with a duplicate-warning marker and retain failed replay sources. Offer newly available microphones through confirmation notifications. Add project discovery metadata and blocking association approval, plus nonblocking workflow requests that start a separate settings-agent conversation. Include regression tests, OpenSpec changes, and verified local Release startup.
176 lines
8.2 KiB
C#
176 lines
8.2 KiB
C#
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<string> 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);
|
|
}
|
|
}
|