docs(daily-prep): add design specs and implementation plans
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
182
docs/superpowers/specs/2026-06-03-daily-prep-design.md
Normal file
182
docs/superpowers/specs/2026-06-03-daily-prep-design.md
Normal file
@@ -0,0 +1,182 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user