diff --git a/src/ClaudeDo.Worker/Report/WeekReportPromptBuilder.cs b/src/ClaudeDo.Worker/Report/WeekReportPromptBuilder.cs new file mode 100644 index 0000000..0bf4414 --- /dev/null +++ b/src/ClaudeDo.Worker/Report/WeekReportPromptBuilder.cs @@ -0,0 +1,71 @@ +using System.Globalization; +using System.Text; + +namespace ClaudeDo.Worker.Report; + +public static class WeekReportPromptBuilder +{ + private const string Instructions = """ + You are generating a concise weekly standup report for a software developer. + Summarize what they accomplished between {0} and {1}. + + Rules: + - Write the ENTIRE report in German. + - Group by day. One "## {{Wochentag}}, {{dd.MM.yyyy}}" section per day that has + activity (German weekday names). Omit days with no activity entirely. + - Within each day: 3-5 first-person, past-tense bullets ("- Habe X umgesetzt", + "- Y behoben"). Merge related small work into one bullet. + - Drop trivia: typo fixes, pure exploration, false starts, tooling/log noise. + - Blend the developer's own notes and the derived activity into ONE deduplicated + bullet list per day. The developer's notes are authoritative - never omit or + contradict their substance. + - Name the project/repo when it adds clarity. + - Output ONLY the dated sections. No preamble, no intro, no closing remarks. + """; + + public static string Build( + DateOnly start, DateOnly end, + IReadOnlyList activity, + IReadOnlyDictionary> notesByDay) + { + var sb = new StringBuilder(); + sb.AppendLine(string.Format(CultureInfo.InvariantCulture, Instructions, + start.ToString("dd.MM.yyyy", CultureInfo.InvariantCulture), + end.ToString("dd.MM.yyyy", CultureInfo.InvariantCulture))); + sb.AppendLine(); + + var days = new SortedDictionary>(); + foreach (var repo in activity) + foreach (var day in repo.Days) + { + if (!days.TryGetValue(day.Date, out var list)) + days[day.Date] = list = new(); + list.Add((repo.RepoPath, day)); + } + foreach (var d in notesByDay.Keys) + if (!days.ContainsKey(d)) days[d] = new(); + + sb.AppendLine("== Activity (from session history) =="); + foreach (var (date, repos) in days) + { + sb.AppendLine($"### {date:yyyy-MM-dd}"); + foreach (var (repoPath, day) in repos) + { + sb.AppendLine($"Repo: {repoPath}"); + foreach (var p in day.Prompts) sb.AppendLine($" Prompt: {p}"); + foreach (var s in day.Summaries) sb.AppendLine($" Summary: {s}"); + } + sb.AppendLine(); + } + + sb.AppendLine("== Developer notes =="); + foreach (var (date, _) in days) + { + if (!notesByDay.TryGetValue(date, out var notes) || notes.Count == 0) continue; + sb.AppendLine($"### {date:yyyy-MM-dd}"); + foreach (var n in notes) sb.AppendLine($" - {n}"); + } + + return sb.ToString(); + } +} diff --git a/tests/ClaudeDo.Worker.Tests/Report/WeekReportPromptBuilderTests.cs b/tests/ClaudeDo.Worker.Tests/Report/WeekReportPromptBuilderTests.cs new file mode 100644 index 0000000..8589d3a --- /dev/null +++ b/tests/ClaudeDo.Worker.Tests/Report/WeekReportPromptBuilderTests.cs @@ -0,0 +1,37 @@ +using ClaudeDo.Worker.Report; + +namespace ClaudeDo.Worker.Tests.Report; + +public class WeekReportPromptBuilderTests +{ + [Fact] + public void Build_IsDayMajor_AndIncludesNotesAndInstructions() + { + var repoA = new RepoActivity { RepoPath = @"C:\Dev\App" }; + var d1 = new DayActivity { Date = new DateOnly(2026, 6, 1) }; + d1.Prompts.Add("Add login"); + d1.Summaries.Add("Implemented login"); + repoA.Days.Add(d1); + var d2 = new DayActivity { Date = new DateOnly(2026, 6, 2) }; + d2.Prompts.Add("Fix bug"); + repoA.Days.Add(d2); + + var notes = new Dictionary> + { + [new DateOnly(2026, 6, 1)] = new() { "Standup um 9" }, + }; + + var prompt = WeekReportPromptBuilder.Build( + new DateOnly(2026, 5, 28), new DateOnly(2026, 6, 3), + new[] { repoA }, notes); + + Assert.Contains("Write the ENTIRE report in German", prompt); + var idxJun1 = prompt.IndexOf("2026-06-01", StringComparison.Ordinal); + var idxJun2 = prompt.IndexOf("2026-06-02", StringComparison.Ordinal); + Assert.True(idxJun1 >= 0 && idxJun2 > idxJun1); + Assert.Contains("Add login", prompt); + Assert.Contains("Implemented login", prompt); + Assert.Contains("Standup um 9", prompt); + Assert.Contains(@"C:\Dev\App", prompt); + } +}