From 8a31a67c7460a794996891aa5ab0d6cda9e6b874 Mon Sep 17 00:00:00 2001 From: mika kuns Date: Wed, 30 Sep 2026 10:05:24 +0200 Subject: [PATCH] feat(report): write weekly report as short bullets with haiku --- src/ClaudeDo.Data/PromptFiles.cs | 23 ++++++++++--------- .../Report/WeekReportService.cs | 12 ++++------ .../Report/WeekReportPromptBuilderTests.cs | 2 +- 3 files changed, 17 insertions(+), 20 deletions(-) diff --git a/src/ClaudeDo.Data/PromptFiles.cs b/src/ClaudeDo.Data/PromptFiles.cs index 14340a7f..384a4565 100644 --- a/src/ClaudeDo.Data/PromptFiles.cs +++ b/src/ClaudeDo.Data/PromptFiles.cs @@ -697,20 +697,21 @@ public static class PromptFiles """; private const string WeeklyReportDefault = """ - You are generating a concise weekly standup report for a software developer, - covering {start} to {end}. + You are writing memory-jogger notes for a developer's weekly standup, + covering {start} to {end}. They read them aloud in their own words, so write + keywords, not sentences. Rules: - - Write the ENTIRE report in German. - - Group by day. One "## , " section per day that has + - Write in German. + - Group by day. One "## , " heading per day that has activity (German weekday names). Omit days with no activity. - - 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 notes are authoritative — never omit or contradict them. - - Name the project/repo when it adds clarity. - - Output ONLY the dated sections. No preamble, no intro, no closing remarks. + - Per day: 1–3 bullets, each at most ~6 words, noun style + ("- LagerApp: Etikettendruck gefixt", "- ClaudeDo Listen-Ordner"). No "Habe …". + - One bullet per topic — merge everything about the same feature/bug. + - Drop trivia: typo fixes, exploration, false starts, tooling/log noise. + - Merge the developer's notes with the derived activity, deduplicated. + The notes are authoritative — never omit or contradict them. + - Output ONLY the day sections. No preamble, no summary, no closing remarks. Two sections follow below: an activity log derived from Claude session history, and the developer's own notes. Base the report on both; the notes are diff --git a/src/ClaudeDo.Worker/Report/WeekReportService.cs b/src/ClaudeDo.Worker/Report/WeekReportService.cs index 489d67e6..391732e2 100644 --- a/src/ClaudeDo.Worker/Report/WeekReportService.cs +++ b/src/ClaudeDo.Worker/Report/WeekReportService.cs @@ -40,14 +40,12 @@ public sealed class WeekReportService public async Task GenerateAsync(DateOnly start, DateOnly end, CancellationToken ct = default) { string[] excludes; - string model; IReadOnlyList noteRows; await using (var ctx = await _dbFactory.CreateDbContextAsync(ct)) { var settings = await new AppSettingsRepository(ctx).GetAsync(ct); excludes = ParseExcludes(settings.ReportExcludedPaths); - model = string.IsNullOrWhiteSpace(settings.DefaultModel) ? "sonnet" : settings.DefaultModel; noteRows = await new DailyNoteRepository(ctx).ListBetweenAsync(start, end, ct); } @@ -65,15 +63,13 @@ public sealed class WeekReportService else { var prompt = WeekReportPromptBuilder.Build(start, end, activity, notesByDay); - // Guard against argument injection via the model setting: model aliases/ids are - // alphanumerics, dashes and dots only. - var safeModel = new string(model.Where(c => char.IsLetterOrDigit(c) || c is '-' or '.').ToArray()); - if (safeModel.Length == 0) safeModel = "sonnet"; + // Summarising notes into bullets doesn't need the user's default model. + const string model = "haiku"; IReadOnlyList args = [ "-p", "--output-format", "stream-json", "--verbose", - "--permission-mode", PermissionModeResolver.Resolve(safeModel, "auto"), - "--model", safeModel, + "--permission-mode", PermissionModeResolver.Resolve(model, "auto"), + "--model", model, ]; var result = await _claude.RunAsync(args, prompt, Path.GetTempPath(), _ => Task.CompletedTask, ct); if (!result.IsSuccess) diff --git a/tests/ClaudeDo.Worker.Tests/Report/WeekReportPromptBuilderTests.cs b/tests/ClaudeDo.Worker.Tests/Report/WeekReportPromptBuilderTests.cs index 8589d3ab..6f9f3877 100644 --- a/tests/ClaudeDo.Worker.Tests/Report/WeekReportPromptBuilderTests.cs +++ b/tests/ClaudeDo.Worker.Tests/Report/WeekReportPromptBuilderTests.cs @@ -25,7 +25,7 @@ public class WeekReportPromptBuilderTests new DateOnly(2026, 5, 28), new DateOnly(2026, 6, 3), new[] { repoA }, notes); - Assert.Contains("Write the ENTIRE report in German", prompt); + Assert.Contains("Write 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);