10 KiB
ClaudeDo.Data
Shared data layer: models, repositories, SQLite infrastructure, and git operations.
Models
- TaskEntity — Id, ListId, Title, Description, Status (
Idle|Queued|Running|WaitingForChildren|WaitingForReview|Done|Failed|Cancelled), PlanningPhase (None|Active|Finalized— parent-only), BlockedByTaskId (nullable FK to predecessor in a chain), ScheduledFor, Result, ReviewFeedback (nullable; reviewer's rejection comment, consumed and cleared by the runner on the next re-run), LogPath, timestamps, CommitType, Model / SystemPrompt / AgentPath / MaxTurns (nullable overrides), IsStarred, IsMyDay, IsManual (reminder only the user can do — automation skips it), Notes, ParentTaskId, PlanningSessionId, PlanningSessionToken, PlanningFinalizedAt, CreatedBy, HandlerBaseCommit / HandlerHeadCommit (nullable; review range for a worktree-less "list handler" host task — Mission Control's "Let Claude handle it" — which commits straight into the list's working dir instead of a per-task worktree:HandlerBaseCommitis stamped to the list repo's HEAD when the host task is created,HandlerHeadCommitwhen it's submitted for review; the Worker'sSubmitTaskForReview/GetTaskDiffand the Ui'sDetailsIslandViewModel/MergeSectionViewModelfall back to this pair wheneverWorktreeis null). Legacy valuesManual/Planning/Planned/Draft/Waitingwere retired; existing rows backfill automatically via theRetireLegacyTaskStatusmigration. - ListEntity — Id, Name, WorkingDir, DefaultCommitType, CreatedAt, IsManual (reminder list — tasks created here default to
IsManual) - ListConfigEntity — ListId (PK, 1:1 with list), Model, SystemPrompt, AgentPath, MaxTurns, SessionSkills, VerifyCommand (all nullable).
VerifyCommandis an optional post-merge gate (seeClaudeDo.Worker/CLAUDE.md→ TaskMergeService): null/blank = today's behavior, no gate. - WorktreeEntity — TaskId (PK, 1:1 with task), Path, BranchName, BaseCommit, HeadCommit, DiffStat, MergeCommit (nullable; SHA of the merge commit this worktree's branch produced on the target branch, stamped by
TaskMergeServicethe moment a merge/continue-merge succeeds — the only thing that makesrevert_mergepossible without heuristically searchinggit log; null for any worktree merged before this field existed), State (Active|Merged|Discarded|Kept) - TaskRunEntity — per-run record (session_id, tokens, turns, result, structured output, exit code, log path, nullable
Model— the model the run actually executed with, columnmodel) - PrimeScheduleEntity — Id, Days (
[Flags] PrimeDaysweekday bitmask, stored asdays_of_weekint), 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) - TaskAttachmentEntity — Id, TaskId (FK to tasks, ON DELETE CASCADE), FileName, ByteSize, CreatedAt → table
task_attachments - AppSettingsEntity also carries
ReportExcludedPaths(string?, JSON array of excluded path prefixes, columnreport_excluded_paths),StandupWeekday(int DayOfWeek, default Wednesday, columnstandup_weekday), andDailyPrepMaxTasks(int, default 5, columndaily_prep_max_tasks— hard cap on how many open tasks the daily-prep / "Prime Claude" feature may place in MyDay) ,ModelPresets(string?, JSON array ofModelPresetrows, columnmodel_presets), andUsageGateFiveHourPct/UsageGateSevenDayPct(int, defaults 80/90, columnsusage_gate_five_hour_pct/usage_gate_seven_day_pct— percentage of the 5h/7d Claude usage window at which the autonomous queue pauses;0= gate off;AppSettingsRepository.UpdateAsyncclamps both to 0..100) - ModelPresets / ModelPreset — per-model run defaults (
Model,Effort,MaxTurns): one row perModelRegistry.Aliasesentry, supplying the global effort and max-turns defaults.Parse/Serializenormalize (unknown models dropped, missing aliases filled fromDefaults, effort validated, turns clamped 1–200) and never throw — a malformed settings row must not stop a run.For(presets, model, fallbackMaxTurns = 30)always returns a usable row:modelis resolved throughModelRegistry.TryNormalizeAliasfirst, so a full CLI model id (e.g.claude-sonnet-4-6) still hits its alias's preset row instead of missing every lookup and falling through; only a model that normalizes to nothing recognized falls back to a synthesized row (EffortRegistry.DefaultLevel+fallbackMaxTurns— callers passAppSettings.DefaultMaxTurnshere so that setting has a real effect instead of a hardcoded number). Ship defaults: haiku medium/20, sonnet high/30, opus high/40, fable high/25. - ModelRegistry.TryNormalizeAlias — non-throwing counterpart to
NormalizeAliasfor the run path: exact alias match, then substring match against a full model id, elsenull. Never throws, unlikeNormalizeAlias(which stays the strict, throwing validator foradd_task/planning model input). - EffortRegistry — the
--effortlevels (low|medium|high|xhigh|max) +NormalizeLevel(blank → null = don't pass the flag) - SubtaskEntity, AppSettingsEntity, AgentInfo — existing helpers / settings / record for scanned agent files
Repositories
All repositories use EF Core LINQ queries via ClaudeDoDbContext. The atomic Queued -> Running claim lives in the Worker's QueuePicker (uses FromSqlRaw), not here.
- TaskRepository — CRUD, planning helpers (
CreateChildAsync,SetPlanningStartedAsync,DiscardPlanningAsync,UpdateChildAsync),UpdateAgentSettingsAsync(model / system-prompt / agent-path overrides). Status-mutation primitivesMarkRunningAsync/MarkDoneAsync/MarkFailedAsync/FlipAllRunningToFailedAsyncareinternaland called only byTaskStateServicein the worker.CreateChildAsyncproduces children withStatus=Idle, PlanningPhase=None; once their parent'sPlanningPhasebecomesFinalized, the chain coordinator queues them. - ListRepository — CRUD,
GetConfigAsync/SetConfigAsync(upsert) /DeleteConfigAsyncforlist_config - WorktreeRepository — CRUD,
UpdateHeadAsync,SetStateAsync,SetMergedAsync(atomically sets State=Merged and stamps MergeCommit in one update — the only writer of MergeCommit) - TaskRunRepository, SubtaskRepository, AppSettingsRepository
- DailyNoteRepository —
ListByDayAsync,ListBetweenAsync,AddAsync,UpdateAsync,DeleteAsync - WeekReportRepository —
GetByRangeAsync,UpsertAsync - TaskAttachmentRepository —
AddAsync,UpdateAsync,GetAsync(taskId, fileName),ListByTaskIdAsync,DeleteAsync(taskId, fileName),DeleteAllForTaskAsync
Infrastructure
- ClaudeDoDbContext — EF Core DbContext; configured with WAL mode and foreign keys via
UseSqliteoptions - IDbContextFactory — registered in DI; used by singleton consumers (e.g. Worker hosted service)
- Paths — expands
~and%USERPROFILE%, resolves relative paths. App root:~/.todo-app - AppSettings — loads
~/.todo-app/ui.config.json(DbPath, SignalRUrl) - AttachmentStore — dependency-free file store; default root
~/.todo-app/attachments/<taskId>/.SaveAsyncenforces a 5 MB cap and path-traversal/containment guard. Also exposesDeleteFile,DeleteTaskDir,TaskDir,Root, andEnumerateTaskIds(used by the worker orphan sweep). Attachment files live outside git worktrees intentionally.
Git
- GitService — async wrapper around git CLI (ProcessStartInfo, no shell). Worktree ops (add — serialized to avoid a commondir race —, remove, prune, list paths for branch), branch ops (current, list local, checkout, delete), staging/commit (status porcelain, add-all, add-path, commit via stdin), diffs (working tree, branch vs base, commit range
base..head— used to show a merged task's diff after the worktree is gone —, per-file, diff-stat, committed files, has-changes), merge (ff-only, no-ff, abort, mid-merge detection, conflicted files), revert (RevertMergeCommitAsync—git revert --no-edit -m 1 <sha>, reverts a merge commit against its first parent;RevertAbortAsync;IsMidRevertAsync—REVERT_HEADpresence, mirrorsIsMidMergeAsync'sMERGE_HEAD),PreviewMergeAsync(non-destructive mergeability check viagit merge-tree --write-tree),CountChangedFilesAsync, rev-parse, is-git-repo. Revert never resets/rewrites — it always produces a new commit, because the working directory it operates on is shared with other concurrent sessions.
Schema
Tables: lists, tasks, worktrees, list_config, task_runs, subtasks, app_settings, prime_schedules, daily_notes, week_reports, task_attachments. 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). Migration AddTaskAttachments created the task_attachments table. Migration AddModelPresetsAndManualFlag added app_settings.model_presets plus the is_manual flags on tasks and lists. Migration AddHandlerCommitRange added the nullable handler_base_commit/handler_head_commit columns to tasks. Migration AddUsageGateAndRunModel added app_settings.usage_gate_five_hour_pct/usage_gate_seven_day_pct (defaults 80/90) and the nullable task_runs.model column. Migration AddWorktreeMergeCommit added the nullable worktrees.merge_commit column. Migration AddVerifyCommand added the nullable list_config.verify_command column. TaskRepository.DeleteAsync and ListRepository.DeleteAsync also delete the on-disk attachment dir(s) via an optional AttachmentStore ctor param (defaults to the production store).
Conventions
- Enum <-> string mapping via EF Core
ValueConverter(configured inIEntityTypeConfiguration<T>) - Entity configurations live in the
Configuration/folder - Primary keys are
init-only strings (GUIDs assigned at creation) - All methods are async with CancellationToken where applicable