Public Access
689 lines
32 KiB
C#
689 lines
32 KiB
C#
using MeetingAssistant.MeetingNotes;
|
|
using MeetingAssistant.Summary;
|
|
using MeetingAssistant.Transcription;
|
|
using Microsoft.Extensions.Options;
|
|
|
|
namespace MeetingAssistant.Tests;
|
|
|
|
public sealed class MeetingSummaryToolTests
|
|
{
|
|
[Fact]
|
|
public async Task ToolsReadMeetingInputsAndWriteSummaryNote()
|
|
{
|
|
var root = Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N"));
|
|
var artifacts = new MeetingSessionArtifacts(
|
|
MeetingNotePath: Path.Combine(root, "Meetings", "Notes", "meeting.md"),
|
|
TranscriptPath: Path.Combine(root, "Meetings", "Transcripts", "transcript.md"),
|
|
AssistantContextPath: Path.Combine(root, "Meetings", "Assistant Context", "context.md"),
|
|
SummaryPath: Path.Combine(root, "Meetings", "Summaries", "summary.md"));
|
|
Directory.CreateDirectory(Path.GetDirectoryName(artifacts.MeetingNotePath)!);
|
|
Directory.CreateDirectory(Path.GetDirectoryName(artifacts.TranscriptPath)!);
|
|
Directory.CreateDirectory(Path.GetDirectoryName(artifacts.AssistantContextPath)!);
|
|
await File.WriteAllTextAsync(
|
|
artifacts.MeetingNotePath,
|
|
"""
|
|
---
|
|
title: Meeting
|
|
start_time: "2026-05-20T10:00:00.0000000+02:00"
|
|
end_time: "2026-05-20T10:30:00.0000000+02:00"
|
|
attendees:
|
|
- Ada
|
|
projects:
|
|
- Meeting Assistant
|
|
transcript: "[[../Transcripts/transcript|Transcript]]"
|
|
assistant_context: "[[../Assistant Context/context|Assistant Context]]"
|
|
summary: "[[../Summaries/summary|Summary]]"
|
|
---
|
|
|
|
User note line.
|
|
""");
|
|
await File.WriteAllTextAsync(artifacts.TranscriptPath, "Ada: Transcript line.");
|
|
await File.WriteAllTextAsync(artifacts.AssistantContextPath, "Context line.");
|
|
var tools = new MeetingSummaryTools(artifacts);
|
|
|
|
Assert.Equal("Ada: Transcript line.", await tools.ReadTranscript());
|
|
Assert.Contains("title: Meeting", await tools.ReadMeetingNote());
|
|
Assert.Equal("Context line.", await tools.ReadContext());
|
|
Assert.Equal("User note line.", await tools.ReadUserNotes());
|
|
Assert.Equal("", await tools.ReadGlossary());
|
|
|
|
Assert.False(tools.SummaryWasWritten);
|
|
var result = await tools.WriteSummary("# Summary\n\n- Done.", "Done items");
|
|
|
|
Assert.Equal(artifacts.SummaryPath, result);
|
|
Assert.True(tools.SummaryWasWritten);
|
|
var summary = await File.ReadAllTextAsync(artifacts.SummaryPath);
|
|
Assert.Contains("title: Meeting", summary);
|
|
Assert.Contains("oneliner: Done items", summary);
|
|
Assert.Contains("start_time: \"2026-05-20T10:00:00.0000000+02:00\"", summary);
|
|
Assert.Contains("end_time: \"2026-05-20T10:30:00.0000000+02:00\"", summary);
|
|
Assert.Contains("attendees:", summary);
|
|
Assert.Contains("- Ada", summary);
|
|
Assert.Contains("projects:", summary);
|
|
Assert.Contains("- Meeting Assistant", summary);
|
|
Assert.Contains("meeting: \"[[../Notes/meeting|Meeting Note]]\"", summary);
|
|
Assert.Contains("transcript: \"[[../Transcripts/transcript|Transcript]]\"", summary);
|
|
Assert.Contains("assistant_context: \"[[../Assistant Context/context|Assistant Context]]\"", summary);
|
|
Assert.DoesNotContain("summary:", summary);
|
|
Assert.Contains("# Summary\n\n- Done.", summary);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task WriteSummaryPreservesExistingSummaryAttendees()
|
|
{
|
|
var root = Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N"));
|
|
var artifacts = new MeetingSessionArtifacts(
|
|
MeetingNotePath: Path.Combine(root, "Meetings", "Notes", "meeting.md"),
|
|
TranscriptPath: Path.Combine(root, "Meetings", "Transcripts", "transcript.md"),
|
|
AssistantContextPath: Path.Combine(root, "Meetings", "Assistant Context", "context.md"),
|
|
SummaryPath: Path.Combine(root, "Meetings", "Summaries", "summary.md"));
|
|
Directory.CreateDirectory(Path.GetDirectoryName(artifacts.MeetingNotePath)!);
|
|
Directory.CreateDirectory(Path.GetDirectoryName(artifacts.SummaryPath)!);
|
|
await File.WriteAllTextAsync(
|
|
artifacts.MeetingNotePath,
|
|
"""
|
|
---
|
|
title: Meeting
|
|
attendees:
|
|
- Ada
|
|
- Grace
|
|
---
|
|
""");
|
|
await File.WriteAllTextAsync(
|
|
artifacts.SummaryPath,
|
|
"""
|
|
---
|
|
title: Existing
|
|
attendees:
|
|
- Preserved
|
|
---
|
|
|
|
Old summary.
|
|
""");
|
|
var tools = new MeetingSummaryTools(artifacts);
|
|
|
|
await tools.WriteSummary("# Summary", "Preserved attendee summary");
|
|
|
|
var summary = await File.ReadAllTextAsync(artifacts.SummaryPath);
|
|
Assert.Contains("attendees:", summary);
|
|
Assert.Contains("- Preserved", summary);
|
|
Assert.DoesNotContain("- Ada", summary);
|
|
Assert.DoesNotContain("- Grace", summary);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task WriteSummaryCopiesCurrentMeetingProjectsOverExistingSummaryProjects()
|
|
{
|
|
var root = Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N"));
|
|
var artifacts = new MeetingSessionArtifacts(
|
|
MeetingNotePath: Path.Combine(root, "Meetings", "Notes", "meeting.md"),
|
|
TranscriptPath: Path.Combine(root, "Meetings", "Transcripts", "transcript.md"),
|
|
AssistantContextPath: Path.Combine(root, "Meetings", "Assistant Context", "context.md"),
|
|
SummaryPath: Path.Combine(root, "Meetings", "Summaries", "summary.md"));
|
|
Directory.CreateDirectory(Path.GetDirectoryName(artifacts.MeetingNotePath)!);
|
|
Directory.CreateDirectory(Path.GetDirectoryName(artifacts.SummaryPath)!);
|
|
await File.WriteAllTextAsync(
|
|
artifacts.MeetingNotePath,
|
|
"""
|
|
---
|
|
title: Meeting
|
|
projects:
|
|
- Current Project
|
|
- Second Project
|
|
---
|
|
""");
|
|
await File.WriteAllTextAsync(
|
|
artifacts.SummaryPath,
|
|
"""
|
|
---
|
|
title: Existing
|
|
projects:
|
|
- Stale Project
|
|
---
|
|
|
|
Old summary.
|
|
""");
|
|
var tools = new MeetingSummaryTools(artifacts);
|
|
|
|
await tools.WriteSummary("# Summary", "Current project summary");
|
|
|
|
var summary = await File.ReadAllTextAsync(artifacts.SummaryPath);
|
|
Assert.Contains("projects:", summary);
|
|
Assert.Contains("- Current Project", summary);
|
|
Assert.Contains("- Second Project", summary);
|
|
Assert.DoesNotContain("- Stale Project", summary);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ToolsCanAddAndRemoveMeetingAttendees()
|
|
{
|
|
var root = Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N"));
|
|
var artifacts = new MeetingSessionArtifacts(
|
|
MeetingNotePath: Path.Combine(root, "Meetings", "Notes", "meeting.md"),
|
|
TranscriptPath: Path.Combine(root, "Meetings", "Transcripts", "transcript.md"),
|
|
AssistantContextPath: Path.Combine(root, "Meetings", "Assistant Context", "context.md"),
|
|
SummaryPath: Path.Combine(root, "Meetings", "Summaries", "summary.md"));
|
|
Directory.CreateDirectory(Path.GetDirectoryName(artifacts.MeetingNotePath)!);
|
|
await File.WriteAllTextAsync(
|
|
artifacts.MeetingNotePath,
|
|
"""
|
|
---
|
|
title: Meeting
|
|
attendees:
|
|
- Ada
|
|
- Grace Hopper <grace@example.com>
|
|
projects:
|
|
- Meeting Assistant
|
|
transcript: "[[../Transcripts/transcript|Transcript]]"
|
|
assistant_context: "[[../Assistant Context/context|Assistant Context]]"
|
|
summary: "[[../Summaries/summary|Summary]]"
|
|
---
|
|
|
|
User note line.
|
|
""");
|
|
var tools = new MeetingSummaryTools(artifacts);
|
|
|
|
Assert.Equal("Added attendee Manuel.", await tools.AddAttendee("Manuel"));
|
|
Assert.Equal("Attendee Ada already exists.", await tools.AddAttendee("Ada"));
|
|
Assert.Equal("Removed attendee Grace Hopper.", await tools.RemoveAttendee("Grace Hopper"));
|
|
|
|
var meetingNote = await File.ReadAllTextAsync(artifacts.MeetingNotePath);
|
|
Assert.Contains("- Ada", meetingNote);
|
|
Assert.Contains("- Manuel", meetingNote);
|
|
Assert.DoesNotContain("Grace Hopper", meetingNote);
|
|
Assert.Contains("User note line.", meetingNote);
|
|
Assert.Contains("transcript: \"[[../Transcripts/transcript|Transcript]]\"", meetingNote);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ToolsOverrideSpeakerInTranscriptAndRecordOverride()
|
|
{
|
|
var root = Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N"));
|
|
var artifacts = new MeetingSessionArtifacts(
|
|
MeetingNotePath: Path.Combine(root, "Meetings", "Notes", "meeting.md"),
|
|
TranscriptPath: Path.Combine(root, "Meetings", "Transcripts", "transcript.md"),
|
|
AssistantContextPath: Path.Combine(root, "Meetings", "Assistant Context", "context.md"),
|
|
SummaryPath: Path.Combine(root, "Meetings", "Summaries", "summary.md"));
|
|
Directory.CreateDirectory(Path.GetDirectoryName(artifacts.TranscriptPath)!);
|
|
Directory.CreateDirectory(Path.GetDirectoryName(artifacts.AssistantContextPath)!);
|
|
await File.WriteAllTextAsync(
|
|
artifacts.TranscriptPath,
|
|
"""
|
|
# Meeting Transcript
|
|
|
|
[00:00:01] Guest-01: Hello from Sabrina.
|
|
[00:00:02] Guest-02: Hello from someone else.
|
|
""");
|
|
await File.WriteAllTextAsync(
|
|
artifacts.AssistantContextPath,
|
|
"""
|
|
---
|
|
state: summarizing
|
|
---
|
|
|
|
Existing context.
|
|
""");
|
|
var tools = new MeetingSummaryTools(artifacts);
|
|
|
|
var result = await tools.OverrideSpeaker("Guest-01", "Sabrina");
|
|
|
|
Assert.Equal("Overrode speaker Guest-01 with Sabrina.", result);
|
|
var transcript = await File.ReadAllTextAsync(artifacts.TranscriptPath);
|
|
Assert.Contains("[00:00:01] Sabrina: Hello from Sabrina.", transcript);
|
|
Assert.Contains("[00:00:02] Guest-02: Hello from someone else.", transcript);
|
|
var context = await File.ReadAllTextAsync(artifacts.AssistantContextPath);
|
|
Assert.Contains("state: summarizing", context);
|
|
Assert.Contains("meeting-assistant:speaker-override", context);
|
|
Assert.Contains("\"from\":\"Guest-01\"", context);
|
|
Assert.Contains("\"to\":\"Sabrina\"", context);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ToolsRefuseOverrideWhenAssistantContextIsMissing()
|
|
{
|
|
var root = Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N"));
|
|
var artifacts = new MeetingSessionArtifacts(
|
|
MeetingNotePath: Path.Combine(root, "Meetings", "Notes", "meeting.md"),
|
|
TranscriptPath: Path.Combine(root, "Meetings", "Transcripts", "transcript.md"),
|
|
AssistantContextPath: Path.Combine(root, "Meetings", "Assistant Context", "context.md"),
|
|
SummaryPath: Path.Combine(root, "Meetings", "Summaries", "summary.md"));
|
|
Directory.CreateDirectory(Path.GetDirectoryName(artifacts.TranscriptPath)!);
|
|
await File.WriteAllTextAsync(
|
|
artifacts.TranscriptPath,
|
|
"""
|
|
# Meeting Transcript
|
|
|
|
[00:00:01] Guest-01: Hello from Sabrina.
|
|
""");
|
|
var tools = new MeetingSummaryTools(artifacts);
|
|
|
|
var result = await tools.OverrideSpeaker("Guest-01", "Sabrina");
|
|
|
|
Assert.StartsWith("Refused: assistant context file does not exist", result, StringComparison.Ordinal);
|
|
Assert.False(File.Exists(artifacts.AssistantContextPath));
|
|
var transcript = await File.ReadAllTextAsync(artifacts.TranscriptPath);
|
|
Assert.Contains("[00:00:01] Guest-01: Hello from Sabrina.", transcript);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ToolsRefuseOverrideWhenReplacementSpeakerAlreadyExistsWithoutMerge()
|
|
{
|
|
var root = Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N"));
|
|
var artifacts = new MeetingSessionArtifacts(
|
|
MeetingNotePath: Path.Combine(root, "Meetings", "Notes", "meeting.md"),
|
|
TranscriptPath: Path.Combine(root, "Meetings", "Transcripts", "transcript.md"),
|
|
AssistantContextPath: Path.Combine(root, "Meetings", "Assistant Context", "context.md"),
|
|
SummaryPath: Path.Combine(root, "Meetings", "Summaries", "summary.md"));
|
|
Directory.CreateDirectory(Path.GetDirectoryName(artifacts.TranscriptPath)!);
|
|
Directory.CreateDirectory(Path.GetDirectoryName(artifacts.AssistantContextPath)!);
|
|
await File.WriteAllTextAsync(
|
|
artifacts.TranscriptPath,
|
|
"""
|
|
# Meeting Transcript
|
|
|
|
[00:00:01] Guest-01: I am probably Sabrina.
|
|
[00:00:02] Sabrina: I am already in the transcript.
|
|
""");
|
|
await File.WriteAllTextAsync(
|
|
artifacts.AssistantContextPath,
|
|
"""
|
|
---
|
|
state: summarizing
|
|
---
|
|
|
|
Existing context.
|
|
""");
|
|
var tools = new MeetingSummaryTools(artifacts);
|
|
|
|
var result = await tools.OverrideSpeaker("Guest-01", "Sabrina");
|
|
|
|
Assert.Equal(
|
|
"Refused: transcript already contains speaker Sabrina. Call override_speaker with merge=true only when you are certain both speakers are the same identity.",
|
|
result);
|
|
var transcript = await File.ReadAllTextAsync(artifacts.TranscriptPath);
|
|
Assert.Contains("[00:00:01] Guest-01: I am probably Sabrina.", transcript);
|
|
Assert.Contains("[00:00:02] Sabrina: I am already in the transcript.", transcript);
|
|
var context = await File.ReadAllTextAsync(artifacts.AssistantContextPath);
|
|
Assert.DoesNotContain("meeting-assistant:speaker-override", context);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ToolsMergeOverrideWhenReplacementSpeakerAlreadyExistsWithMerge()
|
|
{
|
|
var root = Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N"));
|
|
var artifacts = new MeetingSessionArtifacts(
|
|
MeetingNotePath: Path.Combine(root, "Meetings", "Notes", "meeting.md"),
|
|
TranscriptPath: Path.Combine(root, "Meetings", "Transcripts", "transcript.md"),
|
|
AssistantContextPath: Path.Combine(root, "Meetings", "Assistant Context", "context.md"),
|
|
SummaryPath: Path.Combine(root, "Meetings", "Summaries", "summary.md"));
|
|
Directory.CreateDirectory(Path.GetDirectoryName(artifacts.TranscriptPath)!);
|
|
Directory.CreateDirectory(Path.GetDirectoryName(artifacts.AssistantContextPath)!);
|
|
await File.WriteAllTextAsync(
|
|
artifacts.TranscriptPath,
|
|
"""
|
|
# Meeting Transcript
|
|
|
|
[00:00:01] Guest-01: I am Sabrina.
|
|
[00:00:02] Sabrina: I am already in the transcript.
|
|
""");
|
|
await File.WriteAllTextAsync(
|
|
artifacts.AssistantContextPath,
|
|
"""
|
|
---
|
|
state: summarizing
|
|
---
|
|
|
|
Existing context.
|
|
""");
|
|
var tools = new MeetingSummaryTools(artifacts);
|
|
|
|
var result = await tools.OverrideSpeaker("Guest-01", "Sabrina", merge: true);
|
|
|
|
Assert.Equal("Merged speaker Guest-01 into Sabrina.", result);
|
|
var transcript = await File.ReadAllTextAsync(artifacts.TranscriptPath);
|
|
Assert.Contains("[00:00:01] Sabrina: I am Sabrina.", transcript);
|
|
Assert.Contains("[00:00:02] Sabrina: I am already in the transcript.", transcript);
|
|
Assert.DoesNotContain("Guest-01:", transcript);
|
|
var context = await File.ReadAllTextAsync(artifacts.AssistantContextPath);
|
|
Assert.Contains("meeting-assistant:speaker-override", context);
|
|
Assert.Contains("\"from\":\"Guest-01\"", context);
|
|
Assert.Contains("\"to\":\"Sabrina\"", context);
|
|
Assert.Contains("\"merge\":true", context);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ToolsDeleteIdentityInTranscriptAndRecordDeletion()
|
|
{
|
|
var root = Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N"));
|
|
var artifacts = new MeetingSessionArtifacts(
|
|
MeetingNotePath: Path.Combine(root, "Meetings", "Notes", "meeting.md"),
|
|
TranscriptPath: Path.Combine(root, "Meetings", "Transcripts", "transcript.md"),
|
|
AssistantContextPath: Path.Combine(root, "Meetings", "Assistant Context", "context.md"),
|
|
SummaryPath: Path.Combine(root, "Meetings", "Summaries", "summary.md"));
|
|
Directory.CreateDirectory(Path.GetDirectoryName(artifacts.TranscriptPath)!);
|
|
Directory.CreateDirectory(Path.GetDirectoryName(artifacts.AssistantContextPath)!);
|
|
await File.WriteAllTextAsync(
|
|
artifacts.TranscriptPath,
|
|
"""
|
|
# Meeting Transcript
|
|
|
|
[00:00:01] Sabrina: This was incorrectly matched.
|
|
[00:00:02] Removed-1: An earlier removed speaker.
|
|
[00:00:03] Guest-02: Hello from someone else.
|
|
[00:00:04] Sabrina: Another incorrect line.
|
|
""");
|
|
await File.WriteAllTextAsync(
|
|
artifacts.AssistantContextPath,
|
|
"""
|
|
---
|
|
state: summarizing
|
|
---
|
|
|
|
Existing context.
|
|
""");
|
|
var tools = new MeetingSummaryTools(artifacts);
|
|
|
|
var result = await tools.DeleteIdentity("Sabrina");
|
|
|
|
Assert.Equal("Deleted identity Sabrina and relabeled transcript speaker mentions to Removed-2.", result);
|
|
var transcript = await File.ReadAllTextAsync(artifacts.TranscriptPath);
|
|
Assert.Contains("[00:00:01] Removed-2: This was incorrectly matched.", transcript);
|
|
Assert.Contains("[00:00:02] Removed-1: An earlier removed speaker.", transcript);
|
|
Assert.Contains("[00:00:03] Guest-02: Hello from someone else.", transcript);
|
|
Assert.Contains("[00:00:04] Removed-2: Another incorrect line.", transcript);
|
|
Assert.DoesNotContain("Sabrina:", transcript);
|
|
var context = await File.ReadAllTextAsync(artifacts.AssistantContextPath);
|
|
Assert.Contains("state: summarizing", context);
|
|
Assert.Contains("meeting-assistant:speaker-identity-deletion", context);
|
|
Assert.Contains("\"identity\":\"Sabrina\"", context);
|
|
Assert.Contains("\"replacement\":\"Removed-2\"", context);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ToolsRefuseDeleteIdentityWhenAssistantContextIsMissing()
|
|
{
|
|
var root = Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N"));
|
|
var artifacts = new MeetingSessionArtifacts(
|
|
MeetingNotePath: Path.Combine(root, "Meetings", "Notes", "meeting.md"),
|
|
TranscriptPath: Path.Combine(root, "Meetings", "Transcripts", "transcript.md"),
|
|
AssistantContextPath: Path.Combine(root, "Meetings", "Assistant Context", "context.md"),
|
|
SummaryPath: Path.Combine(root, "Meetings", "Summaries", "summary.md"));
|
|
Directory.CreateDirectory(Path.GetDirectoryName(artifacts.TranscriptPath)!);
|
|
await File.WriteAllTextAsync(
|
|
artifacts.TranscriptPath,
|
|
"""
|
|
# Meeting Transcript
|
|
|
|
[00:00:01] Sabrina: This was incorrectly matched.
|
|
""");
|
|
var tools = new MeetingSummaryTools(artifacts);
|
|
|
|
var result = await tools.DeleteIdentity("Sabrina");
|
|
|
|
Assert.StartsWith("Refused: assistant context file does not exist", result, StringComparison.Ordinal);
|
|
Assert.False(File.Exists(artifacts.AssistantContextPath));
|
|
var transcript = await File.ReadAllTextAsync(artifacts.TranscriptPath);
|
|
Assert.Contains("[00:00:01] Sabrina: This was incorrectly matched.", transcript);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ReadToolsSupportClampedLineRanges()
|
|
{
|
|
var root = Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N"));
|
|
var artifacts = new MeetingSessionArtifacts(
|
|
MeetingNotePath: Path.Combine(root, "Meetings", "Notes", "meeting.md"),
|
|
TranscriptPath: Path.Combine(root, "Meetings", "Transcripts", "transcript.md"),
|
|
AssistantContextPath: Path.Combine(root, "Meetings", "Assistant Context", "context.md"),
|
|
SummaryPath: Path.Combine(root, "Meetings", "Summaries", "summary.md"));
|
|
var glossaryPath = Path.Combine(root, "Meetings", "dictation-words.md");
|
|
var projectRoot = Path.Combine(root, "Projects", "Project A");
|
|
var projectFile = Path.Combine(projectRoot, "notes.md");
|
|
Directory.CreateDirectory(Path.GetDirectoryName(artifacts.MeetingNotePath)!);
|
|
Directory.CreateDirectory(Path.GetDirectoryName(artifacts.TranscriptPath)!);
|
|
Directory.CreateDirectory(Path.GetDirectoryName(artifacts.AssistantContextPath)!);
|
|
Directory.CreateDirectory(Path.GetDirectoryName(glossaryPath)!);
|
|
Directory.CreateDirectory(projectRoot);
|
|
await File.WriteAllTextAsync(
|
|
artifacts.MeetingNotePath,
|
|
"""
|
|
---
|
|
projects:
|
|
- Project A
|
|
---
|
|
|
|
note one
|
|
note two
|
|
note three
|
|
""");
|
|
await File.WriteAllTextAsync(artifacts.TranscriptPath, "transcript one\ntranscript two\ntranscript three");
|
|
await File.WriteAllTextAsync(artifacts.AssistantContextPath, "context one\ncontext two\ncontext three");
|
|
await File.WriteAllTextAsync(glossaryPath, "word one\nword two\nword three");
|
|
await File.WriteAllTextAsync(projectFile, "project one\nproject two\nproject three");
|
|
var tools = new MeetingSummaryTools(
|
|
artifacts,
|
|
new MeetingAssistantOptions
|
|
{
|
|
Vault =
|
|
{
|
|
DictationWordsPath = glossaryPath,
|
|
ProjectsFolder = Path.Combine(root, "Projects")
|
|
}
|
|
});
|
|
|
|
Assert.Equal("transcript two", await tools.ReadTranscript(from: 2, to: 2));
|
|
Assert.Equal("context two\ncontext three", await tools.ReadContext(from: 2, to: 9));
|
|
Assert.Equal("note two\nnote three", await tools.ReadUserNotes(from: 2, to: 99));
|
|
Assert.Equal("word two", await tools.ReadGlossary(from: 2, to: 2));
|
|
Assert.Equal("project two", await tools.ReadProjectFile("Project A", "notes.md", from: 2, to: 2));
|
|
Assert.Equal("", await tools.ReadTranscript(from: 3, to: 2));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ToolsWriteAssistantContextBodyWithLineModes()
|
|
{
|
|
var root = Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N"));
|
|
var artifacts = new MeetingSessionArtifacts(
|
|
MeetingNotePath: Path.Combine(root, "Meetings", "Notes", "meeting.md"),
|
|
TranscriptPath: Path.Combine(root, "Meetings", "Transcripts", "transcript.md"),
|
|
AssistantContextPath: Path.Combine(root, "Meetings", "Assistant Context", "context.md"),
|
|
SummaryPath: Path.Combine(root, "Meetings", "Summaries", "summary.md"));
|
|
Directory.CreateDirectory(Path.GetDirectoryName(artifacts.AssistantContextPath)!);
|
|
await File.WriteAllTextAsync(
|
|
artifacts.AssistantContextPath,
|
|
"""
|
|
---
|
|
meeting: "[[../Notes/meeting|Meeting Note]]"
|
|
transcript: "[[../Transcripts/transcript|Transcript]]"
|
|
summary: "[[../Summaries/summary|Summary]]"
|
|
state: summarizing
|
|
---
|
|
|
|
line one
|
|
line two
|
|
""");
|
|
var tools = new MeetingSummaryTools(artifacts);
|
|
|
|
await tools.WriteContext("appended");
|
|
await tools.WriteContext("inserted", insert: 2);
|
|
await tools.WriteContext("replacement", from: 1, to: 1);
|
|
|
|
var context = await File.ReadAllTextAsync(artifacts.AssistantContextPath);
|
|
Assert.Contains("state: summarizing", context);
|
|
Assert.Contains("replacement\ninserted\nline two\nappended", context);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ToolsReplaceAssistantContextBodyOnlyWhenRequested()
|
|
{
|
|
var root = Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N"));
|
|
var artifacts = new MeetingSessionArtifacts(
|
|
MeetingNotePath: Path.Combine(root, "Meetings", "Notes", "meeting.md"),
|
|
TranscriptPath: Path.Combine(root, "Meetings", "Transcripts", "transcript.md"),
|
|
AssistantContextPath: Path.Combine(root, "Meetings", "Assistant Context", "context.md"),
|
|
SummaryPath: Path.Combine(root, "Meetings", "Summaries", "summary.md"));
|
|
Directory.CreateDirectory(Path.GetDirectoryName(artifacts.AssistantContextPath)!);
|
|
await File.WriteAllTextAsync(
|
|
artifacts.AssistantContextPath,
|
|
"""
|
|
---
|
|
state: summarizing
|
|
---
|
|
|
|
line one
|
|
""");
|
|
var tools = new MeetingSummaryTools(artifacts);
|
|
|
|
await tools.WriteContext("replacement", replace_file: true);
|
|
|
|
var context = await File.ReadAllTextAsync(artifacts.AssistantContextPath);
|
|
Assert.Contains("state: summarizing", context);
|
|
Assert.Contains("replacement", context);
|
|
Assert.DoesNotContain("line one", context);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ToolsRefuseWriteContextWhenAssistantContextIsMissing()
|
|
{
|
|
var root = Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N"));
|
|
var artifacts = new MeetingSessionArtifacts(
|
|
MeetingNotePath: Path.Combine(root, "Meetings", "Notes", "meeting.md"),
|
|
TranscriptPath: Path.Combine(root, "Meetings", "Transcripts", "transcript.md"),
|
|
AssistantContextPath: Path.Combine(root, "Meetings", "Assistant Context", "context.md"),
|
|
SummaryPath: Path.Combine(root, "Meetings", "Summaries", "summary.md"));
|
|
var tools = new MeetingSummaryTools(artifacts);
|
|
|
|
var result = await tools.WriteContext("new context");
|
|
|
|
Assert.StartsWith("Refused: assistant context file does not exist", result, StringComparison.Ordinal);
|
|
Assert.False(File.Exists(artifacts.AssistantContextPath));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ToolsWriteAssistantContextStripsAccidentalFrontmatterFromReplacementBody()
|
|
{
|
|
var root = Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N"));
|
|
var artifacts = new MeetingSessionArtifacts(
|
|
MeetingNotePath: Path.Combine(root, "Meetings", "Notes", "meeting.md"),
|
|
TranscriptPath: Path.Combine(root, "Meetings", "Transcripts", "transcript.md"),
|
|
AssistantContextPath: Path.Combine(root, "Meetings", "Assistant Context", "context.md"),
|
|
SummaryPath: Path.Combine(root, "Meetings", "Summaries", "summary.md"));
|
|
Directory.CreateDirectory(Path.GetDirectoryName(artifacts.AssistantContextPath)!);
|
|
await File.WriteAllTextAsync(
|
|
artifacts.AssistantContextPath,
|
|
"""
|
|
---
|
|
meeting: "[[../Notes/meeting|Meeting Note]]"
|
|
transcript: "[[../Transcripts/transcript|Transcript]]"
|
|
summary: "[[../Summaries/summary|Summary]]"
|
|
state: summarizing
|
|
---
|
|
|
|
line one
|
|
""");
|
|
var tools = new MeetingSummaryTools(artifacts);
|
|
|
|
await tools.WriteContext(
|
|
"""
|
|
---
|
|
state: finished
|
|
---
|
|
|
|
# Assistant Context
|
|
|
|
## Live Context
|
|
""",
|
|
replace_file: true);
|
|
|
|
var context = await File.ReadAllTextAsync(artifacts.AssistantContextPath);
|
|
Assert.Contains("state: summarizing", context);
|
|
Assert.DoesNotContain("state: finished", context);
|
|
Assert.Equal(2, context.Split("---", StringSplitOptions.None).Length - 1);
|
|
Assert.Contains("# Assistant Context", context);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ExternalAgentWritesAreAppendedToAssistantContextAsDiffs()
|
|
{
|
|
var root = Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N"));
|
|
var projectsRoot = Path.Combine(root, "Projects");
|
|
var projectRoot = Path.Combine(projectsRoot, "MeetingAssistant");
|
|
var projectFile = Path.Combine(projectRoot, "notes.md");
|
|
var dictionaryPath = Path.Combine(root, "Meetings", "Dictionary.md");
|
|
var artifacts = new MeetingSessionArtifacts(
|
|
MeetingNotePath: Path.Combine(root, "Meetings", "Notes", "meeting.md"),
|
|
TranscriptPath: Path.Combine(root, "Meetings", "Transcripts", "transcript.md"),
|
|
AssistantContextPath: Path.Combine(root, "Meetings", "Assistant Context", "context.md"),
|
|
SummaryPath: Path.Combine(root, "Meetings", "Summaries", "summary.md"));
|
|
Directory.CreateDirectory(projectRoot);
|
|
Directory.CreateDirectory(Path.GetDirectoryName(artifacts.MeetingNotePath)!);
|
|
Directory.CreateDirectory(Path.GetDirectoryName(artifacts.AssistantContextPath)!);
|
|
await File.WriteAllTextAsync(projectFile, "one\ntwo\nthree");
|
|
await File.WriteAllTextAsync(
|
|
artifacts.MeetingNotePath,
|
|
"""
|
|
---
|
|
title: Meeting
|
|
projects:
|
|
- MeetingAssistant
|
|
---
|
|
""");
|
|
await File.WriteAllTextAsync(artifacts.AssistantContextPath, "Context before.");
|
|
var options = new MeetingAssistantOptions
|
|
{
|
|
Vault =
|
|
{
|
|
DictationWordsPath = dictionaryPath,
|
|
ProjectsFolder = projectsRoot
|
|
}
|
|
};
|
|
var audit = new SummaryAgentWriteAudit(artifacts);
|
|
var tools = new MeetingSummaryTools(
|
|
artifacts,
|
|
options,
|
|
new MarkdownDictationWordStore(Options.Create(options)),
|
|
audit);
|
|
|
|
await tools.WriteContext("owned context");
|
|
await tools.WriteSummary("owned summary", "Owned summary");
|
|
await tools.WriteProjectFile("MeetingAssistant", "notes.md", "TWO", from: 2, to: 2);
|
|
await tools.AddDictationWord("PBI");
|
|
await audit.AppendToAssistantContextAsync(CancellationToken.None);
|
|
|
|
var context = await File.ReadAllTextAsync(artifacts.AssistantContextPath);
|
|
Assert.Contains("## Summary Agent External File Changes", context);
|
|
Assert.Contains(projectFile, context);
|
|
Assert.Contains("- two", context);
|
|
Assert.Contains("+ TWO", context);
|
|
Assert.Contains(dictionaryPath, context);
|
|
Assert.Contains("+ - PBI", context);
|
|
Assert.DoesNotContain("owned summary", context);
|
|
Assert.DoesNotContain("- Context before.", context);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task WriteSummaryRefusesOneLinerWithLineBreaks()
|
|
{
|
|
var root = Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N"));
|
|
var artifacts = new MeetingSessionArtifacts(
|
|
MeetingNotePath: Path.Combine(root, "Meetings", "Notes", "meeting.md"),
|
|
TranscriptPath: Path.Combine(root, "Meetings", "Transcripts", "transcript.md"),
|
|
AssistantContextPath: Path.Combine(root, "Meetings", "Assistant Context", "context.md"),
|
|
SummaryPath: Path.Combine(root, "Meetings", "Summaries", "summary.md"));
|
|
Directory.CreateDirectory(Path.GetDirectoryName(artifacts.MeetingNotePath)!);
|
|
await File.WriteAllTextAsync(
|
|
artifacts.MeetingNotePath,
|
|
"""
|
|
---
|
|
title: Meeting
|
|
---
|
|
""");
|
|
var tools = new MeetingSummaryTools(artifacts);
|
|
|
|
var result = await tools.WriteSummary("# Summary", "First line\nsecond line");
|
|
|
|
Assert.Equal("Refused: oneliner must be a single line.", result);
|
|
Assert.False(tools.SummaryWasWritten);
|
|
Assert.False(File.Exists(artifacts.SummaryPath));
|
|
}
|
|
}
|