9.6 KiB
ClaudeDo.Data
Shared data layer: models, repositories, SQLite infrastructure, and git operations.
Models
- TaskEntity — Id, ListId, Title, Description, Status, PlanningPhase, BlockedByTaskId (FK to predecessor in a chain), ScheduledFor, Result, ReviewFeedback, LogPath, timestamps, CommitType, Model / SystemPrompt / AgentPath / MaxTurns (nullable overrides), IsStarred, IsMyDay, IsManual, Notes, ParentTaskId, PlanningSessionId / PlanningSessionToken / PlanningFinalizedAt, CreatedBy, HandlerBaseCommit / HandlerHeadCommit, InteractiveSessionId.
- Status / PlanningPhase / BlockedByTaskId 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 wheneverWorktreeis null → conpty-sessions.InteractiveSessionId= the claude session id an embedded ConPTY interactive task session runs under, persisted byInteractiveLaunchSpecServicebefore launch so a closed/aborted session can be resumed → conpty-sessions.- Legacy status values
Manual/Planning/Planned/Draft/Waitingwere retired; existing rows backfill via theRetireLegacyTaskStatusmigration.
- Status / PlanningPhase / BlockedByTaskId semantics + allowed transitions:
- 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).
VerifyCommandis an optional post-merge gate; null/blank = no gate → review-merge. - 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_mergepossible without searchinggit 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), andTokensIn/TokensOut/CacheReadTokens/CacheWriteTokens. ⚠️ Token fields come from the session transcript, not the stream-json event, as a per-run delta → usage-monitoring. - PrimeScheduleEntity — Id, Days (
[Flags] PrimeDaysweekday bitmask, columndays_of_week), TimeOfDay, Enabled, LastRunAt, PromptOverride, CreatedAt. Recurs on selected weekdays; no date range. - DailyNoteEntity — Id, Date (DateOnly), Text, SortOrder, CreatedAt →
daily_notes - WeekReportEntity — Id, StartDate/EndDate (DateOnly), Markdown, GeneratedAt →
week_reports, unique index on (start_date, end_date) - TaskAttachmentEntity — Id, TaskId (FK, ON DELETE CASCADE), FileName, ByteSize, CreatedAt →
task_attachments - SubtaskEntity, AgentInfo — subtasks / record for scanned agent files
AppSettingsEntity
Beyond the basics it carries:
| Property | Column | Default | Note |
|---|---|---|---|
DefaultMaxTurns |
default_max_turns |
40 | Lowered from 100; AddMaxTurnsCeiling backfilled the seeded row. |
MaxTurnsCeiling |
max_turns_ceiling |
80 | Hard ceiling every resolved max-turns value (task/list/global) is clamped to before a run. UpdateAsync clamps to min 1. |
ModelPresets |
model_presets |
seeded | JSON array of ModelPreset rows. ⚠️ AppSettingsRepository.GetAsync backfills shipping defaults on the first read after it's null, so it's never null once a run has started. |
UsageGateFiveHourPct / UsageGateSevenDayPct |
usage_gate_*_pct |
80 / 90 | Queue pause thresholds; 0 = off. |
UsageThrottle{FiveHour,SevenDay}{Soft,Hard}Pct |
usage_throttle_{five_hour,seven_day}_{soft,hard}_pct |
50 / 65 per bucket | Staged parallelism below the hard gate, per bucket; 0 = that stage off. Edited by dragging the usage-monitor gauges. |
DailyPrepMaxTasks |
daily_prep_max_tasks |
5 | Hard cap on MyDay tasks the daily prep may place. |
ReportExcludedPaths |
report_excluded_paths |
null | JSON array of excluded path prefixes. |
StandupWeekday |
standup_weekday |
Wednesday | int DayOfWeek. |
All four usage percentages are clamped 0..100 by AppSettingsRepository.UpdateAsync.
Gate/throttle semantics → usage-monitoring.
Model / effort registries
- ModelPresets / ModelPreset — per-model run defaults (
Model,Effort,MaxTurns), one row perModelRegistry.Aliasesentry, supplying the global effort and max-turns defaults. Ship defaults: haiku medium/20, sonnet high/30, opus high/40, fable high/25.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)always returns a usable row; resolution order and the fallback trap → worker-task-pipeline. - ModelRegistry —
NormalizeAliasis the strict, throwing validator foradd_task/planning model input.TryNormalizeAliasis the non-throwing counterpart for the run path (exact alias match, then substring match against a full model id, elsenull).ByCostAscending= the cost order the prompts use. - EffortRegistry — the
--effortlevels (low|medium|high|xhigh|max) +NormalizeLevel(blank → null = don't pass the flag).
Repositories
All use EF Core LINQ via ClaudeDoDbContext. The atomic Queued → Running claim lives in the
Worker's QueuePicker (FromSqlRaw), not here.
- TaskRepository — CRUD, planning helpers (
CreateChildAsync,SetPlanningStartedAsync,DiscardPlanningAsync,UpdateChildAsync),UpdateAgentSettingsAsync. ⚠️ Status-mutation primitives (MarkRunningAsync/MarkDoneAsync/MarkFailedAsync/FlipAllRunningToFailedAsync) areinternal— only the Worker'sTaskStateServicemay call them.CreateChildAsyncproduces children withStatus=Idle, PlanningPhase=None. - 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) - TaskAttachmentRepository —
AddAsync,UpdateAsync,GetAsync(taskId, fileName),ListByTaskIdAsync,DeleteAsync(taskId, fileName),DeleteAllForTaskAsync - DailyNoteRepository, WeekReportRepository, TaskRunRepository, SubtaskRepository, AppSettingsRepository
TaskRepository.DeleteAsync and ListRepository.DeleteAsync also delete the on-disk attachment
dir(s) via an optional AttachmentStore ctor param (defaults to the production store).
Infrastructure
- ClaudeDoDbContext — EF Core DbContext; WAL mode + foreign keys via
UseSqliteoptions - IDbContextFactory<ClaudeDoDbContext> — registered in DI; used by singleton consumers (e.g. the 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 a path-traversal/containment guard. AlsoDeleteFile,DeleteTaskDir,TaskDir,Root,EnumerateTaskIds(used by the worker orphan sweep). Attachment files live outside git worktrees intentionally.
Git
GitService — async wrapper around the git CLI (ProcessStartInfo, no shell):
- Worktrees: add (serialized to avoid a commondir race), remove, prune, list paths for branch
- Branches: 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(shows 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 (
MERGE_HEAD), conflicted files - Revert:
RevertMergeCommitAsync(git revert --no-edit -m 1 <sha>),RevertAbortAsync,IsMidRevertAsync(REVERT_HEAD, mirrorsIsMidMergeAsync) PreviewMergeAsync(non-destructive check viagit merge-tree --write-tree),CountChangedFilesAsync, rev-parse, is-git-repo
⚠️ Revert never resets or rewrites — it always produces a new commit, because the working directory it operates on is shared with other concurrent sessions.
Schema
Tables (one per line so parallel migrations don't collide on the same line):
liststasksworktreeslist_configtask_runssubtasksapp_settingsprime_schedulesdaily_notesweek_reportstask_attachments
Managed by EF Core migrations in Migrations/ — ls Migrations/ is the authoritative history;
don't maintain a changelog here. tasks holds status, planning_phase (default none), and
blocked_by_task_id (FK to tasks.id, ON DELETE SET NULL).
Conventions
- Enum ↔ string mapping via EF Core
ValueConverter, configured inIEntityTypeConfiguration<T> - Entity configurations live in
Configuration/ - Primary keys are
init-only strings (GUIDs assigned at creation) - All methods are async with CancellationToken where applicable