diff --git a/src/ClaudeDo.Data/CLAUDE.md b/src/ClaudeDo.Data/CLAUDE.md index fa68110a..961e083f 100644 --- a/src/ClaudeDo.Data/CLAUDE.md +++ b/src/ClaudeDo.Data/CLAUDE.md @@ -4,14 +4,15 @@ Shared data layer: models, repositories, SQLite infrastructure, and git operatio ## Models -- **TaskEntity** — Id, ListId, Title, Description, Status, PlanningPhase, BlockedByTaskId (FK to predecessor in a chain), DependsOnTaskId (FK to a user/MCP-declared predecessor, distinct from BlockedByTaskId), ScheduledFor, Result, ReviewFeedback, LogPath, timestamps, CommitType, Model / SystemPrompt / AgentPath / MaxTurns (nullable overrides), IsStarred, IsMyDay, IsManual, Notes, ParentTaskId, PlanningSessionId / PlanningSessionToken / PlanningFinalizedAt, CreatedBy, HandlerBaseCommit / HandlerHeadCommit, InteractiveSessionId, Number (global immutable human-readable alias). +- **TaskEntity** — Id, ListId, Title, Description, Status, PlanningPhase, BlockedByTaskId (FK to predecessor in a chain), DependsOnTaskId (FK to a user/MCP-declared predecessor, distinct from BlockedByTaskId), ScheduledFor, Result, ReviewFeedback, LogPath, timestamps, CommitType, Model / SystemPrompt / AgentPath / MaxTurns / PermissionMode (nullable overrides), IsStarred, IsMyDay, IsManual, Notes, ParentTaskId, PlanningSessionId / PlanningSessionToken / PlanningFinalizedAt, CreatedBy, HandlerBaseCommit / HandlerHeadCommit, InteractiveSessionId, Number (global immutable human-readable alias). - Status / PlanningPhase / BlockedByTaskId / DependsOnTaskId semantics + allowed transitions: `ClaudeDo.Worker/CLAUDE.md` → Status Model. - `HandlerBaseCommit`/`HandlerHeadCommit` = the review range for a **worktree-less "list handler" host task** ("Let Claude handle it"), which commits straight into the list's working dir instead of a per-task worktree. Everything that reads a task's diff falls back to this pair whenever `Worktree` is null → [conpty-sessions](../../docs/explore-notes/conpty-sessions.md). - `InteractiveSessionId` = the claude session id an embedded ConPTY interactive task session runs under, persisted by `InteractiveLaunchSpecService` before launch so a closed/aborted session can be resumed → [conpty-sessions](../../docs/explore-notes/conpty-sessions.md). - `Number` (INTEGER NOT NULL, unique index `idx_tasks_number`) — a global, monotonically increasing display alias for tasks. **Never reused**: deleting a task leaves a gap in numbering, ensuring that a task number that appears in a log or report always points to the same task (if it exists). Allocated by `TaskNumberAllocator.AddWithNumberAsync` on every insert via a persistent counter (`app_settings.next_task_number`). **Why not `MAX(number) + 1`:** deleting the highest-numbered task would free its number for reuse, breaking the immutability guarantee. The allocator uses an `UPDATE…RETURNING` statement to claim numbers atomically, retrying up to 5 times if a uniqueness collision occurs (the counter advances on each attempt, so retries always allocate fresh numbers). The one-time backfill for pre-existing rows lived in the `AddTaskNumbers` migration, squashed away 2026-08-26. - Legacy status values `Manual`/`Planning`/`Planned`/`Draft`/`Waiting` were retired. The backfill migration was squashed away (2026-08-26) — a database still holding those values can't be upgraded, see Schema below. - **ListEntity** — Id, Name, WorkingDir, DefaultCommitType, CreatedAt, IsManual (reminder list — tasks created here default to `IsManual`) -- **ListConfigEntity** — ListId (PK, 1:1), Model, SystemPrompt, AgentPath, MaxTurns, SessionSkills, VerifyCommand (all nullable). `VerifyCommand` is an optional post-merge gate; null/blank = no gate → [review-merge](../../docs/explore-notes/review-merge.md). +- **ListConfigEntity** — ListId (PK, 1:1), Model, SystemPrompt, AgentPath, MaxTurns, SessionSkills, VerifyCommand, PermissionMode (all nullable) + SerializeOnFileOverlap (bool). `VerifyCommand` is an optional post-merge gate; null/blank = no gate → [review-merge](../../docs/explore-notes/review-merge.md). `PermissionMode` is null = inherit the global default; resolution order task → list → global lives in `EffectiveRunConfigResolver`. + - ⚠️ `ListRepository.SetConfigAsync` copies the entity **verbatim**, so every writer must carry the fields it doesn't own (`SessionSkills`, `SerializeOnFileOverlap`, `PermissionMode`) or they silently reset. `UpdateListConfigDto.SerializeOnFileOverlap` is tri-state (`null` = keep stored) for exactly that reason — only the list-settings modal sends an explicit value. - **WorktreeEntity** — TaskId (PK, 1:1), Path, BranchName, BaseCommit, HeadCommit, DiffStat, MergeCommit (nullable — SHA of the merge commit this branch produced; the only thing making `revert_merge` possible without searching `git log`), State (`Active|Merged|Discarded|Kept`) - **TaskRunEntity** — per-run record: session_id, turns, result, structured output, exit code, log path, nullable `Model` (what the run actually executed with), and `TokensIn`/`TokensOut`/`CacheReadTokens`/`CacheWriteTokens`. ⚠️ Token fields come from the **session transcript**, not the stream-json event, as a per-run delta → [usage-monitoring](../../docs/explore-notes/usage-monitoring.md). - **PrimeScheduleEntity** — Id, Days (`[Flags] PrimeDays` weekday bitmask, column `days_of_week`), TimeOfDay, Enabled, LastRunAt, PromptOverride, Kind (`PrimeActionKind`, column `action_kind`, default `ping`), CreatedAt. Recurs on selected weekdays; no date range. `PromptOverride`'s role depends on `Kind`: ignored for `Ping`, appended to the daily-prep prompt for `FillMyDay`, and the entire prompt for `Custom`. @@ -106,8 +107,14 @@ not a migration chain. Consequences: at `20260825063230_AddPrimeActionKind`). A database that stopped *mid*-chain can no longer be upgraded and throws with a reinstall message instead of being stamped onto a schema it lacks. Covered by `MigrationBaselineTests` — the only test that runs a real `Migrate()` (every other - Data.Tests fixture uses `EnsureCreated`, which skips migrations entirely). -- Don't hand-edit the migration; add new ones on top as usual. + Data.Tests fixture uses `EnsureCreated`, which skips migrations entirely). Its fixture builds the + legacy database by migrating **to** `InitialCreate` and seeding rows with raw SQL: `EnsureCreated` + would build *today's* schema, so every post-squash migration would then re-add an existing column + ("duplicate column name"). Assert "nothing pending", never "InitialCreate is the only history row". +- Don't hand-edit the squashed migration; add new ones on top as usual. + `dotnet ef migrations add --project src/ClaudeDo.Data/ClaudeDo.Data.csproj --configuration + Release` — `Data` is its own startup project (it holds the `Design` package and a design-time + factory), and `--configuration Release` is needed because a running Worker locks `Debug`. `tasks` holds `status`, `planning_phase` (default `none`), `blocked_by_task_id` (FK to `tasks.id`, `ON DELETE SET NULL`), and `depends_on_task_id` (same FK diff --git a/src/ClaudeDo.Worker/CLAUDE.md b/src/ClaudeDo.Worker/CLAUDE.md index adbe7f43..e8cbc1c1 100644 --- a/src/ClaudeDo.Worker/CLAUDE.md +++ b/src/ClaudeDo.Worker/CLAUDE.md @@ -245,14 +245,21 @@ non-obvious, behaviour-changing. A fixed bug is git history, not a finding. - `db_path`, `sandbox_root`, `log_root` - `worktree_root_strategy` (`sibling` | `central`), `central_worktree_root` - `queue_backstop_interval_ms` (30000) — also the gate/throttle recovery timer -- `signalr_port` (47821), `claude_bin` +- `signalr_port` (47821), `claude_bin` — `claude_bin` is the one field the UI can write + (Settings → Dateien → CLAUDE CLI, hub `GetClaudeBin`/`SetClaudeBin`). It edits the DI-singleton + `WorkerConfig` in place *and* persists via `SaveClaudeBin`, so it applies to the next spawn + without a restart. Every other field here is hand-edit only. - `usage_poll_interval_seconds` (60, clamped to min 15 on load) - `online_inbox` — `enabled` (false by default; when false the entire `Online/` stack is not registered), `api_base_url` (must be HTTPS or loopback, validated at startup), `poll_interval_seconds` (60), `zitadel.authority`/`client_id`/`scopes`. The refresh token is **not** in this file — DPAPI-encrypted at `~/.claudeDo/online-inbox.token`. Per-list config (`list_config` in DB) provides defaults for `model`, `system_prompt`, -`agent_path`, `max_turns`, `session_skills`; tasks override each individually. `verify_command` is -list-only — there is no task-level override — and is written via `set_list_config`'s -`verifyCommand` parameter (`ConfigMcpTools`), the only writer besides the UI's list config editor. +`agent_path`, `max_turns`, `session_skills`, `permission_mode`; tasks override each individually +(resolution lives in `EffectiveRunConfigResolver`, so `TaskRunner` and +`get_effective_run_config` can't drift). `verify_command` and `serialize_on_file_overlap` are +list-only — no task-level override. `verify_command` is written via `set_list_config`'s +`verifyCommand` parameter (`ConfigMcpTools`) and the UI's list config editor; +`serialize_on_file_overlap` and `permission_mode` are UI-only (`set_list_config` preserves but +does not expose them). ## Notes