# 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.
