Public Access
Preserve recovery transcripts and add user-approved agent requests
PR and Push Build/Test / build-and-test (push) Successful in 12m0s
PR and Push Build/Test / build-and-test (push) Successful in 12m0s
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.
This commit is contained in:
@@ -1,10 +1,90 @@
|
||||
using MeetingAssistant.MeetingNotes;
|
||||
using MeetingAssistant.Summary;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace MeetingAssistant.Tests;
|
||||
|
||||
public sealed class ProjectKnowledgeToolTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("name: Incomplete project")]
|
||||
[InlineData("description: Missing name")]
|
||||
[InlineData("name: ' '\ndescription: Missing name")]
|
||||
public async Task CatalogUsesLegacyFallbackForIncompleteMetadata(string frontmatter)
|
||||
{
|
||||
var root = Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N"));
|
||||
var projectRoot = Path.Combine(root, "Projects", "Alpha");
|
||||
Directory.CreateDirectory(projectRoot);
|
||||
await File.WriteAllTextAsync(Path.Combine(projectRoot, "PROJECT.md"), $"---\n{frontmatter}\n---\nNotes");
|
||||
var tools = new MeetingSummaryTools(CreateArtifacts(root),
|
||||
new MeetingAssistantOptions { Vault = { ProjectsFolder = Path.Combine(root, "Projects") } });
|
||||
|
||||
using var catalog = JsonDocument.Parse(await tools.ListProjects());
|
||||
|
||||
var project = Assert.Single(catalog.RootElement.EnumerateArray());
|
||||
Assert.Equal("Alpha", project.GetProperty("displayname").GetString());
|
||||
Assert.Equal("", project.GetProperty("description").GetString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProjectMetadataWriteEnforcesDescriptionLimitAndPreservesOtherContent()
|
||||
{
|
||||
var root = Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N"));
|
||||
var projectRoot = Path.Combine(root, "Projects", "Alpha");
|
||||
Directory.CreateDirectory(projectRoot);
|
||||
var artifacts = CreateArtifacts(root);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(artifacts.MeetingNotePath)!);
|
||||
await File.WriteAllTextAsync(artifacts.MeetingNotePath, "---\nprojects: Alpha\n---");
|
||||
var tools = new MeetingSummaryTools(artifacts,
|
||||
new MeetingAssistantOptions { Vault = { ProjectsFolder = Path.Combine(root, "Projects") } });
|
||||
var original = $"---\nname: Alpha Platform\ndescription: {new string('a', 256)}\nowner: Ada\n---\nUser-authored project notes.";
|
||||
Assert.Equal("Alpha/PROJECT.md", await tools.WriteProjectFile("Alpha", "PROJECT.md", original));
|
||||
|
||||
var rejected = await tools.WriteProjectFile("Alpha", "PROJECT.md",
|
||||
$"description: {new string('b', 257)}", from: 3, to: 3);
|
||||
|
||||
Assert.StartsWith("Refused:", rejected);
|
||||
Assert.Equal(original, await File.ReadAllTextAsync(Path.Combine(projectRoot, "PROJECT.md")));
|
||||
Assert.StartsWith("Refused:", await tools.WriteProjectFile("Alpha", "PROJECT.md", "No metadata", replace_file: true));
|
||||
Assert.Equal("Alpha/PROJECT.md", await tools.WriteProjectFile("Alpha", "PROJECT.md",
|
||||
"description: Updated project purpose", from: 3, to: 3));
|
||||
var updated = await tools.ReadProjectFile("Alpha", "PROJECT.md");
|
||||
Assert.Contains("owner: Ada", updated);
|
||||
Assert.Contains("User-authored project notes.", updated);
|
||||
Assert.Contains("Updated project purpose", await tools.ListProjects());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CatalogListsAllProjectsWithMetadataAndLegacyFallbackWithoutGrantingReadAccess()
|
||||
{
|
||||
var root = Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N"));
|
||||
var projectsRoot = Path.Combine(root, "Projects");
|
||||
Directory.CreateDirectory(Path.Combine(projectsRoot, "Alpha"));
|
||||
Directory.CreateDirectory(Path.Combine(projectsRoot, "Legacy"));
|
||||
Directory.CreateDirectory(Path.Combine(projectsRoot, "Malformed"));
|
||||
await File.WriteAllTextAsync(Path.Combine(projectsRoot, "Alpha", "PROJECT.md"),
|
||||
"---\nname: Alpha Platform\ndescription: Meeting automation\n---\nPrivate project notes");
|
||||
await File.WriteAllTextAsync(Path.Combine(projectsRoot, "Alpha", "AGENTS.md"), "Private instructions");
|
||||
await File.WriteAllTextAsync(Path.Combine(projectsRoot, "Malformed", "PROJECT.md"), "---\nname: [broken\n---");
|
||||
var tools = new MeetingSummaryTools(CreateArtifacts(root),
|
||||
new MeetingAssistantOptions { Vault = { ProjectsFolder = projectsRoot } });
|
||||
|
||||
var catalog = await tools.ListProjects();
|
||||
|
||||
using var json = JsonDocument.Parse(catalog);
|
||||
var projects = json.RootElement.EnumerateArray().ToArray();
|
||||
Assert.Equal(3, projects.Length);
|
||||
Assert.Equal("Alpha", projects[0].GetProperty("id").GetString());
|
||||
Assert.Equal("Alpha Platform", projects[0].GetProperty("displayname").GetString());
|
||||
Assert.Equal("Meeting automation", projects[0].GetProperty("description").GetString());
|
||||
Assert.Equal("Legacy", projects[1].GetProperty("displayname").GetString());
|
||||
Assert.Equal("", projects[1].GetProperty("description").GetString());
|
||||
Assert.Equal("Malformed", projects[2].GetProperty("displayname").GetString());
|
||||
Assert.Equal("", projects[2].GetProperty("description").GetString());
|
||||
Assert.DoesNotContain("Private", catalog);
|
||||
Assert.Equal("", await tools.ReadProjectFile("Alpha", "AGENTS.md"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ToolsOperateOnProjectsBoundInMeetingFrontmatter()
|
||||
{
|
||||
@@ -48,7 +128,7 @@ public sealed class ProjectKnowledgeToolTests
|
||||
}
|
||||
});
|
||||
|
||||
Assert.Equal("MeetingAssistant", await tools.ListProjects());
|
||||
Assert.Contains("MeetingAssistant", await tools.ListProjects());
|
||||
Assert.Equal("README.md\nnotes/context.md", await tools.ListProjectFiles("MeetingAssistant"));
|
||||
Assert.Equal("Second alpha line\nThird beta line", await tools.ReadProjectFile("MeetingAssistant", "README.md", 2, 99));
|
||||
|
||||
@@ -56,15 +136,15 @@ public sealed class ProjectKnowledgeToolTests
|
||||
|
||||
Assert.Equal("MeetingAssistant/notes/summary.md", writeResult);
|
||||
Assert.Equal("# Project Update", await File.ReadAllTextAsync(Path.Combine(meetingAssistantRoot, "notes", "summary.md")));
|
||||
Assert.Equal("IgnoredProject/ignored.md", await tools.WriteProjectFile("IgnoredProject", "ignored.md", "changed"));
|
||||
Assert.Equal("alpha should not be searched\nchanged", await File.ReadAllTextAsync(Path.Combine(ignoredRoot, "ignored.md")));
|
||||
Assert.StartsWith("Refused:", await tools.WriteProjectFile("IgnoredProject", "ignored.md", "changed"));
|
||||
Assert.Equal("alpha should not be searched", await File.ReadAllTextAsync(Path.Combine(ignoredRoot, "ignored.md")));
|
||||
Assert.Equal(
|
||||
"README.md:2 Second alpha line",
|
||||
await tools.Search("alpha"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ListProjectsAcceptsScalarProjectFrontmatter()
|
||||
public async Task ProjectFileAccessAcceptsScalarProjectFrontmatter()
|
||||
{
|
||||
var root = Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N"));
|
||||
var projectsRoot = Path.Combine(root, "Projects");
|
||||
@@ -88,7 +168,8 @@ public sealed class ProjectKnowledgeToolTests
|
||||
}
|
||||
});
|
||||
|
||||
Assert.Equal("MeetingAssistant", await tools.ListProjects());
|
||||
Assert.Equal("MeetingAssistant/notes.md", await tools.WriteProjectFile("MeetingAssistant", "notes.md", "Bound project"));
|
||||
Assert.Equal("Bound project", await tools.ReadProjectFile("MeetingAssistant", "notes.md"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -100,8 +181,11 @@ public sealed class ProjectKnowledgeToolTests
|
||||
Directory.CreateDirectory(projectRoot);
|
||||
var projectFile = Path.Combine(projectRoot, "notes.md");
|
||||
await File.WriteAllTextAsync(projectFile, "one\ntwo\nthree\nfour");
|
||||
var artifacts = CreateArtifacts(root);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(artifacts.MeetingNotePath)!);
|
||||
await File.WriteAllTextAsync(artifacts.MeetingNotePath, "---\nprojects: MeetingAssistant\n---\n");
|
||||
var tools = new MeetingSummaryTools(
|
||||
CreateArtifacts(root),
|
||||
artifacts,
|
||||
new MeetingAssistantOptions
|
||||
{
|
||||
Vault =
|
||||
@@ -132,8 +216,13 @@ public sealed class ProjectKnowledgeToolTests
|
||||
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, "---\nprojects: MeetingAssistant\n---\n");
|
||||
var outsidePath = Path.Combine(projectsRoot, "outside.md");
|
||||
await File.WriteAllTextAsync(outsidePath, "Preserve outside content");
|
||||
var tools = new MeetingSummaryTools(
|
||||
CreateArtifacts(root),
|
||||
artifacts,
|
||||
new MeetingAssistantOptions
|
||||
{
|
||||
Vault =
|
||||
@@ -144,6 +233,7 @@ public sealed class ProjectKnowledgeToolTests
|
||||
|
||||
Assert.StartsWith("Refused:", await tools.WriteProjectFile("MissingProject", "notes.md", "content"));
|
||||
Assert.StartsWith("Refused:", await tools.WriteProjectFile("MeetingAssistant", "../outside.md", "content"));
|
||||
Assert.Equal("Preserve outside content", await File.ReadAllTextAsync(outsidePath));
|
||||
Assert.StartsWith("Refused:", await tools.WriteProjectFile("MeetingAssistant", "notes.md", "content", from: 1));
|
||||
Assert.StartsWith("Refused:", await tools.WriteProjectFile("MeetingAssistant", "notes.md", "content", from: 1, to: 1, insert: 1));
|
||||
Assert.StartsWith("Refused:", await tools.WriteProjectFile("MeetingAssistant", "notes.md", "content", from: 1, to: 1, replace_file: true));
|
||||
|
||||
Reference in New Issue
Block a user