# 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<string>` 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 <N>
         --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-<taskId[:8]>.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).
