---
name: task-create
description: "Walk the user through a structured wizard, draft a Metrum subtask description in the project's preferred prompt structure, and post it as the description body of an existing Metrum subtask. Use when the user says /task-create."
argument-hint: "--target <subtask-id> [--quick] [--from <file>] [--dry-run]"
---

# task-create — Subtask Description Drafter

Author the description body of an existing Metrum subtask via a strict, ordered
question wizard. Each answer becomes one section of the draft. The skill validates
R1 partial-view trees and R2 layout-spec shorthand deterministically (no LLM
round-trip), substitutes R6 family defaults on request, and posts the assembled
draft back to Metrum.

The skill **only writes the description body** of an existing empty subtask. It
does not create subtasks, upload images, or assign work.

## Argument syntax

```
/task-create --target <subtask-id>                  # full strict 10-question wizard
/task-create --target <subtask-id> --quick          # 3-question wizard (intent + layout-refs + tree)
/task-create --target <subtask-id> --from <file>    # pre-fill answers from a YAML/JSON file
/task-create --target <subtask-id> --dry-run        # run the wizard but never post; print only
```

`<subtask-id>` may be either a hash form (`OZ10-19000`) or a bare numeric
ID (`19000`). The hash prefix is stripped — only the trailing digits are used as
the Metrum numeric subtask ID.

Flags can appear in any order. `--dry-run` works with all modes. `--quick` and
`--from` are mutually exclusive (report an error and stop if both are present).

## Steps

### 1 — Parse arguments

Extract from `$ARGUMENTS`:

- `--target <id>` (required) — strip a leading `OZ\d+-` or any `[A-Z]+\d+-` prefix; what remains must be all digits.
- `--quick` (boolean)
- `--from <file>` (path to a YAML or JSON file; mutually exclusive with `--quick`)
- `--dry-run` (boolean)

If `--target` is missing or the trailing portion is not numeric, report the error and stop.
If `--quick` and `--from` are both set, report and stop.

### 2 — Fetch the target subtask and count attached screens

Call `mcp__metrum__get_subtask` with the numeric ID. From the response:

1. Read the **title** (used in the report at the end, never embedded in the draft body).
2. Read the **description**. If non-empty, ask the user to confirm overwrite:
   > Subtask `<id>` already has a description (≈N chars). Overwrite? (yes/no)
   On `no`, stop without changes.
3. Count **image attachments** across the subtask's comments. Set `SCREEN_COUNT` = number of images. The user may refer to them as "screen 1" through "screen N" in any answer.

If the MCP call fails (subtask not found, network error, etc.), report and stop.

### 3 — Choose the question set

| Mode | Questions asked |
|---|---|
| default | All 10 questions in order (Step 4) |
| `--quick` | Only Q1 (intent), Q5 (layout references), Q3 (partial-view tree) — in that order |
| `--from <file>` | Skip the interactive wizard; read answers from the file (Step 4-F) |

### 4 — Walk the strict question sequence

Ask the questions in the order shown below. Print each prompt to the user, wait
for the answer, then move on. **Order is fixed in v1** — do not reorder, batch,
or skip questions based on context (adaptive selection is deferred to v2).

Empty answer = skip that section in the draft, **except** Q1 (intent) which is
always required and Q9 (acceptance criteria) which is required for medium+ subtasks.

| # | Question prompt | Required | Section in draft |
|---:|---|---|---|
| 1 | "**Intent** — 1–3 sentences on what we're building and why." | yes | leading paragraph (no heading) |
| 2 | "**Affected models** — list of model class names; flag any that are new." | no (conditional) | `## Models` |
| 3 | "**Partial-view tree** — paste an R1 DSL tree, type `family` to use the family default, or skip." | no | `## Partial views` (fenced ` ```partial-views `) |
| 4 | "**Family** — Article / Directory / Custom / Module (only asked when the family-default tree is needed for Q3)." | conditional | inline note inside `## Partial views` |
| 5 | "**Layout references** — Figma URL(s); cross-project reference path(s); attached screen numbers (1–<SCREEN_COUNT>)." | no | `## Layout reference` |
| 6 | "**Layout-spec shorthand** — global typography/spacing tokens to use (e.g. `fs-18,fw-medium,mb-20`)." | no | `## Default layout` (fenced ` ```layout-spec `) |
| 7 | "**Logic / behaviour** — interactivity, filtering, sort, pagination, etc." | no | `## Logic` |
| 8 | "**WCAG / accessibility** — focus management, keyboard nav, ARIA labels, screen-reader strings." | no | `## Accessibility` |
| 9 | "**Acceptance criteria** — bullet list. (Skip allowed only for trivial subtasks.)" | yes for medium+ | `## Acceptance criteria` |
| 10 | "**Out of scope** — explicit list of things not included." | no | `## Out of scope` |

#### 4.1 — Question 2 (Models): new-class flag

If the user marks any model as new (e.g. *"News (new)"* or *"News [new]"*), include
the flag inline in the rendered list — *"`News` (new)"* — so the implementing
agent knows to scaffold the class.

#### 4.2 — Question 3 (Partial-view tree): R1 DSL validation

If the user pastes a tree, validate it deterministically:

```bash
./.claude/skills/task-create/lib/validate_dsl.sh <<'EOF'
<the user's tree text>
EOF
```

- Exit 0 → tree is syntactically valid; keep the text verbatim.
- Exit 1 → print the validator's stderr message and re-prompt Q3 (do not silently accept).

If the user types `family`, defer the tree until Q4 — the family-default tree
will be substituted there.

#### 4.3 — Question 4 (Family): R6 substitution

Only ask Q4 when:

- Q3 was answered with the literal word `family`, **or**
- Q3 was skipped *and* Q2 listed at least one model.

Read `.claude/conventions/model_families.yaml`. If missing, silently skip Q4.

Present the family list as a multiple-choice prompt:

> Pick a family for the partial-view default tree:
> 1) article    — content-driven page (full/main/content)
> 2) directory  — list/filter/paginate children
> 3) custom     — bespoke layout
> 4) module     — Article body component
> Your choice (1–4 or blank to skip):

If the user picks one, look up `families.<name>.tree`, substitute every `{type}`
with the first model name from Q2 (lowercased), and use the result as the Q3 tree.
The rendered draft includes a one-line note: *"Family: `article` (default tree
substituted)."*.

#### 4.4 — Question 5 (Layout references): screen-number validation

The user's answer may include the literal phrases "screen 1", "screen 2", etc.
For each such reference, check the integer ≤ `SCREEN_COUNT` from Step 2.

- Valid → keep the reference as plain text in the draft.
- Out of range → warn:
  > You referenced "screen N" but only <SCREEN_COUNT> images are attached.
  Re-prompt Q5.

Do **not** rewrite "screen N" as a clickable link, embed the image, or copy the
attachment into the description. The Metrum subtask page already renders the
comment thread alongside the description.

#### 4.5 — Question 6 (Layout-spec shorthand): R2 validation

If the user provides shorthand pairs, validate by calling R2's library mode:

```bash
./.claude/skills/layout-spec/lib/expand.sh "<the user's input>"
```

- Exit 0 → valid; keep the **shorthand** (not the expansion) verbatim in the draft. The implementing agent expands it later via R2.
- Exit 1 → print the validator's stderr message and re-prompt Q6.

Inline shorthand inside other answers (e.g. *"use `![fs-40,fw-bold]` for the
title"* in Q1 or Q7) is **not** validated by this skill — the implementing agent
expands it during execution.

#### 4-F — Pre-fill from a file (`--from <file>`)

The file is YAML or JSON. Top-level keys map to question numbers:

```yaml
intent: |
  Build the news listing page. Two columns on desktop, single column on mobile.
models:
  - News
  - Newsdir
tree: family
family: directory
layout_refs:
  - https://figma.com/file/abc#node=42
  - "screen 1"
default_layout: fs-18,fw-medium,mb-20
logic: |
  Filter by category. Paginate at 12 per page.
accessibility: |
  Pagination buttons announce "page N of M" via aria-label.
acceptance:
  - Renders 12 boxes per page.
  - Filter persists across pagination.
  - Focus moves to first box after page change.
out_of_scope:
  - Server-side filtering (defer to R7).
```

Run all the same validation steps (R1 DSL, R2 shorthand, screen numbers).
If any validation fails, print the offending key + error and stop — do **not**
re-prompt interactively (file mode is non-interactive).

### 5 — Review / edit / send / cancel

After all questions, assemble the draft (Step 6) and present:

```
Draft assembled (≈N chars). Choose:
  [r] review    — print the assembled markdown
  [e] edit      — open tmp/prompt_<id>.md in your editor
  [s] send      — post to Metrum
  [c] cancel    — discard
```

- `r` → print the draft, re-show the menu.
- `e` → write the draft to `tmp/prompt_<id>.md`, prompt the user to edit and confirm when done, then read the file back and re-show the menu.
- `s` → proceed to Step 7.
- `c` → stop without changes.

When `--dry-run` is set, replace `[s] send` with `[s] show-final` — pressing `s`
prints the final draft and stops without calling any write methods.

### 6 — Assemble the draft

Concatenate sections in **this exact order**. Omit empty sections. Use no top-level
H1, no `Task ID:` header, no draft markers.

````
<intent paragraph — no heading>

## Models
- `<Model1>`
- `<Model2>` (new)

## Partial views
Family: `article` (default tree substituted).

```partial-views
<tree text>
```

## Layout reference
- Figma: <url>
- Cross-project ref: <path>
- See screen 1, screen 2.

## Default layout
```layout-spec
fs-18,fw-medium,mb-20
```

## Logic
<paragraph>

## Accessibility
<paragraph or bullets>

## Acceptance criteria
- <criterion 1>
- <criterion 2>

## Out of scope
- <item>
````

Rules enforced by the assembler:

- No top-level H1.
- Section headings are exactly `##` (level-2).
- Sections appear in the table order from Step 4 — the assembler does not reorder.
- Empty sections are omitted entirely (heading included).
- The intent paragraph is always first and has no heading.
- The family note (when present) is always the first line under `## Partial views`.

### 7 — Post the draft

Try MCP first:

```
mcp__metrum__set_subtask_description(id=<numeric>, content=<assembled markdown>)
```

- **Success** → in Step 8, report the Metrum subtask URL from the response.
- **Method missing** (call returns "method not found" or equivalent) → fall back to file:
  - Write the draft to `tmp/prompt_<id>.md` (create `tmp/` if needed; it is gitignored).
  - In Step 8, instruct the user:
    > MCP `set_subtask_description` not available. Draft saved to `tmp/prompt_<id>.md`. Paste it into the subtask manually, then delete the file.
- **Other failure** (network, auth, etc.) → report the error and stop. Do not silently fall back.

The skill must **never** post the draft via `mcp__metrum__add_comment` — descriptions
and comments are different fields, and posting a draft as a comment would silently
break the workflow.

When `--dry-run` is set, skip Step 7 entirely (Step 5 already terminated on `s`).

### 8 — Report

Show the user:

- Subtask ID and title.
- Method used (`mcp set_subtask_description` or `file fallback (tmp/prompt_<id>.md)`).
- Character count of the posted draft.
- Metrum URL when available from the MCP response.

---

## Draft structure reference

Sections appear in fixed order; omit empty ones. The intent paragraph always
leads and never has a heading.

| # | Heading | When emitted |
|---:|---|---|
| 1 | *(intent paragraph, no heading)* | always |
| 2 | `## Models` | Q2 non-empty |
| 3 | `## Partial views` | Q3 non-empty or Q4 picked a family |
| 4 | `## Layout reference` | Q5 non-empty |
| 5 | `## Default layout` | Q6 non-empty |
| 6 | `## Logic` | Q7 non-empty |
| 7 | `## Accessibility` | Q8 non-empty |
| 8 | `## Acceptance criteria` | Q9 non-empty |
| 9 | `## Out of scope` | Q10 non-empty |

## Integration points

| Companion | What we call | Where |
|---|---|---|
| R1 (`partial-views`) | `./.claude/skills/task-create/lib/validate_dsl.sh` (R1 DSL syntax) | Step 4.2 |
| R2 (`layout-spec`) | `./.claude/skills/layout-spec/lib/expand.sh` (shorthand) | Step 4.5 |
| R6 (model families) | `.claude/conventions/model_families.yaml` (family defaults) | Step 4.3 |
| R10 (Metrum MCP) | `mcp__metrum__get_subtask`, `mcp__metrum__set_subtask_description` | Steps 2, 7 |

The DSL validator under `lib/` mirrors R1's parsing rules so we can reject
malformed trees without invoking R1 itself (R1 is a Skill, not a CLI binary).

## Error handling

Report clearly and stop without posting:

| Condition | Action |
|---|---|
| `--target` missing or non-numeric | Report and stop. |
| Both `--quick` and `--from` set | Report and stop. |
| Subtask not found / unreachable | Report MCP error and stop. |
| Subtask description non-empty | Confirm overwrite; stop on `no`. |
| R1 DSL parse error | Print validator stderr, re-prompt Q3 (interactive mode) or stop (file mode). |
| R2 shorthand error | Print validator stderr, re-prompt Q6 (interactive) or stop (file mode). |
| Screen-number out of range | Warn with attachment count, re-prompt Q5 (interactive) or stop (file mode). |
| Unknown family in Q4 | List available families and re-prompt. |
| MCP method missing | Fall back to `tmp/prompt_<id>.md` (Step 7). |
| MCP other failure (auth, network) | Report and stop — never silently fall back. |

## Worked example

**Command:**

```
/task-create --target OZ14-19500
```

**Subtask state at fetch time:**
- Title: *"News listing page — directory layout"*
- Description: empty
- Image attachments in comments: 2 (`SCREEN_COUNT = 2`)

**Wizard transcript (user answers in italics):**

> 1) Intent — 1–3 sentences on what we're building and why.
> *Build the public news listing page. Filter by category, paginate at 12 per page. Replace the placeholder route added in OZ14-19450.*
>
> 2) Affected models — list of model class names; flag any that are new.
> *News, Newsdir*
>
> 3) Partial-view tree — paste an R1 DSL tree, type `family` to use the family default, or skip.
> *family*
>
> 4) Family — Article / Directory / Custom / Module.
> *2*  (directory)
>
> 5) Layout references — Figma URL(s); cross-project reference path(s); screen numbers.
> *Figma: https://figma.com/file/abc?node=42. See screen 1 for desktop, screen 2 for mobile.*
>
> 6) Layout-spec shorthand — global typography/spacing tokens to use.
> *fs-18,fw-medium,mb-20*
>
> 7) Logic / behaviour.
> *Filter by category (taxonomy `T_NEWS_CATEGORIES`). Paginate at 12 per page. Persist filter in the URL query string.*
>
> 8) WCAG / accessibility.
> *Pagination buttons announce "page N of M" via aria-label. Filter chips are real `<button>`s, not divs.*
>
> 9) Acceptance criteria.
> *- Renders 12 boxes per page.*
> *- Filter persists across pagination via `?cat=` query param.*
> *- Focus moves to first box after page change.*
> *- Empty state shown when no news in selected category.*
>
> 10) Out of scope.
> *Server-side full-text search (covered by R7).*
>
> Draft assembled (≈980 chars). Choose: [r] review  [e] edit  [s] send  [c] cancel
> *s*

**Posted to Metrum subtask 19500:**

````markdown
Build the public news listing page. Filter by category, paginate at 12 per page. Replace the placeholder route added in OZ14-19450.

## Models
- `News`
- `Newsdir`

## Partial views
Family: `directory` (default tree substituted).

```partial-views
news/full
  > full_header
  > main
  > full_footer

news/main
  > main_header
  > children
  > main_footer

news/children
  if-empty: count($item->getRequestedChildren()) === 0
  > children_main

news/children_main
  > children_boxes
```

## Layout reference
- Figma: https://figma.com/file/abc?node=42
- See screen 1 for desktop, screen 2 for mobile.

## Default layout
```layout-spec
fs-18,fw-medium,mb-20
```

## Logic
Filter by category (taxonomy `T_NEWS_CATEGORIES`). Paginate at 12 per page. Persist filter in the URL query string.

## Accessibility
Pagination buttons announce "page N of M" via aria-label. Filter chips are real `<button>`s, not divs.

## Acceptance criteria
- Renders 12 boxes per page.
- Filter persists across pagination via `?cat=` query param.
- Focus moves to first box after page change.
- Empty state shown when no news in selected category.

## Out of scope
Server-side full-text search (covered by R7).
````

**Final report:**

```
Subtask: 19500 — News listing page — directory layout
Method: mcp set_subtask_description
Length: 982 chars
URL: https://metrum.example.com/subtasks/19500
```

## Out of scope (v1)

- Adaptive question selection (skip questions based on earlier answers). v2.
- Multi-subtask batch mode (drafting many at once) — use `--from <file>` for the closest equivalent.
- Image upload (the user uploads to Metrum directly as comments).
- Polish-language drafting (`--lang pl`). v2.
- Post-draft critique loop ("review and improve this draft") — the skill produces a faithful draft from answers, not critique.
- Auto-creating new subtasks — the user creates the empty subtask in Metrum first; the skill only writes descriptions.

## Conventions enforced

- Description body only; never a top-level H1, never a Task ID header, never draft/WIP markers.
- Section order fixed (intent → models → partial views → layout reference → default layout → logic → accessibility → acceptance → out-of-scope).
- R1 DSL validated before the draft is sent (deterministic, no LLM round-trip).
- R2 shorthand validated before the draft is sent (deterministic, no LLM round-trip).
- Screen references kept as plain text (`screen 1`); never linked, embedded, or rewritten.
- MCP `set_subtask_description` is the primary write path; file fallback only when the method is missing.
- Drafts are never posted as comments via `mcp__metrum__add_comment`.
