task · git:20260914.2333c1a · 2026-09-14 · sha256 12189c25f7f3f59a

task git:20260914.2333c1aA

Immutable. This exact content is served forever at /api/v1/blob/12189c25f7f3f59a.

---
name: task
description: This skill should be used when the user wants to "create a task", "start a new task", "split a task", "make this an epic", "mark a task in progress", "mark a task done", "complete a task", "run this task autonomously", "save what we decided", "bring the site up" for a task's worktree, or "prune the worktrees" of complete tasks. It makes a new task, moves an old one into the project's tasks folder, changes a task's state, splits one task into a parent with children, sets a task's run mode, saves a mid-stage decision as a note, brings the worktree's own site up and down, or removes the worktrees of complete tasks.
argument-hint: "[create <name> | repair <old-task-folder> | start <task-id> | complete <task-id> | split <parent-task-id> | set-run-mode <task-id> <autonomous|interactive> | save <task-id> | environment <task-id> <show|up|down> | prune [<task-id>]...]"
arguments: [action, target]
allowed-tools: Bash(${CLAUDE_PLUGIN_ROOT}/skills/task/scripts/task-actions.sh *), Agent, EnterWorktree
---

# Task

A task is one unit of work inside a project: a folder holding `task.json` (every field a script
reads) and `task.md` (the goal, in prose, that nothing parses). This skill makes one, moves an old
one into place, changes its state, splits it into a parent with children, or sets its run mode. It
does not run any of the five stages, and it does not pick which task is active: that is
`/aida:next`.

Every action below needs the active project's own folder (the one holding `project.json`, never
the code folder). Resolve that first, with the project skill, before using anything here.

## Determine the run mode

Look for a stated run mode on the task active in this conversation, when one is already active.
Found, and it says `autonomous`: act autonomously through this whole invocation, passing
`--run-mode autonomous` on every call to the script below. Anything else, including no active
task, such as the moment `create` itself runs: act interactively, the safe default. Decide this
once, at the start.

`task-actions.sh` never asks a question on its own. Every fact below that this skill would
otherwise ask for must be decided before the script runs; the script only writes what it is given
and reports what happened. Every action prints summary lines: `task-file:` with the path, `id:`,
`state:`, `parent:`, `children:` and `runMode:`. It never prints the record. Read the file at the
printed path when another field is needed.

## `create <name>`

Makes the task and nothing else: no contract, no interview, no stage. It takes a name and a goal.
Run it only when the person asked for this task in this conversation, by name or by a yes to an
offer. Another skill's hand-off carries that yes. Nothing here invents one.

**1. Name.** Ask what to call it, unless already said. Check it against `^[a-z0-9][a-z0-9-]*$`:
lowercase letters, digits and hyphens, starting with a letter or digit. The worktree folder takes
this name and becomes a hostname label, and DDEV lowercases and rewrites the rest. Existing
tasks keep their ids. On a mismatch, say so and ask again; the script refuses it too, so this
check only saves a round trip.
Autonomous with no name given: **halt.** A task cannot be filed without one.

**2. Goal.** Ask what this task is for, in the spirit of a user story: what someone wants to
accomplish and why, not a ticket. Write it back in one or two sentences and confirm with a plain
yes or no before writing anything. Autonomous with no goal given or implied by the conversation:
**halt.** A task with no stated goal is not a record of anything.

**3. Write it.** Run, with the run mode set as decided above:
```
"${CLAUDE_PLUGIN_ROOT}"/skills/task/scripts/task-actions.sh --run-mode <interactive|autonomous> \
  create --project "<projectPath>" --name "<name>" -- <goal...>
```
It writes the folder, `task.json` with `state: "new"`, and `task.md` with the goal under `## Goal`.
It then makes the task's own git worktree beside the code path, at
`<parent of codePath>/<basename of codePath>-<name>`, on the branch `feature/<name>`, records
both in `task.json`, and commits. The tree is a sibling for one reason. A nested worktree is
invisible to a tool that registers projects by folder, and DDEV hands it to the parent project.
Show the whole output. Exit code 3 means one of three things: the name collided with an existing task, it failed the name rule
the script also enforces, or the worktree could not be made. In the last case the folder is
removed. Say what it printed. For a name, ask for a different one. For the worktree, name the
repair the message gives and stop.

**4. Enter the tree.** Every stage action of this task runs inside that worktree, and refuses
from anywhere else. The `worktree:` line names it. Call the `EnterWorktree` tool with that path,
so scoping in this same window is not refused. The tool asks for approval, because the path is
outside `.claude/worktrees/`; that is expected. From a window outside the code repository the
tool refuses on first entry. On that refusal, print the path and `cd <path> && claude`, which
opens a window in the tree. Say that the site offer comes at `start`, and stop. On a successful
entry, go on to step 5.

**5. Offer the site.** Runs here when this window entered the tree, and at `start` otherwise. A
worktree has the branch's files and no site, so a review or a baseline taken there would capture
the served checkout instead. Dispatch `catalog-identifier` once for the `worktree-environment`
point, naming every framework the project records, the same words the surfaces skill uses for
its points. When the project record has `surfaces.e2e.enabled` or
`surfaces.visualRegression.enabled`, name `e2e-setup` or `visual-regression` in the same
dispatch, so `up` can install that harness in the tree. Pass the answer as
`--recipe <framework>=<path>` or `--lookup-failed <framework>=<word>`, one flag per framework,
and each setup recipe as `--setup-recipe <kind>=<path>`, where the kind is `e2e` or
`visual-regression`, then run `environment <name> show`.
The word is `no-recipe`, `listing-unreachable` or `fetch-failed`; the script refuses any other.
`not-applicable` ends the step: say once that this worktree has files and no site. Otherwise,
interactive: show the commands and the prose, and ask once whether to bring the site up now. A
yes runs `environment <name> up` with the same flags. A no records nothing; say `up` with the
same flags does it later. Autonomous: never bring it up, and say so once.

## `environment <task-id> <show|up|down>`

The worktree's own running site, from the framework's `worktree-environment` recipe. The recipe
holds the commands; this plugin holds none. `show` and `up` take the recipe flags step 5 names,
and the `--setup-recipe` flags. `down` takes none: it reads the recipe path the record holds.
```
"${CLAUDE_PLUGIN_ROOT}"/skills/task/scripts/task-actions.sh --run-mode <interactive|autonomous> \
  environment --project "<projectPath>" "<task-id>" <show|up|down> <recipe flags>
```
`show` prints the recipe path, the preconditions prose and the build-in-place prose. It prints
the token, bring-up, address and tear-down commands with `{codePath}` filled, and runs nothing.
It prints the paths of the `## Files` blocks, the files `up` writes, and one `precondition:`
line per `## Preconditions` command `up` runs.
A recipe with no bring-up block, or no address block, exits 3 from `show` too, so its exit code
says what `up` would do.

`up` is a person's yes, so it refuses unattended at 70. It runs in the task's worktree, with the
output in `records/environment-up.txt`, in this order. First it writes each `## Files` block
absent from the worktree. A file present with other content refuses at 3. Then it runs each
`## Preconditions` line, after the files because the check is a script the recipe ships. A
failing line stops at 3, prints its output, removes the files this run wrote, and commits
nothing. Then it commits the written files alone, so other changed or staged work is never
taken in. Then
each `## Tokens` command, whose first output line is the token's value. A token command that
prints nothing or fails refuses at 4 by the token's name. Then the bring-up lines before the
`## Address` heading. Then the address command, whose output is `key: value` lines. `address:`
is required, and every other key is a token for the later lines and for the tear-down. A `root:`
line that is not the worktree stops at 3 before the later lines: the environment resolved to
another tree. Then the bring-up lines after the heading. Then, for each surfaces kind the
project has on, the `## Install` lines of the setup recipe given as `--setup-recipe`. It
commits nothing after that; what the install left uncommitted is named and stays for the task's
own commit. With no path for a kind it says so and goes on, and the
harness is the person's next step. A line still holding an unfilled `{token}` stops at 3 and
names it. A failing line stops at 4 with a `first:` line. Show that line; do not bring the site
up by hand. It records `environment` in `task.json`: the address, the recipe, when, and the
other address keys. It prints `address:`. Running it twice is safe: the recipe promises every
step runs again cleanly.

`down` runs the tear-down lines, output to `records/environment-down.txt`, and removes
`environment` from `task.json`. It runs unattended too: tearing a copy down loses nothing. With
nothing up it says so and exits 0. Run it before the worktree is removed, or the framework keeps
an orphaned registry entry; the completion body names it when a site is up. Review and `baseline`
read `environment.address` before asking for a base URL.

## `prune [<task-id>]...`

The worktrees of complete tasks. A worktree kept after completion costs disk and a DDEV project
each, and a tree removed too early loses uncommitted work. So this lists first, asks per tree,
and removes only what a person named. Run it from the main checkout, never from inside a tree
it may remove.
```
"${CLAUDE_PLUGIN_ROOT}"/skills/task/scripts/task-actions.sh --run-mode <interactive|autonomous> \
  prune --project "<projectPath>" [--all] [<task-id>]...
```
**1. List.** With no id the script prints one `id:` line per complete task that records a
worktree. The line holds the path, the branch, and whether the branch is merged into the code
path's current branch. It also says whether the tree is on disk and whether a site is up.
`prune: none` means nothing to remove. Show the lines. Autonomous: this is the whole action.
Say once that a tree goes only on a person's yes, and stop. The script refuses an id unattended
at 70.

**2. Ask per tree.** For each listed tree ask a plain yes or no, one at a time, with the line.
Never ask once for all of them. An unmerged branch is a reason to say so before asking: the
tree goes, the branch stays.

**3. Remove.** Run the action once with every id that got a yes, in the order given, or with
`--all` when every tree got one. For each tree the script tears the site down when one is up,
then removes the tree. It deletes the branch when it is merged. It clears `worktree` and
`environment` from `task.json` and commits. It prints one `pruned:` line per tree naming what
happened to the branch. Show them. Exit 3 names the tree it stopped at and why. The task is not
complete, git refused a tree with uncommitted changes, or the tear-down failed. Nothing after
that tree was touched, and nothing is ever forced. Say what it printed and stop.

## `repair <old-task-folder>`

Moves an old task into the project's own `tasks/` folder, the first time it is opened. This is the
only move in the whole skill: everything else here changes a field, never a location.

Given the path to a task folder still sitting under `implementation_process/in_progress/` or
`implementation_process/completed/` inside the project folder, run:
```
"${CLAUDE_PLUGIN_ROOT}"/skills/task/scripts/task-actions.sh repair --project "<projectPath>" "<old-task-folder>"
```
No confirmation is needed: nothing here is destructive. The script refuses outright when the
destination already exists, and it refuses to move a task whose `## Goal` section it cannot find,
rather than moving something it cannot verify. It reads the goal, and the parent and children when
an old header carries them, back from the new location before it reports success. Show the whole
output either way.

Inside the new folder it renames `alignment.md`, `research.md`, `architecture.md` and `research/`
to `alignment.v5.md`, `research.v5.md`, `architecture.v5.md` and `research.v5/`, each when
present. It prints one `KEPT:` line per rename. Version 6 writes under those names, and the old
files are the input the first run of each stage reads. It refuses, before moving anything, when
a `.v5` name already exists in the old folder.

Which old tasks still need this is `/aida:next`'s job: it lists them as `kind: legacy` and runs
this action on the one it loads.

## `start <task-id>`

A task becomes in progress the moment a stage first writes an artifact into it. Each stage's own
script calls this once, before its first write: scope `init`, research `start`, design `start`,
implement `start` and review `checks`. It skips the call when the task is already in progress. So
it is not usually a person typing a command. By hand, run:
```
"${CLAUDE_PLUGIN_ROOT}"/skills/task/scripts/task-actions.sh --run-mode <interactive|autonomous> \
  start --project "<projectPath>" "<task-id>" -- <why...>
```
Already `in_progress`: prints `UNCHANGED` and does nothing further. Already `complete`: refused,
since a completed task is not reopened here. Otherwise it writes the new state, commits, and runs
the task check. Show the whole output. The check reports and never repairs, so a finding here is
the one thing to repair now, before the stage writes anything.

When a person runs this by hand and the record has no `environment`, run `create`'s step 5 here.
The window that made the task may not have entered the tree. A `not-applicable` ends it silently.

## `complete <task-id>`

Writes `state: complete`. This action is the one writer of that state, and the completion skill
calls it last. A person runs the completion skill, not this action: completion reads the review
verdict, records on what grounds the task closed, and writes the pull request body. This action
records none of that, so a task closed here has a state and no grounds.

The completion skill passes the summary the person gave, after `--`. The script refuses an empty
one outright. By hand, the call is:
```
"${CLAUDE_PLUGIN_ROOT}"/skills/task/scripts/task-actions.sh --run-mode <interactive|autonomous> \
  complete --project "<projectPath>" "<task-id>" -- <summary...>
```
It sets the state to `complete`, appends a dated `## Completed` section holding the summary to
`task.md`, and commits everything under `tasks/` together. That commit carries the completion
record and the body when completion called it. Already complete: prints `UNCHANGED`. Show the
whole output.

## `split <parent-task-id>`

Turns one task into a parent with two or more children. Nothing moves, so there is no temporary
build, no atomic swap and no rollback copy: version 5 needed all of that because splitting moved
folders; here nothing does.

Every fact this needs must already be decided before calling it: which children, each child's own
goal, and which criteria hand down to it. This skill never derives them. The research skill's
split advisor recommends the children and their criteria after research closes. That skill's
`split-read` action checks that every criterion is claimed once, before this action runs. This only performs
the mechanical split.

The two-level limit stays: a task that already has a parent cannot be split again, and a child
always lives in the same project as its parent, never another one. Run:
```
"${CLAUDE_PLUGIN_ROOT}"/skills/task/scripts/task-actions.sh --run-mode <interactive|autonomous> \
  split --project "<projectPath>" "<parent-task-id>" \
  --child "<child-id-1>" --goal "<goal-1>" [--criterion "<text>"]... \
  --child "<child-id-2>" --goal "<goal-2>" [--criterion "<text>"]...
```
Repeat `--child ... --goal ... [--criterion ...]` once per child, at least twice. It creates each
child's folder and `task.json` with `parent` set to the split task's own id, writes each child's
goal and any handed-down criteria into its `task.md`, makes each child's own worktree the way
`create` does, adds every new id to the parent's own
`children` list, reads all of that back, and commits everything together. Exit code 1 means it
stopped before writing anything: `NOT FOUND` says the named task does not exist, `REFUSED` says
the two-level limit stopped it. Say which and stop. Show the whole output otherwise.

## `set-run-mode <task-id> <autonomous|interactive>`

Run mode is written only when a person explicitly asks for an autonomous run on this task.
Nothing above asks about it on its own, and nothing here proposes it either. Only call this when
asked. Run:
```
"${CLAUDE_PLUGIN_ROOT}"/skills/task/scripts/task-actions.sh --run-mode <interactive|autonomous> \
  set-run-mode --project "<projectPath>" "<task-id>" <autonomous|interactive>
```
`autonomous` writes the field. `interactive` removes it: there is no `"interactive"` value to
write, since the field's absence already means that. Show the whole output.

## `save <task-id>`

A person stops mid-stage, and a decision this conversation made is in no record yet. This writes
it down for the next window. Only a person invokes it; nothing dispatches it.

The current stage is the `stage` the next skill's report prints for this task, the first whose
close record is absent.
That is scope without `alignment.json`, research without `records/research-check.json` at
`exitCode` 0, design without `design-closed.json`. Read that stage's sidecar,
`records/<stage>-distill.json`. When none exists, dispatch the `distiller` role with the task
folder, the stage, and the stage's record paths, then read the sidecar it writes. A record path
absent mid-stage is normal; the distiller names it as a gap.

Name what this conversation decided that neither the sidecar's `decisions` nor the stage's own
files hold. Each is one sentence: what was decided and what it applies to. Show the list and ask
for a plain yes or no. Nothing is written before yes. Nothing to save is said in one line, and
`save` still runs with no text. That records the time, which clears the compaction hook's refusal.

Yes: run once per sentence, or once with all of them:
```
"${CLAUDE_PLUGIN_ROOT}"/skills/task/scripts/task-actions.sh --run-mode <interactive|autonomous> \
  save --project "<projectPath>" "<task-id>" -- <text...>
```
It appends the text to `<task_folder>/notes/<date>.md` under a `## <UTC time>` heading. It
records `savedAt` in `task.json`, commits, and prints `savedAt:` and `note:` with the path. With no text
it writes no note and prints `savedAt:` only. Show the lines.

A note is never a stage record: the stage action that later records the same decision makes it
stale, and the record wins. The session-start hook names the newest note after `Stage:`, and
`/aida:next` lists its date, so the next window reads it before its first turn.