/sdlc:task-define
Generated from solutions/ontological/skills/task-define/SKILL.md.
Description
Section titled “Description”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.
Allowed tools
Section titled “Allowed tools”ReadEditBashGrepGlobAskUserQuestion
Source
Section titled “Source”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.mdfor the resolution contract it implements, orsdlc task resolve --help).
1. Resolve the task file
Section titled “1. Resolve the task file”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.
2. Read inputs
Section titled “2. Read inputs”- Read the task file end-to-end.
- Read
${CLAUDE_PLUGIN_ROOT}/entities/task/implementation-ready.md.
3. Identify gaps
Section titled “3. Identify gaps”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 jsonSee
sdlc task gap-report --help. It is read-only and emits one JSON object; the text-mode marker isGAP-REPORT gaps=N sections=N placeholders=N touchpoints=N claims=N(exit 0). A missing/unreadable file isINVALID_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_kindandtouchpoints.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*_kindtokens.- A
## Todaywithtoday_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 underunresolved). 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 thereason. 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 matchedphrase, and theline. The op passes these through unfiltered; they need no further filtering. - Missing required sections — each
sections[]entry withpresent == falseis a required-section gap. - Claim resolvers — read
claims[], mapping byseverity: eachseverity: "disqualifier"finding is a gap — relay itsmessage(andline) into the gap description;severity: "warning"is informational only (no-warning-today; the tier exists for a future use). The op’s owngaps[]array already aggregates the disqualifiers, so you can iterategaps[]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_gapsis true iff it is non-empty. Iterate it for the union and read the per-category objects for detail. - Touchpoint shape — read
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).
4. Resolve gaps one at a time
Section titled “4. Resolve gaps one at a time”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-defineonly frommain(or from adocs/<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 onmain; 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
## Todayor## Files to touch, the v3 contract requires those sections to be markdown tables:## Todayis| Location | Role today |.## Files to touchis| 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:, orprs:— those are task-state fields managed elsewhere (/sdlc:task-ensure-readyowns the readiness stamp / gap;/sdlc:task-workStep 10.4 ownsprs:;/sdlc:task-close-outownscompletion_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:
-
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 criteriasection for lines that mention bothvalidate_frontmatter.tsand at least one.mdpath. 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. -
Parse the JSON. If
matchedisfalse(no validate-AC found), ortargetsis empty, skip the rest of this step. -
For each target with at least one
failing_fieldsentry, 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
## Approachsection 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 scopenaming 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.
- Inject into Approach — Edit a bullet under the
-
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. -
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.
5. Re-evaluate
Section titled “5. Re-evaluate”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.
6. Commit
Section titled “6. Commit”If any edits were made:
-
Run the validator:
${CLAUDE_PLUGIN_ROOT}cli/sdlc entities validate <path>If it fails, fix and re-run.
-
Stage only the task file:
git add <path>. -
Commit on the current branch:
docs(tasks): flesh out <basename><one-line summary of what was added> -
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.
7. Hand off
Section titled “7. Hand off”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 four —
sdlc task gap-report(deterministic, NO-LLM composite over sections + placeholders + touchpoints + claims) plus the separatescan_corpus_assumptions.tsadvisory (confirm-before-gap LLM judgment, deliberately outside gap-report). Historical / N-A note: the symbol-resolution command-grep edge-lesson (usecommand grep, not agrep→rgalias that fails on BSD flags) and the parse-error/unresolved dedup are now enforced insidegap-report’s TS (it composesresolve-touchpoints/parse-touchpoints), so this skill no longer shellsgrepitself — the lesson’s intent lives on in the op. - Committing model-generated messages. See
${CLAUDE_PLUGIN_ROOT}conventions/commit-messages.mdfor the canonical pattern (andsdlc 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-onlydocs/<basename>branch, that’s expected — body refinements land in the spec PR.