Files
ClaudeDo/docs/superpowers/specs/2026-06-03-weekly-report-design.md
2026-06-03 08:52:25 +02:00

227 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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<RepoActivity>` where `RepoActivity { RepoPath, Days: List<DayActivity{ Date, Prompts[], Summaries[] }> }`.
**`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: 35 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.