9.3 KiB
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 whosecwdstarts with an excluded prefix is dropped. - Summarization: Claude CLI summarizes. The Worker distills the logs, then runs a
single one-shot
claude -pcall via the existingClaudeProcessand 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 bySortOrder.ListBetweenAsync(DateOnly start, DateOnly end)— bullets in a window (used by the report).AddAsync(DateOnly day, string text)— appends a bullet (assigns nextSortOrder).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; defaultWednesday= 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
cwdstarts with any excluded prefix (case-insensitive, normalized separators). - Keeps messages whose
timestampfalls in[start, end]. - Extracts, per repo (
cwd) → per day:- user prompts:
type == "user"text content (string orcontent[].text). Skip tool-result-only user turns and queue/attachment/hook noise. - assistant closing summaries: the final assistant text block of each turn/session.
- user prompts:
- Output: a structured model, e.g.
IReadOnlyList<RepoActivity>whereRepoActivity { RepoPath, Days: List<DayActivity{ Date, Prompts[], Summaries[] }> }.
WeekReportService (distilled → stored summary)
GenerateAsync(start, end, ct):- Read settings (excluded paths, standup weekday).
ClaudeHistoryReader→ distilled activity.DailyNoteRepository.ListBetweenAsync→ notes grouped by day.- 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.
- Run
ClaudeProcessonce (claude -p, no worktree/agents; working dir = a neutral dir). ReadRunResult.ResultMarkdown. WeekReportRepository.UpsertAsync(start, end, markdown); return markdown.- On Claude failure, surface
RunResult.ErrorMarkdownto 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 (likeWorktreesOverviewModalView), registered inIslandsShellViewModel, opened from a new toolbar button. - Date range: two
ThemedDatePickers, default "since last Wednesday → today" computed fromStandupWeekday. - On open and on range change: call
GetWeekReport.- Stored report exists → render markdown via
MarkdownView, showGeneratedAt, show a Regenerate button. - None → empty state ("Not generated yet") + a Generate button.
- Stored report exists → render markdown via
- Generate/Regenerate: call
GenerateWeekReportwith 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 aTaskEntity). - 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 swellingDetailsIslandViewModel(already ~978 lines); the bullet logic stays isolated and testable. - Day navigator in the editor header:
</>arrows to step days, aThemedDatePickerto 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_settingscolumns and the existingGetAppSettings/UpdateAppSettingspath.
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 fakedClaudeProcess.DailyNoteRepositoryandWeekReportRepository: 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.