Skip to content

/sdlc:task-define

Generated from solutions/ontological/skills/task-define/SKILL.md.

Interactively drive a single task document toward the implementation-ready contract (solutions/ontological/lib/model/entities/task/implementation-ready.md). Walks the user through filling missing or thin sections via AskUserQuestion, edits the body, and commits. Leaves readiness_verified_at to /sdlc:task-ensure-ready; callers should invoke task-ensure-ready after this skill returns to verify the result.

  • Read
  • Edit
  • Bash
  • Grep
  • Glob
  • AskUserQuestion

Usage:

  • /sdlc:task-define <slug-or-filename> — flesh out the named task through guided questions until it satisfies the implementation-ready contract.
  • /sdlc:task-define <absolute-path-to-task-file> — same, by absolute path. Use this form when invoking from another skill.

This skill IS interactive — it asks the user via AskUserQuestion to resolve every gap. If the user can’t or won’t answer, the skill exits gracefully without forcing a state change (the caller decides what to do next, typically by invoking /sdlc:task-ensure-ready to record the incomplete state).

References:

  • ${CLAUDE_PLUGIN_ROOT}/entities/task/implementation-ready.md — the contract this skill fills gaps against.
  • sdlc task resolve <arg> — resolves the argument to an absolute task path (see ${CLAUDE_PLUGIN_ROOT}/entities/task/file-resolution.md for the resolution contract it implements, or sdlc task resolve --help).

Run sdlc task resolve <arg> per ${CLAUDE_PLUGIN_ROOT}/entities/task/file-resolution.md — the canonical call + outcome contract. On success capture the absolute path and basename and continue to Step 2.

This skill uses the interactive ambiguous-policy mode: on AMBIGUOUS: it prompts the user via AskUserQuestion over the --output json candidates and uses the pick. If the user bails (or the op reports NO TASK FOUND), surface NO TASK FOUND for "<arg>" and stop.

  1. Read the task file end-to-end.
  2. Read ${CLAUDE_PLUGIN_ROOT}/entities/task/implementation-ready.md.

Apply the contract from ${CLAUDE_PLUGIN_ROOT}/entities/task/implementation-ready.md. For each requirement that fails or disqualifier that triggers, add an entry to the gap list with:

  • the section or field involved,
  • the nature of the gap (missing / thin / wrong-value / disqualified-by),
  • what an acceptable resolution looks like (so the user question can be specific).

Run the mechanical disqualifiers through the deterministic task ops rather than re-deriving them by reading. This skill must surface the same gaps /sdlc:task-ensure-ready will (it runs immediately after); mirror that skill’s Step 3 interpretation layer so a gap this skill fills is a gap the gate agrees on.

The judged requirements stay LLM work: required-section presence/thinness (a present section whose body is empty, unchanged <...> template text, or too vague to inform the implementer), required-frontmatter fields, subjective ACs, and Approach deferring a design decision to “the implementer”. Read the body for those. The LLM-judged corpus-assumption disqualifier is also LLM work, but it has its own advisory scanner — see “Corpus-assumption advisory” below.

The mechanical disqualifiers run through ONE deterministic composite op, sdlc task gap-report, which runs the four sibling slices (parse-touchpoints + scan-placeholders + check-claims + resolve-touchpoints) plus required-section presence in a single NO-LLM call and returns one structured readiness-gap object. Read that one object instead of fanning out four separate calls.

  • Deterministic gap report. Shell out to the composite op:

    ${CLAUDE_PLUGIN_ROOT}cli/sdlc task gap-report <path> --output json

    See sdlc task gap-report --help. It is read-only and emits one JSON object; the text-mode marker is GAP-REPORT gaps=N sections=N placeholders=N touchpoints=N claims=N (exit 0). A missing/unreadable file is INVALID_INPUT (exit 1) — same as the sibling read-only doc ops. Map its fields onto the gap list, preserving every judgment the four separate calls made:

    • Touchpoint shape — read touchpoints.today_kind and touchpoints.files_to_touch_kind (each ∈ table / prose / missing / bulleted-legacy):
      • files_to_touch_kind == "bulleted-legacy" → gap (the v3 contract is table-only).
      • files_to_touch_kind == "missing" → accepted; the section is optional in presence. sections[] is the authority on what is required, so read it rather than deciding from the *_kind tokens.
      • A ## Today with today_kind == "prose" is accepted (greenfield work — every path-free prose Today is fine); today_kind == "bulleted-legacy" is rejected (v3 admits a table or pure prose, not the bulleted shape). today_kind == "missing" handling is unchanged.
    • Touchpoint parse errors — each entry in touchpoints.parse_errors[] (invalid kind, symbol on glob, malformed location) is a gap; name the cited section/location and the error. The op already de-dups these against the unresolved list (a row the parser rejected is reported once, here — not also under unresolved). This is the parse-error dedup edge-lesson, now enforced inside the op rather than by this skill.
    • Touchpoint existence — each entry in touchpoints.unresolved[] is a gap (the resolver reports existence only): name the cited Location and the reason. Leave any drift narration / migrate recommendation to the question you compose for the user.
    • Placeholder phrases — each entry in placeholders[] (TBD / pick-one / angle-bracket / empty-table-cell) is a gap; name the section, the matched phrase, and the line. The op passes these through unfiltered; they need no further filtering.
    • Missing required sections — each sections[] entry with present == false is a required-section gap.
    • Claim resolvers — read claims[], mapping by severity: each severity: "disqualifier" finding is a gap — relay its message (and line) into the gap description; severity: "warning" is informational only (no-warning-today; the tier exists for a future use). The op’s own gaps[] array already aggregates the disqualifiers, so you can iterate gaps[] directly for the disqualifier union and consult the per-category objects (sections, placeholders, touchpoints, claims) for the full detail to phrase each user question.

    The gaps[] array is the flat, caller-iterable union of every deterministic disqualifier across all four slices plus missing-sections; has_gaps is true iff it is non-empty. Iterate it for the union and read the per-category objects for detail.

Capture the command’s stdout into a variable and parse it; do not pipe a command you gate on through tail/head (see ${CLAUDE_PLUGIN_ROOT}skills/CLAUDE.md).

Corpus-assumption advisory (LLM-judged — separate from gap-report)

Section titled “Corpus-assumption advisory (LLM-judged — separate from gap-report)”

The corpus-assumption disqualifier is deliberately NOT in gap-report — it is confirm-before-gap LLM judgment, not a deterministic fact. Run it as its OWN separate call, after reading the deterministic report:

bun run ${CLAUDE_PLUGIN_ROOT}/skills/task-ensure-ready/scan_corpus_assumptions.ts <path>

The scanner emits one JSON line per candidate ({"section": ..., "signal": ..., "line": ..., "snippet": ...}); exit 0 with empty stdout means no candidates. Each candidate is uniform-corpus phrasing (e.g. “every entity”, “all instances”, “the corpus”) in ## Approach / ## Proposed with no nearby tolerance/strictness signal. For each candidate, inspect the cited line and decide whether the Approach genuinely assumes a single uniform corpus shape the corpus does not have (it is mid-migration: different instances carry different shapes). Only when you CONFIRM the assumption is wrong does it become a gap. If the corpus is genuinely uniform, or the Approach already names the strictness/tolerance split, the candidate is a false positive and is NOT a gap. Capture stdout into a variable if you need to trim it — do not pipe through tail/head when gating.

If the list is empty, report TASK-DEFINE-ALREADY-READY: <basename> on stdout and exit without editing or committing. The caller can run /sdlc:task-ensure-ready to formally stamp the verification. Keep the marker slug-prefixed (see ${CLAUDE_PLUGIN_ROOT}skills/CLAUDE.md; the 2026-05-28 namespacing pass fixed an outer LLM mistaking this signal for its own terminal verdict).

For each gap, ask the user via AskUserQuestion. Be specific:

  • Quote the section header and current content (or "").
  • Propose 2-3 plausible options where the gap admits them (e.g. status downshift options, impact tier).
  • For free-form content gaps (Goal, Approach, ACs), use AskUserQuestion to confirm the user wants to draft now (“Draft now” / “Skip for now” / “Stop”), then if drafting, ask in plain text for the content.

Apply each answer immediately:

  • Branch context — where edits land. Invoke /sdlc:task-define only from main (or from a docs/<basename> spec-only branch). Per [[T-SIHV-task-state-frontmatter-commits-on-main-not-worktree-branch]], task-state frontmatter (status:, readiness_verified_at:, last_reviewed:, definition_gap:, completion_note:, prs:) belongs on main; body content belongs on whatever branch the user is on. This skill edits both kinds in the same commit, so running it from inside a task worktree would land non-state frontmatter on the task branch — defer those to a separate spec-only PR if the gap matters enough to land outside the implementation diff.
  • Body content: use Edit to replace the gap region. Preserve the section header; only the body between headers changes.
  • When the gap is in ## Today or ## Files to touch, the v3 contract requires those sections to be markdown tables:
    • ## Today is | Location | Role today |.
    • ## Files to touch is | Location | Kind | Change |, with Kind ∈ {new, modify, delete}. Locations follow the five-form grammar (file, file+symbol, file+line, directory, glob) documented in ${CLAUDE_PLUGIN_ROOT}/entities/task/implementation-ready.md. When drafting these rows, prefer file+symbol (path#name) over file+line (path:42) — symbols are durable across line drift.
  • Frontmatter: use Edit to set the field. Do NOT touch readiness_verified_at:, touchpoints_verified_at:, definition_gap:, completion_note:, or prs: — those are task-state fields managed elsewhere (/sdlc:task-ensure-ready owns the readiness stamp / gap; /sdlc:task-work Step 10.4 owns prs:; /sdlc:task-close-out owns completion_note:).

If at any point the user picks “Stop” (or equivalent), jump to Step 6 without further questions.

After every gap is resolved (or the user stops), proceed.

4b. Authoring-time advisory: surface schema-violating fields for “make X validate” ACs

Section titled “4b. Authoring-time advisory: surface schema-violating fields for “make X validate” ACs”

For a task shaped “make file X pass validate_frontmatter.ts,” surface which fields of X currently fail so the author can enumerate them in Approach up front. This step is an authoring aid: it does not gate readiness, does not edit Out-of-scope unless the user explicitly opts in, and accepts “decline” as a first-class answer.

Procedure:

  1. Shell out to the co-located scanner:

    bun run ${CLAUDE_PLUGIN_ROOT}skills/task-define/scan_validate_ac.ts <absolute path to task file>

    The scanner inspects the task’s ## Acceptance criteria section for lines that mention both validate_frontmatter.ts and at least one .md path. For each target, it runs the validator and captures the failing fields. Output is a single JSON object on stdout — see the script’s docstring for the exact shape.

  2. Parse the JSON. If matched is false (no validate-AC found), or targets is empty, skip the rest of this step.

  3. For each target with at least one failing_fields entry, present the failures to the user via AskUserQuestion. Quote the path and list each failing pointer + message. Offer three options:

    • Inject into Approach — Edit a bullet under the ## Approach section listing the failing fields (one bullet per target, in citation order). Format: Known schema-violating fields in <path>: <pointer1> (<message1>), <pointer2> (<message2>), .... Place the new bullet at the end of the existing Approach body so ordering of pre-existing steps is preserved.
    • Add to Out of scope — Edit a bullet under ## Out of scope naming the fields that are not in scope for this task (e.g. when the task fixes one field but the file has unrelated violations).
    • Decline — leave the body untouched. The advisory was seen and considered; no edit lands.
  4. For each target where the file doesn’t exist (exists: false) OR the validator passed with no failures (failing_fields: []), surface the finding to the user as informational (“X currently validates clean — the AC may be a regression guard rather than a fix”) but do not Edit the body. The user may want to revise the AC; that’s their call.

  5. Treat user answers as ordinary gap resolutions for the purposes of Step 5 (re-evaluation) and Step 6 (commit). The commit message summary should mention the advisory if it landed an edit (“inject schema-violating fields for <path>”).

This step runs once per /sdlc:task-define invocation, after the section-gap pass and before re-evaluation. It is a no-op on tasks that don’t carry a validate-AC, and a no-op when the user declines the offer.

Re-read the task file. Re-apply the contract. If gaps remain that the user did NOT explicitly skip, ask once more. If they remain after that, treat it as a “Stop” and proceed to Step 6.

If any edits were made:

  1. Run the validator:

    ${CLAUDE_PLUGIN_ROOT}cli/sdlc entities validate <path>

    If it fails, fix and re-run.

  2. Stage only the task file: git add <path>.

  3. Commit on the current branch:

    docs(tasks): flesh out <basename>
    <one-line summary of what was added>
  4. Report on stdout:

    TASK-DEFINE-DEFINED: <basename>
    sections updated: <list>

If no edits were made (user stopped immediately, or there were no gaps), report TASK-DEFINE-NO-CHANGES: <basename> and exit without committing.

Print a one-line pointer:

Next: run /sdlc:task-ensure-ready <basename> to verify and stamp.
  • The doc the user lands on is whatever they typed. Don’t paraphrase, reword, or “improve” their content. Apply minimal edits to put their text into the right section.
  • If the user wants to rewrite a section that’s already populated (not thin or missing, just wrong), this is the right skill but you should confirm via AskUserQuestion before overwriting non-empty content.
  • One commit per run, touching only the task file. Same atomicity contract as /sdlc:task-ensure-ready.
  • Gap detection is two calls, not foursdlc task gap-report (deterministic, NO-LLM composite over sections + placeholders + touchpoints + claims) plus the separate scan_corpus_assumptions.ts advisory (confirm-before-gap LLM judgment, deliberately outside gap-report). Historical / N-A note: the symbol-resolution command-grep edge-lesson (use command grep, not a greprg alias that fails on BSD flags) and the parse-error/unresolved dedup are now enforced inside gap-report’s TS (it composes resolve-touchpoints / parse-touchpoints), so this skill no longer shells grep itself — the lesson’s intent lives on in the op.
  • Committing model-generated messages. See ${CLAUDE_PLUGIN_ROOT}conventions/commit-messages.md for the canonical pattern (and sdlc commit create --help); this skill authors multi-line commits from model output, so follow that convention rather than dropping back to single-line -m.
  • Branch naming. See ${CLAUDE_PLUGIN_ROOT}conventions/branch-naming.md. This skill commits on whatever branch the user is on; if it’s the spec-only docs/<basename> branch, that’s expected — body refinements land in the spec PR.