# Refine Task — Design **Date:** 2026-06-04 **Status:** Approved (pending spec review) ## Goal Add a one-click **Refine Task** action to a task card. Clicking it spawns a headless Claude session that reads the task (and the repo), rewrites the task's description to be clearer and runnable autonomously, and — where it helps — breaks the work into subtasks. The user then reviews/hand-edits the result and queues the task manually. This is **not** an interactive terminal session. It is a fire-and-forget headless run, structurally similar to the existing daily-prep ("Prime Claude") flow (`PrimeRunner`), not the interactive planning flow. ## Non-goals / scope - No new task status. The task stays `Idle` throughout; refine only mutates the task's `Title`/`Description` and its subtasks. - No worktree, no interactive terminal, no auto-queue. - No per-task refine config (model, turns) — uses the worker's defaults. - Refine does not edit repository files; repo access is read-only. ## User flow 1. User clicks the refine icon on an `Idle` task's card. 2. UI calls `WorkerHub.RefineTask(taskId)` → `RefineRunner`. 3. `RefineRunner` spawns `claude -p` headless in the list's working directory, seeded with a fixed refine prompt + the task's title/description/current subtasks + the task id. 4. Claude reads the repo (read-only), then calls: - `mcp__claudedo__update_task` to improve title/description, and - `mcp__claudedo__add_subtask` to add steps where useful. Each MCP call broadcasts `TaskUpdated`, so the description and Steps card update live in the UI. 5. Run finishes; the card's refine button returns to its idle state. User reviews, optionally hand-edits the description/steps, then queues manually. ## Architecture ### Worker — `RefineRunner` - New `Worker/Refine/RefineRunner.cs` implementing `IRefineRunner` (`Worker/Refine/Interfaces/IRefineRunner.cs`). Modeled on `PrimeRunner`. - **Concurrency / single-flight:** an in-flight `HashSet` of task ids guarded by a lock (or `SemaphoreSlim`), so the *same* task cannot refine twice concurrently, but different tasks may refine in parallel. A second click on an already-refining task is a no-op. - **Guards:** only runs when `task.Status == Idle`. Resolves the list's working directory. If the list has **no valid working dir**, fall back to a sandbox directory and run text-only (drop `Read`/`Grep`/`Glob` from the allowlist). - **CLI invocation** (relies on the globally-registered `claudedo` MCP, like daily-prep — no `--mcp-config`): ``` claude -p --output-format stream-json --verbose --permission-mode acceptEdits --max-turns --allowedTools mcp__claudedo__get_task,mcp__claudedo__update_task,mcp__claudedo__add_subtask,Read,Grep,Glob ``` `Edit`/`Write`/`Bash` are deliberately **not** whitelisted, so the run is read-only on the repo even under `acceptEdits`. (Chosen over `plan` mode to avoid the headless "exit plan mode to act" friction; the allowlist is the real read-only gate.) - **Logging:** stream stdout to a per-run log at `logs/refine-.log`, truncated at the start of each run. ### Prompt — `PromptKind.Refine` - Add `Refine` to the `PromptKind` enum in `PromptFiles.cs`, file `prompts/refine.md`, with a bundled default. - Default prompt instructs: refine one ClaudeDo task so it is ready to run autonomously; ground the description in the actual code (read-only); keep scope tight (no scope creep into adjacent work); add steps as subtasks only when they genuinely help; use only `get_task`, `update_task`, `add_subtask` and the read-only tools; never edit files. - Rendered via `PromptFiles.Render` with `{taskId}`, `{title}`, `{description}`, and the current subtask list seeded into the prompt so the agent knows which steps already exist. ### MCP tool — `add_subtask` - New `[McpServerTool]` on `ExternalMcpService` (part of the global `claudedo` MCP), signature `add_subtask(taskId, title, orderNum?)`. - Creates a `SubtaskEntity` via `SubtaskRepository`; `orderNum` defaults to append-at-end (max existing + 1). Refuses if the task is `Running`. Broadcasts `TaskUpdated`. - **Append semantics, not replace:** the current subtasks are already in the prompt, so the agent only adds missing steps; re-running refine will not silently wipe steps the user hand-edited. - `update_task` already exists (title/description/commitType) and is reused unchanged. ### UI — button, icon, feedback - **Icon:** add the supplied SVG as an `Icon.Refine` `StreamGeometry` in `IslandStyles.axaml`, rendered as a **stroked `Path`** (`plan-icon` style, fill none) — it is line art, so per the PathIcon-fills-geometry gotcha it must be stroked, not filled. - **Button:** a new `icon-btn` in `TaskRowView.axaml` near the star button, visible only when the task is `Idle`. Bound to a new `RefineTaskCommand` on `TasksIslandViewModel`. - **Feedback:** new broadcaster events `RefineStarted(taskId)` / `RefineFinished(taskId, ok, error?)` drive an `IsRefining` flag on `TaskRowViewModel`; the button shows a busy/disabled state while running. The description and Steps card update live via the existing `TaskUpdated` events fired by the MCP calls. - Wire `RefineTask` through `IWorkerClient` / `WorkerClient`, the `WorkerHub` method, and update the hand-rolled test fakes in both test projects. ## Testing - `add_subtask`: creates the row, appends order correctly, refuses when `Running`, broadcasts `TaskUpdated`. - Refine prompt builder and CLI-args builder produce the expected prompt/flags (including the text-only fallback when no working dir). - `RefineRunner` guards: `Idle`-only, per-task single-flight no-op on a second concurrent call. - **No test spawns the real `claude` CLI** (project rule). The end-to-end run is a manual smoke step. ## Open implementation calls (decided) - **Permission mode:** `acceptEdits` + restricted allowlist for read-only (rather than `plan` mode). - **`add_subtask`:** append-only (rather than replace-all).