feat(worker): week report prompt builder (day-major pivot)

This commit is contained in:
mika kuns
2026-06-03 09:40:57 +02:00
parent bec87b3d6f
commit e2271b5a50
2 changed files with 108 additions and 0 deletions

View File

@@ -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<RepoActivity> activity,
IReadOnlyDictionary<DateOnly, List<string>> 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<DateOnly, List<(string Repo, DayActivity Day)>>();
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();
}
}

View File

@@ -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<DateOnly, List<string>>
{
[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);
}
}