# Daily Prep ("Prime Claude") — Design Date: 2026-06-03 ## Overview Turn the existing Prime Time warm-up into a **daily preparation** ("Tagesvorbereitung"). At a scheduled time (or on demand), Claude reads the open tasks, estimates effort, and selects a focused subset into the MyDay list — capped so it never moves everything in. Claude does the reasoning itself (agentic), via the already-registered ClaudeDo MCP. This replaces the current `"ping"` behavior entirely. A later phase will feed external tickets (Jira, possibly a second system) into the same candidate pool; that is out of scope for this spec. ## Goals - Scheduled and manual ("Tag vorbereiten" button) daily prep. - Claude picks a subset of open tasks into MyDay, ordered so related tasks sit together. - Effort-aware selection, hard-capped at `X` open MyDay tasks. - Keep existing MyDay tasks across re-runs; only top up to `X`. - Candidates limited to tasks in repos that are **not** excluded from the weekly report. ## Non-Goals - External ticket integration (Jira etc.) — future phase. - Group labels/headers in the MyDay view — grouping is ordering-only via `SortOrder`. - A user-editable prep prompt — the prompt is fixed, parameterized. ## Key Decisions | Topic | Decision | | --- | --- | | Who reasons | Agentic — Claude decides via MCP tools. | | MyDay model | `TaskEntity.IsMyDay` flag (smart list `smart:my-day`). | | Grouping | Ordering only via existing `SortOrder` (no new field, no migration for grouping). | | Selection | Effort estimate, hard cap `X` tasks/day. | | Candidates | `Status == Idle`, `BlockedByTaskId == null`, list `WorkingDir` not under `ReportExcludedPaths`. | | Re-run | Keep existing MyDay tasks; top up to `X`. | | Trigger | Existing Prime schedule **and** a manual button. | | Ping | Removed — daily prep replaces it. | | Prompt | Fixed, with injected parameters (`X`, today's date). | | Tool access | Reuse the globally registered `claudedo` MCP — **no** separate `--mcp-config`. | ## Architecture ### 1. MCP tools (extend `ExternalMcpService`, port 47822) The worker already exposes `ExternalMcpService` as the `claudedo` MCP server. Add two tools; they automatically surface as `mcp__claudedo__get_daily_prep_candidates` and `mcp__claudedo__set_my_day`. - **`get_daily_prep_candidates()`** → JSON containing: - `candidates[]`: open, non-blocked tasks in non-excluded repos, each with `id, title, description, listName, isStarred, scheduledFor, age` (age derived from `CreatedAt`). - `currentMyDay[]`: currently-`IsMyDay` open tasks (so Claude sees remaining capacity). - Filter: `Status == Idle` AND `BlockedByTaskId == null` AND the task's list `WorkingDir` does not start with any prefix in `AppSettings.ReportExcludedPaths` (default `["C:\\Private"]`; case-insensitive prefix match, same semantics as the weekly report). - **`set_my_day(taskId, isMyDay, sortOrder?)`** → - Sets `IsMyDay` and (optionally) `SortOrder` on the task via `TaskRepository`. - Broadcasts `TaskUpdated` via `HubBroadcaster` so the UI updates live. - **Cap-guard:** when `isMyDay == true`, count current open (`Idle`) tasks with `IsMyDay == true`. If `count >= X`, reject with an error message ("MyDay limit {X} reached"). `isMyDay == false` is always allowed. `X = AppSettings.DailyPrepMaxTasks`. This guarantees the "never move everything in" invariant server-side, independent of Claude's behavior. ### 2. `DailyPrepRunner` (replaces ping logic) Rename `IPrimeRunner`/`PrimeRunner` → `IDailyPrepRunner`/`DailyPrepRunner` (the `"ping"` concept is gone). It: - Loads `AppSettings` (`X = DailyPrepMaxTasks`). - Builds the fixed prompt with injected parameters (`X`, today's date). - Invokes `claude -p --output-format stream-json --verbose` with: - `--permission-mode` set so the headless run won't block on permission prompts, - `--allowedTools mcp__claudedo__get_daily_prep_candidates mcp__claudedo__set_my_day`, - `--max-turns 30` (constant), timeout 5 min (constant; larger than the old 60s ping). - **No `--mcp-config`** — relies on the globally registered `claudedo` MCP (the worker runs as the user via the per-user logon Scheduled Task, so the headless run inherits the user-scope registration and its auth). - Returns an outcome (e.g. number of tasks added) for broadcasting. ### 3. Scheduler `PrimeScheduler` is unchanged in structure — it now calls `IDailyPrepRunner` instead of the ping runner. `NextDueCalculator` and the schedule model are untouched. ### 4. Manual trigger - Worker hub method `RunDailyPrepNow()` invokes the same `DailyPrepRunner`. - UI button **"Tag vorbereiten"** in the MyDay list header. - **Single-flight guard:** if a prep run is already in progress, the trigger reports "already running" and does not start a parallel run (applies to both schedule and button). ### 5. Parameter config - New field **`DailyPrepMaxTasks`** (int, default `5`) on `AppSettingsEntity`. - Plumbing: EF config + migration, `AppSettingsRepository`, `WorkerHub` AppSettings DTO, UI DTO mirror + `WorkerClient`, and a numeric editor in the Prime Claude settings tab. - `ReportExcludedPaths` is reused as-is (already on `AppSettings`). ## Data Flow 1. Trigger (schedule due **or** button) → `DailyPrepRunner.RunAsync`. 2. Runner loads `AppSettings` (`X`), builds prompt, launches Claude. 3. Claude → `get_daily_prep_candidates` → DB query returns filtered candidates + current MyDay. 4. Claude estimates effort, tops up to **X total**, calls `set_my_day(id, true, sortOrder)` for each chosen task (consecutive `sortOrder` for related tasks). 5. `ExternalMcpService` writes `IsMyDay`/`SortOrder`, broadcasts `TaskUpdated` → MyDay list updates live. 6. Runner updates `LastRunAt`, broadcasts "prep done" (count added). ## Fixed Prompt (parameterized) Content (parameters in `{}`): > Du bereitest meinen Arbeitstag für **{today}** vor. > 1. Rufe `get_daily_prep_candidates` auf. > 2. Behalte bereits als MyDay markierte offene Tasks. > 3. Fülle bis **maximal {X} offene Tasks gesamt** in MyDay auf — niemals mehr. > 4. Schätze pro Task grob den Aufwand; wähle eine machbare Mischung (nicht nur Großbrocken). > Priorisiere `isStarred`, fällige (`scheduledFor`) und ältere Tasks. > 5. Lege thematisch verwandte Tasks durch aufeinanderfolgende `sortOrder`-Werte nebeneinander. > 6. Setze die Auswahl via `set_my_day(id, true, sortOrder)`. Markiere nichts außerhalb der > Kandidatenliste. Injected parameters: `{today}` (date) and `{X}` (= `DailyPrepMaxTasks`). ## Error Handling - No candidates → Claude marks nothing; runner reports "0 added". - Claude run fails / times out → log + failure broadcast (existing scheduler event channel); `LastRunAt` is set on attempt, as today, to avoid tight retry loops. - `set_my_day` on an invalid/ineligible id → tool returns an error string; Claude adapts. - Cap exceeded → tool returns an error; Claude stops adding. - Concurrent trigger → single-flight guard reports "already running". ## Testing Real SQLite + real git (project convention). - `get_daily_prep_candidates`: only `Idle`; blocked excluded; tasks in excluded repos (`ReportExcludedPaths`) excluded; current MyDay tasks included. - `set_my_day`: sets flag + `SortOrder`; broadcasts `TaskUpdated`; cap-guard rejects at limit; unset always allowed. - `DailyPrepRunner`: prompt contains `{X}` + date; args contain `--allowedTools` + permission-mode + `--max-turns`; success/failure outcomes via an `IClaudeProcess` fake. - Rename `IPrimeRunner` → `IDailyPrepRunner` requires syncing `PrimeScheduler` tests/fakes. ## Files to Create / Modify (high level) **Data** - `Models/AppSettingsEntity.cs` — add `DailyPrepMaxTasks`. - `Configuration/AppSettingsEntityConfiguration.cs` — map new column. - `Migrations/` — new migration for `daily_prep_max_tasks`. - `Repositories/AppSettingsRepository.cs` — persist new field. **Worker** - `External/ExternalMcpService.cs` — add `get_daily_prep_candidates`, `set_my_day` (+ cap-guard). - `Prime/PrimeRunner.cs` → `DailyPrepRunner.cs`; `Prime/Interfaces/IPrimeRunner.cs` → `IDailyPrepRunner.cs`; prompt builder + arg builder. - `Prime/PrimeScheduler.cs` — depend on `IDailyPrepRunner`. - `Hub/WorkerHub.cs` — AppSettings DTO field; `RunDailyPrepNow()`. - `Program.cs` — DI registration update. **UI** - `Services/WorkerClient.cs` + AppSettings DTO mirror — new field; `RunDailyPrepNow` call. - Prime Claude settings tab VM/view — numeric editor for `DailyPrepMaxTasks`. - MyDay list header — "Tag vorbereiten" button + command (Lists/IslandsShell VM). **Tests** - `ClaudeDo.Worker.Tests` — MCP tools, runner, scheduler fakes. - `ClaudeDo.Data.Tests` — AppSettings persistence (if covered there). - `ClaudeDo.Ui.Tests` — settings VM / button wiring as applicable. ## Future Phase (out of scope) External ticket sources (Jira, possibly a second system) feed into the candidate pool used by `get_daily_prep_candidates`, behind a task-source abstraction. Designed separately.