| name | implementation-plan |
| description | Use when a user story or simple epic needs to be broken into atomic, TDD-driven implementation tasks with architectural review, or when planning the TDD task breakdown for a specific story or task ticket. Produces an ordered task list with explicit RED-test specs, identifies file impact and consumer/contract dependencies, runs an architectural review pass with a pass_threshold of 5, and writes the task tickets to the tracker with TDD task structure. Trigger phrases include 'plan this story', 'break this into tasks', 'implementation plan', 'plan the work', 'generate tasks', 'TDD task breakdown', 'how should I implement this story'. |
| user-invocable | true |
| allowed-tools | Read, Write, Edit, Glob, Grep, Bash |
**/dso:implementation-plan is orchestrator-level only. It MUST NOT be invoked as a sub-agent.**
The skill dispatches its own sub-agents (dso:approach-proposer, dso:approach-decision-maker, dso:task-decomposer, opus gap-analysis on COMPLEX). Running implementation-plan ITSELF as a sub-agent means those dispatches become nested Task calls — which CLAUDE.md rule:no-nested-task forbids, and which the platform surfaces as [Tool result missing due to internal error], silently losing the plan output.
If you detect that this skill is running as a sub-agent, STOP immediately and return verbatim:
ERROR: /dso:implementation-plan must be invoked at the orchestrator level, not as a sub-agent.
REASON: This skill dispatches sub-agents internally; nesting would violate rule:no-nested-task.
CALLER ACTION: Return control to your orchestrator and have IT invoke /dso:implementation-plan
directly (via the Skill tool, not the Agent/Task tool).
Do NOT proceed with any of the Phase A/B/C steps. Do NOT dispatch any further sub-agents. Do NOT produce a partial plan.
Proxy detection (best-effort): if the Agent tool is unavailable in your context (<available_tools> does not list Agent / Task), you are running as a sub-agent — refuse per above. If the Agent tool IS available, that is a necessary but not sufficient signal that you are at the orchestrator level; if the orchestrator's most recent user-facing turn does not match the trigger phrases for this skill (per the description: frontmatter), and you were invoked via Skill tool by an upstream skill rather than by the user directly, treat the invocation as suspect and prefer to STOP-and-return rather than proceed.
Caller-side enforcement: orchestrators that detect the ERROR return from this skill MUST halt their own progression rather than swallow the ERROR and continue — silently continuing produces a broken plan with missing sub-agent outputs.
Implementation Plan: Atomic Task Generation
Generate a production-safe implementation plan for a User Story by decomposing it into atomic, TDD-driven tasks with correct dependencies. Prioritize understanding over assumptions — resolve ambiguity before planning.
Config Resolution
At activation, load project commands and the approach-resolution mode:
PLUGIN_SCRIPTS="${CLAUDE_PLUGIN_ROOT}/scripts"
TEST_CMD=$(bash "$PLUGIN_SCRIPTS/read-config.sh" commands.test)
LINT_CMD=$(bash "$PLUGIN_SCRIPTS/read-config.sh" commands.lint)
FORMAT_CHECK_CMD=$(bash "$PLUGIN_SCRIPTS/read-config.sh" commands.format_check)
APPROACH_RESOLUTION=$(bash "$PLUGIN_SCRIPTS/read-config.sh" implementation_plan.approach_resolution)
APPROACH_RESOLUTION="${APPROACH_RESOLUTION:-autonomous}"
Resolution order: see ${CLAUDE_PLUGIN_ROOT}/docs/CONFIG-RESOLUTION.md. Supports dryrun mode (/dso:dryrun /dso:implementation-plan).
Stage-Boundary Entry Check
source "${CLAUDE_PLUGIN_ROOT}/hooks/lib/preconditions-validator-lib.sh" 2>/dev/null || true
_dso_pv_entry_check "implementation-plan" "preplanning" "${STORY_ID:-${primary_ticket_id:-}}" || true
Usage
/dso:implementation-plan # Interactive story selection
/dso:implementation-plan <story-id> # Plan a specific user story
/dso:implementation-plan <epic-id> # Plan simple epic directly (when routed by /dso:sprint)
If <id> is omitted, present an interactive list of open stories. Stories accept tasks under the story; epics (when routed by sprint) accept tasks directly under the epic.
Progress Checklist
Task tracking rule: Only call TaskCreate when /dso:implementation-plan is invoked standalone (directly by the user). When invoked from /dso:sprint, do NOT call TaskCreate — sprint owns the task list. Track progress through inline notes.
Progress:
- [ ] Step 1: Contextual Discovery (story loaded, context gathered, ambiguities resolved, cross-cutting detection done — layers: _, interfaces: _)
- [ ] Step 2: Architectural Review via REVIEW-PROTOCOL-WORKFLOW.md inline (passed / skipped — no new pattern)
- [ ] Step 3: Task Drafting (tasks drafted with E2E + docs coverage)
- [ ] Step 4: Plan Review via REVIEW-PROTOCOL-WORKFLOW.md inline (all dimensions: 5, iteration: _/3)
- [ ] Step 5: Task Creation (tasks created, deps added, health validated)
- [ ] Step 6: Gap Analysis (COMPLEX: opus sub-agent dispatched, findings processed; TRIVIAL: skipped)
Pre-flight Tag Guards
Before any planning work, run a single tag-guard check on the ticket. Capture the exit code explicitly so a lookup failure (exit 2) is treated as fail-open rather than aborting under set -e:
set +e
_guard=$(bash "$PLUGIN_SCRIPTS/implementation-plan/check-tag-guards.sh" "${STORY_ID:-${primary_ticket_id}}")
_guard_rc=$?
set -e
<!-- EMIT-PRECONDITIONS: gate_name=implementation_plan_tag_guard degradation_type=inferred_decision -->
if (( _guard_rc == 2 )); then _guard="OK"; fi
The script returns one of: OK, BLOCKED:scrutiny_pending, BLOCKED:interaction_deferred, BLOCKED:manual_awaiting_user. The first two BLOCKED verdicts halt planning; BLOCKED:manual_awaiting_user is non-halting — it enters the branching logic below.
| Verdict | Action |
|---|
BLOCKED:scrutiny_pending | HALT. Emit: "This epic has not been through scrutiny review. Run /dso:brainstorm <epic-id> first to complete the scrutiny pipeline, then retry /dso:implementation-plan." Do NOT produce any planning output. |
BLOCKED:interaction_deferred | HALT. Emit: "This epic has unresolved cross-epic interaction conflicts. Resolve or override them in /dso:brainstorm <epic-id> before proceeding to /dso:implementation-plan." Do NOT produce any planning output. |
BLOCKED:manual_awaiting_user | Do NOT halt — enter the Manual Story Branching section below. |
OK | Proceed to Step 1 (Contextual Discovery). |
The manual:awaiting_user check is gated by planning.external_dependency_block_enabled — when the flag is absent or false, the script returns OK regardless of tags.
Copy Story Bypass
Before continuing past the tag guards, detect whether this story is a copy story auto-created by dso:story-decomposer (Step C3 of the Copy-Needed Auto-Create Protocol). A story is a copy story when EITHER:
- it carries the
copy-story tag (canonical signal), OR
- its title begins with the verbatim prefix
"Apply gov-copy to " (case-insensitive fallback)
These match the producer-side values written by ${CLAUDE_PLUGIN_ROOT}/agents/story-decomposer.md Step C3. If either signal fires:
- Do NOT decompose — copy stories are dispatched to
dso:gov-copy-writer by /dso:sprint Phase E (see sprint SKILL.md "Copy Story Dispatch" section), not by implementation-plan.
- Emit exactly:
STATUS:bypass REASON:copy_story STORY:<story-id>
- Stop. Do not run Step 1 (Contextual Discovery) or any subsequent step.
The bypass is the canonical handoff from /dso:implementation-plan to /dso:sprint's gov-copy-writer dispatcher. Decomposing a copy story into ordinary tasks would silently bypass the federal-style canon, the deterministic post-processor, and the coordination-pass — defeating the gov-copy epic's entire pipeline.
Manual Story Branching (only when BLOCKED:manual_awaiting_user)
Prep-work detection heuristic: scan the story's done definitions for references to artifacts not yet in the codebase — a verification script path, a user-facing instructions document path, or a CLI wrapper that would need to be authored. Use Glob and test -f to confirm.
Branch A — No prep work needed (done definitions reference no new code artifacts):
- Do NOT decompose into tasks. Emit a refusal diagnostic and
STATUS:blocked REASON:manual_story_no_prep STORY:<story-id>. The manual verification step is never decomposed.
Branch B — Prep work required (done definitions reference at least one missing artifact):
- Decompose ONLY the prep tasks using standard RED/GREEN/UPDATE classification. The manual verification step itself is NEVER a decomposed task.
- Read the parent epic's External Dependencies block (per
${CLAUDE_PLUGIN_ROOT}/docs/contracts/external-dependencies-block.md) to seed prep-task context: use name, verification_command, and justification to populate prep-task descriptions.
- Continue to Step 1 with only the prep tasks in scope.
Step 1: Contextual Discovery
Select Story
If <story-id> was not provided:
.claude/scripts/dso ticket list --type=story --status=open to show open stories
- Fall back to
.claude/scripts/dso ticket list --type=story if none open
- If no stories at all, report and exit
- Present and get selection
Load: .claude/scripts/dso ticket show <story-id>. If not found, report the error and exit.
Re-invocation Guard
Detect existing children before drafting:
set +e
_reinv=$(bash "$PLUGIN_SCRIPTS/implementation-plan/check-reinvocation.sh" "$STORY_ID")
_reinv_rc=$?
set -e
if (( _reinv_rc != 0 && _reinv_rc != 2 )); then
echo "WARN: check-reinvocation.sh exited with unexpected rc=${_reinv_rc} — continuing with verdict=fresh"
_reinv="verdict=fresh"
fi
verdict=$(echo "$_reinv" | grep '^verdict=' | cut -d= -f2-)
closed_count=$(echo "$_reinv" | grep '^closed_count=' | cut -d= -f2-)
in_progress_count=$(echo "$_reinv" | grep '^in_progress_count=' | cut -d= -f2-)
open_count=$(echo "$_reinv" | grep '^open_count=' | cut -d= -f2-)
closed_ids=$(echo "$_reinv" | grep '^closed_ids=' | cut -d= -f2-)
in_progress_ids=$(echo "$_reinv" | grep '^in_progress_ids=' | cut -d= -f2-)
open_ids=$(echo "$_reinv" | grep '^open_ids=' | cut -d= -f2-)
Branch on verdict:
Log a one-liner: Re-invocation guard: <closed_count> closed (read-only), <in_progress_count> in-progress (flagged), <open_count> open (candidates).
Epic Type Detection
Check the type field from .claude/scripts/dso ticket show:
type=epic: enter epic-direct mode — the epic's done definitions are the AC source; tasks become direct children of the epic; skip Context File Check (no parent); use --parent=<epic-id> in Step 5; proceed directly to Architectural Alignment.
- otherwise (task, story, etc.): continue with Context File Check below.
Context File Check
After loading the story, look for a recent preplanning context comment on the parent epic:
- Extract
parent from the story.
- Run
.claude/scripts/dso ticket show <parent-epic-id>. Scan comments for the LAST comment whose body starts with PREPLANNING_CONTEXT:.
- If found AND embedded
generatedAt is within 7 days:
- Parse JSON payload (strip the
PREPLANNING_CONTEXT: prefix). On invalid JSON, treat as not found.
- Load epic data + sibling stories from the payload (skip redundant fetches).
- Carry forward: review findings, walking skeleton flags, classifications, traceability lines, story dashboard.
- Log:
"Context loaded from preplanning comment on epic <parent-epic-id>".
- Skip Input Analysis and proceed directly to Architectural Alignment.
- Else: log
"No recent preplanning context — running full Input Analysis" and proceed below.
schema_version-aware parsing (load-bearing wire format):
- Check
schema_version. If absent or < 2: v1 mode — researchFindings not expected, treat as empty array.
- If
>= 2: researchFindings expected; if absent, treat as empty array (fail-open).
- Fail-open contract: any parsing failure on
researchFindings MUST NOT block context loading — treat as empty, log "researchFindings parse failed on epic <parent-epic-id> — treating as empty", continue.
Input Analysis
.claude/scripts/dso ticket show <story-id>
.claude/scripts/dso ticket show <parent-epic-id>
.claude/scripts/dso ticket deps <parent-epic-id>
If no parent, proceed with story context alone and note the limited scope.
Architectural Alignment
- Glob for
docs/**/*.md and .claude/docs/**/*.md
- Grep for "system context", "architecture", "standardization", "ADR"
- Glob for
docs/adr/**/*.md
Recipe Consultation
Query the recipe registry to surface available mechanical transforms for the story's language context:
bash "$PLUGIN_SCRIPTS/consult-recipe-registry.sh" --language <primary-language>
Record the output as available recipe capabilities. Run without --language if the story spans multiple languages or the language is unknown. Empty output (unknown language or missing registry) is not an error — proceed without recipe context.
Recipe-eligible transforms are mechanical and deterministic: function-signature changes, import normalization. Business logic additions, feature decisions, and architectural changes are NOT recipe-eligible regardless of the registry contents.
Migration-Class Detection
Run the migration-class detector against the story's primary target symbol to classify the migration scope before task drafting begins. This step is unconditional and non-interactive — it runs on every Step 1 execution, writes the marker regardless of outcome, and never prompts before persisting.
Determine TARGET_SYMBOL from the story description or ticket title (the function, method, or class being changed). If no symbol is determinable, pass an empty string — the script emits migration-class=inconclusive cleanly and the marker is still written.
MIGRATION_MARKER=$(bash "$PLUGIN_SCRIPTS/implementation-plan/migration-class-detect.sh" "<target_symbol>" 2>/dev/null)
.claude/scripts/dso ticket comment <story-id> "MIGRATION_CLASS: $MIGRATION_MARKER"
Even when migration-class=inconclusive (ast-grep unavailable or no symbol provided), write the marker. Never silently omit it — inconclusive is a first-class result, not an error state.
Last-wins idiom: re-running Step 1 appends a new MIGRATION_CLASS: comment. Consumers MUST read the last matching MIGRATION_CLASS: comment on the story ticket, ignoring any earlier ones. This mirrors the COMPLEXITY_CLASSIFICATION: scan-for-last-comment idiom.
The full marker contract (JSON shape, field definitions, threshold configuration, sg-unavailable degradation, consumer protocol) is at ${CLAUDE_PLUGIN_ROOT}/docs/contracts/migration-class-marker.md.
Log a one-liner: "Migration-class detection: <migration-class value> (symbol: <target_symbol>)".
Feature-Flag Approval Detection
Run the feature-flag approval lookup for this story immediately after migration-class detection. This step is unconditional and non-interactive — it runs on every Step 1 execution, writes the marker regardless of outcome, and never prompts before persisting.
FEATURE_FLAGS_MARKER=$(bash "$PLUGIN_SCRIPTS/implementation-plan/resolve-feature-flag-approval.sh" "<story-id>" 2>/dev/null)
.claude/scripts/dso ticket comment <story-id> "FEATURE_FLAGS: $FEATURE_FLAGS_MARKER"
The helper performs the two-hop story→parent epic tag lookup and always exits 0 (see safe-default contract). When the rollout:feature-flags-approved tag is absent on both the story and its parent epic, the marker carries "feature-flags":"prohibited" with a non-empty reason.
Last-wins idiom: re-running Step 1 appends a new FEATURE_FLAGS: comment. Consumers MUST read the last matching FEATURE_FLAGS: comment on the story ticket, ignoring any earlier ones. This mirrors the MIGRATION_CLASS: scan-for-last-comment idiom.
The full marker contract (JSON shape, fields, source-of-truth helper, safe-default, consumer protocol) is at ${CLAUDE_PLUGIN_ROOT}/docs/contracts/feature-flags-marker.md.
Log a one-liner: "Feature-flag approval detection: <feature-flags value> (source: <source>, reason: <reason>)".
Ambiguity Scan
Curiosity before planning. A plan built on assumptions is worse than no plan.
Exploration decomposition: when context-gathering involves compound or multi-source questions (multi-layer, web research, ambiguous scope), apply skills/shared/prompts/exploration-decomposition.md. Classify each question as SINGLE_SOURCE or MULTI_SOURCE. Emit DECOMPOSE_RECOMMENDED when a factor is unspecified or two findings contradict.
| Signal | Example | Action |
|---|
| Undefined scope boundaries | "improve performance" — of what? by how much? | Ask for measurable criteria |
| Implicit acceptance criteria | "user can upload files" — types? size limits? | Ask for constraints |
| Conflicting signals | Epic says X, story says Y, codebase does Z | Surface the conflict |
| Missing persona | "as a user" — admin or end-user? | Ask which role |
| Unstated constraints | API story with no auth/rate-limiting mention | Ask if intentionally omitted |
| Ambiguous priority | Multiple criteria, unclear essential vs. nice-to-have | Ask for priority ranking |
Batch all questions in one message. Separate blocking ("cannot plan without this") from defaultable ("I'll assume X unless you say otherwise"). Never ask about things clearly inferrable from the codebase or parent epic.
Unsatisfiable Criteria Detection
After resolving ambiguities, check whether the SC can be satisfied at all:
- SC contradicted by codebase state (e.g., closed ticket permanently removed the feature SC asks for)
- SC items mutually exclusive (A and B cannot both be true simultaneously)
- Architecture makes SC impossible without redesign beyond this story's scope
When any apply, emit and STOP — do NOT proceed:
REPLAN_ESCALATE: brainstorm EXPLANATION:<what SC cannot be satisfied, why (the codebase state contradicting it), and what the orchestrator should investigate>
This signal is terminal — do not emit STATUS:complete or STATUS:blocked after it.
Distinction: STATUS:blocked = user can answer questions to unblock (ambiguous requirements, missing info). REPLAN_ESCALATE = the story intent itself needs brainstorm-level re-examination; no clarifying question can unblock it.
Cross-Cutting Change Detection
Evaluator Shortcut
If complexity-evaluator output was provided (when invoked from /dso:sprint):
- Reuse
layers_touched and interfaces_affected directly.
- Sanity-check against the codebase context from Architectural Alignment. If counts seem wrong, note the discrepancy and proceed with corrected counts.
- Apply the escalation rule below.
- Skip the full analysis.
If no evaluator output (standalone invocation): perform full analysis.
Detection
- Trace data/control flow from entry point to deepest dependency. Count distinct architectural layers touched. Example layer chain: route → service → agent → LLM provider → formatter → DB model → migration. ≥ 3 layers = cross-cutting threshold met.
- Count interfaces/classes needing updates via Grep/Glob for classes, abstract base types, Protocol definitions, and public method signatures. ≥ 5 interfaces/classes = cross-cutting threshold met.
Escalation Rule
| Condition | Action |
|---|
| ≥ 3 architectural layers touched | Force Step 2 — mark CROSS-CUTTING |
| ≥ 5 interfaces/classes need updates | Force Step 2 — mark CROSS-CUTTING |
| Both thresholds met | Force Step 2, note both signals |
| Neither threshold met | Proceed to new-pattern check; otherwise skip to Step 3 |
When escalating, annotate the Step 2 subject:
"Architectural Pattern: {pattern name} [CROSS-CUTTING — {N} layers / {M} interfaces]"
Doc-Only Skip Gate
Before generating proposals, check whether the story exclusively touches documentation or instruction files (.md files in skills/, agents/, prompts/, docs/, CLAUDE.md, or any workflow/config file with no executable behavior). Apply behavioral-testing-standard.md Rule 5: instruction-file changes have only one meaningful implementation and carry no architectural decision. In that case:
- Log:
"Doc-only story — skipping Proposal Generation and approach-decision-maker dispatch"
- Skip directly to Step 3: Atomic Task Drafting with
testing_mode: GREEN for all tasks.
- Do NOT generate proposals or dispatch the decision-maker.
Signals that indicate a doc-only story:
- Story title/description mentions updating a SKILL.md, CLAUDE.md, workflow doc, agent file, or other
.md guidance file
- All affected files in the file impact table are
.md or .conf with no associated executable code change
- Story is testing_mode: GREEN with only instruction file updates in scope
Proposal Generation (via dso:approach-proposer opus sub-agent)
Sync discipline — the contract summary, dispatch wiring, validation steps, and agents/approach-proposer.md are dual-maintained. When updating complexity-gate criteria, the four distinctness axes, the minimum-proposal count, or any other rule below, you MUST update agents/approach-proposer.md in the same change to keep the two in sync. The structural test pins wiring + summary presence but does not detect semantic drift between this SKILL.md block and the agent's embedded protocol.
Contract summary (the agent enforces these; this block is the human-readable contract surface for SKILL.md readers and is pinned by tests/scripts/test-implementation-plan-proposals.sh):
- Minimum count: the agent must produce at least 3 distinct proposals. Fewer is permitted only when the solution space is genuinely constrained (≥ 2 required); the constraint must be documented in
generation_notes.
- Proposal schema (six required fields per
prompts/proposal-schema.md): title, description, files, pros, cons, risk (one of low / medium / high).
- Distinctness validation gate: every pair of proposals must differ on at least one of the four structural axes:
- data layer — where state is stored or retrieved
- control flow — execution path or orchestration strategy
- dependency graph — modules, packages, or services introduced or removed
- interface boundary — where the public contract is drawn
A set with any structurally equivalent pair MUST NOT be emitted by the agent. The agent records pairwise comparisons in
distinctness_summary so the audit trail proves the gate ran.
- Complexity gates (per
${CLAUDE_PLUGIN_ROOT}/skills/shared/prompts/complexity-gate.md): every proposal is gated through Gate 1 (YAGNI), Gate 2 (Rule of Three), and Gate 3 (new-dependency) with outcomes recorded in complexity_gate_summary.
Read shared/prompts/complexity-gate.md to confirm it is present. If unreadable, STOP and emit:
"ERROR: complexity-gate.md not found at skills/shared/prompts/complexity-gate.md — create this file before running implementation-plan."
Dispatch via subagent_type: "dso:approach-proposer" with model: "opus" passed explicitly (do not rely on the agent frontmatter default — distinct-approach reasoning, complexity-gate evaluation, and structural-axis analysis depend on opus-level sustained reasoning, and the explicit param defends against future routing changes that might silently downgrade).
Two distinct fallback concepts — do not conflate:
- Dispatch-target fallback (this paragraph): if the named
subagent_type: "dso:approach-proposer" is unregistered in this session ("Unknown agent type" or similar dispatch error), re-dispatch with subagent_type: "general-purpose" + model: "opus" and agents/approach-proposer.md content read inline as the prompt. The agent still does the work — only the dispatch transport changes. This is NOT inline drafting.
- Inline-drafting fallback (forbidden, see Validation below): the orchestrator itself drafts proposals without the agent. This is the bug class the dispatch was introduced to eliminate and is explicitly prohibited regardless of what dispatch path was taken.
Pass the following as task arguments:
{story-id}, {story-title}, {story-description} from the story ticket
{story-done-definitions} — the story's DDs, one per line, with stable identifiers (dd-1, dd-2, ...). Parse these from the story description's ## Done Definitions section; do NOT rely on session memory.
{codebase-context} — discovery output from Step 1 (relevant files, existing patterns, architectural reviewer findings, walking-skeleton flags)
{counter-proposal-feedback} — if NEW_COUNT ≥ 1, the prior counter-proposal's approach and done_definitions from the cycle state file; otherwise pass (none — first cycle)
The agent returns a JSON object with proposals, distinctness_summary, complexity_gate_summary, and generation_notes. Validate the output in this order — do NOT reorder, the model-requirement check MUST run before schema validation:
- Model-requirement check (first): if the response carries
"error": "model_requirement_unmet", log "Approach proposer model requirement unmet — re-dispatching with explicit model: opus" and retry once via the fallback path. If the retry also returns model_requirement_unmet, HALT with diagnostic: "Approach proposal generation requires opus; configure the environment to allow opus dispatch and re-run /dso:implementation-plan." Do NOT fall back to inline proposal drafting.
- Domain-error escalation (second): if the response carries
"error": "insufficient_solution_space" (documented in agents/approach-proposer.md), this is a legitimate signal that the story is genuinely constrained to fewer than 2 viable approaches — NOT a malformed response. Surface the agent's generation_notes to the user and HALT with diagnostic: "Approach proposer: insufficient_solution_space — <generation_notes joined>. Refine the story constraints (or accept the single-approach path manually) and re-run /dso:implementation-plan." Do NOT proceed to schema validation; an empty proposals array under this error code is the intended shape.
- Schema validation (third): with both error-envelope branches ruled out, validate that
proposals conforms to prompts/proposal-schema.md (every proposal has all six fields), distinctness_summary is a non-empty array of pairwise comparisons (every pair has differs_on non-empty), and complexity_gate_summary has one entry per proposal with all three gate outcomes recorded. If schema validation fails, HALT with diagnostic: "Approach proposer returned malformed output; cannot proceed without verified proposals." Do NOT fall back to inline drafting — inline drafting is the failure mode this phase exists to prevent.
Why halt (not skip-and-continue) here? Proposal generation is a correctness gate: the selected approach drives every subsequent task in Step 3, and inline orchestrator drafting (the only available fallback) is the bug class this dispatch was introduced to eliminate.
Approach resolution routing (config-driven via APPROACH_RESOLUTION):
autonomous (default): pass the agent's full proposals set to the decision-maker; use the selected proposal as the basis for Step 3. Do NOT show proposals to the user or wait for selection.
interactive: display the agent's proposals to the user (title, description, pros, cons, risk); wait for user selection before Step 3. Do NOT dispatch the decision-maker.
Resolution Loop
After generating a valid distinct proposal set, dispatch dso:approach-decision-maker to evaluate and select.
Cycle State
CYCLE_COUNT=$(bash "$PLUGIN_SCRIPTS/implementation-plan/approach-cycle-state.sh" read "$STORY_ID")
State file /tmp/approach-resolution-${STORY_ID}.json (4h TTL); stale files reset to 0 automatically.
Dispatch
Dispatch dso:approach-decision-maker (subagent_type, model: opus, timeout: 600000) with:
- All proposals (full set with title, description, files, pros, cons, risk)
- Story success criteria and done definitions
- Current codebase context
Inline fallback: if the Agent tool rejects the subagent type ("Unknown agent type", "not supported", or any pre-run dispatch failure), read agents/approach-decision-maker.md inline and execute its evaluation directly with the same inputs. The inline path must still produce a valid APPROACH_DECISION: output conforming to docs/contracts/approach-decision-output.md.
Parse Response
Scan output for the APPROACH_DECISION: prefix per docs/contracts/approach-decision-output.md. Extract the JSON block between ```json and ``` fences. Validate mode.
If output is absent, malformed, missing the prefix, or has an unrecognized mode: log a warning and surface to the user for manual selection. Do NOT autonomously fall back to any proposal.
Accept Path (mode: "selection")
- Read
selected_proposal_index; extract the corresponding proposal.
- Log the ADR rationale (
context, decision, consequences, rationale_summary).
- Autonomous mode: proceed directly to Step 3 with the selected proposal. Interactive mode: present selection and rationale; confirm before Step 3.
- Clear cycle state:
bash "$PLUGIN_SCRIPTS/implementation-plan/approach-cycle-state.sh" clear "$STORY_ID". # shim-exempt: documentation reference in skill prompt
Revise Path (mode: "counter_proposal")
NEW_COUNT=$(bash "$PLUGIN_SCRIPTS/implementation-plan/approach-cycle-state.sh" increment "$STORY_ID")
If NEW_COUNT <= 2: incorporate the counter-proposal's approach and done_definitions as additional constraints; return to Proposal Generation with explicit guidance (new proposals must satisfy the original SC AND the counter-proposal's requirements); re-enter the Resolution Loop. If NEW_COUNT > 2: Escalate Path.
In interactive mode, briefly note that the decision-maker requested revisions and the loop is retrying.
Escalate Path (after 2 cycles)
Present:
- All proposals from the most recent generation
- All counter-proposal feedback across cycles (from state file + current agent output)
- Summary: "The decision-maker could not reach a satisfactory selection after 2 cycles. Please review the proposals and counter-proposal feedback, then select an approach manually."
In autonomous mode, emit STATUS:blocked REASON:approach_escalated_to_user STORY:<story-id> and pause. Do NOT proceed autonomously. After user selects, clear cycle state.
Step 2: Consistency & Architectural Review
If the implementation does not require a new pattern (or modification to an existing one), skip to Step 3.
If a pattern change is proposed, read and execute ${CLAUDE_PLUGIN_ROOT}/docs/workflows/REVIEW-PROTOCOL-WORKFLOW.md inline with:
- subject:
"Architectural Pattern: {pattern name}"
- artifact: proposed pattern + relevant architecture docs and existing patterns from Step 1
- pass_threshold:
4
- start_stage:
1
- perspectives: from reviewer files in
docs/reviewers/architectural/:
Remediation Re-dispatch
On Step 2 architectural-reviewer FAIL verdict (i.e., the architectural-review protocol returns a non-passing score after all autonomous resolution cycles), re-dispatch dso:approach-proposer with model: "opus" on the dispatch payload to generate a revised approach.
Build a remediation_context block before dispatching. Pass all artifact file paths by reference (absolute file paths — do not inline file contents). The shape MUST match the schema declared in ${CLAUDE_PLUGIN_ROOT}/agents/approach-proposer.md under ### Remediation Context (optional):
{
"remediation_context": {
"reviewer_artifact_paths": [
"<absolute path to reviewer-findings.json from the failing review cycle>",
"<absolute path to the original approach proposal artifact>"
],
"findings": [
{
"target": "<proposal-N id that triggered the FAIL verdict, or 'n/a' for set-wide concerns>",
"description": "<verbatim evidence quote from reviewer findings — copy the exact reviewer text that triggered the FAIL verdict>"
}
],
"target_story_id": "<story id this delta cycle is scoped to>"
}
}
The orchestrator-level dispatch operates in mode: remediation — i.e., the presence of remediation_context activates dso:approach-proposer's DELTA OUTPUT MODE and produces only the changed proposals rather than a full regeneration. Do NOT add a mode field inside the remediation_context object itself (the schema does not declare it).
Also pass the following scratch namespacing args so the sub-agent can write its output via the scratch store:
SCRATCH_TICKET_ID: The story ticket id for this implementation-plan run — the sub-agent uses this to namespace the scratch write.
SCRATCH_KEY: The literal string implementation-plan:step2:arch-review-draft — the sub-agent uses this as the scratch key when writing its output.
Sub-agent output contract (include verbatim in the dispatched prompt):
<output_contract>
You MUST write your full JSON output (proposals, distinctness_summary, complexity_gate_summary,
generation_notes) to the ticket scratch store before returning, using the SCRATCH_TICKET_ID and
SCRATCH_KEY values provided:
bash "$PLUGIN_SCRIPTS/ticket-scratch.sh" set "$SCRATCH_TICKET_ID" "$SCRATCH_KEY" "<your-json-output>"
After writing, return ONLY a 3-field receipt JSON — do NOT embed the draft body in your return block:
{"ticket_id": "<SCRATCH_TICKET_ID>", "key": "<SCRATCH_KEY>", "byte_count": <N>}
where byte_count is the byte length of the JSON payload you wrote.
NEGATIVE EXAMPLE (contract violation — do NOT do this):
{"ticket_id": "...", "key": "...", "byte_count": 4096, "proposals": [...]}
^^^ Extra fields beyond the 3-field envelope are a contract violation and will
cause RECEIPT_PARSE_ERROR on the orchestrator side.
NEGATIVE EXAMPLE (contract violation — do NOT do this):
Here are the revised proposals: proposals: [...], distinctness_summary: [...]
^^^ Returning the draft body inline (not via scratch) is a contract violation.
A SCRATCH_MISS on the orchestrator side is NOT a fallback signal to send
the payload inline — it is a hard error requiring the agent to be re-dispatched.
</output_contract>
After receiving the revised proposals, re-enter the Resolution Loop from Dispatch with the new proposal set. If the re-dispatched approach-proposer returns model_requirement_unmet, apply the same retry-once rule as in the Proposal Generation section.
Record exactly one .claude/scripts/dso ticket comment <id> "REMEDIATION_CYCLE:<N> approach-proposer re-dispatched after Step 2 FAIL" per cycle per SC5 single-comment policy.
Fallback: if the review fails after autonomous resolution (review.max_cycles, default: 4) and user escalation, revert to existing patterns and note the unresolved concern. If no existing pattern solves the story, halt and consult the user.
Remediation loop protocol (Step 2 — architectural review re-dispatch)
When dso:approach-proposer returns revised proposals after a Step 2 FAIL verdict, the orchestrator enters a remediation loop governed by the shared protocol. Source MAX_CYCLES from get_max_remediation_cycles() in ${CLAUDE_PLUGIN_ROOT}/hooks/lib/planning-config.sh before entering the loop.
Per-cycle declaration: At the start of every remediation cycle, emit exactly:
Current cycle: N of MAX_CYCLES
where N is the 1-based cycle counter and MAX_CYCLES is the resolved maximum. This line is required before any findings, deltas, or token emissions within that cycle.
Oscillation-check hard gate (cycle >= 2): On any cycle where N >= 2, the orchestrator MUST invoke /dso:oscillation-check (Skill) before proceeding with findings analysis. Skipping this gate requires an explicit OSCILLATION_CHECK_SKIPPED: <reason> ticket comment capturing the rationale; no other skip reason is permitted.
Mid-loop success exit: If the revised proposals pass architectural review on any cycle (before reaching MAX_CYCLES), the loop exits immediately and Step 2 proceeds to Step 3. No terminal token is emitted on success.
HALT-vs-REPLAN exclusivity check: REPLAN_ESCALATE MUST be emitted IFF cycle_count == MAX_CYCLES AND findings non-empty. No other condition may emit REPLAN_ESCALATE. All other terminal states use a different token — human-input required → HALT_FOR_USER; oscillation detected → OSCILLATION_HALT; illegal state transition → PROTOCOL_ERROR. Emitting REPLAN_ESCALATE under any other condition, or emitting it together with PROTOCOL_ERROR, is itself a PROTOCOL_ERROR.
Terminal REPLAN_ESCALATE: On cycle MAX_CYCLES with findings still non-empty, emit (per the canonical ${CLAUDE_PLUGIN_ROOT}/docs/contracts/replan-escalate-signal.md colon-space prefix):
REPLAN_ESCALATE: planner_supplied EXPLANATION:<explanation text>
The upstream for implementation-plan Step 2 is planner_supplied (per the upstream enum in Section 6 of the protocol doc). Emit this as a standalone ticket comment so the orchestrator's parent session can detect and route the escalation. The literal prefix REPLAN_ESCALATE: (colon followed by a single space) and the EXPLANATION: field label are fixed by contract — do not vary them.
PROTOCOL_ERROR on invariant violation: Any illegal state transition — emitting REPLAN_ESCALATE when cycle_count < MAX_CYCLES, emitting multiple terminal tokens, or violating the HALT-vs-REPLAN exclusivity invariant — MUST emit PROTOCOL_ERROR and halt remediation immediately.
See: ${CLAUDE_PLUGIN_ROOT}/skills/shared/workflows/remediation-loop-protocol.md
Parse Output (Receipt-Only Contract — Step 2 cycle recorder)
After the sub-agent returns, validate its receipt via receipt-parse.sh before reading any payload:
PARSE_RESULT=$(printf '%s' "<sub-agent-return-block>" | \
bash "$PLUGIN_SCRIPTS/receipt-parse.sh" implementation-plan:step2 dso:approach-proposer)
PARSE_EXIT=$?
if [ "$PARSE_EXIT" -ne 0 ]; then
bash "$PLUGIN_SCRIPTS/ticket-scratch.sh" clear "$SCRATCH_TICKET_ID" "implementation-plan:step2:arch-review-draft" 2>/dev/null || true
echo "HALT: approach-proposer returned a malformed receipt — see RECEIPT_PARSE_ERROR above. Re-dispatch the sub-agent to fix the return contract before continuing." >&2
exit 1
fi
SCRATCH_TICKET_ID_OUT=$(echo "$PARSE_RESULT" | awk '{print $1}')
SCRATCH_KEY_OUT=$(echo "$PARSE_RESULT" | awk '{print $2}')
SCRATCH_RESULT=$(bash "$PLUGIN_SCRIPTS/ticket-scratch.sh" get "$SCRATCH_TICKET_ID_OUT" "$SCRATCH_KEY_OUT")
SCRATCH_STATUS=$(echo "$SCRATCH_RESULT" | python3 -c "import json,sys; print(json.load(sys.stdin)['status'])")
if [ "$SCRATCH_STATUS" != "hit" ]; then
bash "$PLUGIN_SCRIPTS/ticket-scratch.sh" clear "$SCRATCH_TICKET_ID_OUT" "$SCRATCH_KEY_OUT" 2>/dev/null || true
echo "HALT: scratch key '$SCRATCH_KEY_OUT' not found for ticket '$SCRATCH_TICKET_ID_OUT' (status=$SCRATCH_STATUS). A SCRATCH_MISS is not a signal to accept an inline payload — re-dispatch the sub-agent." >&2
exit 1
fi
ARCH_REVIEW_JSON=$(echo "$SCRATCH_RESULT" | python3 -c "import json,sys; print(json.load(sys.stdin)['value'])")
bash "$PLUGIN_SCRIPTS/ticket-scratch.sh" clear "$SCRATCH_TICKET_ID_OUT" "$SCRATCH_KEY_OUT" 2>/dev/null || true
After retrieving the arch-review payload, record the cycle artifact via the recorder:
python3 ${CLAUDE_PLUGIN_ROOT}/scripts/append_review_cycle.py \
--artifact <path-to-architectural-review-artifact-for-step2> \
--n <cycle-number> \
--draft-hash <sha256 of DELTA OUTPUT block> \
--findings-count <int> \
--verdict <pass|fail|escalate>
After the append, emit exactly ONE ticket comment per cycle:
.claude/scripts/dso ticket comment <ticket-id> "Remediation cycle <n> recorded; arch-review draft stored under implementation-plan:step2:arch-review-draft"
Single-comment policy (SC5): The ticket comment references the scratch key and does NOT duplicate the cycle body — cycle entries live in the scratch payload, not in ticket comments. One comment per cycle; no further detail in the comment body.
Step 3: Atomic Task Drafting (via dso:task-decomposer opus sub-agent)
Draft tasks that collectively fulfill all done definitions of the User Story. If a new pattern was approved in Step 2, include consistency tasks. Task drafting is delegated to the dso:task-decomposer sub-agent; the orchestrator collects inputs, dispatches, validates output, then proceeds to Step 4 with the resulting draft set.
Dispatch
Dispatch via subagent_type: "dso:task-decomposer" with model: "opus" passed explicitly.
Two distinct fallback concepts — do not conflate:
- Dispatch-target fallback (this paragraph): if the named
subagent_type: "dso:task-decomposer" is unregistered ("Unknown agent type" or similar dispatch error), re-dispatch with subagent_type: "general-purpose" + model: "opus" and agents/task-decomposer.md content read inline as the prompt. The agent still does the work — only the dispatch transport changes. This is NOT inline drafting.
- Inline-drafting fallback (forbidden, see Validation below): the orchestrator itself drafts tasks without the agent. This is the bug class the dispatch was introduced to eliminate and is explicitly prohibited regardless of what dispatch path was taken.
Pass the following as task arguments:
{story-id}, {story-title}, {story-description} from the story ticket
{story-done-definitions} — the story's DDs with stable identifiers (dd-1, dd-2, ...). Parse from the story description's ## Done Definitions section.
{selected-approach} — the proposal selected by dso:approach-decision-maker (full proposal: title, description, files, pros, cons, risk)
{file-impact-table} — the orchestrator's enumeration from "File Impact Enumeration" below (every file × action × test-classification)
{project-commands} — commands.test_unit, commands.lint, commands.format_check from dso-config.conf (verbatim)
{testing-mode} — the story's testing-mode classification (RED / GREEN / UPDATE; default RED)
{migration-marker} — the migration-class marker emitted by Step 1 (Migration-Class Detection). Source it by scanning the story ticket for the LAST MIGRATION_CLASS: comment (last-wins idiom) and passing the verbatim single-line JSON payload that follows the MIGRATION_CLASS: prefix. If no MIGRATION_CLASS: comment is present (older stories planned before this step existed), pass an empty block — the task-decomposer treats an absent/unparseable marker as an inert no-op (the absent-marker path emits no migration pair, preserving backward compatibility). The agent parses migration-class from THIS passed-in arg; it never fetches it from the ticket (the agent has no ticket access).
{feature-flags-marker} — the feature-flag approval marker emitted by Step 1 (Feature-Flag Approval Detection). Source it by scanning the story ticket for the LAST FEATURE_FLAGS: comment (last-wins idiom) and passing the verbatim single-line JSON payload that follows the FEATURE_FLAGS: prefix. If no FEATURE_FLAGS: comment is present (older stories planned before this step existed), pass an empty block — the task-decomposer treats an absent/unparseable marker as an inert no-op (backward-compatible default). The agent reads feature-flags from THIS passed-in arg; it never performs the two-hop story→parent lookup itself (the agent has no tracker access; the lookup is done by the orchestrator in Step 1 via resolve-feature-flag-approval.sh).
The agent returns a JSON object with dd_partition_map, task_drafts, and decomposition_notes. Validate in this order — do NOT reorder:
- Model-requirement check (first): if the response carries
"error": "model_requirement_unmet", log "Task decomposer model requirement unmet — re-dispatching with explicit model: opus" and retry once via the fallback path. If the retry also returns model_requirement_unmet, HALT with diagnostic: "Task decomposition requires opus; configure the environment to allow opus dispatch and re-run /dso:implementation-plan." Do NOT fall back to inline task drafting.
- Domain-error escalation (second): if the response carries
"error": "decomposition_blocked" (documented in agents/task-decomposer.md), this is a legitimate signal that the inputs are insufficient or contradictory — NOT a malformed response. Surface the agent's decomposition_notes to the user and HALT with diagnostic: "Task decomposer: decomposition_blocked — <decomposition_notes joined>. Re-supply the missing input (selected approach, file impact table, or story DDs) and re-run /dso:implementation-plan." Do NOT proceed to schema validation; empty task_drafts / dd_partition_map arrays under this error code are the intended shape.
- Schema validation (third): with both error-envelope branches ruled out, validate that
dd_partition_map covers every story DD (union of all owning_task_ids is non-empty for every DD, and there are no duplicate ownerships unless explicitly sub-partitioned in decomposition_notes); task_drafts is a non-empty array with the required fields per the agent's output spec; every task's description field begins with a literal ## Story DD Coverage section header (verbatim — the completeness reviewer's dd_collective_ac_coverage audit greps for this exact heading; a missing header silently invalidates the audit); every task's first three acceptance_criteria lines are the Universal Criteria using the project commands verbatim; every task whose testing_mode is RED has a tdd_test_spec Given/When/Then sentence. If schema validation fails, HALT with diagnostic: "Task decomposer returned malformed output; cannot proceed without verified drafts." Do NOT fall back to inline drafting — inline drafting is the failure mode this dispatch exists to prevent.
Why halt (not skip-and-continue) here, while Step 6 Gap Analysis falls through on failure? Task decomposition is a correctness gate: every subsequent step (Plan Review, Task Creation, Gap Analysis) consumes the draft set, and inline orchestrator drafting (the only available fallback) is the bug class this dispatch eliminates. Step 6 Gap Analysis is a quality gate with a defined skip path — losing it degrades coverage but does not break downstream steps.
The sub-sections below mirror the rules embedded in agents/task-decomposer.md. They are kept here for human reference and for the orchestrator's input-construction step (file impact enumeration in particular). The agent does NOT load them at dispatch time — it carries its own embedded protocol. When updating gate criteria, AC-library instructions, or sequencing rules below, you MUST update agents/task-decomposer.md in the same change to keep the two in sync. The orchestrator itself does not draft tasks inline; it only enumerates the file impact table (which the agent consumes), validates the agent's output, and proceeds to Step 4.
Directives
- TDD First — every task must specify a concrete failing test to write first.
- 3-Gate Granularity — every task must pass all three gates conjunctively. Gate 3 only mandates splitting when the split would not violate Gate 1 or Gate 2.
- Gate 1 — Testable Behavior: the task must produce testable behavior. Grepping a source file to verify code exists is not a valid test. A valid test executes the code and asserts on output, exit code, or side effects.
- Gate 2 — Codebase Green: after committing only this task, all tests pass and the system is deployable. Tasks must never require being committed together. A task that deploys an inert feature (a guard reading files no one writes yet) is acceptable — inert is not broken.
- Gate 3 — Maximum Granularity: it must not be possible to split into smaller tasks each meeting Gate 1 and Gate 2. If two changes within a task each produce independently verifiable behavior and each leaves the codebase green on its own, they must be separate tasks. Bundling is acceptable only when splitting would violate Gate 1 (neither half produces testable behavior alone) or Gate 2 (intermediate broken state — e.g., a rename across import sites).
- Acceptance Criteria — every task must include AC passed via
-d/--description at creation time, composed from ${CLAUDE_PLUGIN_ROOT}/docs/ACCEPTANCE-CRITERIA-LIBRARY.md. Read the library once at the start of Step 3. For each task:
- Start with Universal Criteria (always included)
- Select applicable category blocks based on task type
- Fill in parameterized slots ({path}, {ClassName}, {N}, etc.)
- Add task-specific criteria not covered by templates
- Every criterion must include a
Verify: command that returns exit 0 on pass.
- Sequential Order:
- Data Model Updates — backward compatible (nullable fields, defaults).
- API/Service Updates — backward compatible (versioning or optional parameters).
- UI/Frontend Updates — consume the new API/version.
- Cleanup — remove legacy fields, deprecated API versions, or bridge code.
File Impact Enumeration
Before drafting tasks, enumerate every file the story affects. Produces an auditable file impact table mapping each source file to its change action, associated tests, and test classification. The table drives task type selection in TDD Task Structure.
-
List affected source files via Glob/Grep, tracing from entry points through all layers.
Prefer sg (ast-grep) for cross-file dependency discovery — syntax-aware. Guard:
if command -v sg >/dev/null 2>&1; then
sg --pattern 'import $MODULE' --lang python .
sg --pattern 'from $MODULE import $_' --lang python .
else
grep -r 'import <module>' .
fi
-
Find associated tests for each source file:
-
Classify each test by what the story does to the source:
Non-executable instruction files are always still-valid — before applying the table, identify any file that is an LLM instruction document: SKILL.md, agent definitions (files in agents/), prompt files (files in prompts/), workflow docs (.md prose files in docs/ — excludes schemas, specs, configs, or any executable artifact stored under docs/), hook guidance, CLAUDE.md, or other .md prose files with no executable code. Per behavioral-testing-standard Rule 5, these files cannot be deterministically tested for behavioral correctness. Always classify them as still-valid regardless of the change action — create, modify, or remove. Do NOT produce a RED or UPDATE test task for instruction file changes. If the Doc-Only Skip Gate (above) should have caught a purely-instruction story, apply it retroactively here.
| Source change action | Test classification |
|---|
create (new source file) | needs-creation — write a new test file |
modify (behavior change) | needs-modification — update existing test(s) |
remove (source deleted) | needs-removal — remove or prune tests |
modify (no behavior change, e.g., refactor) | still-valid — existing tests remain correct |
| any action on non-executable instruction file | still-valid — no test task ever needed |
-
Build the file impact table:
| Source file | Action | Associated tests | Test classification |
|---|
src/foo.py | modify | tests/test_foo.py | needs-modification |
src/bar.py | create | (none yet) | needs-creation |
src/legacy.py | remove | tests/test_legacy.py | needs-removal |
src/util.py | modify | tests/test_util.py | still-valid |
still-valid requires no test task. needs-modification → modify-existing-test RED task. needs-removal → remove-test task. needs-creation → create-test RED task.
Consumer Detection Pass
For every file in the impact table whose action is modify or remove, run a downstream consumer detection pass to identify callers/callsites outside the immediate task scope.
Prefer sg over text grep — syntax-aware. Guard:
if command -v sg >/dev/null 2>&1; then
sg --pattern '$FUNC($$$)' --lang python . | grep -F '<symbol_name>'
sg --pattern '<symbol_name>($$$)' --lang python .
else
grep -rn '<symbol_name>(' .
fi
When external consumers are found, document them in the task's File Impact section with one disposition:
- Update — change the external callsite in this task; add the consumer file with action
modify and pull its tests in.
- Accept the breaking change — record the rationale and link the consumer's owner story or follow-on ticket.
A modify/remove task with un-triaged external consumers is incomplete and must be revised before it leaves Step 3.
Testing Mode Classification
Each task carries an explicit testing_mode field — RED, GREEN, or UPDATE — derived from the file impact table. The classification describes what the code does to observable behavior, not what text it adds or removes.
The mode applies to the source file task (not the test task). A test task for a RED source file is a "RED test task" but the implementation task for that same file also carries testing_mode: RED.
| Source condition | testing_mode | Meaning |
|---|
Source action = create, classification = needs-creation | RED | New behavioral content; must have a preceding RED test task |
Source action = modify, behavior changes, needs-modification | UPDATE | Existing file with observable behavior change; existing tests must be updated to assert new behavior before implementation runs |
Source action = modify, no behavior change (refactor), still-valid | GREEN | Implementation change only; existing tests remain correct |
Source action = remove, needs-removal | GREEN | Deleting behavior; remove corresponding tests to keep the suite honest |
| Task matches a recipe capability from the registry | recipe: | Mechanical transform; execution delegates to the recipe executor; specify recipe_name, recipe_params, and intent |
Behavioral framing rule: testing_mode reflects what the code does — observable outputs, decisions, or side effects — not what it contains. A refactor that renames internal methods without changing returned values is GREEN regardless of line count.
Emit per task:
Task: <task title>
testing_mode: RED | GREEN | UPDATE | recipe:
Recipe Task Type
Assign testing_mode: recipe: when a task matches a capability in the recipe registry (from Recipe Consultation in Step 1).
When to assign recipe:
- Task is a mechanical, deterministic code transform whose entire behavior is described by a registry
capability_description
- Examples: adding a parameter to a function signature and updating all callers (
add-parameter); sorting and deduplicating import statements (normalize-imports)
- No judgment about business behavior is required — the transform is fully parameterizable
When NOT to assign recipe: feature additions, algorithm changes, API contract decisions, or any task where the "what" requires domain reasoning rather than parameterization.
Required fields for recipe: tasks (in addition to standard task fields):
testing_mode: recipe:
recipe_name: <name field from the registry — e.g., add-parameter>
recipe_params: <key=value pairs for the transform — e.g., function_name=process_request, param_name=timeout, param_type=int, default_value=30>
intent: <natural-language description of this specific transform, derived from capability_description>
recipe: tasks do not require a preceding RED test task; the recipe executor owns the test contract for mechanical transforms.
TDD Task Structure
Behavioral content is code with conditional logic, data transformation, or decision points — any code where output varies by input or state. Every task adding/modifying behavioral content must have a preceding RED test task as a declared dependency before any implementation task.
A RED test may modify existing tests, not just create new test files. When a story changes existing behavior, the RED test edits an existing test file to assert the new expected behavior — modifying existing tests is a first-class RED-phase activity.
testing_mode maps to TDD task type:
- RED → Create-test task (source
create, needs-creation): write a new test file asserting expected behavior. Implementation depends on this task.
- UPDATE → Modify-existing-test task (source
modify with behavior change, needs-modification): update an existing test to assert new behavior. The modified test must fail (RED) before implementation runs because the new behavior does not yet exist. Name the specific file and describe which assertions change.
- GREEN (refactor or deletion) → no test task needed; for deletion, optionally a remove-test task (source
remove, needs-removal) which may run before or in parallel with the source removal (no behavioral assertion to run RED).
A RED test task:
Behavioral Test Requirement
RED tests must follow the Shared Behavioral Testing Standard — read it before writing any test task.
Test approach framing: each task that produces a RED test must include a Given / When / Then test approach sentence:
- Given: preconditions and inputs (fixture, initial state)
- When: invocation (what the code under test is called with)
- Then: observable outcome asserted (return value, exit code, file written, side effect)
If the test approach describes grepping a source file rather than invoking the code under test, the task must be revised to describe a behavioral assertion.
Unit Test Exemption Criteria
A task may omit the RED test task dependency only if all of the following apply:
- The code has no conditional logic — purely structural (e.g., a class definition with no branching).
- Any test would be a change-detector test — only asserts the code exists, not that it behaves correctly.
- The task is infrastructure-boundary-only — only configuration wiring, dependency injection setup, or module registration with no business logic.
Document all three as a justification requirement in the task description; the plan reviewer validates the exemption in Step 4.
Integration Test Task Rule
For tasks crossing an external boundary (database, external API, message queue, file system), include a dedicated integration test task verifying the boundary interaction end-to-end. Integration test tasks do not require a RED-first dependency — they may be written after the implementation task.
Omit only if:
- existing coverage — an existing integration test already exercises this boundary in a way that would fail if the task's behavior were broken.
- no test environment — the boundary is unavailable in CI and no suitable mock/contract test is feasible.
Either exemption requires justification documented in the task description and validated by the plan reviewer in Step 4.
Primary path constraint: when the story's SC describe a user-facing flow (sign-in, checkout, form submission, browser API call), the integration test must exercise that exact path — not an admin/server-side/CLI bypass that skips user-facing infrastructure (e.g., OAuth browser callback, CSRF validation, session cookie issuance). Document which user-facing path is covered.
Test Filename Conventions (Fuzzy-Match Compatibility)
The tech-stack-agnostic test gate associates source files with their tests via fuzzy matching: source basename normalized (non-alphanumeric stripped, lowercased) and checked as a substring against normalized test basenames.
| Source file | Compatible test filenames |
|---|
bump_version.py | test_bump_version.py, test_bumpversion.py |
bump-version.sh | test-bump-version.sh, test_bumpversion_unit.sh |
auth_service.py | test_auth_service.py, auth_service_test.go |
parser.ts | parser.test.ts, parser.spec.ts |
Rule: verify the proposed test filename is fuzzy-match compatible:
- Normalize source basename: strip all non-
[a-z0-9], lowercase (e.g., bump-version.sh → bumpversionsh).
- Normalize proposed test basename the same way (e.g.,
test-bump-version.sh → testbumpversionsh).
- Confirm the normalized source string appears as a substring of the normalized test string.
If the test filename would NOT be caught by fuzzy match (e.g., test_retry_logic.py for connection_handler.py), the task's AC must include a .test-index entry as an explicit criterion:
- [ ] `.test-index` entry added mapping `<source-file>` to `<test-file>`
Verify: grep -q '<source-file>' $(git rev-parse --show-toplevel)/.test-index
The .test-index file is the authorized fallback for unconventional test names; it must be present before commit or the test gate produces a false negative. Common mistake: naming a test after a behavior rather than the source file.
Wireframe Design Decision
When the story involves UI changes:
- Design wireframes inline (Recommended) — create wireframes as part of planning.
- Defer wireframes — only when visual design is not part of the story's scope.
If inline, verify /dso:preplanning has run for the parent epic — dso:ui-designer is dispatched by preplanning Phase H Step 7 and produces the Design Manifest (spatial layout, SVG wireframe, token overlay) as a UI_DESIGNER_PAYLOAD. If no design artifacts exist, re-run /dso:preplanning <epic-id> first. Include a wireframe task that references existing design artifacts before implementation tasks. UI implementation tasks depend on the wireframe task.
If deferring, document the rationale (e.g., "Visual design is out of scope — wireframes will come from a dedicated design story").
E2E Testing Requirement
If the story introduces or modifies user-facing behavior, API endpoints, or cross-component flows, include a dedicated E2E test task:
- New user flows: E2E covering happy path and key error states.
- Modified flows: update existing E2E; add new tests for new paths.
- API-only changes: E2E if the change affects responses consumed by frontend or external clients.
- Place in
tests/e2e/ following existing conventions.
- E2E task depends on all implementation tasks (runs last).
If purely internal, document why E2E coverage is not needed.
Visual Verification Metadata
When a task's File Impact contains UI files (.css, .js, .ts, .tsx, .html, .jinja2, or files inside component directories), the generated task description MUST declare visual verification:
- Add
requires_visual_verification: true to the task description.
- Add a Playwright AC: "Run
playwright test targeting the affected component; verify no visual regression against baseline."
When the task touches no UI files, omit both the field and the Playwright AC entirely (do not emit requires_visual_verification: false — absence is the signal).
The sub-agent executing the task is responsible for running Playwright as part of satisfying its AC. The sprint orchestrator does NOT add a separate Playwright dispatch step. The token requires_visual_verification is a structural contract surface — use the literal verbatim.
Documentation Updates
If the story introduces or modifies patterns, conventions, or significant technical decisions, run the gates in ${CLAUDE_PLUGIN_ROOT}/skills/shared/prompts/doc-router.md to select the target file. Do not infer the target from intuition; the router replaces the legacy heuristic that biased toward CLAUDE.md and away from skill-scoped docs.
If at least one router gate fires, include a documentation task. The task MUST:
- State which router gate fired (1–5) and the resolved target file in the task description.
- Set the target per the router, in order of preference: Gate 1 →
${CLAUDE_PLUGIN_ROOT}/skills/<skill>/SKILL.md; Gate 2 → existing reference doc (HOOKS-REFERENCE.md, AGENTS.md, WORKTREE-GUIDE.md, CONFIGURATION-REFERENCE.md, CI-INTEGRATION.md, contract docs, KNOWN-ISSUES.md); Gate 3 → INSTALL.md / README.md / docs/user/; Gate 4 → new ADR in docs/adr/; Gate 5 → CLAUDE_MD_SUGGESTED_CHANGE report only (no direct CLAUDE.md edit).
- Never name CLAUDE.md as the direct target. A task that would edit CLAUDE.md must instead emit a
CLAUDE_MD_SUGGESTED_CHANGE report and route through the orchestrator.
- Include the attestation requirement in the task acceptance criteria: the executing sub-agent emits
DOC_ROUTER_ATTESTATION in its completion report.
- If
.claude/docs/DOCUMENTATION-GUIDE.md exists, follow it for formatting and structure.
If no router gate fires, omit the documentation task and note the rationale (e.g., "doc-router: no gate fired — internal refactor; existing docs remain accurate").
The doc task depends on the implementation tasks it documents and references Step 2 feedback if applicable.
Contract Detection Pass
After file impact analysis and before finalizing the task list, run a contract detection pass to identify cross-component interfaces needing explicit contracts.
When to run: file impact includes two or more components. Skip for purely internal, single-component changes.
V1 detection heuristics:
- Pattern A — Signal emit/parse pairs: contract needed when one file produces structured output (
STATUS:, RESULT:, REPORT: markers) and another component parses/consumes it.
- Pattern B — Orchestrator/sub-agent report schema: contract needed when a skill/orchestrator dispatches sub-agents AND defines an expected return format (
CONTRACT_REPORT or contract report schema). When a dispatcher and a report schema are both in scope, the interface requires a contract artifact.
Contract artifact: for each detected interface, create ${CLAUDE_PLUGIN_ROOT}/docs/contracts/<interface-name>.md with sections: Signal Name, Emitter, Parser, Fields (with types and required/optional), Example (representative payload).
Cross-Story Deduplication: before creating a contract task, check the epic for an existing one:
.claude/scripts/dso ticket deps <parent-epic-id>
Scan output for an existing task whose title contains Contract: and the same interface name. If found, wire implementation tasks as dependents of that existing task — do not duplicate. Else create:
.claude/scripts/dso ticket create task "Contract: <interface-name> signal emit/parse interface" --parent=<parent-epic-id> --priority=2
Contract task as first dependency: declare it as a dependency of all implementation tasks touching either side of the interface.
Retry Budget
Each implementation task carries a retry budget the orchestrator parses and enforces when dispatching sub-agents.
## Retry Budget
MAX_ATTEMPTS: 3 (sonnet model)
On 3 consecutive sonnet failures: escalate to opus with full diagnostic context (all 3 failure messages)
On 3 consecutive opus failures (6 total): escalate to user with full failure history
If MAX_AGENTS: 0 at sonnet→opus escalation time: skip opus step, escalate to user immediately
Include this block verbatim in every task description — MAX_ATTEMPTS is the integration token sub-agent dispatchers parse for the per-tier attempt cap.
Opus Escalation
When sonnet fails MAX_ATTEMPTS consecutive times, the orchestrator re-dispatches at opus with full diagnostic context: each failed sub-agent's final report, test output / error messages from each failure, files modified across attempts (with diffs if available), any RESOLUTION_RESULT or contract-violation signals. If MAX_AGENTS: 0 at escalation time, skip opus and go directly to user escalation — opus dispatch is gated by usage capacity.
User Escalation
After 6 total consecutive failures (3 sonnet + 3 opus), the orchestrator terminates the autonomous retry loop and escalates to the user with the full history: 6 failed sub-agent reports in chronological order, the diagnostic context passed to opus, a concise summary of attempts and failure reasons, and the current working-tree state. Same path triggers immediately when MAX_AGENTS: 0 blocks opus dispatch (report contains the 3 sonnet failures plus an explicit note that opus was skipped due to throttling).
Pattern Reference
When upstream dso:complexity-evaluator output specifies pattern_familiarity: low or medium for a task, enrich the task description with a ## Pattern Reference block containing up to 30 lines of representative codebase examples. Gives the implementation sub-agent concrete prior art, reducing the chance of inventing a novel pattern.
Gating:
pattern_familiarity: low — REQUIRED.
pattern_familiarity: medium — REQUIRED.
pattern_familiarity: high (or no evaluator output) — OMIT entirely.
Retrieval rules: local grep/glob only — no external lookups, no nested LLM calls. Search anchors come from the task's file impact list and the evaluator's identified pattern keywords. Cap at ≤ 30 lines total across all examples; truncate single examples >30 lines with # .... Cite each example with its source path (e.g., # from src/utils/example.sh:42-58).
Step 4: Implementation Plan Review
Step 4 (Plan Review) is mandatory for every non-TRIVIAL story. There is NO skip path. Do NOT skip or abbreviate plan review for any of the following reasons — each is an explicitly prohibited rationalization:
- "session efficiency" or "we're running long"
- "context pressure" or "token budget concerns"
- "the task-decomposer already validated the plan"
- "the story is small enough" or "the plan is obvious"
- "I already see the gaps" or "plan review is redundant"
- "user authorized 'push through'"
The plan review protocol (REVIEW-PROTOCOL-WORKFLOW.md) is the only authorized mechanism for closing Step 4. After Step 4 completes, tag the story ticket: .claude/scripts/dso ticket tag <story-id> plan_review:pass before proceeding to Step 5.
Read docs/review-criteria.md for the full reviewer table, launch instructions, score aggregation rules, and conflict detection guidance.
Read and execute ${CLAUDE_PLUGIN_ROOT}/docs/workflows/REVIEW-PROTOCOL-WORKFLOW.md inline:
-
subject: "Implementation Plan for: {story title}"
-
artifact: user story (title + full description) + numbered task list with titles, descriptions, TDD requirements, and dependencies
-
pass_threshold: 5 (safety-critical — plan must be safe for unsupervised agent execution)
-
start_stage: 1
-
perspectives: from ${CLAUDE_PLUGIN_ROOT}/skills/implementation-plan/docs/reviewers/plan/:
task-design.md — "Task Design"
tdd.md — "TDD"
safety.md — "Safety"
dependencies.md — "Dependencies"
completeness.md — "Completeness"
If any reviewer file cannot be read: HALT immediately. Do NOT synthesize inline perspectives or construct an ad-hoc rubric. Report: "Step 4 blocked: reviewer file <path> not found — create the missing reviewer file before proceeding."
The plan must achieve all dimension scores of 5. The review protocol workflow's revision protocol handles the iteration loop (max 3 cycles). After 3 attempts, present the plan at its current score with remaining issues to the user for judgment.
Remediation Re-dispatch
On Step 4 plan-reviewer findings (non-empty findings list), re-dispatch dso:task-decomposer with literal model: "opus" on the dispatch payload to revise the task list in response to reviewer concerns.
Build a remediation_context block before dispatching. Pass all artifact file paths by reference (absolute paths — do not inline file contents). The shape MUST match the schema declared in ${CLAUDE_PLUGIN_ROOT}/agents/task-decomposer.md under ### Remediation Context (optional):
{
"remediation_context": {
"reviewer_artifact_paths": [
"<absolute path to reviewer-findings.json from the failing plan review cycle>",
"<absolute path to the original task plan artifact, if available>"
],
"findings": [
{
"target": "<temp_id or existing task id this finding targets, or 'n/a' for set-wide concerns>",
"description": "<verbatim evidence quote from reviewer findings — copy the exact reviewer text that triggered the finding>"
}
],
"target_story_id": "<story id this delta cycle is scoped to>"
}
}
The orchestrator-level dispatch operates in mode: remediation — i.e., the presence of remediation_context activates dso:task-decomposer's DELTA OUTPUT MODE and produces only the changed tasks rather than a full re-decomposition. Do NOT add a mode field inside the remediation_context object itself (the schema does not declare it).
Delta-mode behavior: the agent will preserve-by-omission — tasks not named in any finding are absent from the delta output but preserved verbatim in the merged set. Only tasks explicitly named in findings (via the target field) appear in the delta output with the modifications the findings require.
Also pass the following scratch-handoff arguments to the sub-agent:
SCRATCH_TICKET_ID: The story ticket id (the <story-id> value for this implementation-plan run) — the sub-agent uses this to namespace the scratch write.