Files
ClaudeDo/docs/superpowers/specs/2026-06-04-refine-task-design.md
mika kuns 0867bc8296 docs(refine): add Refine Task design spec
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-04 22:53:17 +02:00

128 lines
6.0 KiB
Markdown

# 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).