to-task · diff

git:20260927.22f77e8 to git:20260927.37481be

42 added, 44 removed. Audit A to A.

---
name: to-task
- description: 'Capture the chat (or a roadmap item) into `.task/task/<slug>.md` — `## Description` only, no `## Plan`.'
+ description: 'Capture the chat (or a roadmap item) into `.task/task/<slug>.md` — `## Description` plus `## Plan` (Goal/Touches/Logic), and `## Tests` when the testing policy calls for it.'
argument-hint: '[<roadmap-slug>[#N] | context]'
disable-model-invocation: true
user-invocable: true
allowed-tools: 'Bash(bash *skills/_lib/preflight.sh* *) Bash(bash *skills/_lib/write-task.sh* *) Bash(bash *skills/_lib/roadmap-items.sh* *) Bash(bash *skills/_lib/detect-project.sh* *) Bash(bash *skills/validate/validate.sh* *)'
---
- Distil the chat discussion so far (or a roadmap item) into `.task/task/<slug>.md` — `## Description` only, no `## Plan`. The lightest capture: the "what and why", recorded before implementing directly or before `to-plan` later. The written file is the handle, and a fresh session implements it by reading `## Execution`.
+ Distil the chat discussion so far (or a roadmap item) into `.task/task/<slug>.md` — `## Description` **and** `## Plan` (Goal/Touches/Logic steps), plus `## Tests` when the testing policy calls for it, and the `## Execution` pointer. The one single-task capture: a task worth an artifact is a task whose approach is worth writing down, so the Plan is never optional. The slug is the filename; the artifact path is the handle, and a fresh session implements it by reading `## Execution`.
**Input:** `$ARGUMENTS` — optional. Recognized forms:
- (empty) — draft from the chat discussion so far.
- `<roadmap-slug>` or `<roadmap-slug>#<N>` — open from that roadmap item instead of the chat.
- anything else — free-form context to fold into the draft alongside the chat discussion.
+ **Format contract:** [docs/contract.md](../../docs/contract.md) is the single source of truth for the output structure — read it if anything below is ambiguous.
+
## Step 0: Setup gate
The entry state, gathered before this skill reached you — no tool call of your own:
!`bash "${CLAUDE_PLUGIN_ROOT}/skills/_lib/preflight.sh" task`
[docs/contract.md § Helpers](../../docs/contract.md#helpers) owns that block's shape. Read it, then act:
1. `AI_DIR:` is the pipeline root: `.task/task/<slug>.md` below means `$AI_DIR/task/<slug>.md`, **never a cwd-relative path** ([contract § Setup-gate categories](../../docs/contract.md#setup-gate-categories)).
- 2. **`CONFIG: absent` → inline setup.** Read `${CLAUDE_PLUGIN_ROOT}/skills/_lib/setup.md` and follow it — it owns the sub-steps and the `.task/CLAUDE.md` template. **No confirmation chip:** the file is written first, and a wrong detected value is fixed by editing it. Then continue with the original `$ARGUMENTS` unchanged. (A relative `AI_DIR: .task` means no git repository and no `.task/` yet; setup establishes `<ROOT>/.task`.)
+ 2. **`CONFIG: absent` → inline setup.** Read `${CLAUDE_PLUGIN_ROOT}/skills/_lib/setup.md` and follow it — it owns the sub-steps and the `.task/CLAUDE.md` template, and there is no separate setup command. **No confirmation chip:** the file is written first, and a wrong detected value is fixed by editing it. Then continue with the original `$ARGUMENTS` unchanged. (A relative `AI_DIR: .task` means no git repository and no `.task/` yet; setup establishes `<ROOT>/.task`.)
3. **`CONFIG: present` → leave the file alone.** It is user-owned: never rewrite it, never re-detect its values, never "repair" a missing section. Only a missing `.task/.gitignore` is recreated, by the preflight above itself; `task.root` is written by first-run setup and not restored afterwards. Regenerating it is the user's move — delete, then re-run any capture.
- 4. `ROADMAPS:` / `TASKS:` / `SPECS:` are what already exists. Step 1 branches on the roadmap lines and Step 2's slug-collision guard checks `TASKS:` — neither lists a directory of its own.
+ 4. `ROADMAPS:` / `TASKS:` / `SPECS:` are what already exists. Step 1 branches on the roadmap lines and the slug-collision guards check `TASKS:` — neither lists a directory of its own.
If that block arrived unexpanded — the command line itself rather than its output — the preprocessing did not fire: run that command yourself and continue exactly as above.
- There is no full-scan validate call here: the writer validates the one file it writes, and pre-existing artifacts are checked on demand with `validate.sh all`, never as an entry gate.
+ There is no full-scan validate call here — the file this run writes is validated after the write (Step 7), and pre-existing artifacts are checked on demand with `validate.sh all`, never as an entry gate.
## Step 1: Entry
- No pointer to resolve — the artifact path is the handle. Branch on `$ARGUMENTS`:
+ No pointer to resolve — the artifact path is the handle, and every run is a fresh capture. Branch on `$ARGUMENTS`:
1. `$ARGUMENTS` names a `ROADMAPS:` slug, with or without `#<N>` → **from-roadmap mode**, Step 1a.
2. Otherwise, chat discussion **or** free-form `$ARGUMENTS` to draft from — either alone is enough → **chat-draft mode**, Step 2.
3. Neither, but some `ROADMAPS:` line carries an `unchecked=` list other than `none` → `AskUserQuestion` (convention (c)), "How do you want to start this task?" — **Draft from this chat** / **Open from a roadmap**, the latter chipping the roadmap slugs and continuing as from-roadmap mode.
- 4. Nothing at all → **stop** rather than drafting a Description from nothing: "nothing to capture yet — describe the task in chat, or name it directly. → Next: `/task:to-task <what to capture>`"
+ 4. Nothing at all → **stop** rather than drafting from nothing: "nothing to capture yet — describe the task in chat, or name it directly. → Next: `/task:to-task <what to capture>`"
### Step 1a: From-roadmap mode
- 1. Follow `${CLAUDE_PLUGIN_ROOT}/skills/_lib/roadmap-item.md` — the one owner of the from-roadmap block, shared with `to-plan` and the driver's plan agent: resolve the roadmap, pick `#<N>`, read the ready description, collect the spec slugs, derive `<item-slug>`, check whose file it is. Its footers take this skill's own command, so a stop reads `→ Next: \`/task:to-task <slug>#3\``.
+ 1. Follow `${CLAUDE_PLUGIN_ROOT}/skills/_lib/roadmap-item.md` — the one owner of the from-roadmap block, shared with the driver's plan agent: resolve the roadmap, pick `#<N>`, read the ready description, collect the spec slugs, derive `<item-slug>`, check whose file it is, and note the roadmap's `## Architecture` if it has one. Its footers take this skill's own command, so a stop reads `→ Next: \`/task:to-task <slug>#3\``.
- 2. Draft the `## Description` body from the ready description — the why from `### Context`, the what from Goal / Outcomes / Invariants / Acceptance criteria. Nothing else: `## Plan` and `## Tests` are `to-plan`'s contract.
+ 2. **Slug collision.** `roadmap-item.md` step 5 already separated this item's earlier capture from an unrelated namesake (the namesake gets a disambiguated slug and is never touched). When the file is **this item's own**, run the one sanctioned chip (convention (b)) before writing anything: state the existing file's headings as message text first ("Existing `.task/task/<item-slug>.md` has: Description, Plan (4 steps), Tests (2)."), then pose an `AskUserQuestion`: **Regenerate from the item** *(Recommended — the Description is re-derived from the ready description, so only the old Plan and any hand edits are replaced)* / **Decline — stop without writing**. Only the first chip earns `--force` in Step 7. **Decline** says plainly that nothing was written and closes with `→ Next: implement the existing file in a fresh session: \`implement .task/task/<item-slug>.md\``.
- 3. **Slug collision.** `roadmap-item.md` step 5 already separated this item's earlier capture from an unrelated namesake. This item's, Description only → rewriting it is safe (`--force`). This item's but already carrying a `## Plan` → an overwrite would destroy it: run Step 2.1's guard, whose chips recommend deepening via `/task:to-plan`.
+ 3. Hold what it produces for Step 7's write — the item title, the roadmap slug, `#<N>`, the distinct spec slugs, and the Description drafted from the ready description (why from Context, what from Goal / Outcomes / Invariants / Acceptance criteria). Do not write the file yet: Description, Plan and Tests are assembled and written once, together, in Step 7. Continue to Steps 3–6.
- 4. Write it per **Step 2.4** below with `--slug <item-slug>`, `--title "{item title}"`, step 2's body as `--description`, plus `--roadmap <roadmap-slug> --item <N>` and a `--spec` per cited spec. Then continue to Step 3, using `<item-slug>` as `<slug>` there.
+ ## Step 2: Chat-draft mode
- ### Step 2: Chat-draft mode
+ 1. **Slug.** A short kebab-case slug (2–4 words) from the chat's essence, in English whatever `.task/CLAUDE.md` → Language says — it is a filename, a parser-stable string. This is both the filename and the task's identity — no task-id, no bracket.
- 1. **Slug.** Derive a kebab-case English slug (2–5 words) from the chat's essence / the drafted title. This is both the filename and the task's identity — no task-id, no bracket.
+ **Slug collision.** If it is already in `TASKS:`, surface that before writing — this is the one sanctioned chip (convention (b)), a pre-write guard on a destructive action. State the existing file's headings as message text first, so the user knows what an overwrite costs ("Existing `.task/task/<slug>.md` has: Description, Plan (4 steps)."), then pose an `AskUserQuestion`: **Pick a different slug** *(Recommended)* / **Overwrite it** / **Decline — stop without writing**. `.task/` is ignored by its own `.task/.gitignore` and never committed, so an overwritten file cannot be restored. Only the middle chip earns `--force` in Step 7; an unrelated namesake is always better disambiguated than overwritten. **Decline** says plainly that nothing was written and closes with `→ Next: \`/task:to-task <a different slug>\`` (convention (a)).
+ 2. **Read `.task/CLAUDE.md`** for Language and Testing Policy. Step 0 only *reports* that it exists; the platform's auto-load fires for file-read tools only, so its contents are not in front of you yet.
+ 3. **Distil the chat** — not the codebase yet — into the `## Description` body: the why + what in the user's own framing, per `.task/CLAUDE.md` → Language (section labels stay English). Use `### Problem` / `### Outcome` / `### Scope` / `### Constraints` where the discussion gives signal; omit a sub-header rather than inventing content, and fabricate nothing that was not discussed.
+ 4. Hold the title and that body for Step 7, plus a spec slug for each `SPECS:` entry the discussion clearly relies on — the executing session reads them as fixed anchors. Never invent a reference, and never author the spec — that is `to-spec`'s job. Continue to Steps 3–6.
- **Slug collision.** If the slug is already in Step 0's `TASKS:` list, surface it before writing. First **read the existing file's headings** and state them as message text above the chip, so the user knows what an overwrite costs — e.g. "Existing `.task/task/<slug>.md` has: Description, Plan (4 steps), Tests (2)." Then pose an `AskUserQuestion`:
- - **The existing file has a `## Plan`** → an overwrite would destroy it, and `.task/` is ignored by its own `.task/.gitignore` and never committed, so there is nothing to restore from. Recommend deepening instead: chips **Deepen it — `/task:to-plan`** *(Recommended)* / **Pick a different slug** / **Overwrite (loses the Plan)** / **Decline → stop without writing**.
- - **Description only** → the cheap case: chips **Accept overwrite** / **Edit → propose a different slug** / **Decline → stop without writing**.
+ ## Steps 3–6: Anchors, analysis, `tests_required`, draft, self-check
- Either non-writing outcome says plainly that nothing was written and closes with `→ Next: \`/task:to-task <a different slug>\`` (convention (a)).
- 2. **Read `.task/CLAUDE.md`** for Language before drafting. Step 0 only *reports* whether the file exists; the platform's auto-load fires for file-read tools only, so nothing has put its contents in front of you yet.
- 3. **Distil the chat** — the discussion, not the codebase — into the `## Description` body: the why + what in the user's own framing, per `.task/CLAUDE.md` → Language (section labels stay English). Use `### Problem` / `### Outcome` / `### Scope` / `### Constraints` where the discussion gives signal; omit a sub-header rather than inventing content, and fabricate nothing that was not discussed.
+ Read `${CLAUDE_PLUGIN_ROOT}/skills/_lib/plan-driver.md` § **Core** and follow steps 1–5. It is the single owner of the plan pipeline — the anchors to read (roadmap architecture included), the ascending-cost codebase analysis, the `tests_required` decision, the three-layer `### Step N:` contract, and the self-check list. The driver's per-item plan agent follows the same copy, so the interactive and non-interactive paths cannot drift.
- Hold a spec slug for each `SPECS:` entry the discussion clearly relies on — the executing session reads them as fixed anchors. Only ones actually relevant, never invented, and never authored here.
- 4. **Write it with `skills/_lib/write-task.sh`** — no in-chat draft, no confirmation prompt. The discussion was the review; the file is the deliverable, and Step 3's digest lets the user judge whether to open it. (The collision guard above still runs first.) The script owns the header link forms, the `---` separator, the stamped `## Execution` pointer and the validate call — see [contract § task.md format](../../docs/contract.md#taskmd-format-tasktaskslugmd) for the shape it produces. One Bash call, the Description body through a **quote-delimited** heredoc written flush-left (so backticks and `$` reach the file verbatim), **without** its `## Description` heading:
+ One thing Core leaves to this skill, because it needs a user: **`tests_required`, the testing-adjacent case** (tests mentioned, but not whether *new* ones are wanted) → resolve it with one `AskUserQuestion` (convention (c)) before drafting `## Tests`: **Add tests** / **No tests this run**. Core's fallback of `false` is for the driver, which has nobody to ask.
- ```bash
- d=$(mktemp)
- cat >"$d" <<'DESC'
- {drafted Description body}
- DESC
- bash "${CLAUDE_PLUGIN_ROOT}/skills/_lib/write-task.sh" --fresh \
- --slug <slug> --title "{Short task title}" --description "$d" \
- --spec <spec-slug> # one per relevant spec; omit if none
- rm -f "$d"
- ```
+ ## Step 7: Write
- No `--plan` and no `--tests`: both are `to-plan`'s contract. Exit 4 means the file already exists and nothing was written; **exit 5** means the write itself failed (unwritable `.task/`, full disk) — no `WROTE:` line was printed, so say that plainly instead of printing Step 3's digest, and close with `→ Next: fix the write failure (permissions or free space under \`$AI_DIR\`), then rerun \`/task:to-task\`.` `--force` is earned by the collision chips above — or, in from-roadmap mode, by Step 1a.3 having identified the file as this item's own Description-only capture. The `WROTE:` / `VALIDATE:` lines it prints are what Step 3 reports; only a setup-precondition failure (validate exit 2) hard-stops.
+ `plan-driver.md` § Core step 6 is the write: `skills/_lib/write-task.sh`, one Bash call, bodies through quoted heredocs, always `--fresh` — plus `--roadmap <roadmap-slug> --item <N>` from a roadmap item and one `--spec` per cited spec. No in-chat draft and no confirmation prompt — the chat discussion was the review, and Step 8's digest lets the user judge whether to open the file.
- ## Step 3: Output — digest
+ - **exit 4** — `--fresh` on a slug that already exists, and nothing was written. That is the collision guard's job (Step 1a.2 / Step 2.1); `--force` only after its overwrite chip.
+ - **exit 5** — the write itself could not happen (unwritable `.task/`, full disk). No `WROTE:` line was printed, so report the failure plainly, never print Step 8's digest for a path that does not exist, and close with `→ Next: fix the write failure (permissions or free space under \`$AI_DIR\`), then rerun \`/task:to-task\`.`
- Print the structural digest (convention (b)) as message text — enough to judge at a glance whether to open the file:
+ The `WROTE:` / `VALIDATE:` lines it prints are what Step 8's digest reports; only a setup-precondition failure (validate exit 2) hard-stops.
+ ## Step 8: Output — digest
+
+ Print the structural digest of what was written (convention (b)) as message text — enough for the user to judge at a glance whether to open the file, without re-reading a full draft:
+
```
Wrote `.task/task/<slug>.md`
# {Title}
- Sections: Description, Execution
- Captured:
- - {the why, one line}
- - {the what / scope, one line}
+ Sections: Description, Plan ({N} steps)[, Tests ({N})], Execution
+ Plan:
+ - Step 1: {short title}
+ - Step 2: {…}
+ architecture: {followed | deviated in step N — {reason} | none}
validate: {OK — 0 errors, N warning(s) | the FAIL lines}
```
- On a result that is **not** clean (any WARN or FAIL), append the re-check invocation — `validate` is not a slash command, so it is worth spelling out. Omit it on a clean result:
+ The `architecture:` line reports how the Plan relates to the source roadmap's `## Architecture` (`plan-driver.md` § Core step 1); `none` when the task has no roadmap or the roadmap has no such section.
+ When the validate result is **not** clean (any WARN or FAIL), append one more line so the user can re-check after editing the file by hand — `validate` is not a slash command, so the invocation is worth spelling out. Omit it entirely on a clean result:
+
```
re-check after editing: bash "${CLAUDE_PLUGIN_ROOT}/skills/validate/validate.sh" task <slug>
```
The file is already written — to change anything, just say so. Then close with the handoff footer (convention (a), flag-free), naming the path explicitly:
- `→ Next: implement it now, deepen it into a plan with \`/task:to-plan\`, or in a fresh session run: \`implement .task/task/<slug>.md\``
+ `→ Next: implement it now, or in a fresh session run: \`implement .task/task/<slug>.md\``
## Forbidden
- - Write a `## Plan` or `## Tests` section — both are `to-plan`'s contract; this skill captures the Description only.
- - Scan the codebase beyond `CLAUDE.md` + top-level manifests. This skill captures discussion; it does not investigate implementation.
- - Modify the source roadmap or write a spec file — read-only from here. Ticking `- [x]` is `task:code-reviewer`'s job, once its review of the item passes; specs are authored only by `to-spec`.
- - Silently overwrite an existing task file, or invent an active-task pointer. There is none: the artifact path is the only handle.
+ - Silently overwrite an existing task file — every existing slug goes through a collision chip first — or invent an active-task pointer. There is none: the artifact path is the only handle.
+ - Modify the source roadmap or any referenced spec — read-only from here. Ticking a checkbox is `task:code-reviewer`'s job, once its review of the item passes; specs are authored only by `to-spec`.
+ - Write a task file without a `## Plan` — a Description with no approach is chat, not an artifact.