Public Access
Fix past meeting summary tool edge cases
PR and Push Build/Test / build-and-test (push) Successful in 10m54s
PR and Push Build/Test / build-and-test (push) Successful in 10m54s
This commit is contained in:
@@ -29,7 +29,7 @@ public sealed class BoundMeetingProjectResolver
|
||||
return [];
|
||||
}
|
||||
|
||||
var projectNames = await ReadMeetingProjectNamesAsync(artifacts.MeetingNotePath, cancellationToken);
|
||||
var projectNames = await ReadMeetingProjectNamesAsync(artifacts, cancellationToken);
|
||||
if (projectNames.Count == 0)
|
||||
{
|
||||
return [];
|
||||
@@ -42,6 +42,13 @@ public sealed class BoundMeetingProjectResolver
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public Task<HashSet<string>> ReadMeetingProjectNamesAsync(
|
||||
MeetingSessionArtifacts artifacts,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return ReadMeetingProjectNamesAsync(artifacts.MeetingNotePath, cancellationToken);
|
||||
}
|
||||
|
||||
private static async Task<HashSet<string>> ReadMeetingProjectNamesAsync(
|
||||
string meetingNotePath,
|
||||
CancellationToken cancellationToken)
|
||||
|
||||
@@ -322,15 +322,16 @@ public sealed class MeetingSummaryTools
|
||||
|
||||
var summaries = await GetScopedPastSummariesAsync(scope.ProjectNames);
|
||||
var effectivePageSize = Math.Clamp(page_size, 1, 100);
|
||||
var effectivePage = Math.Max(1, page);
|
||||
var requestedPage = Math.Max(1, page);
|
||||
var totalPages = Math.Max(1, (int)Math.Ceiling(summaries.Count / (double)effectivePageSize));
|
||||
var effectivePage = Math.Min(requestedPage, totalPages);
|
||||
var pageItems = summaries
|
||||
.Skip((effectivePage - 1) * effectivePageSize)
|
||||
.Take(effectivePageSize)
|
||||
.ToList();
|
||||
var lines = new List<string>
|
||||
{
|
||||
$"page {Math.Min(effectivePage, totalPages)}/{totalPages}, page_size {effectivePageSize}, total {summaries.Count}"
|
||||
$"page {effectivePage}/{totalPages}, page_size {effectivePageSize}, total {summaries.Count}"
|
||||
};
|
||||
lines.AddRange(pageItems.Select(summary =>
|
||||
$"{summary.ToolPath} | {FormatSummaryStartTime(summary.StartTime)} | {summary.Title} | projects: {string.Join(", ", summary.Projects)}"));
|
||||
@@ -499,22 +500,17 @@ public sealed class MeetingSummaryTools
|
||||
|
||||
private async Task<ProjectScope> ResolveCurrentMeetingProjectScopeAsync(string[]? projects)
|
||||
{
|
||||
var currentProjects = await ReadCurrentMeetingProjectNamesAsync();
|
||||
if (currentProjects.Count == 0)
|
||||
{
|
||||
return new ProjectScope(new HashSet<string>(StringComparer.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
if (projects is null || projects.Length == 0)
|
||||
{
|
||||
return new ProjectScope(currentProjects.ToHashSet(StringComparer.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
var requested = projects
|
||||
var requested = (projects ?? [])
|
||||
.Where(project => !string.IsNullOrWhiteSpace(project))
|
||||
.Select(project => project.Trim())
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
var currentProjects = await ReadCurrentMeetingProjectNamesAsync();
|
||||
if (requested.Count == 0)
|
||||
{
|
||||
return new ProjectScope(currentProjects.ToHashSet(StringComparer.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
var outOfScope = requested
|
||||
.Where(project => !currentProjects.Contains(project))
|
||||
.Order(StringComparer.OrdinalIgnoreCase)
|
||||
@@ -531,11 +527,7 @@ public sealed class MeetingSummaryTools
|
||||
|
||||
private async Task<HashSet<string>> ReadCurrentMeetingProjectNamesAsync()
|
||||
{
|
||||
var note = await ReadMeetingNoteAsync();
|
||||
return note.Frontmatter.Projects
|
||||
.Where(project => !string.IsNullOrWhiteSpace(project))
|
||||
.Select(project => project.Trim())
|
||||
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
return await projectResolver.ReadMeetingProjectNamesAsync(artifacts);
|
||||
}
|
||||
|
||||
private async Task<List<BoundMeetingProject>> GetExistingProjectsInScopeAsync(IReadOnlySet<string> projectNames)
|
||||
@@ -767,23 +759,25 @@ public sealed class MeetingSummaryTools
|
||||
string keywords,
|
||||
bool singleProject)
|
||||
{
|
||||
System.Text.RegularExpressions.Regex regex;
|
||||
try
|
||||
{
|
||||
regex = new System.Text.RegularExpressions.Regex(keywords);
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
return $"Search failed: invalid .NET regular expression: {ex.Message}";
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var regex = new System.Text.RegularExpressions.Regex(keywords);
|
||||
var matches = new List<string>();
|
||||
foreach (var project in projects)
|
||||
{
|
||||
foreach (var file in Directory.EnumerateFiles(project.Path, "*", SearchOption.AllDirectories))
|
||||
{
|
||||
var lines = File.ReadAllLines(file);
|
||||
for (var index = 0; index < lines.Length; index++)
|
||||
{
|
||||
if (regex.IsMatch(lines[index]))
|
||||
{
|
||||
var relative = Path.Combine(project.Name, Path.GetRelativePath(project.Path, file));
|
||||
matches.Add($"{FormatSearchPath(relative, singleProject)}:{index + 1} {lines[index]}");
|
||||
}
|
||||
}
|
||||
var relative = Path.Combine(project.Name, Path.GetRelativePath(project.Path, file));
|
||||
AddFileMatches(matches, regex, file, FormatSearchPath(relative, singleProject));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -794,9 +788,9 @@ public sealed class MeetingSummaryTools
|
||||
|
||||
return string.Join('\n', matches);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException)
|
||||
{
|
||||
return "";
|
||||
return $"Search failed: {ex.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -877,6 +871,7 @@ public sealed class MeetingSummaryTools
|
||||
|
||||
private async Task<PastMeetingSummaryResolution> ResolvePastSummaryAsync(string path)
|
||||
{
|
||||
path = NormalizePastMeetingSummaryToolPath(path);
|
||||
if (string.IsNullOrWhiteSpace(path) || Path.IsPathRooted(path))
|
||||
{
|
||||
return PastMeetingSummaryResolution.Refused("Refused: summary path must be relative to the configured summaries folder.");
|
||||
@@ -914,6 +909,15 @@ public sealed class MeetingSummaryTools
|
||||
return new PastMeetingSummaryResolution(summary);
|
||||
}
|
||||
|
||||
private static string NormalizePastMeetingSummaryToolPath(string path)
|
||||
{
|
||||
var toolPath = ToToolPath(path.Trim());
|
||||
const string pastMeetingPrefix = "past-meetings/";
|
||||
return toolPath.StartsWith(pastMeetingPrefix, StringComparison.OrdinalIgnoreCase)
|
||||
? toolPath[pastMeetingPrefix.Length..]
|
||||
: path;
|
||||
}
|
||||
|
||||
private async Task<PastMeetingSummary?> TryReadPastSummaryAsync(string path, string summariesRoot)
|
||||
{
|
||||
var content = await File.ReadAllTextAsync(path);
|
||||
@@ -923,8 +927,17 @@ public sealed class MeetingSummaryTools
|
||||
return null;
|
||||
}
|
||||
|
||||
var frontmatter = YamlDeserializer.Deserialize<SummaryFrontmatterYaml>(document.Frontmatter)
|
||||
?? new SummaryFrontmatterYaml();
|
||||
SummaryFrontmatterYaml frontmatter;
|
||||
try
|
||||
{
|
||||
frontmatter = YamlDeserializer.Deserialize<SummaryFrontmatterYaml>(document.Frontmatter)
|
||||
?? new SummaryFrontmatterYaml();
|
||||
}
|
||||
catch (YamlDotNet.Core.YamlException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var projects = (frontmatter.Projects ?? [])
|
||||
.Where(project => !string.IsNullOrWhiteSpace(project))
|
||||
.Select(project => project.Trim())
|
||||
|
||||
Reference in New Issue
Block a user