docs(spec): add planning sessions design
Interactive "Open planning Session" context menu: launches a scoped MCP-backed Claude CLI session in Windows Terminal, letting the user brainstorm and Claude break a rough task into concrete child-tasks under a flat parent-child hierarchy. Includes schema, MCP tool surface, terminal launch, UI changes, lifecycle, testing, and a three-plan phasing (A foundation, then B worker + C UI in parallel). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
468
docs/superpowers/specs/2026-04-23-planning-sessions-design.md
Normal file
468
docs/superpowers/specs/2026-04-23-planning-sessions-design.md
Normal file
@@ -0,0 +1,468 @@
|
|||||||
|
# Planning Sessions — Design
|
||||||
|
|
||||||
|
**Status:** Approved for implementation
|
||||||
|
**Date:** 2026-04-23
|
||||||
|
**Scope:** Feature — "Open planning Session" context menu on tasks that spawns an interactive Windows Terminal with Claude (Sonnet 4.6, medium thinking) and a scoped MCP server, letting the user brainstorm and have Claude break a rough task into concrete executable child-tasks.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Goal
|
||||||
|
|
||||||
|
Allow a user to take a vague task (a title plus some TODO-style notes) and convert it — via interactive dialogue with Claude in a terminal — into a structured set of concrete, executable child-tasks that the worker queue can pick up and run.
|
||||||
|
|
||||||
|
The interaction is driven by Claude calling MCP tools against a scoped server running inside the existing `ClaudeDo.Worker` process. The parent task becomes a "Planning" container that holds its children as a flat (single-level) hierarchy.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Status Flow
|
||||||
|
|
||||||
|
**Parent (new statuses `Planning`, `Planned`):**
|
||||||
|
|
||||||
|
```
|
||||||
|
Manual ──[Open planning Session]──▶ Planning ──[finalize]──▶ Planned
|
||||||
|
│ │
|
||||||
|
│ (all children reach terminal state)
|
||||||
|
│ ▼
|
||||||
|
│ Done
|
||||||
|
│ or
|
||||||
|
│ Failed (if any child Failed)
|
||||||
|
▼
|
||||||
|
[Discard] ──▶ Manual
|
||||||
|
```
|
||||||
|
|
||||||
|
**Child (new status `Draft`):**
|
||||||
|
|
||||||
|
```
|
||||||
|
Draft ──[finalize]──▶ Manual | Queued (if "agent" tag) ──▶ Running ──▶ Done | Failed
|
||||||
|
```
|
||||||
|
|
||||||
|
**Rules:**
|
||||||
|
- Parent with status `Planning` or `Planned` is **never** picked up by the queue.
|
||||||
|
- Children with status `Draft` are **never** picked up by the queue.
|
||||||
|
- Hierarchy is strictly **one level deep**: a child task cannot itself become a planning parent (enforced app-side: Plan menu item hidden/disabled if `ParentTaskId IS NOT NULL`).
|
||||||
|
- One planning session per parent task at a time (`StartPlanningSessionAsync` errors if parent is already `Planning`; use Resume instead).
|
||||||
|
- Parent auto-status on child completion (evaluated after any child reaches `Done` or `Failed`):
|
||||||
|
- At least one child `Failed` and no children still in non-terminal states → Parent `Failed`.
|
||||||
|
- All children `Done` → Parent `Done`.
|
||||||
|
- Any child still `Manual`/`Queued`/`Running`/`Draft` → Parent stays `Planned`.
|
||||||
|
- Worktree state (`Merged`/`Discarded`/`Kept`) is orthogonal; only `Task.Status` determines completion.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Data Model
|
||||||
|
|
||||||
|
### 3.1 Schema changes to `Tasks` table
|
||||||
|
|
||||||
|
| Column | Type | Nullable | Purpose |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `ParentTaskId` | `string` (FK → `Tasks.Id`, `DeleteBehavior.Restrict`) | yes | When set, row is a child of a planning parent. NULL = top-level task. |
|
||||||
|
| `PlanningSessionId` | `string` | yes | Claude CLI session ID captured after first run; used with `--resume`. Only set on planning parents. |
|
||||||
|
| `PlanningSessionToken` | `string` | yes | Random 32-byte Base64 token generated per session; acts as bearer for MCP calls. NULL when no active session. |
|
||||||
|
| `PlanningFinalizedAt` | `DateTime` | yes | Timestamp when `finalize` was called. NULL until finalized. |
|
||||||
|
|
||||||
|
Index: `(ParentTaskId)` for fast children lookup.
|
||||||
|
|
||||||
|
### 3.2 Status enum additions
|
||||||
|
|
||||||
|
`ClaudeDo.Data.Models.TaskStatus` gains:
|
||||||
|
- `Planning` — parent, session active or paused, drafts may exist.
|
||||||
|
- `Planned` — parent, finalized, children are real tasks (may still be running).
|
||||||
|
- `Draft` — child, created during session, not yet finalized.
|
||||||
|
|
||||||
|
Existing values unchanged: `Manual | Queued | Running | Done | Failed`. Persisted via `ValueConverter` to string (existing convention — confirmed via `TaskEntity.cs`).
|
||||||
|
|
||||||
|
### 3.3 Navigation properties
|
||||||
|
|
||||||
|
On `TaskEntity`:
|
||||||
|
```csharp
|
||||||
|
public string? ParentTaskId { get; set; }
|
||||||
|
public TaskEntity? Parent { get; set; }
|
||||||
|
public ICollection<TaskEntity> Children { get; set; } = new List<TaskEntity>();
|
||||||
|
public string? PlanningSessionId { get; set; }
|
||||||
|
public string? PlanningSessionToken { get; set; }
|
||||||
|
public DateTime? PlanningFinalizedAt { get; set; }
|
||||||
|
```
|
||||||
|
|
||||||
|
In `TaskEntityConfiguration`:
|
||||||
|
```csharp
|
||||||
|
.HasOne(t => t.Parent)
|
||||||
|
.WithMany(t => t.Children)
|
||||||
|
.HasForeignKey(t => t.ParentTaskId)
|
||||||
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Rationale for `Restrict`:** cascade delete would orphan worktrees of in-flight child tasks. UI must handle the `DbUpdateException` and prompt the user to discard children first.
|
||||||
|
|
||||||
|
### 3.4 Repository additions
|
||||||
|
|
||||||
|
`ITaskRepository` gains:
|
||||||
|
- `Task<IReadOnlyList<TaskEntity>> GetChildrenAsync(string parentId, CancellationToken ct)`
|
||||||
|
- `Task<TaskEntity> CreateChildAsync(string parentId, string title, string? description, IReadOnlyList<string>? tagNames, string? commitType, CancellationToken ct)` — creates with `Status = Draft`, `ParentTaskId = parentId`.
|
||||||
|
- `Task<int> FinalizePlanningAsync(string parentId, bool queueAgentTasks, CancellationToken ct)` — transactional: all Drafts → `Manual` (or `Queued` if tagged "agent" and `queueAgentTasks=true`), parent → `Planned`, set `PlanningFinalizedAt`, clear `PlanningSessionToken`. Returns count of finalized children.
|
||||||
|
- `Task<bool> DiscardPlanningAsync(string parentId, CancellationToken ct)` — deletes all Drafts, parent → `Manual`, clears `PlanningSessionId/Token/FinalizedAt`.
|
||||||
|
- `Task<TaskEntity?> SetPlanningStartedAsync(string taskId, string sessionToken, CancellationToken ct)` — sets parent `Status = Planning`, stores token; returns null if parent not in `Manual` state.
|
||||||
|
- `Task UpdatePlanningSessionIdAsync(string parentId, string sessionId, CancellationToken ct)` — captures Claude CLI session ID after launch.
|
||||||
|
- `Task<TaskEntity?> FindByPlanningTokenAsync(string token, CancellationToken ct)` — used by MCP auth handler.
|
||||||
|
|
||||||
|
`GetNextQueuedAgentTaskAsync` — verify the existing query filters on `Status = Queued`; no additional filter needed since Planning/Planned/Draft are different statuses. Add explicit regression test.
|
||||||
|
|
||||||
|
### 3.5 Auto-status hook
|
||||||
|
|
||||||
|
After every `MarkDoneAsync`/`MarkFailedAsync` on a task with `ParentTaskId != null`, check parent children. If all in terminal state:
|
||||||
|
- Any `Failed` → parent `Failed` with `FinishedAt = now()`.
|
||||||
|
- All `Done` (or worktrees `Discarded`) → parent `Done` with `FinishedAt = now()`.
|
||||||
|
|
||||||
|
Implemented as a private helper `TryCompleteParentAsync(string parentId, CancellationToken ct)` called at the end of the two Mark methods.
|
||||||
|
|
||||||
|
### 3.6 Migration
|
||||||
|
|
||||||
|
`dotnet ef migrations add AddPlanningSupport` — adds four columns and the `(ParentTaskId)` index. No data migration needed (new columns all nullable).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. MCP Server Surface
|
||||||
|
|
||||||
|
### 4.1 Transport
|
||||||
|
|
||||||
|
**HTTP (streamable) inside the existing Worker Kestrel host.** Mount on `/mcp` alongside the existing SignalR hub at `127.0.0.1:47821`. No separate process, no stdio proxy.
|
||||||
|
|
||||||
|
Library: `ModelContextProtocol` (official C# MCP SDK).
|
||||||
|
|
||||||
|
### 4.2 Authentication
|
||||||
|
|
||||||
|
Per-session bearer token:
|
||||||
|
1. `StartPlanningSessionAsync` generates a 32-byte random token, persists to `Tasks.PlanningSessionToken`.
|
||||||
|
2. Token is written into the session's `mcp.json` as `Authorization: Bearer <token>`.
|
||||||
|
3. Every MCP request passes through an auth filter that looks up the token via `FindByPlanningTokenAsync`. If found, the parent task ID is stored in the request context. If not, 401.
|
||||||
|
4. Token is invalidated (set NULL) on `finalize` or `discard`.
|
||||||
|
|
||||||
|
### 4.3 Tools
|
||||||
|
|
||||||
|
All tools are scoped to the parent task resolved from the request's token. `parent_id` is never an argument.
|
||||||
|
|
||||||
|
| Tool | Params | Returns | Effect |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `create_child_task` | `title: string`, `description?: string`, `tags?: string[]`, `commit_type?: string` | `{ task_id, status: "Draft" }` | Creates a Draft child under this session's parent. |
|
||||||
|
| `list_child_tasks` | — | `[{ task_id, title, description, status, tags }]` | Lists children of this parent (in session context, always Drafts). |
|
||||||
|
| `update_child_task` | `task_id: string`, optional: `title`, `description`, `tags`, `commit_type` | `{ task }` | Errors if target is not a Draft or not a child of this parent. |
|
||||||
|
| `delete_child_task` | `task_id: string` | `{ ok: true }` | Errors if target is not a Draft or not a child of this parent. |
|
||||||
|
| `update_planning_task` | `title?: string`, `description?: string` | `{ task }` | Only title/description on the parent itself. |
|
||||||
|
| `finalize` | `queue_agent_tasks?: bool = true` | `{ finalized_count: int }` | Calls `FinalizePlanningAsync`. Token invalidated. |
|
||||||
|
|
||||||
|
### 4.4 Real-time UI
|
||||||
|
|
||||||
|
After each successful tool call, the MCP handler fires a `TaskUpdated` event on the Worker's SignalR hub. The UI subscribes as it already does; drafts appear/update live in the tasks list while the user chats with Claude in the terminal.
|
||||||
|
|
||||||
|
### 4.5 Errors
|
||||||
|
|
||||||
|
- 401 for missing/invalid token.
|
||||||
|
- MCP error `-32602` "task not found or not a child of this planning session" for cross-parent access attempts.
|
||||||
|
- MCP error `-32602` "cannot modify finalized task" for `update/delete` on non-Draft.
|
||||||
|
- Token validation short-circuits before tool dispatch.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Terminal Launch & Claude CLI Invocation
|
||||||
|
|
||||||
|
### 5.1 Launcher service
|
||||||
|
|
||||||
|
New interface `IPlanningTerminalLauncher` in the UI or App layer:
|
||||||
|
```csharp
|
||||||
|
Task LaunchAsync(PlanningSessionStart info, CancellationToken ct);
|
||||||
|
Task LaunchResumeAsync(PlanningSessionResume info, CancellationToken ct);
|
||||||
|
```
|
||||||
|
|
||||||
|
`PlanningSessionStart` contains: `WorkingDir`, `McpConfigPath`, `InitialPromptPath`, `SystemPromptPath`.
|
||||||
|
`PlanningSessionResume` contains: `WorkingDir`, `McpConfigPath`, `ClaudeSessionId`.
|
||||||
|
|
||||||
|
### 5.2 Per-session files
|
||||||
|
|
||||||
|
Path: `~/.todo-app/planning-sessions/<parentTaskId>/`
|
||||||
|
- `mcp.json` — MCP config referencing the HTTP endpoint with bearer token.
|
||||||
|
- `system-prompt.md` — planning-mode system prompt (append, not replace).
|
||||||
|
- `initial-prompt.txt` — first user-visible message (title + description + short instructions).
|
||||||
|
|
||||||
|
Cleanup:
|
||||||
|
- `Discard` → remove directory.
|
||||||
|
- `Finalize` → keep directory (for audit; prune on app start if older than N days, optional).
|
||||||
|
|
||||||
|
### 5.3 `mcp.json` format
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"claudedo": {
|
||||||
|
"type": "http",
|
||||||
|
"url": "http://127.0.0.1:47821/mcp",
|
||||||
|
"headers": { "Authorization": "Bearer <PlanningSessionToken>" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.4 Claude CLI invocation (new session)
|
||||||
|
|
||||||
|
```
|
||||||
|
wt.exe -d "<list.WorkingDir>" cmd /k ^
|
||||||
|
claude ^
|
||||||
|
--model claude-sonnet-4-6 ^
|
||||||
|
--append-system-prompt "<contents of system-prompt.md>" ^
|
||||||
|
--mcp-config "<mcp.json absolute path>" ^
|
||||||
|
--allowedTools "mcp__claudedo__*,Read,Grep,Glob,WebFetch,WebSearch,Skill" ^
|
||||||
|
"<contents of initial-prompt.txt>"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.5 Claude CLI invocation (resume)
|
||||||
|
|
||||||
|
```
|
||||||
|
wt.exe -d "<list.WorkingDir>" cmd /k ^
|
||||||
|
claude --resume <PlanningSessionId> --mcp-config "<mcp.json>"
|
||||||
|
```
|
||||||
|
Resume inherits model, system prompt, and allowed tools from the original session.
|
||||||
|
|
||||||
|
### 5.6 System prompt (draft, refined in Plan B)
|
||||||
|
|
||||||
|
> You are in a ClaudeDo planning session for a task. Your job is to brainstorm with the user, then break their rough intent into concrete, independently-executable child-tasks. Each child-task should be something a single automated agent can pick up and complete autonomously. Use the `mcp__claudedo__*` tools to create/update/delete drafts in real time. You may read the repository for context (Read/Grep/Glob) but must not modify any files. When the user is satisfied, call `finalize`. Skills you may find useful: `superpowers:writing-plans`, `superpowers:writing-clearly-and-concisely`.
|
||||||
|
|
||||||
|
### 5.7 Initial prompt (template)
|
||||||
|
|
||||||
|
```
|
||||||
|
<Parent task title>
|
||||||
|
|
||||||
|
<Parent task description, if any>
|
||||||
|
|
||||||
|
---
|
||||||
|
We're planning this task together. Brainstorm with me, then create concrete child-tasks via the MCP tools. I'll call `finalize` when we're done.
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.8 Unknowns to resolve during Plan B implementation
|
||||||
|
|
||||||
|
These are left **open** in this spec; they'll be pinned down during implementation via `mcp__plugin_context7_context7__query-docs` for the Claude Code CLI:
|
||||||
|
|
||||||
|
1. Exact flag for thinking budget (`--thinking-budget medium`? model suffix `claude-sonnet-4-6-thinking`? something else?).
|
||||||
|
2. Exact casing of tool names in `--allowedTools` (`Read`/`read`, `WebFetch`/`web_fetch`, `Skill`).
|
||||||
|
3. Whether `--append-system-prompt` accepts a file reference (`@path`) or requires inline string.
|
||||||
|
4. Whether Claude CLI supports a `--session-id` flag for pre-assigning the session ID, or whether we must read it back from `~/.claude/projects/<hash>/sessions/` after the process starts.
|
||||||
|
|
||||||
|
If (4) resolves to "read back", strategy:
|
||||||
|
- Poll `~/.claude/projects/<hash>/sessions/` directory modtimes shortly after launch; newest session file after launch timestamp is ours.
|
||||||
|
- Cache the result on the parent task via `UpdatePlanningSessionIdAsync`.
|
||||||
|
- If session ID can't be captured, Resume falls back to `claude --continue` (last session in that project).
|
||||||
|
|
||||||
|
### 5.9 Pre-flight checks
|
||||||
|
|
||||||
|
On `LaunchAsync`:
|
||||||
|
- `wt.exe` resolvable in PATH → else throw `PlanningLaunchException("Windows Terminal not found")`, UI shows install hint.
|
||||||
|
- `claude` resolvable in PATH → else `PlanningLaunchException("Claude CLI not installed")`.
|
||||||
|
- `list.WorkingDir` exists → else `PlanningLaunchException("Working directory not found: <path>")`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. UI Changes
|
||||||
|
|
||||||
|
### 6.1 Context menu (`TaskRowView.axaml`)
|
||||||
|
|
||||||
|
New entries, conditional on status:
|
||||||
|
- `Manual` + `ParentTaskId IS NULL` → **"Open planning Session"**
|
||||||
|
- `Planning` → **"Resume planning Session"** and **"Discard planning session"**
|
||||||
|
- `Planned` / `Done` / `Failed` (parent) → no planning-related entries (7c: no re-planning).
|
||||||
|
- Children (`ParentTaskId IS NOT NULL`) → never show planning entries.
|
||||||
|
|
||||||
|
### 6.2 Hierarchy rendering (`TasksIslandView.axaml`)
|
||||||
|
|
||||||
|
Approach: **flat stream with indentation**, not a `TreeView`.
|
||||||
|
|
||||||
|
- `TasksIslandViewModel` builds `OpenItems`/`CompletedItems`/etc. as flat `ObservableCollection<TaskRowViewModel>` with parents followed by their children if expanded.
|
||||||
|
- `TaskRowViewModel` gets `IsChild: bool` and `IsPlanningParent: bool` and `IsExpanded: bool`.
|
||||||
|
- `TaskRowView` indents 24px when `IsChild`, shows a thin left border in `TextFaintBrush`.
|
||||||
|
- Parents with `IsPlanningParent` render a chevron (▸/▾) that toggles `IsExpanded`; collapsed parents hide their children from the flat stream.
|
||||||
|
- Expanded-state map kept in the VM (`Dictionary<string, bool>`, default `true`).
|
||||||
|
|
||||||
|
### 6.3 Draft and planning styling (`TaskRowView`)
|
||||||
|
|
||||||
|
- `Status = Draft` → row italic, 70% opacity, small left-aligned badge "DRAFT".
|
||||||
|
- Parent `Status = Planning` → badge "PLANNING" (accent: warning-amber).
|
||||||
|
- Parent `Status = Planned` → badge "PLANNED" (accent: neutral-blue).
|
||||||
|
|
||||||
|
### 6.4 Unfinished-session dialog
|
||||||
|
|
||||||
|
Trigger: on app start **and** on any context-menu click against a `Planning` parent.
|
||||||
|
|
||||||
|
Modal (built with existing `TaskCompletionSource<T>` dialog pattern):
|
||||||
|
|
||||||
|
```
|
||||||
|
Unfinished planning session
|
||||||
|
"<Parent title>"
|
||||||
|
<N> draft tasks waiting to be finalized.
|
||||||
|
|
||||||
|
[Resume] [Finalize now] [Discard]
|
||||||
|
```
|
||||||
|
|
||||||
|
- Resume → `ResumePlanningSessionAsync`, opens terminal with `--resume`.
|
||||||
|
- Finalize now → `FinalizePlanningSessionAsync` (server-side, no terminal). Useful when the user is confident drafts are good.
|
||||||
|
- Discard → `DiscardPlanningSessionAsync`.
|
||||||
|
|
||||||
|
### 6.5 TasksIslandViewModel commands
|
||||||
|
|
||||||
|
- `[RelayCommand] OpenPlanningSessionAsync(TaskRowViewModel? row)`
|
||||||
|
- `[RelayCommand] ResumePlanningSessionAsync(TaskRowViewModel? row)`
|
||||||
|
- `[RelayCommand] DiscardPlanningSessionAsync(TaskRowViewModel? row)`
|
||||||
|
- `[RelayCommand] FinalizePlanningSessionAsync(TaskRowViewModel? row)`
|
||||||
|
- `[RelayCommand] ToggleExpand(TaskRowViewModel parentRow)`
|
||||||
|
|
||||||
|
### 6.6 WorkerClient additions (`ClaudeDo.Ui/Services/WorkerClient.cs`)
|
||||||
|
|
||||||
|
- `Task<PlanningSessionLaunchInfo> StartPlanningSessionAsync(string taskId, CancellationToken ct)` — returns `{ WorkingDir, McpConfigPath, InitialPromptPath, SystemPromptPath }`.
|
||||||
|
- `Task<PlanningSessionResumeInfo> ResumePlanningSessionAsync(string taskId, CancellationToken ct)` — returns `{ WorkingDir, McpConfigPath, ClaudeSessionId }`.
|
||||||
|
- `Task<int> FinalizePlanningSessionAsync(string taskId, CancellationToken ct)` — returns finalized count.
|
||||||
|
- `Task DiscardPlanningSessionAsync(string taskId, CancellationToken ct)`.
|
||||||
|
- `Task<int> GetPendingDraftCountAsync(string taskId, CancellationToken ct)` — for the unfinished-session dialog.
|
||||||
|
|
||||||
|
Existing `TaskUpdated` event covers live draft updates; no new event needed.
|
||||||
|
|
||||||
|
### 6.7 Delete handling
|
||||||
|
|
||||||
|
When the user tries to delete a parent with children:
|
||||||
|
- Repository throws `DbUpdateException` (FK Restrict).
|
||||||
|
- UI catches, shows: "This task has N child tasks. Discard drafts and delete? / Delete all including children? / Cancel."
|
||||||
|
- "Delete all including children" → UI iterates children and deletes them first, then the parent.
|
||||||
|
- "Discard drafts" option only appears if parent status is `Planning` (drafts exist to discard).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Lifecycle & Error Handling
|
||||||
|
|
||||||
|
### 7.1 Worker queue isolation
|
||||||
|
|
||||||
|
`GetNextQueuedAgentTaskAsync` filters on `Status = Queued` — Planning/Planned/Draft are excluded by status. Add explicit regression test to lock this in.
|
||||||
|
|
||||||
|
### 7.2 Parent auto-completion (repeat of 2, for implementation reference)
|
||||||
|
|
||||||
|
After `MarkDoneAsync`/`MarkFailedAsync`:
|
||||||
|
```csharp
|
||||||
|
if (task.ParentTaskId is not null)
|
||||||
|
await TryCompleteParentAsync(task.ParentTaskId, ct);
|
||||||
|
```
|
||||||
|
where `TryCompleteParentAsync` loads children, checks terminal status, sets parent accordingly.
|
||||||
|
|
||||||
|
### 7.3 Session-start errors
|
||||||
|
|
||||||
|
Table in §5.9 Pre-flight checks. UI receives typed exceptions, shows appropriate dialog.
|
||||||
|
|
||||||
|
### 7.4 Session-runtime errors
|
||||||
|
|
||||||
|
- Terminal crashes → drafts + token persist. Resume via dialog (§6.4).
|
||||||
|
- Worker restart → drafts + token persist. Resume rebuilds HTTP connection.
|
||||||
|
- MCP call fails transiently → Claude CLI retries or the model reports the error to the user in terminal; drafts remain in whatever state the last successful call left them.
|
||||||
|
- No session timeout — brainstorming may be long.
|
||||||
|
|
||||||
|
### 7.5 Concurrency
|
||||||
|
|
||||||
|
- Different parents → independent sessions, one token per parent.
|
||||||
|
- Same parent launched twice → `StartPlanningSessionAsync` throws; UI says "Already planning; use Resume".
|
||||||
|
- Cleanup on app exit: nothing — planning state is fully persisted in DB and files.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Testing
|
||||||
|
|
||||||
|
### 8.1 Automated (in `ClaudeDo.Worker.Tests`)
|
||||||
|
|
||||||
|
**Schema & repository:**
|
||||||
|
- Migration applies cleanly on fresh DB.
|
||||||
|
- `GetChildrenAsync` returns only direct children, sorted.
|
||||||
|
- `CreateChildAsync` sets Status=Draft, ParentTaskId correctly.
|
||||||
|
- `FinalizePlanningAsync` transactionally transitions drafts to Manual/Queued, sets parent to Planned, sets timestamp, clears token. On simulated DB error, rolls back fully.
|
||||||
|
- `DiscardPlanningAsync` removes drafts, resets parent.
|
||||||
|
- `GetNextQueuedAgentTaskAsync` ignores Drafts, Planning parents, Planned parents.
|
||||||
|
- `Restrict` cascade: delete parent with children throws `DbUpdateException`.
|
||||||
|
|
||||||
|
**Auto-status hook (§7.2):**
|
||||||
|
- All children Done → parent Done.
|
||||||
|
- Mix: some Done, at least one Failed, rest in terminal state → parent Failed.
|
||||||
|
- Mix with one still Running → parent stays Planned.
|
||||||
|
- Parent stays Planned while any Draft exists (defensive — finalize should have cleared them).
|
||||||
|
|
||||||
|
**MCP handlers (against SQLite + in-process HTTP):**
|
||||||
|
- Valid token → tool executes.
|
||||||
|
- Missing/invalid token → 401.
|
||||||
|
- `create_child_task` → creates Draft, emits TaskUpdated event.
|
||||||
|
- `update_child_task` on non-Draft → MCP error.
|
||||||
|
- `delete_child_task` on non-Draft → MCP error.
|
||||||
|
- `finalize` called twice: first succeeds, second errors because token is invalidated.
|
||||||
|
- Cross-parent access: tool with `task_id` belonging to another parent's session → MCP error.
|
||||||
|
|
||||||
|
**SignalR endpoints (integration with Worker host):**
|
||||||
|
- Start → token generated, session directory + files created, `mcp.json` contains token.
|
||||||
|
- Start on already-`Planning` parent → error.
|
||||||
|
- Resume → no new token, reads `PlanningSessionId` from DB.
|
||||||
|
- Discard → drafts gone, directory removed, token NULL, parent back to Manual.
|
||||||
|
|
||||||
|
### 8.2 Manual (added to `docs/open.md` checklist)
|
||||||
|
|
||||||
|
- Windows Terminal spawn with real `wt.exe`.
|
||||||
|
- Real Claude CLI end-to-end session (requires `ANTHROPIC_API_KEY`).
|
||||||
|
- Avalonia hierarchy rendering (chevron, indentation, draft styling, badges).
|
||||||
|
- Session-ID capture from `~/.claude/projects/...` (timing-sensitive, platform-specific).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Phasing
|
||||||
|
|
||||||
|
Work is delivered in **three sequential-then-parallel** plans. Plan A must merge before B and C can merge.
|
||||||
|
|
||||||
|
### 9.1 Plan A — Foundation
|
||||||
|
|
||||||
|
Schema migration, enum additions, repository methods, auto-status hook, delete-Restrict, regression test for queue filter. No UI-visible changes (other than delete-with-children now failing with a generic error until Plan C handles it).
|
||||||
|
|
||||||
|
Scope files (approximate):
|
||||||
|
- `src/ClaudeDo.Data/Models/TaskEntity.cs`
|
||||||
|
- `src/ClaudeDo.Data/Configuration/TaskEntityConfiguration.cs`
|
||||||
|
- `src/ClaudeDo.Data/Migrations/<new>_AddPlanningSupport.cs`
|
||||||
|
- `src/ClaudeDo.Data/Repositories/TaskRepository.cs` (+ `ITaskRepository`)
|
||||||
|
- `src/ClaudeDo.Worker/...` auto-status hook call-site updates.
|
||||||
|
- `tests/ClaudeDo.Worker.Tests/...` new test classes.
|
||||||
|
|
||||||
|
### 9.2 Plan B — Worker MCP + SignalR + Launcher (starts after A merges)
|
||||||
|
|
||||||
|
MCP service with HTTP transport, token auth, six tools. New SignalR hub endpoints for Start/Resume/Discard/Finalize/GetPendingDraftCount. Session directory management. `IPlanningTerminalLauncher` implementation for `wt.exe`. Resolves the four unknowns from §5.8.
|
||||||
|
|
||||||
|
Scope files (approximate):
|
||||||
|
- `src/ClaudeDo.Worker/Planning/PlanningMcpService.cs` (new)
|
||||||
|
- `src/ClaudeDo.Worker/Planning/PlanningSessionManager.cs` (new)
|
||||||
|
- `src/ClaudeDo.Worker/Hub/WorkerHub.cs` (extend)
|
||||||
|
- `src/ClaudeDo.Worker/Program.cs` (DI + endpoint mapping)
|
||||||
|
- `src/ClaudeDo.App/` or `src/ClaudeDo.Ui/Services/` — `IPlanningTerminalLauncher` + `WindowsTerminalPlanningLauncher`.
|
||||||
|
- `tests/ClaudeDo.Worker.Tests/Planning/...`
|
||||||
|
|
||||||
|
### 9.3 Plan C — UI (parallel to B after A merges)
|
||||||
|
|
||||||
|
Context menu entries, hierarchy rendering, draft styling, unfinished-session dialog, WorkerClient extensions, delete-with-children handling. During parallel development, mocks the WorkerClient against Plan B's interface contract.
|
||||||
|
|
||||||
|
Scope files (approximate):
|
||||||
|
- `src/ClaudeDo.Ui/ViewModels/Islands/TasksIslandViewModel.cs`
|
||||||
|
- `src/ClaudeDo.Ui/ViewModels/Islands/TaskRowViewModel.cs`
|
||||||
|
- `src/ClaudeDo.Ui/Views/Islands/TasksIslandView.axaml`
|
||||||
|
- `src/ClaudeDo.Ui/Views/Islands/TaskRowView.axaml`
|
||||||
|
- `src/ClaudeDo.Ui/Services/WorkerClient.cs`
|
||||||
|
- `src/ClaudeDo.Ui/Design/IslandStyles.axaml` (draft/badge styling)
|
||||||
|
- `src/ClaudeDo.Ui/Views/Dialogs/UnfinishedPlanningDialog.axaml` (new)
|
||||||
|
|
||||||
|
### 9.4 Integration points between B and C
|
||||||
|
|
||||||
|
Interface contract locked before parallel work begins:
|
||||||
|
- SignalR method names, parameters, return DTOs (listed in §6.6).
|
||||||
|
- `TaskUpdated` event payload unchanged; carries the task's new parent-id and status so the UI can re-bucket.
|
||||||
|
- Session directory path shape: `~/.todo-app/planning-sessions/<parentTaskId>/`.
|
||||||
|
- `mcp.json` and session-file formats are internal to Plan B; UI never reads them.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Out of scope (for now)
|
||||||
|
|
||||||
|
- Nested planning (children of children). Explicitly one level.
|
||||||
|
- Cross-list planning (parent in list A, children in list B).
|
||||||
|
- Multi-user collaboration on the same planning session.
|
||||||
|
- Session timeouts / auto-discard.
|
||||||
|
- Planning-session history / audit UI. Directory is kept on finalize but not surfaced.
|
||||||
|
- Re-planning a finalized parent (7c: no).
|
||||||
Reference in New Issue
Block a user