Files
meeting-assistant/MeetingAssistant/Summary/ProjectMetadata.cs
codex 7b2bcd3631
PR and Push Build/Test / build-and-test (push) Successful in 12m0s
Preserve recovery transcripts and add user-approved agent requests
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.
2026-09-25 11:58:34 +02:00

56 lines
2.3 KiB
C#

using MeetingAssistant.MeetingNotes;
using YamlDotNet.Core;
using YamlDotNet.Serialization;
namespace MeetingAssistant.Summary;
public sealed record ProjectMetadata(string Id, string DisplayName, string Description)
{
public static async Task<string> BuildContextAsync(BoundMeetingProject project, CancellationToken cancellationToken = default)
{
var metadata = await ReadAsync(project, cancellationToken);
var context = $"# {metadata.Id}\nDisplay name: {metadata.DisplayName}\nDescription: {metadata.Description}";
var agentsPath = Path.Combine(project.Path, "AGENTS.md");
if (File.Exists(agentsPath))
{
var instructions = await File.ReadAllTextAsync(agentsPath, cancellationToken);
if (!string.IsNullOrWhiteSpace(instructions)) context += "\n\n" + instructions.Trim();
}
return context;
}
public static bool IsValidContent(string content) => Parse(content) is not null;
public static async Task<ProjectMetadata> ReadAsync(BoundMeetingProject project, CancellationToken cancellationToken = default)
{
var path = Path.Combine(project.Path, "PROJECT.md");
var metadata = File.Exists(path) ? Parse(await File.ReadAllTextAsync(path, cancellationToken)) : null;
return new(project.Name,
string.IsNullOrWhiteSpace(metadata?.Name) ? project.Name : metadata.Name.Trim(),
metadata?.Description?.Trim() ?? "");
}
private static Frontmatter? Parse(string content)
{
try
{
var document = MarkdownDocumentParser.SplitOptional(content);
if (!document.HasFrontmatter) return null;
var metadata = FrontmatterYamlDeserializer.Create().Deserialize<Frontmatter>(document.Frontmatter);
return string.IsNullOrWhiteSpace(metadata?.Name) || metadata.Description is null || metadata.Description.Length > 256
? null
: metadata;
}
catch (YamlException) { return null; }
catch (InvalidDataException) { return null; }
}
private sealed class Frontmatter
{
[YamlMember(Alias = "name")]
public string? Name { get; set; }
[YamlMember(Alias = "description")]
public string? Description { get; set; }
}
}