docs: add weekly report feature design spec
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
193
docs/superpowers/specs/2026-06-03-weekly-report-design.md
Normal file
193
docs/superpowers/specs/2026-06-03-weekly-report-design.md
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
# 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.
|
||||||
|
- **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. Build one prompt: instructions for a short, standup-focused summary grouped by
|
||||||
|
repo and day, plus the distilled activity and the daily notes. 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.
|
||||||
|
|
||||||
|
### 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.
|
||||||
Reference in New Issue
Block a user