Public Access
Centralize summary frontmatter metadata
PR and Push Build/Test / build-and-test (push) Successful in 9m47s
PR and Push Build/Test / build-and-test (push) Successful in 9m47s
This commit is contained in:
@@ -24,14 +24,27 @@ public sealed class MeetingSummaryFailureWriterTests
|
|||||||
title: Failure Meeting
|
title: Failure Meeting
|
||||||
start_time: "2026-05-20T10:00:00.0000000+02:00"
|
start_time: "2026-05-20T10:00:00.0000000+02:00"
|
||||||
end_time: "2026-05-20T10:30:00.0000000+02:00"
|
end_time: "2026-05-20T10:30:00.0000000+02:00"
|
||||||
attendees: []
|
attendees:
|
||||||
projects: []
|
- Ada
|
||||||
|
projects:
|
||||||
|
- Current Project
|
||||||
transcript: "[[../Transcripts/transcript|Transcript]]"
|
transcript: "[[../Transcripts/transcript|Transcript]]"
|
||||||
assistant_context: "[[../Assistant Context/context|Assistant Context]]"
|
assistant_context: "[[../Assistant Context/context|Assistant Context]]"
|
||||||
summary: "[[../Summaries/summary|Summary]]"
|
summary: "[[../Summaries/summary|Summary]]"
|
||||||
---
|
---
|
||||||
""");
|
""");
|
||||||
await File.WriteAllTextAsync(artifacts.SummaryPath, "# Old Summary");
|
await File.WriteAllTextAsync(
|
||||||
|
artifacts.SummaryPath,
|
||||||
|
"""
|
||||||
|
---
|
||||||
|
attendees:
|
||||||
|
- Preserved
|
||||||
|
projects:
|
||||||
|
- Stale Project
|
||||||
|
---
|
||||||
|
|
||||||
|
# Old Summary
|
||||||
|
""");
|
||||||
var writer = new MeetingSummaryFailureWriter(
|
var writer = new MeetingSummaryFailureWriter(
|
||||||
Options.Create(new MeetingAssistantOptions
|
Options.Create(new MeetingAssistantOptions
|
||||||
{
|
{
|
||||||
@@ -54,6 +67,12 @@ public sealed class MeetingSummaryFailureWriterTests
|
|||||||
Assert.Equal(artifacts.SummaryPath, result.SummaryPath);
|
Assert.Equal(artifacts.SummaryPath, result.SummaryPath);
|
||||||
Assert.Contains("Summary Generation Failed", content);
|
Assert.Contains("Summary Generation Failed", content);
|
||||||
Assert.Contains("title: Failure Meeting", content);
|
Assert.Contains("title: Failure Meeting", content);
|
||||||
|
Assert.Contains("attendees:", content);
|
||||||
|
Assert.Contains("- Preserved", content);
|
||||||
|
Assert.DoesNotContain("- Ada", content);
|
||||||
|
Assert.Contains("projects:", content);
|
||||||
|
Assert.Contains("- Current Project", content);
|
||||||
|
Assert.DoesNotContain("- Stale Project", content);
|
||||||
Assert.Contains("meeting: \"[[../Notes/meeting|Meeting Note]]\"", content);
|
Assert.Contains("meeting: \"[[../Notes/meeting|Meeting Note]]\"", content);
|
||||||
Assert.Contains("transcript: \"[[../Transcripts/transcript|Transcript]]\"", content);
|
Assert.Contains("transcript: \"[[../Transcripts/transcript|Transcript]]\"", content);
|
||||||
Assert.Contains("assistant_context: \"[[../Assistant Context/context|Assistant Context]]\"", content);
|
Assert.Contains("assistant_context: \"[[../Assistant Context/context|Assistant Context]]\"", content);
|
||||||
|
|||||||
@@ -52,11 +52,11 @@ public sealed class MeetingSummaryFailureWriter : IMeetingSummaryFailureWriter
|
|||||||
var meetingNote = File.Exists(artifacts.MeetingNotePath)
|
var meetingNote = File.Exists(artifacts.MeetingNotePath)
|
||||||
? await meetingNoteStore.ReadAsync(artifacts.MeetingNotePath, cancellationToken)
|
? await meetingNoteStore.ReadAsync(artifacts.MeetingNotePath, cancellationToken)
|
||||||
: new MeetingNote("", new MeetingNoteFrontmatter(), "");
|
: new MeetingNote("", new MeetingNoteFrontmatter(), "");
|
||||||
var frontmatter = MeetingArtifactFrontmatterRenderer.Create(
|
var frontmatter = await MeetingSummaryFrontmatterFactory.CreateAsync(
|
||||||
artifacts,
|
artifacts,
|
||||||
meetingNote,
|
meetingNote,
|
||||||
MeetingArtifactFrontmatterRenderer.DefaultTitle(meetingNote, "Meeting Summary"),
|
MeetingArtifactFrontmatterRenderer.DefaultTitle(meetingNote, "Meeting Summary"),
|
||||||
artifacts.SummaryPath);
|
cancellationToken);
|
||||||
await File.WriteAllTextAsync(
|
await File.WriteAllTextAsync(
|
||||||
artifacts.SummaryPath,
|
artifacts.SummaryPath,
|
||||||
MeetingArtifactFrontmatterRenderer.Render(
|
MeetingArtifactFrontmatterRenderer.Render(
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
using MeetingAssistant.MeetingNotes;
|
||||||
|
using YamlDotNet.Serialization;
|
||||||
|
|
||||||
|
namespace MeetingAssistant.Summary;
|
||||||
|
|
||||||
|
internal static class MeetingSummaryFrontmatterFactory
|
||||||
|
{
|
||||||
|
private static readonly IDeserializer YamlDeserializer = new DeserializerBuilder()
|
||||||
|
.IgnoreUnmatchedProperties()
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
public static async Task<MeetingArtifactFrontmatter> CreateAsync(
|
||||||
|
MeetingSessionArtifacts artifacts,
|
||||||
|
MeetingNote meetingNote,
|
||||||
|
string title,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var frontmatter = MeetingArtifactFrontmatterRenderer.Create(
|
||||||
|
artifacts,
|
||||||
|
meetingNote,
|
||||||
|
title,
|
||||||
|
artifacts.SummaryPath);
|
||||||
|
frontmatter.Attendees = await ResolveSummaryAttendeesAsync(
|
||||||
|
artifacts.SummaryPath,
|
||||||
|
meetingNote,
|
||||||
|
cancellationToken);
|
||||||
|
frontmatter.Projects = CopyNonEmptyList(meetingNote.Frontmatter.Projects);
|
||||||
|
return frontmatter;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<List<string>?> ResolveSummaryAttendeesAsync(
|
||||||
|
string summaryPath,
|
||||||
|
MeetingNote meetingNote,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var existingAttendees = await ReadExistingSummaryAttendeesAsync(summaryPath, cancellationToken);
|
||||||
|
return existingAttendees ?? CopyNonEmptyList(meetingNote.Frontmatter.Attendees);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<string>? CopyNonEmptyList(IReadOnlyCollection<string> values)
|
||||||
|
{
|
||||||
|
return values.Count == 0 ? null : values.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<List<string>?> ReadExistingSummaryAttendeesAsync(
|
||||||
|
string summaryPath,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (!File.Exists(summaryPath))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var content = await File.ReadAllTextAsync(summaryPath, cancellationToken);
|
||||||
|
var document = MarkdownDocumentParser.SplitOptional(content);
|
||||||
|
if (!document.HasFrontmatter)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var yaml = YamlDeserializer.Deserialize<SummaryFrontmatterYaml>(document.Frontmatter);
|
||||||
|
return yaml?.Attendees;
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class SummaryFrontmatterYaml
|
||||||
|
{
|
||||||
|
[YamlMember(Alias = "attendees")]
|
||||||
|
public List<string>? Attendees { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -101,13 +101,11 @@ public sealed class MeetingSummaryTools
|
|||||||
var summaryTitle = string.IsNullOrWhiteSpace(meetingNote.Frontmatter.Title)
|
var summaryTitle = string.IsNullOrWhiteSpace(meetingNote.Frontmatter.Title)
|
||||||
? title
|
? title
|
||||||
: meetingNote.Frontmatter.Title;
|
: meetingNote.Frontmatter.Title;
|
||||||
var frontmatter = MeetingArtifactFrontmatterRenderer.Create(
|
var frontmatter = await MeetingSummaryFrontmatterFactory.CreateAsync(
|
||||||
artifacts,
|
artifacts,
|
||||||
meetingNote,
|
meetingNote,
|
||||||
string.IsNullOrWhiteSpace(summaryTitle) ? "Meeting Summary" : summaryTitle,
|
string.IsNullOrWhiteSpace(summaryTitle) ? "Meeting Summary" : summaryTitle,
|
||||||
artifacts.SummaryPath);
|
CancellationToken.None);
|
||||||
frontmatter.Attendees = await ResolveSummaryAttendeesAsync(meetingNote);
|
|
||||||
frontmatter.Projects = ResolveSummaryProjects(meetingNote);
|
|
||||||
await File.WriteAllTextAsync(
|
await File.WriteAllTextAsync(
|
||||||
artifacts.SummaryPath,
|
artifacts.SummaryPath,
|
||||||
MeetingArtifactFrontmatterRenderer.Render(frontmatter, markdown));
|
MeetingArtifactFrontmatterRenderer.Render(frontmatter, markdown));
|
||||||
@@ -579,44 +577,6 @@ public sealed class MeetingSummaryTools
|
|||||||
.Replace(Path.AltDirectorySeparatorChar.ToString(), "/", StringComparison.Ordinal);
|
.Replace(Path.AltDirectorySeparatorChar.ToString(), "/", StringComparison.Ordinal);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<List<string>?> ResolveSummaryAttendeesAsync(MeetingNote meetingNote)
|
|
||||||
{
|
|
||||||
var existingAttendees = await ReadExistingSummaryAttendeesAsync();
|
|
||||||
if (existingAttendees is not null)
|
|
||||||
{
|
|
||||||
return existingAttendees;
|
|
||||||
}
|
|
||||||
|
|
||||||
return meetingNote.Frontmatter.Attendees.Count == 0
|
|
||||||
? null
|
|
||||||
: meetingNote.Frontmatter.Attendees.ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<string>? ResolveSummaryProjects(MeetingNote meetingNote)
|
|
||||||
{
|
|
||||||
return meetingNote.Frontmatter.Projects.Count == 0
|
|
||||||
? null
|
|
||||||
: meetingNote.Frontmatter.Projects.ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<List<string>?> ReadExistingSummaryAttendeesAsync()
|
|
||||||
{
|
|
||||||
if (!File.Exists(artifacts.SummaryPath))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var content = await File.ReadAllTextAsync(artifacts.SummaryPath);
|
|
||||||
var document = MarkdownDocumentParser.SplitOptional(content);
|
|
||||||
if (!document.HasFrontmatter)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var yaml = YamlDeserializer.Deserialize<SummaryFrontmatterYaml>(document.Frontmatter);
|
|
||||||
return yaml?.Attendees;
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed record ProjectFileTarget(BoundMeetingProject Project, string Path);
|
private sealed record ProjectFileTarget(BoundMeetingProject Project, string Path);
|
||||||
|
|
||||||
private sealed record FileLineEditMode(
|
private sealed record FileLineEditMode(
|
||||||
@@ -679,12 +639,6 @@ public sealed class MeetingSummaryTools
|
|||||||
public string? Summary { get; set; }
|
public string? Summary { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class SummaryFrontmatterYaml
|
|
||||||
{
|
|
||||||
[YamlDotNet.Serialization.YamlMember(Alias = "attendees")]
|
|
||||||
public List<string>? Attendees { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
private static DateTimeOffset? ParseDateTime(string? value)
|
private static DateTimeOffset? ParseDateTime(string? value)
|
||||||
{
|
{
|
||||||
return DateTimeOffset.TryParse(value, out var parsed)
|
return DateTimeOffset.TryParse(value, out var parsed)
|
||||||
|
|||||||
Reference in New Issue
Block a user