docs(refine): add Refine Task design spec
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
127
docs/superpowers/specs/2026-06-04-refine-task-design.md
Normal file
127
docs/superpowers/specs/2026-06-04-refine-task-design.md
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
# 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).
|
||||||
Reference in New Issue
Block a user