Public Access
Normalize scalar frontmatter lists
PR and Push Build/Test / build-and-test (push) Successful in 8m49s
PR and Push Build/Test / build-and-test (push) Successful in 8m49s
This commit is contained in:
@@ -105,6 +105,36 @@ public sealed class MeetingNoteStoreTests
|
|||||||
Assert.Equal(userNotes, reloaded.UserNotes);
|
Assert.Equal(userNotes, reloaded.UserNotes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ReadConvertsScalarListFrontmatterToSingleItemLists()
|
||||||
|
{
|
||||||
|
var (vaultRoot, store) = CreateStore();
|
||||||
|
var notePath = Path.Combine(vaultRoot, "Meetings", "Notes", "manual.md");
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(notePath)!);
|
||||||
|
await File.WriteAllTextAsync(
|
||||||
|
notePath,
|
||||||
|
"""
|
||||||
|
---
|
||||||
|
title: Manual frontmatter
|
||||||
|
start_time: "2026-05-19T10:00:00.0000000+02:00"
|
||||||
|
end_time: ""
|
||||||
|
attendees: Ada
|
||||||
|
projects: Meeting Assistant
|
||||||
|
transcript: "[[../Transcripts/manual-transcript|Transcript]]"
|
||||||
|
assistant_context: "[[../Assistant Context/manual-context|Assistant Context]]"
|
||||||
|
summary: "[[../Summaries/manual-summary|Summary]]"
|
||||||
|
---
|
||||||
|
|
||||||
|
User notes.
|
||||||
|
""");
|
||||||
|
|
||||||
|
var loaded = await store.ReadAsync(notePath, CancellationToken.None);
|
||||||
|
|
||||||
|
Assert.Equal(["Ada"], loaded.Frontmatter.Attendees);
|
||||||
|
Assert.Equal(["Meeting Assistant"], loaded.Frontmatter.Projects);
|
||||||
|
Assert.Equal("User notes.", loaded.UserNotes);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ActionLinkEscapesSummaryFileName()
|
public void ActionLinkEscapesSummaryFileName()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -63,6 +63,34 @@ public sealed class ProjectKnowledgeToolTests
|
|||||||
await tools.Search("alpha"));
|
await tools.Search("alpha"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ListProjectsAcceptsScalarProjectFrontmatter()
|
||||||
|
{
|
||||||
|
var root = Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N"));
|
||||||
|
var projectsRoot = Path.Combine(root, "Projects");
|
||||||
|
Directory.CreateDirectory(Path.Combine(projectsRoot, "MeetingAssistant"));
|
||||||
|
var artifacts = CreateArtifacts(root);
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(artifacts.MeetingNotePath)!);
|
||||||
|
await File.WriteAllTextAsync(
|
||||||
|
artifacts.MeetingNotePath,
|
||||||
|
"""
|
||||||
|
---
|
||||||
|
projects: MeetingAssistant
|
||||||
|
---
|
||||||
|
""");
|
||||||
|
var tools = new MeetingSummaryTools(
|
||||||
|
artifacts,
|
||||||
|
new MeetingAssistantOptions
|
||||||
|
{
|
||||||
|
Vault =
|
||||||
|
{
|
||||||
|
ProjectsFolder = projectsRoot
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.Equal("MeetingAssistant", await tools.ListProjects());
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task WriteProjectFileSupportsAppendReplaceInsertOverwriteAndCreate()
|
public async Task WriteProjectFileSupportsAppendReplaceInsertOverwriteAndCreate()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using YamlDotNet.Serialization;
|
||||||
|
using YamlDotNet.Serialization.NamingConventions;
|
||||||
|
|
||||||
|
namespace MeetingAssistant.MeetingNotes;
|
||||||
|
|
||||||
|
internal static class FrontmatterYamlDeserializer
|
||||||
|
{
|
||||||
|
public static IDeserializer Create()
|
||||||
|
{
|
||||||
|
return new DeserializerBuilder()
|
||||||
|
.WithTypeConverter(new ScalarStringListYamlTypeConverter())
|
||||||
|
.IgnoreUnmatchedProperties()
|
||||||
|
.Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IDeserializer CreateWithUnderscoredNaming()
|
||||||
|
{
|
||||||
|
return new DeserializerBuilder()
|
||||||
|
.WithNamingConvention(UnderscoredNamingConvention.Instance)
|
||||||
|
.WithTypeConverter(new ScalarStringListYamlTypeConverter())
|
||||||
|
.IgnoreUnmatchedProperties()
|
||||||
|
.Build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,9 +16,7 @@ public sealed class MarkdownMeetingNoteStore : IMeetingNoteStore
|
|||||||
{
|
{
|
||||||
this.options = options.Value;
|
this.options = options.Value;
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
yamlDeserializer = new DeserializerBuilder()
|
yamlDeserializer = FrontmatterYamlDeserializer.Create();
|
||||||
.IgnoreUnmatchedProperties()
|
|
||||||
.Build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<MeetingNote> SaveAsync(MeetingNote note, CancellationToken cancellationToken)
|
public async Task<MeetingNote> SaveAsync(MeetingNote note, CancellationToken cancellationToken)
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
using YamlDotNet.Core;
|
||||||
|
using YamlDotNet.Core.Events;
|
||||||
|
using YamlDotNet.Serialization;
|
||||||
|
|
||||||
|
namespace MeetingAssistant.MeetingNotes;
|
||||||
|
|
||||||
|
internal sealed class ScalarStringListYamlTypeConverter : IYamlTypeConverter
|
||||||
|
{
|
||||||
|
public bool Accepts(Type type)
|
||||||
|
{
|
||||||
|
return type == typeof(List<string>);
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer)
|
||||||
|
{
|
||||||
|
if (parser.TryConsume<Scalar>(out var scalar))
|
||||||
|
{
|
||||||
|
return string.IsNullOrWhiteSpace(scalar.Value)
|
||||||
|
? []
|
||||||
|
: new List<string> { scalar.Value };
|
||||||
|
}
|
||||||
|
|
||||||
|
var values = new List<string>();
|
||||||
|
parser.Consume<SequenceStart>();
|
||||||
|
while (!parser.TryConsume<SequenceEnd>(out _))
|
||||||
|
{
|
||||||
|
var item = parser.Consume<Scalar>();
|
||||||
|
values.Add(item.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer)
|
||||||
|
{
|
||||||
|
serializer(value, typeof(IEnumerable<string>));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,9 +7,7 @@ public sealed record BoundMeetingProject(string Name, string Path);
|
|||||||
|
|
||||||
public sealed class BoundMeetingProjectResolver
|
public sealed class BoundMeetingProjectResolver
|
||||||
{
|
{
|
||||||
private static readonly IDeserializer YamlDeserializer = new DeserializerBuilder()
|
private static readonly IDeserializer YamlDeserializer = FrontmatterYamlDeserializer.Create();
|
||||||
.IgnoreUnmatchedProperties()
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
private readonly MeetingAssistantOptions options;
|
private readonly MeetingAssistantOptions options;
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,7 @@ namespace MeetingAssistant.Summary;
|
|||||||
|
|
||||||
internal static class MeetingSummaryFrontmatterFactory
|
internal static class MeetingSummaryFrontmatterFactory
|
||||||
{
|
{
|
||||||
private static readonly IDeserializer YamlDeserializer = new DeserializerBuilder()
|
private static readonly IDeserializer YamlDeserializer = FrontmatterYamlDeserializer.Create();
|
||||||
.IgnoreUnmatchedProperties()
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
public static async Task<MeetingArtifactFrontmatter> CreateAsync(
|
public static async Task<MeetingArtifactFrontmatter> CreateAsync(
|
||||||
MeetingSessionArtifacts artifacts,
|
MeetingSessionArtifacts artifacts,
|
||||||
|
|||||||
@@ -7,9 +7,7 @@ namespace MeetingAssistant.Summary;
|
|||||||
|
|
||||||
public sealed class MeetingSummaryTools
|
public sealed class MeetingSummaryTools
|
||||||
{
|
{
|
||||||
private static readonly IDeserializer YamlDeserializer = new DeserializerBuilder()
|
private static readonly IDeserializer YamlDeserializer = FrontmatterYamlDeserializer.Create();
|
||||||
.IgnoreUnmatchedProperties()
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
private readonly MeetingSessionArtifacts artifacts;
|
private readonly MeetingSessionArtifacts artifacts;
|
||||||
private readonly MeetingAssistantOptions options;
|
private readonly MeetingAssistantOptions options;
|
||||||
|
|||||||
@@ -12,16 +12,12 @@ using Microsoft.Extensions.Configuration;
|
|||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using YamlDotNet.Core;
|
using YamlDotNet.Core;
|
||||||
using YamlDotNet.Serialization;
|
using YamlDotNet.Serialization;
|
||||||
using YamlDotNet.Serialization.NamingConventions;
|
|
||||||
|
|
||||||
namespace MeetingAssistant.Workflow;
|
namespace MeetingAssistant.Workflow;
|
||||||
|
|
||||||
public sealed class WorkflowRulesEditorTools
|
public sealed class WorkflowRulesEditorTools
|
||||||
{
|
{
|
||||||
private static readonly IDeserializer YamlDeserializer = new DeserializerBuilder()
|
private static readonly IDeserializer YamlDeserializer = FrontmatterYamlDeserializer.CreateWithUnderscoredNaming();
|
||||||
.WithNamingConvention(UnderscoredNamingConvention.Instance)
|
|
||||||
.IgnoreUnmatchedProperties()
|
|
||||||
.Build();
|
|
||||||
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web)
|
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web)
|
||||||
{
|
{
|
||||||
WriteIndented = true
|
WriteIndented = true
|
||||||
|
|||||||
@@ -48,6 +48,12 @@ Generated artifact notes SHALL link only to the other notes from the same run an
|
|||||||
- **WHEN** Meeting Assistant creates a meeting note
|
- **WHEN** Meeting Assistant creates a meeting note
|
||||||
- **THEN** the note frontmatter links to the configured transcript, assistant context, and summary note locations
|
- **THEN** the note frontmatter links to the configured transcript, assistant context, and summary note locations
|
||||||
|
|
||||||
|
#### Scenario: Scalar list frontmatter is normalized
|
||||||
|
- **GIVEN** a meeting note frontmatter field that Meeting Assistant expects as a list is stored as a scalar string
|
||||||
|
- **WHEN** Meeting Assistant reads the meeting note
|
||||||
|
- **THEN** it treats the scalar string as a one-item list
|
||||||
|
- **AND** continues processing the meeting note
|
||||||
|
|
||||||
#### Scenario: Generated artifacts do not self-reference
|
#### Scenario: Generated artifacts do not self-reference
|
||||||
- **WHEN** Meeting Assistant writes transcript, assistant context, or summary artifact frontmatter
|
- **WHEN** Meeting Assistant writes transcript, assistant context, or summary artifact frontmatter
|
||||||
- **THEN** the artifact frontmatter links to the other run notes
|
- **THEN** the artifact frontmatter links to the other run notes
|
||||||
|
|||||||
Reference in New Issue
Block a user