Make meeting lifecycle stateful

This commit is contained in:
2026-05-27 12:55:17 +02:00
parent d607b957bb
commit e85274829a
91 changed files with 4076 additions and 479 deletions
@@ -16,9 +16,13 @@ public sealed class MeetingArtifactStoreTests
TranscriptPath: Path.Combine(root, "Meetings", "Transcripts", "20260519-transcript.md"),
AssistantContextPath: contextPath,
SummaryPath: Path.Combine(root, "Meetings", "Summaries", "20260519-summary.md"));
var meetingNote = MeetingNoteTemplate.Create(
"Planning Sync",
DateTimeOffset.Parse("2026-05-19T10:00:00+02:00"));
await artifactStore.CreateAssistantContextAsync(
artifacts,
meetingNote,
"Review previous decisions\nAgree next steps",
DateTimeOffset.Parse("2026-05-19T11:00:00+02:00"),
CancellationToken.None);
@@ -26,7 +30,9 @@ public sealed class MeetingArtifactStoreTests
var content = await File.ReadAllTextAsync(contextPath);
Assert.Contains("# Assistant Context", content);
Assert.StartsWith("---", content, StringComparison.Ordinal);
Assert.Contains("state: transcribing", content);
Assert.Contains("title: Planning Sync", content);
Assert.Contains("start_time: \"2026-05-19T10:00:00.0000000+02:00\"", content);
Assert.Contains("state: collecting metadata", content);
Assert.Contains("agenda: |-", content);
Assert.Contains(" Review previous decisions", content);
Assert.Contains(" Agree next steps", content);
@@ -34,6 +40,7 @@ public sealed class MeetingArtifactStoreTests
Assert.Contains("meeting: \"[[../Notes/20260519-meeting|Meeting Note]]\"", content);
Assert.Contains("transcript: \"[[../Transcripts/20260519-transcript|Transcript]]\"", content);
Assert.Contains("summary: \"[[../Summaries/20260519-summary|Summary]]\"", content);
Assert.DoesNotContain("assistant_context:", content);
Assert.Contains("[[../Notes/20260519-meeting|Meeting Note]]", content);
Assert.Contains("[[../Transcripts/20260519-transcript|Transcript]]", content);
Assert.Contains("[[../Summaries/20260519-summary|Summary]]", content);
@@ -50,9 +57,13 @@ public sealed class MeetingArtifactStoreTests
TranscriptPath: Path.Combine(root, "Meetings", "Transcripts", "20260519-transcript.md"),
AssistantContextPath: contextPath,
SummaryPath: Path.Combine(root, "Meetings", "Summaries", "20260519-summary.md"));
var meetingNote = MeetingNoteTemplate.Create(
"Planning Sync",
DateTimeOffset.Parse("2026-05-19T10:00:00+02:00"));
await artifactStore.CreateAssistantContextAsync(
artifacts,
meetingNote,
"Initial agenda",
DateTimeOffset.Parse("2026-05-19T11:00:00+02:00"),
CancellationToken.None);
@@ -65,6 +76,8 @@ public sealed class MeetingArtifactStoreTests
var content = await File.ReadAllTextAsync(contextPath);
Assert.Contains("state: summarizing", content);
Assert.Contains("title: Planning Sync", content);
Assert.Contains("start_time: \"2026-05-19T10:00:00.0000000+02:00\"", content);
Assert.Contains("agenda: |-", content);
Assert.Contains(" Initial agenda", content);
Assert.Contains("scheduled_end: \"2026-05-19T11:00:00.0000000+02:00\"", content);
@@ -82,10 +95,82 @@ public sealed class MeetingArtifactStoreTests
TranscriptPath: Path.Combine(root, "Meetings", "Transcripts", "20260519-transcript.md"),
AssistantContextPath: contextPath,
SummaryPath: Path.Combine(root, "Meetings", "Summaries", "20260519-summary.md"));
var meetingNote = MeetingNoteTemplate.Create(
"Planning Sync",
DateTimeOffset.Parse("2026-05-19T10:00:00+02:00"));
await artifactStore.CreateAssistantContextAsync(artifacts, "", null, CancellationToken.None);
await artifactStore.CreateAssistantContextAsync(artifacts, meetingNote, "", null, CancellationToken.None);
var content = await File.ReadAllTextAsync(contextPath);
Assert.DoesNotContain("scheduled_end:", content);
}
[Fact]
public async Task StoreUpdatesAssistantContextMetadataAndMeetingFrontmatter()
{
var root = Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N"));
var contextPath = Path.Combine(root, "Meetings", "Assistant Context", "20260519-context.md");
var artifactStore = new MarkdownMeetingArtifactStore(NullLogger<MarkdownMeetingArtifactStore>.Instance);
var artifacts = new MeetingSessionArtifacts(
MeetingNotePath: Path.Combine(root, "Meetings", "Notes", "20260519-meeting.md"),
TranscriptPath: Path.Combine(root, "Meetings", "Transcripts", "20260519-transcript.md"),
AssistantContextPath: contextPath,
SummaryPath: Path.Combine(root, "Meetings", "Summaries", "20260519-summary.md"));
await artifactStore.CreateAssistantContextAsync(
artifacts,
MeetingNoteTemplate.Create("Meeting 2026-05-19 10:00", DateTimeOffset.Parse("2026-05-19T10:00:00+02:00")),
"",
null,
CancellationToken.None);
await artifactStore.UpdateAssistantContextMetadataAsync(
artifacts,
MeetingNoteTemplate.Create("Architecture Sync", DateTimeOffset.Parse("2026-05-19T10:00:00+02:00")),
"Calendar agenda",
DateTimeOffset.Parse("2026-05-19T11:00:00+02:00"),
CancellationToken.None);
var content = await File.ReadAllTextAsync(contextPath);
Assert.Contains("title: Architecture Sync", content);
Assert.Contains("start_time: \"2026-05-19T10:00:00.0000000+02:00\"", content);
Assert.Contains("state: collecting metadata", content);
Assert.Contains("agenda: |-", content);
Assert.Contains(" Calendar agenda", content);
Assert.Contains("scheduled_end: \"2026-05-19T11:00:00.0000000+02:00\"", content);
}
[Fact]
public async Task StoreUpdatesAssistantContextMeetingEndAndPreservesCalendarMetadata()
{
var root = Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N"));
var contextPath = Path.Combine(root, "Meetings", "Assistant Context", "20260519-context.md");
var artifactStore = new MarkdownMeetingArtifactStore(NullLogger<MarkdownMeetingArtifactStore>.Instance);
var artifacts = new MeetingSessionArtifacts(
MeetingNotePath: Path.Combine(root, "Meetings", "Notes", "20260519-meeting.md"),
TranscriptPath: Path.Combine(root, "Meetings", "Transcripts", "20260519-transcript.md"),
AssistantContextPath: contextPath,
SummaryPath: Path.Combine(root, "Meetings", "Summaries", "20260519-summary.md"));
await artifactStore.CreateAssistantContextAsync(
artifacts,
MeetingNoteTemplate.Create("Architecture Sync", DateTimeOffset.Parse("2026-05-19T10:00:00+02:00")),
"Calendar agenda",
DateTimeOffset.Parse("2026-05-19T11:00:00+02:00"),
CancellationToken.None);
await artifactStore.UpdateAssistantContextMeetingAsync(
artifacts,
MeetingNoteTemplate.Create(
"Architecture Sync",
DateTimeOffset.Parse("2026-05-19T10:00:00+02:00"),
DateTimeOffset.Parse("2026-05-19T10:30:00+02:00")),
CancellationToken.None);
var content = await File.ReadAllTextAsync(contextPath);
Assert.Contains("title: Architecture Sync", content);
Assert.Contains("start_time: \"2026-05-19T10:00:00.0000000+02:00\"", content);
Assert.Contains("end_time: \"2026-05-19T10:30:00.0000000+02:00\"", content);
Assert.Contains("agenda: |-", content);
Assert.Contains(" Calendar agenda", content);
Assert.Contains("scheduled_end: \"2026-05-19T11:00:00.0000000+02:00\"", content);
}
}