6.0 KiB
6.0 KiB
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
Idlethroughout; refine only mutates the task'sTitle/Descriptionand 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
- User clicks the refine icon on an
Idletask's card. - UI calls
WorkerHub.RefineTask(taskId)→RefineRunner. RefineRunnerspawnsclaude -pheadless in the list's working directory, seeded with a fixed refine prompt + the task's title/description/current subtasks + the task id.- Claude reads the repo (read-only), then calls:
mcp__claudedo__update_taskto improve title/description, andmcp__claudedo__add_subtaskto add steps where useful. Each MCP call broadcastsTaskUpdated, so the description and Steps card update live in the UI.
- 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.csimplementingIRefineRunner(Worker/Refine/Interfaces/IRefineRunner.cs). Modeled onPrimeRunner. - Concurrency / single-flight: an in-flight
HashSet<string>of task ids guarded by a lock (orSemaphoreSlim), 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 (dropRead/Grep/Globfrom the allowlist). - CLI invocation (relies on the globally-registered
claudedoMCP, 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,GlobEdit/Write/Bashare deliberately not whitelisted, so the run is read-only on the repo even underacceptEdits. (Chosen overplanmode 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
Refineto thePromptKindenum inPromptFiles.cs, fileprompts/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_subtaskand the read-only tools; never edit files. - Rendered via
PromptFiles.Renderwith{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]onExternalMcpService(part of the globalclaudedoMCP), signatureadd_subtask(taskId, title, orderNum?). - Creates a
SubtaskEntityviaSubtaskRepository;orderNumdefaults to append-at-end (max existing + 1). Refuses if the task isRunning. BroadcastsTaskUpdated. - 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_taskalready exists (title/description/commitType) and is reused unchanged.
UI — button, icon, feedback
- Icon: add the supplied SVG as an
Icon.RefineStreamGeometryinIslandStyles.axaml, rendered as a strokedPath(plan-iconstyle, fill none) — it is line art, so per the PathIcon-fills-geometry gotcha it must be stroked, not filled. - Button: a new
icon-btninTaskRowView.axamlnear the star button, visible only when the task isIdle. Bound to a newRefineTaskCommandonTasksIslandViewModel. - Feedback: new broadcaster events
RefineStarted(taskId)/RefineFinished(taskId, ok, error?)drive anIsRefiningflag onTaskRowViewModel; the button shows a busy/disabled state while running. The description and Steps card update live via the existingTaskUpdatedevents fired by the MCP calls. - Wire
RefineTaskthroughIWorkerClient/WorkerClient, theWorkerHubmethod, and update the hand-rolled test fakes in both test projects.
Testing
add_subtask: creates the row, appends order correctly, refuses whenRunning, broadcastsTaskUpdated.- Refine prompt builder and CLI-args builder produce the expected prompt/flags (including the text-only fallback when no working dir).
RefineRunnerguards:Idle-only, per-task single-flight no-op on a second concurrent call.- No test spawns the real
claudeCLI (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 thanplanmode). add_subtask: append-only (rather than replace-all).