feat(data): session skills entity, repository, and migration
This commit is contained in:
@@ -51,6 +51,7 @@ public class ClaudeDoDbContext : DbContext
|
|||||||
public DbSet<PrimeScheduleEntity> PrimeSchedules => Set<PrimeScheduleEntity>();
|
public DbSet<PrimeScheduleEntity> PrimeSchedules => Set<PrimeScheduleEntity>();
|
||||||
public DbSet<DailyNoteEntity> DailyNotes => Set<DailyNoteEntity>();
|
public DbSet<DailyNoteEntity> DailyNotes => Set<DailyNoteEntity>();
|
||||||
public DbSet<WeekReportEntity> WeekReports => Set<WeekReportEntity>();
|
public DbSet<WeekReportEntity> WeekReports => Set<WeekReportEntity>();
|
||||||
|
public DbSet<SessionSkillEntity> SessionSkills => Set<SessionSkillEntity>();
|
||||||
|
|
||||||
private static readonly ValueConverter<DateTime, DateTime> UtcConverter =
|
private static readonly ValueConverter<DateTime, DateTime> UtcConverter =
|
||||||
new(v => v, v => DateTime.SpecifyKind(v, DateTimeKind.Utc));
|
new(v => v, v => DateTime.SpecifyKind(v, DateTimeKind.Utc));
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ public class AppSettingsEntityConfiguration : IEntityTypeConfiguration<AppSettin
|
|||||||
builder.Property(s => s.DailyPrepMaxTasks)
|
builder.Property(s => s.DailyPrepMaxTasks)
|
||||||
.HasColumnName("daily_prep_max_tasks").IsRequired().HasDefaultValue(5);
|
.HasColumnName("daily_prep_max_tasks").IsRequired().HasDefaultValue(5);
|
||||||
|
|
||||||
|
builder.Property(s => s.SessionSkills).HasColumnName("session_skills");
|
||||||
|
|
||||||
builder.HasData(new AppSettingsEntity { Id = AppSettingsEntity.SingletonId });
|
builder.HasData(new AppSettingsEntity { Id = AppSettingsEntity.SingletonId });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,5 +16,6 @@ public class ListConfigEntityConfiguration : IEntityTypeConfiguration<ListConfig
|
|||||||
builder.Property(c => c.SystemPrompt).HasColumnName("system_prompt");
|
builder.Property(c => c.SystemPrompt).HasColumnName("system_prompt");
|
||||||
builder.Property(c => c.AgentPath).HasColumnName("agent_path");
|
builder.Property(c => c.AgentPath).HasColumnName("agent_path");
|
||||||
builder.Property(c => c.MaxTurns).HasColumnName("max_turns");
|
builder.Property(c => c.MaxTurns).HasColumnName("max_turns");
|
||||||
|
builder.Property(c => c.SessionSkills).HasColumnName("session_skills");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using ClaudeDo.Data.Models;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Data.Configuration;
|
||||||
|
|
||||||
|
public class SessionSkillEntityConfiguration : IEntityTypeConfiguration<SessionSkillEntity>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<SessionSkillEntity> builder)
|
||||||
|
{
|
||||||
|
builder.ToTable("session_skills");
|
||||||
|
|
||||||
|
builder.HasKey(s => s.Name);
|
||||||
|
builder.Property(s => s.Name).HasColumnName("name");
|
||||||
|
builder.Property(s => s.SourceUrl).HasColumnName("source_url").IsRequired();
|
||||||
|
builder.Property(s => s.PinnedRef).HasColumnName("pinned_ref").IsRequired();
|
||||||
|
builder.Property(s => s.Subpath).HasColumnName("subpath").IsRequired();
|
||||||
|
builder.Property(s => s.Description).HasColumnName("description").IsRequired();
|
||||||
|
builder.Property(s => s.AddedAt).HasColumnName("added_at").IsRequired();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -91,6 +91,7 @@ public class TaskEntityConfiguration : IEntityTypeConfiguration<TaskEntity>
|
|||||||
builder.Property(t => t.IsMyDay).HasColumnName("is_my_day").HasDefaultValue(false);
|
builder.Property(t => t.IsMyDay).HasColumnName("is_my_day").HasDefaultValue(false);
|
||||||
builder.Property(t => t.Notes).HasColumnName("notes");
|
builder.Property(t => t.Notes).HasColumnName("notes");
|
||||||
builder.Property(t => t.SortOrder).HasColumnName("sort_order").IsRequired().HasDefaultValue(0);
|
builder.Property(t => t.SortOrder).HasColumnName("sort_order").IsRequired().HasDefaultValue(0);
|
||||||
|
builder.Property(t => t.SessionSkills).HasColumnName("session_skills");
|
||||||
|
|
||||||
builder.Property(t => t.ParentTaskId).HasColumnName("parent_task_id");
|
builder.Property(t => t.ParentTaskId).HasColumnName("parent_task_id");
|
||||||
builder.Property(t => t.PlanningSessionId).HasColumnName("planning_session_id");
|
builder.Property(t => t.PlanningSessionId).HasColumnName("planning_session_id");
|
||||||
|
|||||||
@@ -0,0 +1,786 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using ClaudeDo.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace ClaudeDo.Data.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(ClaudeDoDbContext))]
|
||||||
|
[Migration("20260703072917_AddSessionSkills")]
|
||||||
|
partial class AddSessionSkills
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder.HasAnnotation("ProductVersion", "8.0.11");
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.AppSettingsEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<string>("CentralWorktreeRoot")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("central_worktree_root");
|
||||||
|
|
||||||
|
b.Property<int>("DailyPrepMaxTasks")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(5)
|
||||||
|
.HasColumnName("daily_prep_max_tasks");
|
||||||
|
|
||||||
|
b.Property<string>("DefaultClaudeInstructions")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValue("")
|
||||||
|
.HasColumnName("default_claude_instructions");
|
||||||
|
|
||||||
|
b.Property<int>("DefaultMaxTurns")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(30)
|
||||||
|
.HasColumnName("default_max_turns");
|
||||||
|
|
||||||
|
b.Property<string>("DefaultModel")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValue("sonnet")
|
||||||
|
.HasColumnName("default_model");
|
||||||
|
|
||||||
|
b.Property<string>("DefaultPermissionMode")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValue("bypassPermissions")
|
||||||
|
.HasColumnName("default_permission_mode");
|
||||||
|
|
||||||
|
b.Property<int>("MaxParallelExecutions")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(1)
|
||||||
|
.HasColumnName("max_parallel_executions");
|
||||||
|
|
||||||
|
b.Property<string>("RepoImportFolders")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("repo_import_folders");
|
||||||
|
|
||||||
|
b.Property<string>("ReportExcludedPaths")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("report_excluded_paths");
|
||||||
|
|
||||||
|
b.Property<string>("SessionSkills")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("session_skills");
|
||||||
|
|
||||||
|
b.Property<int>("StandupWeekday")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(3)
|
||||||
|
.HasColumnName("standup_weekday");
|
||||||
|
|
||||||
|
b.Property<int>("WorktreeAutoCleanupDays")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(7)
|
||||||
|
.HasColumnName("worktree_auto_cleanup_days");
|
||||||
|
|
||||||
|
b.Property<bool>("WorktreeAutoCleanupEnabled")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(false)
|
||||||
|
.HasColumnName("worktree_auto_cleanup_enabled");
|
||||||
|
|
||||||
|
b.Property<string>("WorktreeStrategy")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValue("sibling")
|
||||||
|
.HasColumnName("worktree_strategy");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("app_settings", (string)null);
|
||||||
|
|
||||||
|
b.HasData(
|
||||||
|
new
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
DailyPrepMaxTasks = 5,
|
||||||
|
DefaultClaudeInstructions = "",
|
||||||
|
DefaultMaxTurns = 100,
|
||||||
|
DefaultModel = "sonnet",
|
||||||
|
DefaultPermissionMode = "auto",
|
||||||
|
MaxParallelExecutions = 1,
|
||||||
|
StandupWeekday = 3,
|
||||||
|
WorktreeAutoCleanupDays = 7,
|
||||||
|
WorktreeAutoCleanupEnabled = false,
|
||||||
|
WorktreeStrategy = "sibling"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.DailyNoteEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<DateOnly>("Date")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("note_date");
|
||||||
|
|
||||||
|
b.Property<int>("SortOrder")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("sort_order");
|
||||||
|
|
||||||
|
b.Property<string>("Text")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Date");
|
||||||
|
|
||||||
|
b.ToTable("daily_notes", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.ListConfigEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("ListId")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("list_id");
|
||||||
|
|
||||||
|
b.Property<string>("AgentPath")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("agent_path");
|
||||||
|
|
||||||
|
b.Property<int?>("MaxTurns")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("max_turns");
|
||||||
|
|
||||||
|
b.Property<string>("Model")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("model");
|
||||||
|
|
||||||
|
b.Property<string>("SessionSkills")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("session_skills");
|
||||||
|
|
||||||
|
b.Property<string>("SystemPrompt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("system_prompt");
|
||||||
|
|
||||||
|
b.HasKey("ListId");
|
||||||
|
|
||||||
|
b.ToTable("list_config", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.ListEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<string>("DefaultCommitType")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValue("chore")
|
||||||
|
.HasColumnName("default_commit_type");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("name");
|
||||||
|
|
||||||
|
b.Property<int>("SortOrder")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(0)
|
||||||
|
.HasColumnName("sort_order");
|
||||||
|
|
||||||
|
b.Property<string>("WorkingDir")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("working_dir");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("SortOrder")
|
||||||
|
.HasDatabaseName("idx_lists_sort");
|
||||||
|
|
||||||
|
b.ToTable("lists", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.PrimeScheduleEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<int>("Days")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(31)
|
||||||
|
.HasColumnName("days_of_week");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(true)
|
||||||
|
.HasColumnName("enabled");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset?>("LastRunAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("last_run_at");
|
||||||
|
|
||||||
|
b.Property<string>("PromptOverride")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("prompt_override");
|
||||||
|
|
||||||
|
b.Property<TimeSpan>("TimeOfDay")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("time_of_day");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("prime_schedules", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.SessionSkillEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("name");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("AddedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("added_at");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("description");
|
||||||
|
|
||||||
|
b.Property<string>("PinnedRef")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("pinned_ref");
|
||||||
|
|
||||||
|
b.Property<string>("SourceUrl")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("source_url");
|
||||||
|
|
||||||
|
b.Property<string>("Subpath")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("subpath");
|
||||||
|
|
||||||
|
b.HasKey("Name");
|
||||||
|
|
||||||
|
b.ToTable("session_skills", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.SubtaskEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<bool>("Completed")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(false)
|
||||||
|
.HasColumnName("completed");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<int>("OrderNum")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("order_num");
|
||||||
|
|
||||||
|
b.Property<string>("TaskId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("task_id");
|
||||||
|
|
||||||
|
b.Property<string>("Title")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("title");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("TaskId")
|
||||||
|
.HasDatabaseName("idx_subtasks_task_id");
|
||||||
|
|
||||||
|
b.ToTable("subtasks", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.TaskAttachmentEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<long>("ByteSize")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("byte_size");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<string>("FileName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("file_name");
|
||||||
|
|
||||||
|
b.Property<string>("TaskId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("task_id");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("TaskId")
|
||||||
|
.HasDatabaseName("idx_task_attachments_task_id");
|
||||||
|
|
||||||
|
b.ToTable("task_attachments", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.TaskEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<string>("AgentPath")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("agent_path");
|
||||||
|
|
||||||
|
b.Property<string>("BlockedByTaskId")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("blocked_by_task_id");
|
||||||
|
|
||||||
|
b.Property<string>("CommitType")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValue("chore")
|
||||||
|
.HasColumnName("commit_type");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<string>("CreatedBy")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created_by");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("description");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("FinishedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("finished_at");
|
||||||
|
|
||||||
|
b.Property<bool>("IsMyDay")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(false)
|
||||||
|
.HasColumnName("is_my_day");
|
||||||
|
|
||||||
|
b.Property<bool>("IsStarred")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(false)
|
||||||
|
.HasColumnName("is_starred");
|
||||||
|
|
||||||
|
b.Property<string>("ListId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("list_id");
|
||||||
|
|
||||||
|
b.Property<string>("LogPath")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("log_path");
|
||||||
|
|
||||||
|
b.Property<int?>("MaxTurns")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("max_turns");
|
||||||
|
|
||||||
|
b.Property<string>("Model")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("model");
|
||||||
|
|
||||||
|
b.Property<string>("Notes")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("notes");
|
||||||
|
|
||||||
|
b.Property<string>("ParentTaskId")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("parent_task_id");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("PlanningFinalizedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("planning_finalized_at");
|
||||||
|
|
||||||
|
b.Property<string>("PlanningPhase")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValue("none")
|
||||||
|
.HasColumnName("planning_phase");
|
||||||
|
|
||||||
|
b.Property<string>("PlanningSessionId")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("planning_session_id");
|
||||||
|
|
||||||
|
b.Property<string>("PlanningSessionToken")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("planning_session_token");
|
||||||
|
|
||||||
|
b.Property<string>("Result")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("result");
|
||||||
|
|
||||||
|
b.Property<string>("ReviewFeedback")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("review_feedback");
|
||||||
|
|
||||||
|
b.Property<int>("RoadblockCount")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(0)
|
||||||
|
.HasColumnName("roadblock_count");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("ScheduledFor")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("scheduled_for");
|
||||||
|
|
||||||
|
b.Property<string>("SessionSkills")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("session_skills");
|
||||||
|
|
||||||
|
b.Property<int>("SortOrder")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(0)
|
||||||
|
.HasColumnName("sort_order");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("StartedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("started_at");
|
||||||
|
|
||||||
|
b.Property<string>("Status")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("status");
|
||||||
|
|
||||||
|
b.Property<string>("SystemPrompt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("system_prompt");
|
||||||
|
|
||||||
|
b.Property<string>("Title")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("title");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("BlockedByTaskId")
|
||||||
|
.HasDatabaseName("idx_tasks_blocked_by");
|
||||||
|
|
||||||
|
b.HasIndex("ListId")
|
||||||
|
.HasDatabaseName("idx_tasks_list_id");
|
||||||
|
|
||||||
|
b.HasIndex("ParentTaskId")
|
||||||
|
.HasDatabaseName("idx_tasks_parent_task_id");
|
||||||
|
|
||||||
|
b.HasIndex("Status")
|
||||||
|
.HasDatabaseName("idx_tasks_status");
|
||||||
|
|
||||||
|
b.HasIndex("ListId", "SortOrder")
|
||||||
|
.HasDatabaseName("idx_tasks_list_sort");
|
||||||
|
|
||||||
|
b.ToTable("tasks", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.TaskRunEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<string>("ErrorMarkdown")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("error_markdown");
|
||||||
|
|
||||||
|
b.Property<int?>("ExitCode")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("exit_code");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("FinishedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("finished_at");
|
||||||
|
|
||||||
|
b.Property<bool>("IsRetry")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(false)
|
||||||
|
.HasColumnName("is_retry");
|
||||||
|
|
||||||
|
b.Property<string>("LogPath")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("log_path");
|
||||||
|
|
||||||
|
b.Property<string>("Prompt")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("prompt");
|
||||||
|
|
||||||
|
b.Property<string>("ResultMarkdown")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("result_markdown");
|
||||||
|
|
||||||
|
b.Property<int>("RunNumber")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("run_number");
|
||||||
|
|
||||||
|
b.Property<string>("SessionId")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("session_id");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("StartedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("started_at");
|
||||||
|
|
||||||
|
b.Property<string>("StructuredOutputJson")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("structured_output");
|
||||||
|
|
||||||
|
b.Property<string>("TaskId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("task_id");
|
||||||
|
|
||||||
|
b.Property<int?>("TokensIn")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("tokens_in");
|
||||||
|
|
||||||
|
b.Property<int?>("TokensOut")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("tokens_out");
|
||||||
|
|
||||||
|
b.Property<int?>("TurnCount")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("turn_count");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("TaskId")
|
||||||
|
.HasDatabaseName("idx_task_runs_task_id");
|
||||||
|
|
||||||
|
b.ToTable("task_runs", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.WeekReportEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<DateOnly>("EndDate")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("end_date");
|
||||||
|
|
||||||
|
b.Property<DateTime>("GeneratedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("generated_at");
|
||||||
|
|
||||||
|
b.Property<string>("Markdown")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("markdown");
|
||||||
|
|
||||||
|
b.Property<DateOnly>("StartDate")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("start_date");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("StartDate", "EndDate")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("week_reports", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.WorktreeEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("TaskId")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("task_id");
|
||||||
|
|
||||||
|
b.Property<string>("BaseCommit")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("base_commit");
|
||||||
|
|
||||||
|
b.Property<string>("BranchName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("branch_name");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<string>("DiffStat")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("diff_stat");
|
||||||
|
|
||||||
|
b.Property<string>("HeadCommit")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("head_commit");
|
||||||
|
|
||||||
|
b.Property<string>("Path")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("path");
|
||||||
|
|
||||||
|
b.Property<string>("State")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValue("active")
|
||||||
|
.HasColumnName("state");
|
||||||
|
|
||||||
|
b.HasKey("TaskId");
|
||||||
|
|
||||||
|
b.ToTable("worktrees", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.ListConfigEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.ListEntity", "List")
|
||||||
|
.WithOne("Config")
|
||||||
|
.HasForeignKey("ClaudeDo.Data.Models.ListConfigEntity", "ListId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("List");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.SubtaskEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TaskEntity", "Task")
|
||||||
|
.WithMany("Subtasks")
|
||||||
|
.HasForeignKey("TaskId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Task");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.TaskAttachmentEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TaskEntity", "Task")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("TaskId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Task");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.TaskEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TaskEntity", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("BlockedByTaskId")
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.ListEntity", "List")
|
||||||
|
.WithMany("Tasks")
|
||||||
|
.HasForeignKey("ListId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TaskEntity", "Parent")
|
||||||
|
.WithMany("Children")
|
||||||
|
.HasForeignKey("ParentTaskId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
|
||||||
|
b.Navigation("List");
|
||||||
|
|
||||||
|
b.Navigation("Parent");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.TaskRunEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TaskEntity", "Task")
|
||||||
|
.WithMany("Runs")
|
||||||
|
.HasForeignKey("TaskId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Task");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.WorktreeEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TaskEntity", "Task")
|
||||||
|
.WithOne("Worktree")
|
||||||
|
.HasForeignKey("ClaudeDo.Data.Models.WorktreeEntity", "TaskId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Task");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.ListEntity", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Config");
|
||||||
|
|
||||||
|
b.Navigation("Tasks");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.TaskEntity", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Children");
|
||||||
|
|
||||||
|
b.Navigation("Runs");
|
||||||
|
|
||||||
|
b.Navigation("Subtasks");
|
||||||
|
|
||||||
|
b.Navigation("Worktree");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace ClaudeDo.Data.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddSessionSkills : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "session_skills",
|
||||||
|
table: "tasks",
|
||||||
|
type: "TEXT",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "session_skills",
|
||||||
|
table: "list_config",
|
||||||
|
type: "TEXT",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "session_skills",
|
||||||
|
table: "app_settings",
|
||||||
|
type: "TEXT",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "session_skills",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
name = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
source_url = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
pinned_ref = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
subpath = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
description = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
added_at = table.Column<DateTimeOffset>(type: "TEXT", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_session_skills", x => x.name);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "app_settings",
|
||||||
|
keyColumn: "id",
|
||||||
|
keyValue: 1,
|
||||||
|
column: "session_skills",
|
||||||
|
value: null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "session_skills");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "session_skills",
|
||||||
|
table: "tasks");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "session_skills",
|
||||||
|
table: "list_config");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "session_skills",
|
||||||
|
table: "app_settings");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -74,6 +74,10 @@ namespace ClaudeDo.Data.Migrations
|
|||||||
.HasColumnType("TEXT")
|
.HasColumnType("TEXT")
|
||||||
.HasColumnName("report_excluded_paths");
|
.HasColumnName("report_excluded_paths");
|
||||||
|
|
||||||
|
b.Property<string>("SessionSkills")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("session_skills");
|
||||||
|
|
||||||
b.Property<int>("StandupWeekday")
|
b.Property<int>("StandupWeekday")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("INTEGER")
|
.HasColumnType("INTEGER")
|
||||||
@@ -168,6 +172,10 @@ namespace ClaudeDo.Data.Migrations
|
|||||||
.HasColumnType("TEXT")
|
.HasColumnType("TEXT")
|
||||||
.HasColumnName("model");
|
.HasColumnName("model");
|
||||||
|
|
||||||
|
b.Property<string>("SessionSkills")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("session_skills");
|
||||||
|
|
||||||
b.Property<string>("SystemPrompt")
|
b.Property<string>("SystemPrompt")
|
||||||
.HasColumnType("TEXT")
|
.HasColumnType("TEXT")
|
||||||
.HasColumnName("system_prompt");
|
.HasColumnName("system_prompt");
|
||||||
@@ -256,6 +264,41 @@ namespace ClaudeDo.Data.Migrations
|
|||||||
b.ToTable("prime_schedules", (string)null);
|
b.ToTable("prime_schedules", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.SessionSkillEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("name");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("AddedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("added_at");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("description");
|
||||||
|
|
||||||
|
b.Property<string>("PinnedRef")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("pinned_ref");
|
||||||
|
|
||||||
|
b.Property<string>("SourceUrl")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("source_url");
|
||||||
|
|
||||||
|
b.Property<string>("Subpath")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("subpath");
|
||||||
|
|
||||||
|
b.HasKey("Name");
|
||||||
|
|
||||||
|
b.ToTable("session_skills", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("ClaudeDo.Data.Models.SubtaskEntity", b =>
|
modelBuilder.Entity("ClaudeDo.Data.Models.SubtaskEntity", b =>
|
||||||
{
|
{
|
||||||
b.Property<string>("Id")
|
b.Property<string>("Id")
|
||||||
@@ -437,6 +480,10 @@ namespace ClaudeDo.Data.Migrations
|
|||||||
.HasColumnType("TEXT")
|
.HasColumnType("TEXT")
|
||||||
.HasColumnName("scheduled_for");
|
.HasColumnName("scheduled_for");
|
||||||
|
|
||||||
|
b.Property<string>("SessionSkills")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("session_skills");
|
||||||
|
|
||||||
b.Property<int>("SortOrder")
|
b.Property<int>("SortOrder")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("INTEGER")
|
.HasColumnType("INTEGER")
|
||||||
|
|||||||
@@ -27,4 +27,7 @@ public sealed class AppSettingsEntity
|
|||||||
|
|
||||||
// Max number of open tasks the daily prep ("Prime Claude") may place in MyDay.
|
// Max number of open tasks the daily prep ("Prime Claude") may place in MyDay.
|
||||||
public int DailyPrepMaxTasks { get; set; } = 5;
|
public int DailyPrepMaxTasks { get; set; } = 5;
|
||||||
|
|
||||||
|
// JSON array of session skill names applied by default to new tasks.
|
||||||
|
public string? SessionSkills { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ public sealed class ListConfigEntity
|
|||||||
public string? SystemPrompt { get; set; }
|
public string? SystemPrompt { get; set; }
|
||||||
public string? AgentPath { get; set; }
|
public string? AgentPath { get; set; }
|
||||||
public int? MaxTurns { get; set; }
|
public int? MaxTurns { get; set; }
|
||||||
|
public string? SessionSkills { get; set; }
|
||||||
|
|
||||||
// Navigation property
|
// Navigation property
|
||||||
public ListEntity List { get; set; } = null!;
|
public ListEntity List { get; set; } = null!;
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
namespace ClaudeDo.Data.Models;
|
||||||
|
|
||||||
|
public sealed class SessionSkillEntity
|
||||||
|
{
|
||||||
|
public required string Name { get; init; }
|
||||||
|
public required string SourceUrl { get; set; }
|
||||||
|
public required string PinnedRef { get; set; }
|
||||||
|
public required string Subpath { get; set; }
|
||||||
|
public required string Description { get; set; }
|
||||||
|
public required DateTimeOffset AddedAt { get; set; }
|
||||||
|
}
|
||||||
@@ -45,6 +45,7 @@ public sealed class TaskEntity
|
|||||||
public bool IsMyDay { get; set; }
|
public bool IsMyDay { get; set; }
|
||||||
public string? Notes { get; set; }
|
public string? Notes { get; set; }
|
||||||
public int SortOrder { get; set; }
|
public int SortOrder { get; set; }
|
||||||
|
public string? SessionSkills { get; set; }
|
||||||
|
|
||||||
public string? ParentTaskId { get; set; }
|
public string? ParentTaskId { get; set; }
|
||||||
public string? PlanningSessionId { get; set; }
|
public string? PlanningSessionId { get; set; }
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
using ClaudeDo.Data.Models;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Data.Repositories;
|
||||||
|
|
||||||
|
public sealed class SessionSkillRepository
|
||||||
|
{
|
||||||
|
private readonly ClaudeDoDbContext _context;
|
||||||
|
|
||||||
|
public SessionSkillRepository(ClaudeDoDbContext context) => _context = context;
|
||||||
|
|
||||||
|
public async Task<IReadOnlyList<SessionSkillEntity>> ListAsync(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
return await _context.SessionSkills.AsNoTracking()
|
||||||
|
.OrderBy(s => s.Name)
|
||||||
|
.ToListAsync(ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<SessionSkillEntity?> GetAsync(string name, CancellationToken ct = default) =>
|
||||||
|
await _context.SessionSkills.AsNoTracking().FirstOrDefaultAsync(s => s.Name == name, ct);
|
||||||
|
|
||||||
|
public async Task UpsertAsync(SessionSkillEntity entity, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var existing = await _context.SessionSkills.FirstOrDefaultAsync(s => s.Name == entity.Name, ct);
|
||||||
|
if (existing is null)
|
||||||
|
{
|
||||||
|
_context.SessionSkills.Add(entity);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
existing.SourceUrl = entity.SourceUrl;
|
||||||
|
existing.PinnedRef = entity.PinnedRef;
|
||||||
|
existing.Subpath = entity.Subpath;
|
||||||
|
existing.Description = entity.Description;
|
||||||
|
existing.AddedAt = entity.AddedAt;
|
||||||
|
}
|
||||||
|
await _context.SaveChangesAsync(ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteAsync(string name, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await _context.SessionSkills
|
||||||
|
.Where(s => s.Name == name)
|
||||||
|
.ExecuteDeleteAsync(ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteBySourceAsync(string sourceUrl, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await _context.SessionSkills
|
||||||
|
.Where(s => s.SourceUrl == sourceUrl)
|
||||||
|
.ExecuteDeleteAsync(ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IReadOnlyList<SessionSkillEntity>> ListBySourceAsync(string sourceUrl, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
return await _context.SessionSkills.AsNoTracking()
|
||||||
|
.Where(s => s.SourceUrl == sourceUrl)
|
||||||
|
.OrderBy(s => s.Name)
|
||||||
|
.ToListAsync(ct);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
using ClaudeDo.Data;
|
||||||
|
using ClaudeDo.Data.Models;
|
||||||
|
using ClaudeDo.Data.Repositories;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Data.Tests;
|
||||||
|
|
||||||
|
public sealed class SessionSkillRepositoryTests : IDisposable
|
||||||
|
{
|
||||||
|
private readonly string _dbPath;
|
||||||
|
private readonly ClaudeDoDbContext _ctx;
|
||||||
|
private readonly SessionSkillRepository _repo;
|
||||||
|
|
||||||
|
public SessionSkillRepositoryTests()
|
||||||
|
{
|
||||||
|
_dbPath = Path.Combine(Path.GetTempPath(), $"claudedo_skills_{Guid.NewGuid():N}.db");
|
||||||
|
var options = new DbContextOptionsBuilder<ClaudeDoDbContext>()
|
||||||
|
.UseSqlite($"Data Source={_dbPath}")
|
||||||
|
.Options;
|
||||||
|
_ctx = new ClaudeDoDbContext(options);
|
||||||
|
_ctx.Database.EnsureCreated();
|
||||||
|
_repo = new SessionSkillRepository(_ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_ctx.Dispose();
|
||||||
|
foreach (var suffix in new[] { "", "-wal", "-shm" })
|
||||||
|
try { File.Delete(_dbPath + suffix); } catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SessionSkillEntity MakeSkill(string name, string sourceUrl = "https://example.com/repo") => new()
|
||||||
|
{
|
||||||
|
Name = name,
|
||||||
|
SourceUrl = sourceUrl,
|
||||||
|
PinnedRef = "main",
|
||||||
|
Subpath = "skills/" + name,
|
||||||
|
Description = "desc for " + name,
|
||||||
|
AddedAt = DateTimeOffset.UtcNow,
|
||||||
|
};
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task UpsertAsync_then_GetAsync_roundtrips()
|
||||||
|
{
|
||||||
|
var skill = MakeSkill("brainstorming");
|
||||||
|
|
||||||
|
await _repo.UpsertAsync(skill);
|
||||||
|
var found = await _repo.GetAsync("brainstorming");
|
||||||
|
|
||||||
|
Assert.NotNull(found);
|
||||||
|
Assert.Equal(skill.SourceUrl, found!.SourceUrl);
|
||||||
|
Assert.Equal(skill.PinnedRef, found.PinnedRef);
|
||||||
|
Assert.Equal(skill.Subpath, found.Subpath);
|
||||||
|
Assert.Equal(skill.Description, found.Description);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task UpsertAsync_updates_existing_row()
|
||||||
|
{
|
||||||
|
await _repo.UpsertAsync(MakeSkill("brainstorming", "https://example.com/old"));
|
||||||
|
await _repo.UpsertAsync(MakeSkill("brainstorming", "https://example.com/new"));
|
||||||
|
|
||||||
|
var found = await _repo.GetAsync("brainstorming");
|
||||||
|
|
||||||
|
Assert.NotNull(found);
|
||||||
|
Assert.Equal("https://example.com/new", found!.SourceUrl);
|
||||||
|
|
||||||
|
var all = await _repo.ListAsync();
|
||||||
|
Assert.Single(all);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetAsync_returns_null_when_missing()
|
||||||
|
{
|
||||||
|
var found = await _repo.GetAsync("nope");
|
||||||
|
Assert.Null(found);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ListAsync_returns_all_ordered_by_name()
|
||||||
|
{
|
||||||
|
await _repo.UpsertAsync(MakeSkill("zeta"));
|
||||||
|
await _repo.UpsertAsync(MakeSkill("alpha"));
|
||||||
|
|
||||||
|
var all = await _repo.ListAsync();
|
||||||
|
|
||||||
|
Assert.Equal(2, all.Count);
|
||||||
|
Assert.Equal("alpha", all[0].Name);
|
||||||
|
Assert.Equal("zeta", all[1].Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DeleteAsync_removes_only_matching_row()
|
||||||
|
{
|
||||||
|
await _repo.UpsertAsync(MakeSkill("keep"));
|
||||||
|
await _repo.UpsertAsync(MakeSkill("remove"));
|
||||||
|
|
||||||
|
await _repo.DeleteAsync("remove");
|
||||||
|
|
||||||
|
var all = await _repo.ListAsync();
|
||||||
|
Assert.Single(all);
|
||||||
|
Assert.Equal("keep", all[0].Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DeleteBySourceAsync_removes_multiple_rows_same_source()
|
||||||
|
{
|
||||||
|
const string source = "https://example.com/shared-repo";
|
||||||
|
await _repo.UpsertAsync(MakeSkill("skill-a", source));
|
||||||
|
await _repo.UpsertAsync(MakeSkill("skill-b", source));
|
||||||
|
await _repo.UpsertAsync(MakeSkill("skill-c", "https://example.com/other"));
|
||||||
|
|
||||||
|
await _repo.DeleteBySourceAsync(source);
|
||||||
|
|
||||||
|
var all = await _repo.ListAsync();
|
||||||
|
Assert.Single(all);
|
||||||
|
Assert.Equal("skill-c", all[0].Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ListBySourceAsync_returns_only_matching_rows()
|
||||||
|
{
|
||||||
|
const string source = "https://example.com/shared-repo";
|
||||||
|
await _repo.UpsertAsync(MakeSkill("skill-a", source));
|
||||||
|
await _repo.UpsertAsync(MakeSkill("skill-b", source));
|
||||||
|
await _repo.UpsertAsync(MakeSkill("skill-c", "https://example.com/other"));
|
||||||
|
|
||||||
|
var result = await _repo.ListBySourceAsync(source);
|
||||||
|
|
||||||
|
Assert.Equal(2, result.Count);
|
||||||
|
Assert.All(result, s => Assert.Equal(source, s.SourceUrl));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using ClaudeDo.Data;
|
||||||
|
using ClaudeDo.Data.Models;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Data.Tests;
|
||||||
|
|
||||||
|
public sealed class SessionSkillsColumnRoundtripTests : IDisposable
|
||||||
|
{
|
||||||
|
private readonly string _dbPath;
|
||||||
|
private readonly ClaudeDoDbContext _ctx;
|
||||||
|
|
||||||
|
public SessionSkillsColumnRoundtripTests()
|
||||||
|
{
|
||||||
|
_dbPath = Path.Combine(Path.GetTempPath(), $"claudedo_skillscol_{Guid.NewGuid():N}.db");
|
||||||
|
var options = new DbContextOptionsBuilder<ClaudeDoDbContext>()
|
||||||
|
.UseSqlite($"Data Source={_dbPath}")
|
||||||
|
.Options;
|
||||||
|
_ctx = new ClaudeDoDbContext(options);
|
||||||
|
_ctx.Database.EnsureCreated();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_ctx.Dispose();
|
||||||
|
foreach (var suffix in new[] { "", "-wal", "-shm" })
|
||||||
|
try { File.Delete(_dbPath + suffix); } catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Task_SessionSkills_json_array_roundtrips()
|
||||||
|
{
|
||||||
|
var names = new List<string> { "a", "b" };
|
||||||
|
var list = new ListEntity { Id = "l1", Name = "Test", CreatedAt = DateTime.UtcNow };
|
||||||
|
var task = new TaskEntity
|
||||||
|
{
|
||||||
|
Id = "t1",
|
||||||
|
ListId = "l1",
|
||||||
|
Title = "T",
|
||||||
|
Status = TaskStatus.Idle,
|
||||||
|
CreatedAt = DateTime.UtcNow,
|
||||||
|
SessionSkills = JsonSerializer.Serialize(names),
|
||||||
|
};
|
||||||
|
_ctx.Lists.Add(list);
|
||||||
|
_ctx.Tasks.Add(task);
|
||||||
|
await _ctx.SaveChangesAsync();
|
||||||
|
|
||||||
|
await using var freshCtx = new ClaudeDoDbContext(
|
||||||
|
new DbContextOptionsBuilder<ClaudeDoDbContext>().UseSqlite($"Data Source={_dbPath}").Options);
|
||||||
|
var reloaded = await freshCtx.Tasks.AsNoTracking().FirstAsync(t => t.Id == "t1");
|
||||||
|
|
||||||
|
var roundtripped = JsonSerializer.Deserialize<List<string>>(reloaded.SessionSkills!);
|
||||||
|
Assert.Equal(names, roundtripped);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ListConfig_SessionSkills_json_array_roundtrips()
|
||||||
|
{
|
||||||
|
var names = new List<string> { "a", "b" };
|
||||||
|
var list = new ListEntity { Id = "l1", Name = "Test", CreatedAt = DateTime.UtcNow };
|
||||||
|
var config = new ListConfigEntity { ListId = "l1", SessionSkills = JsonSerializer.Serialize(names) };
|
||||||
|
_ctx.Lists.Add(list);
|
||||||
|
_ctx.ListConfigs.Add(config);
|
||||||
|
await _ctx.SaveChangesAsync();
|
||||||
|
|
||||||
|
await using var freshCtx = new ClaudeDoDbContext(
|
||||||
|
new DbContextOptionsBuilder<ClaudeDoDbContext>().UseSqlite($"Data Source={_dbPath}").Options);
|
||||||
|
var reloaded = await freshCtx.ListConfigs.AsNoTracking().FirstAsync(c => c.ListId == "l1");
|
||||||
|
|
||||||
|
var roundtripped = JsonSerializer.Deserialize<List<string>>(reloaded.SessionSkills!);
|
||||||
|
Assert.Equal(names, roundtripped);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AppSettings_SessionSkills_json_array_roundtrips()
|
||||||
|
{
|
||||||
|
var names = new List<string> { "a", "b" };
|
||||||
|
var settings = await _ctx.AppSettings.FirstAsync(s => s.Id == AppSettingsEntity.SingletonId);
|
||||||
|
settings.SessionSkills = JsonSerializer.Serialize(names);
|
||||||
|
await _ctx.SaveChangesAsync();
|
||||||
|
|
||||||
|
await using var freshCtx = new ClaudeDoDbContext(
|
||||||
|
new DbContextOptionsBuilder<ClaudeDoDbContext>().UseSqlite($"Data Source={_dbPath}").Options);
|
||||||
|
var reloaded = await freshCtx.AppSettings.AsNoTracking().FirstAsync(s => s.Id == AppSettingsEntity.SingletonId);
|
||||||
|
|
||||||
|
var roundtripped = JsonSerializer.Deserialize<List<string>>(reloaded.SessionSkills!);
|
||||||
|
Assert.Equal(names, roundtripped);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user