docs: document daily-prep across area CLAUDE.md files; add Installer CLAUDE.md
Worker/Ui/Data CLAUDE.md updated for the daily-prep feature (Prime/ area, new MCP tools, hub methods, broadcaster events, prep mode, DailyPrepMaxTasks); new ClaudeDo.Installer/CLAUDE.md maps the WPF installer (modes, pipelines, steps, MCP registration, Startup-shortcut autostart). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -12,7 +12,7 @@ Shared data layer: models, repositories, SQLite infrastructure, and git operatio
|
||||
- **PrimeScheduleEntity** — Id, Days (`[Flags] PrimeDays` weekday bitmask, stored as `days_of_week` int), TimeOfDay, Enabled, LastRunAt, PromptOverride, CreatedAt. Recurs on the selected weekdays; no date range.
|
||||
- **DailyNoteEntity** — Id, Date (DateOnly), Text, SortOrder, CreatedAt → table `daily_notes`
|
||||
- **WeekReportEntity** — Id, StartDate/EndDate (DateOnly), Markdown, GeneratedAt → table `week_reports`, unique index on (start_date, end_date)
|
||||
- **AppSettingsEntity** also carries `ReportExcludedPaths` (string?, JSON array of excluded path prefixes, column `report_excluded_paths`) and `StandupWeekday` (int DayOfWeek, default Wednesday, column `standup_weekday`)
|
||||
- **AppSettingsEntity** also carries `ReportExcludedPaths` (string?, JSON array of excluded path prefixes, column `report_excluded_paths`), `StandupWeekday` (int DayOfWeek, default Wednesday, column `standup_weekday`), and `DailyPrepMaxTasks` (int, default 5, column `daily_prep_max_tasks` — hard cap on how many open tasks the daily-prep / "Prime Claude" feature may place in MyDay)
|
||||
- **SubtaskEntity**, **AppSettingsEntity**, **AgentInfo** — existing helpers / settings / record for scanned agent files
|
||||
|
||||
## Repositories
|
||||
@@ -39,7 +39,7 @@ All repositories use EF Core LINQ queries via `ClaudeDoDbContext`. The atomic `Q
|
||||
|
||||
## Schema
|
||||
|
||||
Tables: `lists`, `tasks`, `worktrees`, `list_config`, `task_runs`, `subtasks`, `app_settings`, `prime_schedules`, `daily_notes`, `week_reports`. Managed by EF Core migrations in the `Migrations/` folder. The `tasks` table holds `status`, `planning_phase` (default `none`), and `blocked_by_task_id` (FK to `tasks.id`, `ON DELETE SET NULL`). Migration `WeeklyReport` added `daily_notes`, `week_reports`, and the two new `app_settings` columns.
|
||||
Tables: `lists`, `tasks`, `worktrees`, `list_config`, `task_runs`, `subtasks`, `app_settings`, `prime_schedules`, `daily_notes`, `week_reports`. Managed by EF Core migrations in the `Migrations/` folder. The `tasks` table holds `status`, `planning_phase` (default `none`), and `blocked_by_task_id` (FK to `tasks.id`, `ON DELETE SET NULL`). Migration `WeeklyReport` added `daily_notes`, `week_reports`, and the two new `app_settings` columns. Migration `DailyPrepMaxTasks` added the `daily_prep_max_tasks` column to `app_settings` (no new tables).
|
||||
|
||||
## Conventions
|
||||
|
||||
|
||||
110
src/ClaudeDo.Installer/CLAUDE.md
Normal file
110
src/ClaudeDo.Installer/CLAUDE.md
Normal file
@@ -0,0 +1,110 @@
|
||||
# ClaudeDo.Installer
|
||||
|
||||
WPF GUI installer, updater, and configuration tool for ClaudeDo. Not WiX/NSIS — the app is its own installer.
|
||||
|
||||
Note: this is the one project where `System.Windows` is correct (WPF, not Avalonia).
|
||||
|
||||
## Project Facts
|
||||
|
||||
- `<UseWPF>true</UseWPF>`, `WinExe`, `net8.0-windows`
|
||||
- `<EnableWindowsTargeting>true</EnableWindowsTargeting>` — allows Linux CI to cross-compile
|
||||
- Single-file framework-dependent publish: `dotnet publish -r win-x64 -p:PublishSingleFile=true` (needs .NET 8 Desktop Runtime)
|
||||
- Entry point: `App.xaml` / `App.xaml.cs` (no `Program.cs`)
|
||||
- References: `ClaudeDo.Data`, `ClaudeDo.Releases`, `ClaudeDo.Localization`
|
||||
- Manifests: `app.manifest` (requireAdministrator, Release) / `app.debug.manifest` (asInvoker, Debug)
|
||||
- Only CLI arg: `--replace-self <old-path>` (self-update handoff)
|
||||
|
||||
## Startup Sequence (`App.OnStartup`)
|
||||
|
||||
1. Load locale
|
||||
2. Self-update preflight — `SelfUpdater.DecideUpdateAsync` checks Gitea API; if a newer installer exists, download + checksum verify + relaunch with `--replace-self <old-path>`
|
||||
3. Detect mode — `InstallModeDetector` reads `install.json` + Gitea API
|
||||
4. Open `WizardWindow` (FreshInstall / Update) or `SettingsWindow` (Config)
|
||||
|
||||
## Modes (`Core/InstallerMode.cs`)
|
||||
|
||||
| Mode | Condition | Window |
|
||||
|---|---|---|
|
||||
| `FreshInstall` | No `install.json` | Full wizard (all pages) |
|
||||
| `Update` | `install.json` present + newer release available | Wizard — Welcome + Install pages only |
|
||||
| `Config` | Current version, or Gitea API unreachable | `SettingsWindow` (settings / repair / uninstall) |
|
||||
|
||||
## Install Pipelines
|
||||
|
||||
Each step implements `IInstallStep`; `InstallerService` runs them sequentially, stops on failure.
|
||||
|
||||
**FreshInstall:**
|
||||
`DownloadAndExtractStep` → `WriteConfigStep` → `InitDatabaseStep` → `RegisterMcpStep` (optional) → `RegisterAutostartStep` → `CreateShortcutsStep` → `WriteUninstallRegistryStep` → `WriteInstallManifestStep` → `StartWorkerStep`
|
||||
|
||||
**Update:**
|
||||
`StopWorkerStep` → `DownloadAndExtractStep` → `RegisterAutostartStep` → `RegisterMcpStep` → `StartWorkerStep` → `WriteInstallManifestStep` → `WriteUninstallRegistryStep`
|
||||
|
||||
**Repair** (via `SettingsViewModel`):
|
||||
`StopWorkerStep` → `DownloadAndExtractStep` → `RegisterAutostartStep` → `StartWorkerStep`
|
||||
|
||||
**Uninstall** (`UninstallRunner`):
|
||||
Stop worker → remove legacy task/service → delete HKLM uninstall key + shortcuts → delete install dir (cmd.exe trampoline if uninstaller exe is inside it) → optionally delete `~/.todo-app`
|
||||
|
||||
## Folder Layout
|
||||
|
||||
```
|
||||
Installer/
|
||||
Steps/ — one class per action (see pipeline lists above)
|
||||
Core/ — InstallContext, InstallerMode, InstallModeDetector, InstallManifest(+Store),
|
||||
ConfigModels, InstallerService, UninstallRunner, PageResolver,
|
||||
AutostartShortcut, ShortcutFactory, ProcessRunner, DarkTitleBar
|
||||
Interfaces/ — IInstallStep + StepResult/StepStatus/StepProgress, IInstallerPage
|
||||
Pages/ — WelcomePage, PathsPage, ServicePage, UiSettingsPage, InstallPage
|
||||
(each: ViewModel + View.xaml)
|
||||
Views/ — WizardWindow(+WizardViewModel), SettingsWindow(+SettingsViewModel),
|
||||
SelfUpdatePromptWindow
|
||||
```
|
||||
|
||||
## Key Step Behaviors
|
||||
|
||||
**`RegisterMcpStep`** — registers the external MCP endpoint with the Claude CLI:
|
||||
```
|
||||
claude mcp remove --scope user claudedo
|
||||
claude mcp add --transport http --scope user claudedo http://127.0.0.1:{ExternalMcpPort}/mcp
|
||||
```
|
||||
Non-fatal if `claude` CLI is missing or too old (prints the manual command). Server name: `claudedo`.
|
||||
|
||||
**`RegisterAutostartStep`** — creates a per-user Startup-folder shortcut `ClaudeDo Worker.lnk` (`Environment.SpecialFolder.Startup`). Also migrates away from legacy mechanisms:
|
||||
- Deletes legacy Windows service: `sc.exe stop/delete ClaudeDoWorker`
|
||||
- Deletes legacy scheduled task: `schtasks /Delete /TN ClaudeDoWorker`
|
||||
|
||||
No new service or scheduled task is created. Rationale: the worker must run in the user's interactive session so Claude CLI auth works.
|
||||
|
||||
## `InstallContext` Defaults
|
||||
|
||||
| Property | Default |
|
||||
|---|---|
|
||||
| `InstallDirectory` | `C:\Program Files\ClaudeDo` |
|
||||
| `DbPath` | `~/.todo-app/todo.db` |
|
||||
| `LogRoot` | `~/.todo-app/logs` |
|
||||
| `SandboxRoot` | `~/.todo-app/sandbox` |
|
||||
| `WorktreeRootStrategy` | `sibling` |
|
||||
| `SignalRPort` | `47821` |
|
||||
| `ExternalMcpPort` | `47822` |
|
||||
| `QueueBackstopIntervalMs` | `30000` |
|
||||
| `ClaudeBin` | `claude` |
|
||||
| `AutoStart` | `true` |
|
||||
| `SignalRUrl` | `http://127.0.0.1:47821/hub` |
|
||||
|
||||
## Files Written by Install
|
||||
|
||||
| Path | Content |
|
||||
|---|---|
|
||||
| `~/.todo-app/worker.config.json` | Worker config |
|
||||
| `~/.todo-app/ui.config.json` | UI config |
|
||||
| `~/.todo-app/todo.db` | SQLite DB (EF migrations) |
|
||||
| `<InstallDir>\install.json` | Install manifest |
|
||||
| `<InstallDir>\app\` | UI binaries |
|
||||
| `<InstallDir>\worker\` | Worker binaries |
|
||||
| `<InstallDir>\uninstaller\ClaudeDo.Installer.exe` | Uninstaller copy |
|
||||
| `HKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall\ClaudeDo` | Uninstall registry key |
|
||||
| Start Menu shortcut | `ClaudeDo.lnk` |
|
||||
| Desktop shortcut (optional) | `ClaudeDo.lnk` |
|
||||
| `%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\ClaudeDo Worker.lnk` | Worker autostart |
|
||||
|
||||
The Apps & Features uninstall string and "Rerun Installer" both point at `<InstallDir>\uninstaller\ClaudeDo.Installer.exe` with no `/uninstall` flag — Config mode is detected from `install.json`.
|
||||
@@ -19,9 +19,12 @@ MVVM with CommunityToolkit.Mvvm source generators:
|
||||
- **StatusBarView** — Connection status indicator, active task display
|
||||
- **ListSettingsModalView** — edits list name, working dir, default commit type, and per-list Model/SystemPrompt/AgentPath; also deletes the list (and its tasks) via a confirmed "Delete list" button. Opened via context menu or gear button on a list row.
|
||||
- **RepoImportModalView** — bulk-creates lists from git repos discovered under chosen parent folders. Opened via the folder button beside "New list" in the Lists island, or the "Add repos as lists…" Help-menu item. Repos already wired to a list show as disabled/"(already added)".
|
||||
- **DetailsIslandView** — contains an "Agent settings (overrides)" expander with per-task Model/SystemPrompt/AgentPath, showing inherited effective values. Disabled while task is running. When notes mode is active (`IsNotesMode`), it hosts **NotesEditorView** instead of the task detail.
|
||||
- **DetailsIslandView** — contains an "Agent settings (overrides)" expander with per-task Model/SystemPrompt/AgentPath, showing inherited effective values. Disabled while task is running. When notes mode is active (`IsNotesMode`), it hosts **NotesEditorView** instead of the task detail. When prep mode is active (`IsPrepMode`), it hosts the daily-prep panel (Plan day button, empty-state hint, embedded **SessionTerminalView**). The task header, metadata footer (delete/close), and **AgentStripView** are gated on `IsTaskDetailVisible` — they are hidden in both notes and prep mode.
|
||||
- **WeeklyReportModalView** — opened from Help menu ("Wochenbericht…"); date-range pickers default to "since last standup weekday → today"; Generate/Regenerate button; renders markdown via MarkdownView; reports are cached per range.
|
||||
- **NotesEditorView** — day navigator (prev/next/date-picker/Today), bullet add/edit/delete for daily notes.
|
||||
- **SessionTerminalView** — reusable log terminal; exposes StyledProperties `Entries`, `Label`, `IsRunning`, `IsDone`, `IsFailed`. Used for both the task `Log` and the prep `PrepLog`.
|
||||
- **SettingsModalView** — Prime Claude tab contains a `DailyPrepMaxTasks` numeric editor.
|
||||
- **TasksIslandView** (MyDay header) — icon buttons visible only when `IsMyDayList`: broom icon = `ClearDayCommand`, stroked-sun icon ("Plan My Day") = `ShowPrepLogCommand`.
|
||||
|
||||
All views use compiled bindings (`x:DataType`).
|
||||
|
||||
@@ -35,12 +38,12 @@ All views use compiled bindings (`x:DataType`).
|
||||
- **StatusBarViewModel** — connection state and active tasks
|
||||
- **WeeklyReportModalViewModel** — drives the weekly report modal
|
||||
- **NotesEditorViewModel** — manages daily note bullet CRUD for the selected day
|
||||
- **DetailsIslandViewModel** gains `IsNotesMode`, `ShowNotes()`, and hosts `NotesEditorViewModel`
|
||||
- **TasksIslandViewModel** gains a pinned "Notes" pseudo-row (`ShowNotesRow`, `OpenNotesCommand`, `NotesRequested` event) that switches the Details island to notes mode
|
||||
- **DetailsIslandViewModel** gains `IsNotesMode`, `ShowNotes()`, and hosts `NotesEditorViewModel`. Also gains daily-prep mode: `IsPrepMode`, `PrepLog` (`ObservableCollection<LogLineViewModel>`), `ShowPrep()`, `PlanDayCommand` (calls `RunDailyPrepNowAsync`), `ShowPrepEmptyState`, and computed `IsTaskDetailVisible` (= `!IsNotesMode && !IsPrepMode`). Subscribes to `PrepStartedEvent`, `PrepLineEvent`, `PrepFinishedEvent`; streams lines into `PrepLog` via `StreamLineFormatter` (same path as task logs). On open, if log is empty and no run is in progress, loads persisted last run via `GetLastPrepLogAsync`.
|
||||
- **TasksIslandViewModel** gains a pinned "Notes" pseudo-row (`ShowNotesRow`, `OpenNotesCommand`, `NotesRequested` event) that switches the Details island to notes mode. Also gains `IsMyDayList` (true when selected list is `smart:my-day`), `ShowPrepLogCommand` (raises `PrepRequested` event → shell calls `Details.ShowPrep()`), and `ClearDayCommand` (calls `ClearMyDayAsync`).
|
||||
|
||||
## Services
|
||||
|
||||
- **WorkerClient** / **IWorkerClient** — SignalR client connecting to `http://127.0.0.1:47821/hub`. Auto-reconnect with exponential backoff. Methods: StartAsync, RunNowAsync, CancelTaskAsync, WakeQueueAsync, ContinueTaskAsync, ResetTaskAsync, GetAgentsAsync, UpdateAppSettingsAsync, UpdateListAsync, UpdateListConfigAsync, GetListConfigAsync, UpdateTaskAgentSettingsAsync, `GetWeekReportAsync`, `GenerateWeekReportAsync`, `GetDailyNotesAsync`, `AddDailyNoteAsync`, `UpdateDailyNoteAsync`, `DeleteDailyNoteAsync`. Events: TaskStarted, TaskFinished, TaskMessage, TaskUpdated, WorktreeUpdated, ListUpdated
|
||||
- **WorkerClient** / **IWorkerClient** — SignalR client connecting to `http://127.0.0.1:47821/hub`. Auto-reconnect with exponential backoff. Methods: StartAsync, RunNowAsync, CancelTaskAsync, WakeQueueAsync, ContinueTaskAsync, ResetTaskAsync, GetAgentsAsync, UpdateAppSettingsAsync, UpdateListAsync, UpdateListConfigAsync, GetListConfigAsync, UpdateTaskAgentSettingsAsync, `GetWeekReportAsync`, `GenerateWeekReportAsync`, `GetDailyNotesAsync`, `AddDailyNoteAsync`, `UpdateDailyNoteAsync`, `DeleteDailyNoteAsync`, `RunDailyPrepNowAsync`, `ClearMyDayAsync`, `GetLastPrepLogAsync`. Events: TaskStarted, TaskFinished, TaskMessage, TaskUpdated, WorktreeUpdated, ListUpdated, `PrepStartedEvent`, `PrepLineEvent`, `PrepFinishedEvent`
|
||||
- **INotesApi** / **WorkerNotesApi** — thin wrapper over the daily-note WorkerClient methods (mirrors `IPrimeScheduleApi`). UI DTO: `DailyNoteDto(Id, Date, Text, SortOrder)`.
|
||||
|
||||
## Converters
|
||||
@@ -57,3 +60,4 @@ Editor dialogs use `TaskCompletionSource<bool>` — the dialog sets the result o
|
||||
- Context menus are on both list items and task items
|
||||
- Right-click selects the item before showing the context menu
|
||||
- "Run Now" CanExecute re-evaluates when worker connection state changes
|
||||
- Icon gotcha: `PathIcon` fills geometry. Line-art/stroke icons must be defined as filled geometry or rendered as a stroked `Path` (e.g. `Icon.PlanDay` via the `Path.plan-icon` style); a pure stroke path used with `PathIcon` is invisible.
|
||||
|
||||
@@ -16,6 +16,7 @@ Worker/
|
||||
External/ — ExternalMcpService
|
||||
Hub/ — WorkerHub, HubBroadcaster
|
||||
Report/ — ClaudeHistoryReader, WeekReportPromptBuilder, WeekReportService; interfaces in Report/Interfaces/
|
||||
Prime/ — daily-prep ("Prime Claude"): PrimeScheduler (BackgroundService), PrimeRunner (runs the daily prep), DailyPrepPrompt (fixed prompt + CLI args + LogPath() helper), NextDueCalculator, PrimeScheduleSignal; interfaces in Prime/Interfaces/ (IPrimeRunner, IPrimeClock, IPrimeScheduleSignal, IPrimeBroadcaster)
|
||||
```
|
||||
|
||||
Interfaces (e.g. `IQueueWaker`, `IPrimeClock`, `ITaskStateService`) live in an `Interfaces/` subfolder within their area; the namespace stays the area namespace.
|
||||
@@ -35,6 +36,21 @@ Interfaces (e.g. `IQueueWaker`, `IPrimeClock`, `ITaskStateService`) live in an `
|
||||
- `AgentMcpTools` — `ListAgents`
|
||||
- `LifecycleMcpTools` — `ResetFailedTask`
|
||||
- `AppSettingsMcpTools` — `GetAppSettings` (read-only)
|
||||
- `ExternalMcpService` also exposes two daily-prep tools:
|
||||
- `GetDailyPrepCandidates` — returns Idle, non-blocked tasks in a git repo NOT excluded by `AppSettings.ReportExcludedPaths` and not already `IsMyDay`, plus the current Idle MyDay tasks and `maxTasks` (= `DailyPrepMaxTasks`). Repo-exclusion logic lives in the `DailyPrepFilter` helper (same file).
|
||||
- `SetMyDay` — sets a task's `IsMyDay` (+ optional `SortOrder`); server-side cap-guard rejects turning on MyDay beyond `DailyPrepMaxTasks` open (Idle) MyDay tasks.
|
||||
|
||||
## Daily Prep (Prime Claude)
|
||||
|
||||
- **PrimeScheduler** (hosted `BackgroundService`) computes the next due time from the `prime_schedules` table and at that time calls `IPrimeRunner.FireAsync`. A manual run arrives via `WorkerHub.RunDailyPrepNow`. A `SemaphoreSlim` single-flight gate **in `PrimeRunner`** prevents overlapping runs (returns "already running"); both scheduled and manual runs go through it.
|
||||
- **PrimeRunner** builds a fixed prompt via `DailyPrepPrompt.BuildPrompt`, parameterized by `AppSettings.DailyPrepMaxTasks` and today's date, then invokes:
|
||||
```
|
||||
claude -p --output-format stream-json --verbose --permission-mode acceptEdits --max-turns 30
|
||||
--allowedTools mcp__claudedo__get_daily_prep_candidates mcp__claudedo__set_my_day
|
||||
```
|
||||
It relies on the globally-registered `claudedo` MCP (installer's `RegisterMcpStep`) — no separate `--mcp-config`. This replaced the old warm-up "ping".
|
||||
- Each stdout line is streamed to the UI via `IPrimeBroadcaster.PrepLineAsync` AND written to `DailyPrepPrompt.LogPath()` = `<appdata>/logs/daily-prep.log` (truncated at the start of each run → last run only). `PrepStarted`/`PrepFinished` events bracket the run.
|
||||
- Agentic behaviour: Claude calls `get_daily_prep_candidates`, picks an effort-aware subset capped at `DailyPrepMaxTasks`, and marks them via `set_my_day` (which broadcasts `TaskUpdated` so the UI updates live).
|
||||
|
||||
## Status Model
|
||||
|
||||
@@ -105,9 +121,9 @@ Each CLI invocation is recorded in the `task_runs` table via `TaskRunRepository`
|
||||
|
||||
## SignalR Hub
|
||||
|
||||
**WorkerHub** methods: `Ping`, `GetActive`, `RunNow`, `CancelTask`, `WakeQueue`, `ContinueTask`, `ResetTask`, `ApproveReview`, `RejectReviewToQueue`, `RejectReviewToIdle`, `CancelReview`, `GetAgents`, `RefreshAgents`, `GetAppSettings`, `UpdateAppSettings`, `CleanupFinishedWorktrees`, `ResetAllWorktrees`, `MergeTask`, `GetMergeTargets`, `UpdateList`, `UpdateListConfig`, `GetListConfig`, `UpdateTaskAgentSettings`, `GetWeekReport`, `GenerateWeekReport`, `GetDailyNotes`, `AddDailyNote`, `UpdateDailyNote`, `DeleteDailyNote`
|
||||
**WorkerHub** methods: `Ping`, `GetActive`, `RunNow`, `CancelTask`, `WakeQueue`, `ContinueTask`, `ResetTask`, `ApproveReview`, `RejectReviewToQueue`, `RejectReviewToIdle`, `CancelReview`, `GetAgents`, `RefreshAgents`, `GetAppSettings`, `UpdateAppSettings`, `CleanupFinishedWorktrees`, `ResetAllWorktrees`, `MergeTask`, `GetMergeTargets`, `UpdateList`, `UpdateListConfig`, `GetListConfig`, `UpdateTaskAgentSettings`, `GetWeekReport`, `GenerateWeekReport`, `GetDailyNotes`, `AddDailyNote`, `UpdateDailyNote`, `DeleteDailyNote`, `RunDailyPrepNow`, `ClearMyDay`, `GetLastPrepLog`
|
||||
|
||||
**HubBroadcaster** events: `TaskStarted`, `TaskFinished`, `TaskMessage`, `WorktreeUpdated`, `TaskUpdated`, `RunCreated`, `ListUpdated`
|
||||
**HubBroadcaster** events: `TaskStarted`, `TaskFinished`, `TaskMessage`, `WorktreeUpdated`, `TaskUpdated`, `RunCreated`, `ListUpdated`, `PrimeFired`, `PrepStarted`, `PrepLine`, `PrepFinished`
|
||||
|
||||
## Config
|
||||
|
||||
|
||||
Reference in New Issue
Block a user