feat(worker): PlanningSessionManager.StartAsync
Add PlanningSessionFiles, PlanningSessionStartContext/ResumeContext DTOs, PlanningSessionManager.StartAsync (file scaffolding + status transition), and integration tests. Also fix migration discovery by adding [DbContext] attribute to all migration classes and switch DbFixture to EnsureCreated. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
@@ -8,6 +10,8 @@ using Microsoft.EntityFrameworkCore.Migrations;
|
||||
namespace ClaudeDo.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
[DbContext(typeof(ClaudeDoDbContext))]
|
||||
[Migration("20260416064948_InitialCreate")]
|
||||
public partial class InitialCreate : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ClaudeDo.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
[DbContext(typeof(ClaudeDoDbContext))]
|
||||
[Migration("20260420075929_AddTaskFlagsAndNotes")]
|
||||
public partial class AddTaskFlagsAndNotes : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ClaudeDo.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
[DbContext(typeof(ClaudeDoDbContext))]
|
||||
[Migration("20260421113614_AddAppSettings")]
|
||||
public partial class AddAppSettings : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
@@ -5,6 +7,8 @@ using Microsoft.EntityFrameworkCore.Migrations;
|
||||
namespace ClaudeDo.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
[DbContext(typeof(ClaudeDoDbContext))]
|
||||
[Migration("20260422120000_AddTaskSortOrder")]
|
||||
public partial class AddTaskSortOrder : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
@@ -6,6 +8,8 @@ using Microsoft.EntityFrameworkCore.Migrations;
|
||||
namespace ClaudeDo.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
[DbContext(typeof(ClaudeDoDbContext))]
|
||||
[Migration("20260423154708_AddPlanningSupport")]
|
||||
public partial class AddPlanningSupport : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
|
||||
12
src/ClaudeDo.Worker/Planning/PlanningSessionContext.cs
Normal file
12
src/ClaudeDo.Worker/Planning/PlanningSessionContext.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace ClaudeDo.Worker.Planning;
|
||||
|
||||
public sealed record PlanningSessionStartContext(
|
||||
string ParentTaskId,
|
||||
string WorkingDir,
|
||||
PlanningSessionFiles Files);
|
||||
|
||||
public sealed record PlanningSessionResumeContext(
|
||||
string ParentTaskId,
|
||||
string WorkingDir,
|
||||
string ClaudeSessionId,
|
||||
string McpConfigPath);
|
||||
7
src/ClaudeDo.Worker/Planning/PlanningSessionFiles.cs
Normal file
7
src/ClaudeDo.Worker/Planning/PlanningSessionFiles.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace ClaudeDo.Worker.Planning;
|
||||
|
||||
public sealed record PlanningSessionFiles(
|
||||
string SessionDirectory,
|
||||
string McpConfigPath,
|
||||
string SystemPromptPath,
|
||||
string InitialPromptPath);
|
||||
111
src/ClaudeDo.Worker/Planning/PlanningSessionManager.cs
Normal file
111
src/ClaudeDo.Worker/Planning/PlanningSessionManager.cs
Normal file
@@ -0,0 +1,111 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using ClaudeDo.Data.Models;
|
||||
using ClaudeDo.Data.Repositories;
|
||||
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;
|
||||
|
||||
namespace ClaudeDo.Worker.Planning;
|
||||
|
||||
public sealed class PlanningSessionManager
|
||||
{
|
||||
private const string McpServerUrl = "http://127.0.0.1:47821/mcp";
|
||||
|
||||
private readonly TaskRepository _tasks;
|
||||
private readonly ListRepository _lists;
|
||||
private readonly string _rootDirectory;
|
||||
|
||||
public PlanningSessionManager(TaskRepository tasks, ListRepository lists, string rootDirectory)
|
||||
{
|
||||
_tasks = tasks;
|
||||
_lists = lists;
|
||||
_rootDirectory = rootDirectory;
|
||||
}
|
||||
|
||||
public async Task<PlanningSessionStartContext> StartAsync(string taskId, CancellationToken ct)
|
||||
{
|
||||
var task = await _tasks.GetByIdAsync(taskId, ct)
|
||||
?? throw new InvalidOperationException($"Task {taskId} not found.");
|
||||
if (task.ParentTaskId is not null)
|
||||
throw new InvalidOperationException("Cannot start a planning session on a child task.");
|
||||
if (task.Status != TaskStatus.Manual)
|
||||
throw new InvalidOperationException($"Task is in status {task.Status}; only Manual can start planning.");
|
||||
|
||||
var token = GenerateToken();
|
||||
_ = await _tasks.SetPlanningStartedAsync(taskId, token, ct)
|
||||
?? throw new InvalidOperationException("Failed to transition task to Planning.");
|
||||
|
||||
var sessionDir = Path.Combine(_rootDirectory, taskId);
|
||||
Directory.CreateDirectory(sessionDir);
|
||||
|
||||
var files = new PlanningSessionFiles(
|
||||
sessionDir,
|
||||
Path.Combine(sessionDir, "mcp.json"),
|
||||
Path.Combine(sessionDir, "system-prompt.md"),
|
||||
Path.Combine(sessionDir, "initial-prompt.txt"));
|
||||
|
||||
await File.WriteAllTextAsync(files.McpConfigPath, BuildMcpConfigJson(token), ct);
|
||||
await File.WriteAllTextAsync(files.SystemPromptPath, BuildSystemPrompt(), ct);
|
||||
await File.WriteAllTextAsync(files.InitialPromptPath, BuildInitialPrompt(task), ct);
|
||||
|
||||
var list = await _lists.GetByIdAsync(task.ListId, ct)
|
||||
?? throw new InvalidOperationException($"List {task.ListId} not found.");
|
||||
return new PlanningSessionStartContext(taskId, list.WorkingDir, files);
|
||||
}
|
||||
|
||||
private static string GenerateToken()
|
||||
{
|
||||
var bytes = RandomNumberGenerator.GetBytes(32);
|
||||
return Convert.ToBase64String(bytes)
|
||||
.Replace('+', '-')
|
||||
.Replace('/', '_')
|
||||
.TrimEnd('=');
|
||||
}
|
||||
|
||||
private static string BuildMcpConfigJson(string token)
|
||||
{
|
||||
var payload = new
|
||||
{
|
||||
mcpServers = new
|
||||
{
|
||||
claudedo = new
|
||||
{
|
||||
type = "http",
|
||||
url = McpServerUrl,
|
||||
headers = new Dictionary<string, string>
|
||||
{
|
||||
["Authorization"] = $"Bearer {token}"
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
return JsonSerializer.Serialize(payload, new JsonSerializerOptions { WriteIndented = true });
|
||||
}
|
||||
|
||||
private static string BuildSystemPrompt() =>
|
||||
"""
|
||||
You are a planning assistant for ClaudeDo.
|
||||
Your role is to help break down a task into smaller, actionable subtasks.
|
||||
|
||||
Use the available MCP tools (mcp__claudedo__*) to create child tasks.
|
||||
When you are done planning, finalize the session.
|
||||
|
||||
Be concise and focused. Each subtask should be independently executable.
|
||||
""";
|
||||
|
||||
private static string BuildInitialPrompt(TaskEntity task)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine($"# Task: {task.Title}");
|
||||
if (!string.IsNullOrWhiteSpace(task.Description))
|
||||
{
|
||||
sb.AppendLine();
|
||||
sb.AppendLine(task.Description);
|
||||
}
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("---");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("Please analyze this task and break it down into concrete subtasks.");
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user