osdlc

task-define

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.

osdlc 3 Updated 2w ago

Resources

1
GitHub

Install

npx skillscat add osdlc/osdlc/task-define

Install via the SkillsCat registry.

SKILL.md

$sdlc:task-define

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}/lib/model/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}/lib/model/entities/task/file-resolution.md for the
    resolution contract it implements, or sdlc task resolve --help).

1. Resolve the task file

Run sdlc task resolve <arg> per
${CLAUDE_PLUGIN_ROOT}/lib/model/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

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

3. Identify gaps

Apply the contract from
${CLAUDE_PLUGIN_ROOT}/lib/model/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 scan-placeholders + check-claims +
a read of parse-touchpoints (## Areas shape, informational only)
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 separate calls.

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

    scripts/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 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:

    • Touchpoint shape — read touchpoints.areas_kind (∈ table /
      prose / missing / bulleted-legacy) and
      touchpoints.parse_errors[]. As of v7, ## Areas is advisory and
      ## Today is fully free-form prose — neither is a resolution gate,
      and NEITHER of these fields ever becomes a gap. They exist only so
      you can narrate the shape to the user if it seems worth mentioning.
    • 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 (placeholders, missing sections, claims —
    ## Today/## Areas shape is never one of them); 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)

See ${CLAUDE_PLUGIN_ROOT}/conventions/corpus-assumptions-scanner.md for
what the scanner checks and how to confirm a candidate. Run it as its own
separate call, after reading the deterministic report:

scripts/sdlc task scan-corpus-assumptions <path>

Only a candidate you CONFIRM against the cited line is a gap.

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

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. state 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 (state:, 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.
  • ## Today is free-form prose — write whatever orients an
    implementer, no required shape. ## Areas is advisory: when it reads
    best as a table, use | Area | Note | (a package/directory/module
    and a one-line gloss); prose or a bulleted list is equally fine.
    Neither section is resolved against the codebase, so there is no
    existence requirement to satisfy — see
    ${CLAUDE_PLUGIN_ROOT}/lib/model/entities/task/implementation-ready.md.
  • 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

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 scanner:

    scripts/sdlc task scan-validate-ac <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 op'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.

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

If any edits were made:

  1. Run the validator:

    scripts/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.

7. Hand off

Print a one-line pointer:

Next: run $sdlc:task-ensure-ready <basename> to verify and stamp.

Notes

  • 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 — sdlc task gap-report
    (deterministic, NO-LLM composite over sections + placeholders +
    claims, plus an informational-only read of ## Areas shape) plus the
    separate corpus-assumption advisory (see
    ${CLAUDE_PLUGIN_ROOT}/conventions/corpus-assumptions-scanner.md). As of
    v7, ## Today and ## Areas are never resolved against the codebase, so
    there is no symbol/path existence probe left in this path at all.
  • 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.