# Weekly Report — Design **Date:** 2026-06-03 **Status:** Approved (pending spec review) ## Goal Generate a short, standup-focused report of what the user did over the past week, for the Wednesday standup. The report is built from the user's Claude Code session history across all repos, distilled and summarized by Claude. Personal repos under a configurable excluded path (default `C:\Private`) are left out. The user can author per-day bullet notes inside ClaudeDo (via the My Day list) that are folded into the report. ## Decisions (from brainstorming) - **Data source:** all Claude Code history in `~/.claude/projects/*/*.jsonl`, both manual sessions and ClaudeDo-run tasks, grouped by repo. - **Exclusion:** a configurable list of path prefixes (default `["C:\\Private"]`). Any session whose `cwd` starts with an excluded prefix is dropped. - **Summarization:** Claude CLI summarizes. The Worker distills the logs, then runs a single one-shot `claude -p` call via the existing `ClaudeProcess` and returns the result markdown. No worktree, no task row, no queue. - **Period:** default "since last Wednesday → today", computed from a configurable standup weekday. The range is adjustable in the modal. - **Signal fed to Claude:** user prompts (intent), assistant closing summaries, and the user's daily notes. No git-commit scanning. - **Report shape:** German, grouped by day, first-person past-tense bullets, ~3-5 bullets/day with trivia merged/dropped, notes blended into one deduplicated list per day. See the Report Prompt section. - **Placement:** a "Weekly Report" overlay modal opened from the toolbar, rendering via the existing `MarkdownView`. - **Output:** view-only in-app (no export). - **Notes UI:** authored in the My Day list via a pinned non-task "Notes" pseudo-row that repurposes the Details island into a bullet-notes editor. Per-day bullets with a day navigator (prev/next arrows + date picker + Today). - **Report persistence:** generated reports are stored, keyed by exact date range, and reused. Generation is button-driven (never automatic); a Regenerate button overwrites. ## Architecture Overview ``` UI (WeeklyReportModal, Details-island notes mode) │ SignalR ▼ WorkerHub ── GetWeekReport / GenerateWeekReport / daily-notes CRUD │ ├── WeekReportService ──► ClaudeHistoryReader (scan ~/.claude/projects) │ │ (distilled activity) │ ├── DailyNoteRepository (notes in window) │ ├── ClaudeProcess (one-shot summarize) │ └── WeekReportRepository (store/reuse) └── DailyNoteRepository (CRUD) Data: DailyNoteEntity, WeekReportEntity + repositories + EF migration AppSettingsEntity: ReportExcludedPaths, StandupWeekday ``` ## Components ### 1. Data layer (`ClaudeDo.Data`) **`DailyNoteEntity`** (table `daily_notes`) - `Id` (GUID string, init-only PK) - `Date` (date-only; the day the bullet belongs to) - `Text` (string, the bullet content) - `SortOrder` (int; ordering within a day) - `CreatedAt` (DateTime) **`DailyNoteRepository`** (async, CancellationToken, follows existing repo pattern) - `ListByDayAsync(DateOnly day)` — bullets for one day, ordered by `SortOrder`. - `ListBetweenAsync(DateOnly start, DateOnly end)` — bullets in a window (used by the report). - `AddAsync(DateOnly day, string text)` — appends a bullet (assigns next `SortOrder`). - `UpdateAsync(string id, string text)` - `DeleteAsync(string id)` **`WeekReportEntity`** (table `week_reports`) - `Id` (GUID string, init-only PK) - `StartDate`, `EndDate` (date-only; the report window — unique together) - `Markdown` (string; the generated report) - `GeneratedAt` (DateTime) **`WeekReportRepository`** - `GetByRangeAsync(DateOnly start, DateOnly end)` — stored report for an exact range, or null. - `UpsertAsync(DateOnly start, DateOnly end, string markdown)` — insert or overwrite by range. **`AppSettingsEntity`** — two new columns: - `ReportExcludedPaths` (string, JSON array of path prefixes; default `["C:\\Private"]`) - `StandupWeekday` (int, `DayOfWeek`; default `Wednesday` = 3) **Migration** — one EF migration adds `daily_notes`, `week_reports`, and the two `app_settings` columns. Entity configs in `Configuration/` (date-only and enum/JSON conversion via `ValueConverter`, per existing convention). ### 2. Worker (`ClaudeDo.Worker`) — new `Report/` folder **`ClaudeHistoryReader`** (raw → distilled) - Input: date window + excluded path prefixes. - Enumerates `~/.claude/projects/*/*.jsonl`. - Parses each line as JSON; tolerant of malformed lines (skip, never throw). - Drops a session entirely if its `cwd` starts with any excluded prefix (case-insensitive, normalized separators). - Keeps messages whose `timestamp` falls in `[start, end]`. - Extracts, per repo (`cwd`) → per day: - **user prompts**: `type == "user"` text content (string or `content[].text`). Skip tool-result-only user turns and queue/attachment/hook noise. - **assistant closing summaries**: the final assistant text block of each turn/session. - Output: a structured model, e.g. `IReadOnlyList` where `RepoActivity { RepoPath, Days: List }`. **`WeekReportService`** (distilled → stored summary) - `GenerateAsync(start, end, ct)`: 1. Read settings (excluded paths, standup weekday). 2. `ClaudeHistoryReader` → distilled activity. 3. `DailyNoteRepository.ListBetweenAsync` → notes grouped by day. 4. Pivot the distilled activity (repo→day from the reader) into **day-major** (day→repo) to match the day-grouped report, and build the prompt from the template in the Report Prompt section. Empty window → produce a "no activity" report without calling Claude. 5. Run `ClaudeProcess` once (`claude -p`, no worktree/agents; working dir = a neutral dir). Read `RunResult.ResultMarkdown`. 6. `WeekReportRepository.UpsertAsync(start, end, markdown)`; return markdown. 7. On Claude failure, surface `RunResult.ErrorMarkdown` to the caller (do not store). - `GetStoredAsync(start, end)` → `WeekReportRepository.GetByRangeAsync`. Interfaces live in `Report/Interfaces/` per the area convention. #### Report Prompt `WeekReportService` assembles this prompt. Instructions are in English (more reliable steering); the output is forced to German. `{...}` are filled at build time. ``` You are generating a concise weekly standup report for a software developer. Summarize what they accomplished between {start:dd.MM.yyyy} and {end:dd.MM.yyyy}. 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. == Activity (from session history) == {day-major: for each day → for each repo → its prompts + closing summaries} == Developer notes == {day-major: for each day → the bullets} ``` ### 3. IPC (Hub + WorkerClient) **`WorkerHub`** new methods: - `GetWeekReport(string startIso, string endIso)` → stored markdown or null. - `GenerateWeekReport(string startIso, string endIso)` → generates, stores, returns markdown. - `GetDailyNotes(string dayIso)` → bullets for a day. - `AddDailyNote(string dayIso, string text)` → created bullet. - `UpdateDailyNote(string id, string text)`. - `DeleteDailyNote(string id)`. **`WorkerClient`** (UI) mirrors these, following the existing `WorkerPrimeScheduleApi`/AppSettings method pattern. ### 4. UI (`ClaudeDo.Ui`) **Weekly Report modal** (`WeeklyReportModalView` + `WeeklyReportModalViewModel`) - Overlay modal in the `Modals/` pattern (like `WorktreesOverviewModalView`), registered in `IslandsShellViewModel`, opened from a new toolbar button. - Date range: two `ThemedDatePicker`s, default "since last Wednesday → today" computed from `StandupWeekday`. - On open and on range change: call `GetWeekReport`. - Stored report exists → render markdown via `MarkdownView`, show `GeneratedAt`, show a **Regenerate** button. - None → empty state ("Not generated yet") + a **Generate** button. - **Generate**/**Regenerate**: call `GenerateWeekReport` with a busy/spinner state; render the returned markdown. Generation only ever runs from these buttons. - View-only; no export. **Notes in My Day** - The My Day smart list (`smart:my-day`) pins a fixed, non-task "Notes" pseudo-row at the top, recognized by the list/selection code (not a `TaskEntity`). - Selecting it puts the **Details island** into **notes mode** (task fields hidden, notes editor shown). The island hosts a dedicated `NotesEditorViewModel` + small view rather than swelling `DetailsIslandViewModel` (already ~978 lines); the bullet logic stays isolated and testable. - **Day navigator** in the editor header: `<` / `>` arrows to step days, a `ThemedDatePicker` to jump to any date, and a "Today" button. Defaults to today; the pinned row's default day rolls over at midnight (no data lost — past days remain reachable via the navigator). - **Bullet editing** for the selected day: list of bullets with add / inline-edit / delete / reorder (`SortOrder`). Each operation goes through the daily-notes hub CRUD. ### 5. Settings - Add the excluded-path list and the standup weekday to the existing Settings modal, persisted via the new `app_settings` columns and the existing `GetAppSettings`/`UpdateAppSettings` path. ## Error Handling - Malformed/unreadable JSONL lines are skipped, never fatal. - Empty window → a "no activity" report, no Claude call. - Claude call failure → error surfaced in the modal; nothing stored. - Date ranges normalized to date-only; the stored report key is the exact (start, end). ## Testing - **`ClaudeHistoryReader`** (Worker tests, fixture `.jsonl`): date-window filtering, excluded-prefix dropping (case/separator normalization), prompt/summary extraction, malformed-line tolerance, repo/day grouping. - **`WeekReportService`**: prompt-building from distilled activity + notes; empty-window short-circuit; storage upsert; with a faked `ClaudeProcess`. - **`DailyNoteRepository`** and **`WeekReportRepository`**: CRUD / upsert / range lookup against real SQLite (matches existing test style). ## Out of Scope - Report export (clipboard/file) — view-only for now. - Git-commit scanning. - Editing or summarizing full transcripts; only prompts + closing summaries are used.