Imported from navapbc/digital-service-orchestra (
plugins/dso/skills/preplanning/SKILL.md). Install upstream withnpx skills add navapbc/digital-service-orchestra --skill preplanning. Copyright stays with the author.
Startup Configuration
At the very start of execution (immediately after passing the SUB-AGENT-GUARD check), read the interactive mode flag:
PLUGIN_SCRIPTS="${CLAUDE_PLUGIN_ROOT}/scripts"
PREPLANNING_INTERACTIVE=$(bash "$PLUGIN_SCRIPTS/read-config.sh" preplanning.interactive 2>/dev/null || echo 'true') # shim-exempt: internal orchestration script
PREPLANNING_INTERACTIVE=$(echo "$PREPLANNING_INTERACTIVE" | tr '[:upper:]' '[:lower:]')
# Default: true (interactive) when the key is absent or empty
if [[ -z "$PREPLANNING_INTERACTIVE" ]]; then
PREPLANNING_INTERACTIVE="true"
fi
Default is true (interactive) when the key is absent — new projects without the key should default to interactive mode. Only set preplanning.interactive=false in .claude/dso-config.conf for automated pipelines.
Pre-Planning: High-Fidelity Story Mapping
Act as a Senior Technical Product Manager (Google-style) to audit, reconcile, and decompose a ticket Epic into prioritized User Stories with measurable Done Definitions that bridge the epic's vision to task-level acceptance criteria.
Supports dryrun mode. Use /dso:dryrun /dso:preplanning to preview without changes.
Usage
/dso:preplanning # Interactive epic selection
/dso:preplanning <epic-id> # Pre-plan specific epic
/dso:preplanning <epic-id> --lightweight # Enrich epic without creating stories (used by /dso:sprint for MODERATE epics)
Arguments
<epic-id>(optional): The ticket epic to decompose. If omitted, presents an interactive list of open epics.--lightweight(optional): Enrich the epic with done definitions and considerations without creating child stories. ReturnsENRICHEDorESCALATED. Used by/dso:sprintfor MODERATE-complexity epics. If the scope scan discovers COMPLEX qualitative overrides, returnsESCALATEDso the orchestrator can re-invoke in full mode.
Process Overview
This skill transforms epics into implementable stories:
- Entry routing (Phase A Step 1.5): classify epic complexity (SIMPLE →
/dso:implementation-plan; MODERATE → lightweight mode; COMPLEX → full preplanning). Skipped when--lightweightwas explicitly passed. - Phase A: Reconciliation — audit existing work, clarify scope
- Phase B: External Dependencies Reading (flag-gated) — read epic's External Dependencies block, generate manual:awaiting_user stories
- Story Decomposition: dispatch
dso:story-decomposer(opus) to draft the new vertical-slice stories needed so the collective set covers every epic Success Criterion - Phase C: Risk & Scope Scan — flag cross-cutting concerns, identify split candidates
- Phase D: Integration Research — verify external integrations pre-slicing
- Phase E: Adversarial Review — red/blue team review for cross-story blind spots (3+ stories only)
- Refusal Gate: External Dependencies coverage check (flag-gated) — halt if externally-shaped SCs lack block coverage
- Phase F: Walking Skeleton & Vertical Slicing — prioritize minimum viable path, Foundation/Enhancement splits
- Phase G: Story-Level Research — research per-story decomposition gaps post-slicing
- Phase H: Verification & Traceability — create stories, link criteria, validate, wireframe UI stories
Lightweight mode (--lightweight): runs an abbreviated subset (Phase A Step 1, abbreviated Phase C); see Lightweight Mode Appendix for the authoritative spec. Returns ENRICHED or ESCALATED.
Migration Check
Idempotently apply plugin-shipped ticket migrations (marker-gated; no-op once migrated, never blocks the skill):
PLUGIN_SCRIPTS="${CLAUDE_PLUGIN_ROOT}/scripts"
bash "$PLUGIN_SCRIPTS/migrate-design-notes-to-design-md.sh" 2>/dev/null || true # shim-exempt: internal orchestration script
Scrutiny Gate
Before proceeding, check if the epic has a scrutiny:pending tag:
- Run
.claude/scripts/dso ticket show <epic-id>and check thetagsfield - If
scrutiny:pendingis present in the tags array: HALT immediately. Output: "This epic has not been through scrutiny review. Run/dso:brainstorm <epic-id>first to complete the scrutiny pipeline, then retry/dso:preplanning." Do NOT produce any planning output. - If
scrutiny:pendingis NOT present (or tags field is empty/absent): proceed normally.
This is a presence-based check — only block when the tag IS present. Existing epics without the tags field are NOT blocked.
Interaction Conflict Gate
Before proceeding, check if the epic has an interaction:deferred tag:
- Run
.claude/scripts/dso ticket show <epic-id>and check thetagsfield - If
interaction:deferredis present in the tags array: HALT immediately. Output: "This epic has unresolved cross-epic interaction conflicts. Resolve or override them in/dso:brainstorm <epic-id>before proceeding to/dso:preplanning." Do NOT produce any planning output. - If
interaction:deferredis NOT present (or tags field is empty/absent): proceed normally.
This is a presence-based check — only block when the tag IS present. Existing epics without the tags field are NOT blocked.
If ticket show fails, treat the tag as absent and proceed (fail-open).
UI Probes Deferred Gate
Before proceeding, check if the epic has a ui_probes:deferred tag:
- Run
.claude/scripts/dso ticket show <epic-id>and check thetagsfield - If
ui_probes:deferredis present in the tags array: HALT immediately. Output: "This epic has unresolved UX probe gaps from a non-interactive brainstorm run. Re-run/dso:brainstorm <epic-id>interactively to complete the UX probe questions, then retry/dso:preplanning." Do NOT produce any planning output. - If
ui_probes:deferredis NOT present (or tags field is empty/absent): proceed normally.
This is a presence-based check — only block when the tag IS present. Existing epics without the tags field are NOT blocked.
If ticket show fails, treat the tag as absent and proceed (fail-open).
Preconditions Entry Gate (/dso:preplanning)
[Instructions for the LLM: Before Phase A Step 1, validate that a brainstorm PRECONDITIONS event exists.
Run: .claude/scripts/dso preconditions-validator.sh <epic_id> brainstorm_complete [--event-file=<path if known>]
(or use preconditions-record.sh invocation from brainstorm; fail-open if script not found)
If exit 0: continue. If exit 1: BLOCK with PRECONDITIONS_GATE_BLOCKED diagnostic.
If exit 2 (not found): BLOCK with "Run /dso:brainstorm first" message.
Fail-open: if preconditions-validator.sh itself is not found (command not found), emit WARN and continue. This gate is depth-agnostic — unknown fields in the event are ignored, not rejected.]
Phase A: Reconciliation (/dso:preplanning)
Step 1: Select and Load Epic (/dso:preplanning)
If <epic-id> was not provided:
[CP1 non-interactive] If PREPLANNING_INTERACTIVE=false and no <epic-id> was provided: log INTERACTIVITY_DEFERRED: preplanning.interactive=false — no epic-id provided and exit with error.
- Run
.claude/scripts/dso ticket list --type=epic - If no open epics exist, report and exit
- Present epics to the user (if more than 5, show first 5 with option to see more)
- Get user selection
Load the epic:
.claude/scripts/dso ticket show <epic-id>
Step 1.5: Complexity Classification & Routing (/dso:preplanning)
Classify the epic's complexity to determine whether full preplanning is warranted, whether the epic should run in --lightweight mode, or whether the epic can skip preplanning entirely and route directly to /dso:implementation-plan.
Skip this step entirely when --lightweight was passed as an explicit argument. An explicit --lightweight flag is a deliberate caller choice (user or /dso:sprint) and must be honored without re-classification — continue to Step 2.
Step 1.5a: Dispatch Complexity Evaluator Agent
Dispatch the dedicated complexity evaluator agent. Read agents/complexity-evaluator.md inline and dispatch as subagent_type: "general-purpose" with model: "haiku". Pass the epic ID as the argument and tier_schema=SIMPLE. (dso:complexity-evaluator is an agent file identifier, NOT a valid subagent_type — the Agent tool only accepts built-in types.)
Agent tool:
subagent_type: "general-purpose"
model: "haiku"
argument: <epic-id>
context:
tier_schema: SIMPLE
success_criteria_count: <count of SC bullet items parsed from the epic description's `## Success Criteria` section>
scenario_survivor_count: <count of scenarios surviving blue team filter from the epic's Planning Intelligence Log, or 0 if no scenario analysis was recorded>
Compute success_criteria_count by parsing the ## Success Criteria section of the epic description (do NOT rely on session memory — the value must be derived from the ticket text). Read scenario_survivor_count from the ### Planning Intelligence Log event in the epic (written by the canonical scrutiny pipeline during brainstorm); use 0 if the pipeline did not run scenario analysis.
If the agent fails or returns malformed JSON, log a warning and fall through to full preplanning (safe fallback — proceed to Step 2 in full mode).
Step 1.5b: Route Based on Classification
Apply the routing table below. Always consult the table — do NOT skip preplanning based on prose heuristics. Only SIMPLE epics bypass preplanning. (tier_schema=SIMPLE is the epic-level vocabulary per agents/complexity-evaluator.md, where the lowest tier is named SIMPLE rather than TRIVIAL.)
Session-signal override (applies before the routing table): If EITHER is true, override to COMPLEX regardless of evaluator output:
success_criteria_count ≥ 7— count from the spec text (do NOT rely on session memory which may be lost after compaction)scenario_survivor_count ≥ 10— read from the Planning Intelligence Log, or re-count from the## Scenario Analysissection
Log the override: "Epic classified as COMPLEX (session-signal override: <reason>) — continuing with full preplanning"
| Classification | scope_certainty | Routing |
|---|---|---|
| SIMPLE | High (always) | Invoke /dso:implementation-plan <epic-id> and exit preplanning |
| MODERATE | High | Continue preplanning in lightweight mode (jump to Lightweight Mode Appendix) |
| MODERATE | Medium | Continue preplanning in lightweight mode (jump to Lightweight Mode Appendix) |
| MODERATE | Low | Promoted to COMPLEX by evaluator |
| COMPLEX | any | Continue preplanning in full mode (proceed to Step 2) |
Rationale: SIMPLE epics route directly to /dso:implementation-plan — the brainstorm dialogue produced task-level detail. MODERATE+High/Medium runs a risk/scope scan and writes structured done definitions before implementation planning. MODERATE+Low is converted to COMPLEX by the evaluator (row listed for completeness). COMPLEX epics require full story decomposition.
Step 1.5c: Apply Routing
Output the classification line and apply the routing in the same response — do not yield to the user:
Epic classified as <TIER> (scope_certainty: <HIGH|MEDIUM|LOW>) — <action>
Then immediately (same response, no pause):
- SIMPLE: Invoke
/dso:implementation-planvia the Skill tool withargs: "<epic-id>". Exit preplanning — do NOT proceed to Step 2. Control returns here only if the invoked skill escalates. - MODERATE (High or Medium): Treat as if
--lightweightwas passed for the remainder of execution; jump to the Lightweight Mode Appendix. Do NOT proceed to Step 2 (lightweight mode skips escalation policy selection anyway). - COMPLEX (or any session-signal override): Continue to Step 2 in full mode.
Step 2: Select Escalation Policy (/dso:preplanning)
[CP2 non-interactive] If PREPLANNING_INTERACTIVE=false: skip AskUserQuestion, default {escalation_policy_label} = "Escalate when blocked" (full text from Phase H Step 2 table), and continue.
Use AskUserQuestion to ask the user which escalation policy should apply to all stories in this epic. Skip this step in --lightweight mode.
- Question: "Which escalation policy should agents apply when working on stories in this epic?"
- Header: "Escalation"
- Options:
- Autonomous — Agents proceed with best judgment at all times. Reasonable assumptions are made and documented. No escalation for uncertainty.
- Escalate when blocked — Agents proceed unless a significant assumption is required to continue — one that could send the implementation in the wrong direction. Escalate only when genuinely blocked without a reasonable inference. All assumptions made without escalating are documented.
- Escalate unless confident — Agents escalate whenever high confidence is absent. "High confidence" means clear evidence from the codebase or ticket context — not inference or reasonable assumption. When in doubt, stop and ask rather than guess.
Store the selected policy label and its full text as {escalation_policy_label} and {escalation_policy_text} for use in Phase H Step 2.
Lightweight Mode Gate (/dso:preplanning)
If --lightweight was passed: jump to the Lightweight Mode Appendix (single authoritative source). Do not continue with Phase A Steps 3–5 or any subsequent phase.
If --lightweight was NOT passed, continue to Phase A Step 3.
Source the planning flag helper now to determine whether External Dependencies processing is enabled:
source "${CLAUDE_PLUGIN_ROOT}/hooks/lib/planning-config.sh"
If is_external_dep_block_enabled returns exit 1 (flag absent or false), set EXTERNAL_DEP_BLOCK_ENABLED=false — Phase B will be skipped. When the function returns exit 0, set EXTERNAL_DEP_BLOCK_ENABLED=true.
Step 3: Audit Existing Children (/dso:preplanning)
Gather all existing child items:
.claude/scripts/dso ticket deps <epic-id>
For each child, run .claude/scripts/dso ticket show <child-id> to read full details.
Step 4: Reconcile Existing Work (/dso:preplanning)
For each existing child:
completed → Keep as-is
in_progress → Review for reuse
pending → Fits new vision? Yes: Keep | No: Modify | Conflict: Delete
For each existing child, classify it:
- Reuse: Child aligns with the epic's success criteria and can be used as-is
- Modify: Child is partially relevant but needs updated description or success criteria
- Delete: Child conflicts with the epic's vision or is redundant
Important: If boundaries are unclear or if existing tasks conflict with the new vision, pause and ask:
- "Tell me more about the intended scope for [Feature]... should it include [X]?"
- "I see existing tasks for [Y]. Should these be absorbed into our new story map or kept separate?"
[CP3 non-interactive] If PREPLANNING_INTERACTIVE=false and scope clarification is required: log INTERACTIVITY_DEFERRED: preplanning.interactive=false — scope clarification required and exit with error.
Step 5: Document Reconciliation Plan (/dso:preplanning)
Before creating new stories, present a reconciliation summary:
| Child ID | Title | Status | Recommendation | Rationale |
|---|---|---|---|---|
| xxx-123 | ... | pending | Reuse | Aligns with Epic criterion 1 |
| xxx-124 | ... | in_progress | Modify | Needs updated success criteria |
| xxx-125 | ... | pending | Delete | Redundant with new story approach |
[CP4 non-interactive] If PREPLANNING_INTERACTIVE=false: auto-apply reconciliation (skip AskUserQuestion); in-progress guard: do NOT auto-apply Delete on in_progress children — skip and log "Skipping Delete for in_progress story <id> — manual review required."; continue.
Use AskUserQuestion to get user approval before proceeding:
- Question: "The reconciliation plan above summarizes how existing children will be handled. Do you approve this plan?"
- Options: ["Approve — proceed with story creation", "Request changes"]
If the user requests changes, iterate on the reconciliation plan and re-present.
Phase B: External Dependencies Reading (/dso:preplanning)
Skip this phase when EXTERNAL_DEP_BLOCK_ENABLED=false.
Purpose
After auditing existing children (Phase A Step 3), read the parent epic's ## External Dependencies block (conforming to ${CLAUDE_PLUGIN_ROOT}/docs/contracts/external-dependencies-block.md) and generate the corresponding child stories.
Reading the Block
Parse the epic's description field for a YAML block under the ## External Dependencies heading. If no block exists, skip this phase entirely and proceed to Phase C.
Validation: For each entry, if confirmation_token_required: true is present alongside a verification_command, log a warning and ignore confirmation_token_required: "Entry <name>: confirmation_token_required is only meaningful when verification_command is absent — ignoring." Do not block story creation.
Idempotency Check
Before creating any story for a block entry, get the epic's current children:
.claude/scripts/dso ticket deps <epic-id>
Parse the children field from the JSON output (not deps or blockers). Check whether any child is already tagged manual:awaiting_user with a title matching the entry's name field. If a match is found, skip creation for that entry to avoid duplicates. Log: "Skipping <name> — existing manual:awaiting_user story already created (idempotency)."
Story Generation
For each entry in the external_dependencies block:
handling: claude_auto entries:
- Create a standard automation story as a child of the epic (same Phase H story creation flow).
- Story title:
"Verify and integrate <name>". - Done definition: verification_command passes and Claude has confirmed access.
handling: user_manual entries:
- Create a story tagged
manual:awaiting_useras a child of the epic. - Story title:
"Complete manual step: <name>". - Done definitions must include:
- The entry's
justificationfield verbatim (if present, explain why the step requires human action). - If
verification_commandis present: the command as the verification step. - If
verification_commandis absent: a confirmation-token prompt usingconfirmation_token_required(iftrue) or a simple acknowledgment (iffalseor absent).
- The entry's
Story Decomposition (/dso:preplanning)
Purpose. The orchestrator MUST NOT draft new stories inline. After reconciliation (Phase A) and external-dependency story generation (Phase B), dispatch the dso:story-decomposer opus sub-agent to produce the new vertical-slice story drafts needed so the collective story set covers every epic Success Criterion. The orchestrator writes the drafts to the tracker in Phase H — this phase only produces the drafts.
Rationale. Inline drafting by the orchestrator has been observed to produce under-specified Done Definitions and to drop SCs through summarization. Dispatching a dedicated opus sub-agent (mirroring the Phase E red-team and the sprint-tier opus arbiter) restores sustained multi-document reasoning for the highest-leverage step in preplanning.
Skip Condition
Skip this phase entirely when running under --lightweight (lightweight mode does not create stories — see the Lightweight Mode Appendix).
Dispatch
Named-agent dispatch invariant: Attempt dispatch via
subagent_type: "dso:<agent-name>"first. Read the agent file (e.g.,agents/<agent-name>.md) and pass its content inline only when the dispatch returns an "Unknown agent" or "agent type not registered" error in the current session. Reading the agent file before the dispatch attempt wastes context tokens, leaks agent internals into the orchestrator window, and inverts the fallback contract. Exception: agents documented as "NOT a validsubagent_typevalue" (e.g.,dso:complexity-evaluator) always usegeneral-purpose+ inline read — no named dispatch to attempt.
Dispatch via subagent_type: "dso:story-decomposer" with model: "opus" passed explicitly (do not rely on the agent frontmatter default — vertical slicing, INVEST-checking, and SC-coverage decomposition require opus, and the explicit param defends against future routing changes that might silently downgrade). If the named type is unregistered in this session, fall back to subagent_type: "general-purpose" with model: "opus" and agents/story-decomposer.md content read inline as the prompt.
Pass the following as task arguments:
{epic-title}: Epic title from Phase A{epic-description}: Epic description from Phase A{epic-success-criteria}: Bullet items from the epic's## Success Criteriasection parsed from the ticket text (do NOT rely on session memory). Number them with stable identifiers in a Markdown list, e.g.:
If the epic has no- sc-1: Users can export reviewed rules as Rego. - sc-2: Review state persists across sessions. - sc-3: An admin can audit who approved each rule.## Success Criteriasection, pass(none — epic has no Success Criteria section)so the agent records the absence indecomposition_notesand returns empty drafts rather than fabricating SCs.{epic-closure-checks}: Bullet items from the epic's## Closure Checkssection parsed from the ticket text (do NOT rely on session memory). Number them with stable identifierscc-1, cc-2, ...analogously to SCs:
If the epic has no- cc-1: The reviewer registry rejects duplicate registrations at startup. - cc-2: All closed child stories have a DSO-Story-Merge trailer reachable from HEAD.## Closure Checkssection, pass(none — epic has no Closure Checks section). The agent will return an empty (or omitted)cc_coverage_planin that case. Stories that validate a CC use the← Validates Closure Check: "<verbatim CC text>"DD annotation form (in addition to or instead of← Satisfies: sc-N).{existing-stories}: The kept-or-modified children from Phase A reconciliation. For each, include id, title, description, current Done Definitions, and considerations. Format as a Markdown list with one entry per story.{external-dep-stories}: Themanual:awaiting_userstories created in Phase B. Same format as{existing-stories}. If Phase B was skipped (flag off or no entries), pass(none).{escalation-policy}: Both the{escalation_policy_label}from Phase A Step 2 and its full text, so the agent can write the label verbatim into every draft'sescalation_policyfield for Phase H.SCRATCH_TICKET_ID: The epic ticket id (the<epic-id>value for this preplanning run) — the sub-agent uses this to namespace the scratch write.SCRATCH_KEY: The literal stringpreplanning:step4:story-decomp-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 (sc_coverage_plan, story_drafts, decomposition_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>" # shim-exempt: sub-agent instruction block
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, "story_drafts": [...]}
^^^ 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 story drafts: sc_coverage_plan: [...], story_drafts: [...]
^^^ 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>
Parse Output (Receipt-Only Contract)
After the sub-agent returns, validate its receipt via receipt-parse.sh before reading any payload:
# Prompt Alignment Finding 1: receipt validation co-located with the read site
PARSE_RESULT=$(printf '%s' "<sub-agent-return-block>" | \
bash "$PLUGIN_SCRIPTS/receipt-parse.sh" preplanning:step4 dso:story-decomposer) # shim-exempt: sub-agent instruction block
PARSE_EXIT=$?
if [ "$PARSE_EXIT" -ne 0 ]; then
# RECEIPT_PARSE_ERROR — halt workflow; structured error already logged to stderr by receipt-parse.sh
# Inline cleanup: remove any partial scratch written before the failure
bash "$PLUGIN_SCRIPTS/ticket-scratch.sh" clear "$SCRATCH_TICKET_ID" "preplanning:step4:story-decomp-draft" 2>/dev/null || true # shim-exempt: sub-agent instruction block
echo "HALT: story decomposer 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
# Extract ticket_id and key from the validated receipt
SCRATCH_TICKET_ID_OUT=$(echo "$PARSE_RESULT" | awk '{print $1}')
SCRATCH_KEY_OUT=$(echo "$PARSE_RESULT" | awk '{print $2}')
# Retrieve the payload from scratch
SCRATCH_RESULT=$(bash "$PLUGIN_SCRIPTS/ticket-scratch.sh" get "$SCRATCH_TICKET_ID_OUT" "$SCRATCH_KEY_OUT") # shim-exempt: sub-agent instruction block
SCRATCH_STATUS=$(echo "$SCRATCH_RESULT" | python3 -c "import json,sys; print(json.load(sys.stdin)['status'])")
# Prompt Alignment Finding 1: SCRATCH_MISS guard co-located with the get call
if [ "$SCRATCH_STATUS" != "hit" ]; then
# SCRATCH_MISS — hard error; do NOT fall back to accepting inline payload # precondition-emit-ok: negation, no degradation event
# Inline cleanup: no payload to remove, but clear any stale key
bash "$PLUGIN_SCRIPTS/ticket-scratch.sh" clear "$SCRATCH_TICKET_ID_OUT" "$SCRATCH_KEY_OUT" 2>/dev/null || true # shim-exempt: sub-agent instruction block
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
DECOMP_JSON=$(echo "$SCRATCH_RESULT" | python3 -c "import json,sys; print(json.load(sys.stdin)['value'])")
The retrieved DECOMP_JSON is a JSON object with sc_coverage_plan, story_drafts, and decomposition_notes. Validate:
sc_coverage_planis an array with one entry per SC passed in. Every entry hassc_id,sc_text,verdict,covering_story_ids,draft_idsfields;gap_summaryis present whenverdict∈ {partial_coverage,uncovered,out_of_scope_for_stories} — must match the agent's field-table condition so a regression that dropsgap_summaryonuncoveredSCs is caught at validation time.story_draftsis an array. Each draft hastemp_id(matching the patterndraft-N),title(user-story-shaped),priority(integer 0–4),description,done_definitions(each ending with← Satisfies: sc-N),depends_on,split_candidate, andescalation_policy.- Every SC with verdict
uncoveredorpartial_coverageis referenced by at least one draft (cross-check: the SC'ssc_idappears in at least one DD's← Satisfies: sc-Nannotation, and at least onedraft_idfor that SC is non-empty). - Every draft's
depends_onreferences either an existing story id or anothertemp_idin this same response — no dangling references.
On model_requirement_unmet: If the scratch payload carries {"story_drafts": [], "sc_coverage_plan": [], "error": "model_requirement_unmet"}, log "Story decomposer model requirement unmet — re-dispatching with explicit model: opus" and retry once via the fallback path (general-purpose + model: "opus" + inline prompt). If the retry also returns model_requirement_unmet, HALT preplanning with diagnostic: "Story decomposition requires opus; configure the environment to allow opus dispatch and re-run /dso:preplanning." Do NOT fall back to inline drafting.
On schema validation failure: HALT preplanning with diagnostic: "Story decomposer returned malformed output; cannot proceed without verified drafts." 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, while Phase E falls through to Phase F on failure? Story decomposition is a correctness gate: every subsequent phase consumes its output, and inline orchestrator drafting (the only available fallback) is the bug class this phase was introduced to eliminate. Phase E's adversarial review is a quality gate with a defined skip path — losing it degrades coverage but does not break downstream phases. The asymmetry is intentional.
Persist the Decomposition Artifact
The full decomposition JSON is already stored in scratch under key preplanning:step4:story-decomp-draft. Post a one-line ticket comment on the epic for visibility:
.claude/scripts/dso ticket comment <epic-id> "Story decomposition: <N> drafts produced. Coverage: <fully> existing / <new_drafts> new / <oos> out-of-scope. Payload stored in scratch key preplanning:step4:story-decomp-draft."
After Phase H creates the story tickets from the drafts, clear the scratch key:
bash "$PLUGIN_SCRIPTS/ticket-scratch.sh" clear "$SCRATCH_TICKET_ID" "preplanning:step4:story-decomp-draft" # shim-exempt: sub-agent instruction block
Feed Downstream Phases
- Phase C scans the union of (existing stories from Phase A) + (external-dep stories from Phase B) + (drafts from this phase). Drafts identified as
split_candidate: truehere are pre-flagged; Phase C may add more. Read drafts fromDECOMP_JSON(already retrieved from scratch above). - Phase E's red-team receives the same draft set and re-audits SC coverage at the cross-story level; the two audits are intentionally redundant — decomposition is the constructive step, the red team is the adversarial check. Pass
DECOMP_JSONdirectly — do not re-read from scratch. - Phase F's Walking Skeleton step uses the
priorityordering produced here as its starting point. Read fromDECOMP_JSON. - Phase H Step 1 creates tickets from the drafts via
.claude/scripts/dso ticket create, copyingtitle,description,done_definitions,considerations,depends_on,priority, andescalation_policyinto the ticket body. Read fromDECOMP_JSON; clear scratch after all tickets are written.
Phase C: Risk & Scope Scan (/dso:preplanning)
Scan all drafted stories (new drafts from the Story Decomposition phase, plus modified children from Phase A, plus external-dep stories from Phase B) as a batch to flag cross-cutting concerns that individual tasks would be too granular to catch. This is a lightweight analysis — no sub-agent dispatch, no scored review, no revision cycles.
Concern Areas
| Area | Reviewer File | What to flag |
|---|---|---|
| Security | docs/reviewers/security.md | New endpoints, data exposure, auth boundaries |
| Performance | docs/reviewers/performance.md | Large data processing, new queries, batch operations |
| Accessibility | docs/reviewers/accessibility.md | New interactive pages, UI flows, form elements |
| Testing | docs/reviewers/testing.md | New LLM interactions, external integrations, complex state |
| Reliability | docs/reviewers/reliability.md | New failure points, external dependencies, data integrity |
| Maintainability | docs/reviewers/maintainability.md | Cross-cutting patterns, shared abstractions, documentation gaps |
Evaluate the full set of stories against all six areas. Examples of flags to raise:
- Security: "Story X exposes a new API — authentication coverage needed"
- Performance: "Story Y processes user uploads — consider batch size and timeout behavior"
- Accessibility: "Story Z adds a new interactive page — WCAG 2.1 AA compliance required"
- Testing: "Stories X and Y introduce a new LLM interaction — ensure mock-compatible interface"
- Reliability: "Story W depends on an external API — consider graceful degradation"
- Maintainability: "Stories X and Z both need similar data validation — consider a shared pattern"
Output
Produce a Risk Register — a flat list of one-line flags, each referencing the affected story IDs:
| # | Area | Stories | Concern |
|---|------|---------|---------|
| 1 | Testing | X, Y | New LLM interaction — ensure mock-compatible interface |
| 2 | Performance | Y | Large file processing — consider timeout behavior |
| 3 | Accessibility | Z | New interactive page — WCAG 2.1 AA compliance |
Flags are added to the affected stories' descriptions as Considerations — context for /dso:implementation-plan to incorporate into task-level acceptance criteria. They are not hard requirements at the story level.
Split Candidates
While scanning, flag stories where scope risk is high — stories where the minimum functional goal (walking skeleton) and the ideal implementation diverge significantly. Common indicators:
- Significant UI work where design may propose an ambitious overhaul
- New architectural patterns where a simpler interim approach could deliver value first
- New infrastructure or integrations where a lightweight version proves the concept
Mark these stories as split candidates. Phase F evaluates whether a Foundation/Enhancement split actually makes sense (see "Foundation/Enhancement Splitting" below).
Phase D: Integration Research (/dso:preplanning)
After story decomposition and risk scanning, research integration capabilities for stories that involve external tools or services. This step surfaces verified constraints while the user is engaged and can redirect.
Qualification
A story qualifies for integration research if it references any of:
- Third-party CLI tools
- External APIs/services
- CI/CD workflow changes
- Infrastructure provisioning
- Data format migrations
- Authentication/credential flows
Research Process
Follow the shared research procedure in prompts/research-process.md (single authoritative source — covers researchFindings dedup, the WebSearch-and-verify steps, per-command contract check for CLI tools, REPLAN_ESCALATE emission, and graceful degradation). This phase is the pre-slicing trigger for that procedure. Track feasibility_cycle_count (state variable exposed for planning-intelligence log consumption) per the shared procedure. # precondition-emit-ok
Skip Condition
If no stories in the plan qualify for integration research, log: "No stories with external integration signals — skipping integration research." and proceed to Phase E.
Phase E: Adversarial Review (branch — ≥3 stories; skipped under --lightweight)
Trigger: Phase C completed and the story map has ≥ 3 stories. If fewer, log "Adversarial review skipped: fewer than 3 stories (<N> stories)." and proceed directly to Phase F. Skipped entirely under --lightweight (lightweight mode does not create stories).
Load: ${CLAUDE_PLUGIN_ROOT}/skills/preplanning/prompts/phase-e-adversarial-review.md and follow it. The phase dispatches dso:red-team-reviewer (opus, passed explicitly via model: "opus") with mode: story_review to (a) audit that every epic Success Criterion is fully covered by the collective story Done Definitions and (b) perform cross-story gap analysis. The red team returns a mandatory sc_coverage_summary block plus a findings array tagged with the 8-value taxonomy_category enum (including sc_coverage_gap). dso:blue-team-filter (sonnet) then triages the findings; surviving findings are applied per the Finding Type table (new_story, modify_done_definition, add_dependency, add_consideration, escalate_to_epic); the full exchange — sc_coverage_summary + red/blue findings — is persisted to $ARTIFACTS_DIR/adversarial-review-<epic-id>.json; and REPLAN_ESCALATE: brainstorm is emitted when a finding escalates to the epic and sprint.max_replan_cycles has not been exhausted.
Re-dispatch loop (b345 block — Phase E remediation loop protocol)
When dso:blue-team-filter returns non-empty findings, the orchestrator enters a remediation loop governed by the shared protocol below. 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 dso:blue-team-filter returns empty findings on any cycle (before reaching MAX_CYCLES), the loop exits immediately and Phase E proceeds to Phase F. 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: brainstorm EXPLANATION:<explanation text>
The upstream for preplanning Phase E is brainstorm (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
Per-cycle artifact append (084d/83d4 block — cycle recorder)
After each cycle's re-dispatch and review, atomically append a cycles[] entry to the adversarial-review artifact using the writer:
python3 "${CLAUDE_PLUGIN_ROOT}/scripts/append_review_cycle.py" \ # shim-exempt: sub-agent instruction block
--artifact "$ARTIFACTS_DIR/adversarial-review-<epic-id>.json" \
--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 that references the artifact path:
.claude/scripts/dso ticket comment <ticket-id> "Remediation cycle <n> recorded at $ARTIFACTS_DIR/adversarial-review-<epic-id>.json"
Single-comment policy (SC5): The ticket comment references the artifact path and does NOT duplicate the cycle body — cycle entries live in the artifact's cycles[] array, not in ticket comments. One comment per cycle; no further detail in the comment body.
Refusal Gate: External Dependencies Block Check (/dso:preplanning)
Skip this gate when EXTERNAL_DEP_BLOCK_ENABLED=false.
Before proceeding to Phase F story decomposition, check whether the parent epic's External Dependencies block adequately covers any externally-shaped Success Criteria.
When to fire
This gate fires when ALL of the following are true:
planning.external_dependency_block_enabledis on (set during Phase B flag check)- The epic has Success Criteria that are externally-shaped (their outcomes are observable only in deployed or external contexts — e.g., "users can log in with OAuth", "emails are delivered", "the API responds to external callers")
Gate check
- Identify externally-shaped SCs from the epic's Success Criteria list (SCs whose pass/fail depends on an external system, credential, or deployed environment).
- For each externally-shaped SC, check whether the parent epic's
## External Dependenciesblock contains an entry covering that dependency. - If ANY externally-shaped SC has no matching block entry (block is missing or the relevant entry is absent or incomplete per the schema in
${CLAUDE_PLUGIN_ROOT}/docs/contracts/external-dependencies-block.md):- HALT decomposition. Do not proceed to Phase F.
- Emit the following diagnostic, naming the specific SC(s) without block coverage:
"Preplanning cannot decompose this epic: the following success criteria are externally-shaped but have no corresponding entry in the External Dependencies block: [list of SC names]. Run
/dso:brainstorm <epic-id>to capture the dependency information, then retry/dso:preplanning."
- If all externally-shaped SCs have valid block entries (or there are no externally-shaped SCs): proceed to Phase F normally.
Behavioral testing note
SKILL.md is a non-executable LLM instruction file. The structural tests verify only that this section heading exists. Behavioral correctness of the refusal logic is probabilistic per Rule 5 (behavioral-testing-standard.md).
Phase F: Walking Skeleton & Vertical Slicing (/dso:preplanning)
Step 1: Identify the Walking Skeleton (/dso:preplanning)
The Walking Skeleton is the absolute minimum end-to-end path required to prove the technical concept.
Ask: "What is the simplest possible flow that demonstrates this feature works?"
Prioritize these stories first - they unblock all downstream work.
Step 2: Apply INVEST Framework (/dso:preplanning)
Each story must satisfy INVEST — Independent, Negotiable, Valuable, Estimable, Small (one sub-agent session), Testable. For any story that fails one principle: add dependencies/split (I), remove implementation details (N), combine with others (V), add context (E), split (S), or add specific acceptance criteria (T).
Step 3: Vertical Slicing (/dso:preplanning)
Focus on functional "slices" of value, not horizontal technical layers.
Good (vertical slice):
- "User can upload a PDF and see extraction results"
Bad (horizontal layer):
- "Create database schema for documents"
- "Build document upload API"
- "Add frontend upload component"
The vertical slice includes all layers necessary to deliver value.
Step 4: Foundation/Enhancement Splitting (/dso:preplanning)
For each story flagged as a split candidate in Phase C, evaluate whether splitting delivers better outcomes than keeping it as a single story.
The question: "Does the minimum that delivers the functional goal differ significantly from the ideal experience or architecture?"
- Foundation: Delivers the functional goal and proves the concept. This IS the walking skeleton slice for the story — it may use simpler approaches, existing patterns, or existing components.
- Enhancement: Invests in the ideal experience — better UX, proper architecture, performance optimization. Depends on Foundation.
Split if:
- The Foundation alone delivers user value (it's a complete vertical slice)
- The Enhancement represents a meaningful scope increase (not just polish)
- Combining both would make the story too large for a single agent session
Don't split if:
- The "Foundation" wouldn't deliver value without the "Enhancement"
- The scope difference is marginal
- The story is already small enough
Examples:
| Story | Foundation | Enhancement |
|---|---|---|
| "User can review extracted rules" | Review page with approve/reject using existing table component | Custom review interface with inline editing, bulk actions, and keyboard shortcuts |
| "System stores extraction results" | Persist results in existing job table with JSON column | Dedicated results table with normalization, indexing, and query optimization |
| "User can export reviewed rules as Rego" | Download button that generates Rego file | Export wizard with format options, preview, and validation |
For each split:
- Create both stories as children of the epic
- Foundation gets higher priority than Enhancement
- Add dependency:
.claude/scripts/dso ticket link <enhancement-id> <foundation-id> depends_on - Both trace to the same epic criterion
Note: dso:ui-designer has its own Pragmatic Scope Splitter (Phase 3 Step 10) that may trigger UI-specific splits during design. If preplanning already split a story, the design agent works within the Foundation story's scope. Enforcement: the splitRole guard in ui-designer-dispatch-protocol.md Section 5 enforces this precedence rule — agent scope_split_proposals are skipped entirely when a splitRole: Foundation or splitRole: Enhancement marker is detected on the story.
Phase G: Story-Level Research (/dso:preplanning)
After Phase F completes story slicing and splitting, perform targeted research for stories where decomposition has revealed knowledge gaps. This phase fires per-story and is distinct from Phase D (Integration Research): Phase D fires for stories with external integration signals (third-party tools, APIs); Phase G fires for any decomposition gap regardless of whether an external integration is involved.
Trigger Conditions
A story qualifies for story-level research if any of the following apply:
- Undocumented API behavior: The story depends on an external API or internal interface whose behavior is undocumented, ambiguous, or not verified in the epic context.
- Assumed data format: The story assumes a data format, schema, or protocol not described in the epic context (e.g., the exact shape of a webhook payload or file format encoding).
- Low agent confidence: Agent confidence on a key implementation decision is low — the approach is unclear, multiple conflicting patterns exist, or the story references technology the agent is uncertain about.
When a story qualifies, follow the Research Process defined in prompts/research-process.md. Record findings in the story spec under a Research Notes section, noting the trigger condition, query summary, source URLs, and key insight for each gap. If research resolves the gap, update the story's done definition or considerations. If research surfaces new risks, flag the story as high-risk for Phase H review.
Graceful Degradation
If WebSearch or WebFetch fails or is unavailable, continue without research rather than blocking the workflow. Log: "Story-level research skipped for <story-id>: WebSearch/WebFetch unavailable." and proceed to Phase H.
Skip Condition
If no stories qualify under the trigger conditions above, log: "No stories with decomposition gaps — skipping story-level research." and proceed to Phase H.
Phase H: Verification & Traceability (/dso:preplanning)
Step 1: Create/Modify Stories in Tickets (/dso:preplanning)
For new stories, create the ticket then immediately write the full story body into the ticket file:
# Assemble the story body from the Story Decomposition artifact and downstream phases:
# - Title, Description, Done Definitions, depends_on, priority, escalation_policy:
# copied verbatim from the matching draft in story-decomposition-<epic-id>.json
# - Considerations: merge the draft's `considerations` with any new flags raised by
# Phase C (Risk & Scope Scan), Phase D (Integration Research), and Phase E
# (Adversarial Review) for this story
# - Foundation/Enhancement: if Phase F split the draft, the `splitRole` field is set
# on the resulting tickets per Phase F Step 4
# Never assemble the body from session memory — always source from the artifact and
# the recorded phase outputs (bug class: inline drafting summarized SCs and DDs past).
STORY_ID=$(.claude/scripts/dso ticket create story "As a [persona], [goal]" --parent=<epic-id> --priority=<priority> -d "$(cat <<'DESCRIPTION'
## Description
**What**: <what the feature or change is>
**Why**: <how this advances the epic's vision>
**Scope**:
- IN: <items explicitly in scope>
- OUT: <items explicitly out of scope>
## Done Definitions
- When this story is complete, <observable outcome 1>
← Satisfies: sc-N (sc-N: "<quoted epic criterion>")
- When this story is complete, <observable outcome 2>
← Satisfies: sc-N (sc-N: "<quoted epic criterion>")
The `sc-N` form is the stable SC identifier assigned in the Story Decomposition phase
artifact (story-decomposition-<epic-id>.json) — copy it verbatim from the draft's
`done_definitions` strings. The parenthetical preserves human-readable context for the
ticket reader. Machine-parsing downstream (SC contradiction check, completion verifier)
keys off `sc-N`, not the quoted text.
## Closure Checks
- (items routed from verifiable-sc-check.md option (c) — durable end-state intent that is not session-verifiable)
## Considerations
- [<Area>] <concern from Risk & Scope Scan>
## Escalation Policy
**Escalation policy**: <verbatim escalation policy text from Phase A Step 2>
DESCRIPTION
)" | tail -1)
Omit the ## Escalation Policy section if the user selected Autonomous in Phase A Step 2. The ticket must never be left as a bare title — always include the structured body at creation time.
For modified stories, use .claude/scripts/dso ticket comment <existing-id> "<updated content>" to record changes.
For stories to delete:
.claude/scripts/dso ticket transition <id> open closed
Step 1b: Write Verify Commands (intent-fidelity-pipeline Phase 2)
After creating each story ticket in Step 1, write the DD-level verify commands from the story-decomposer's verify_commands output:
.claude/scripts/dso ticket set-verify-commands "$STORY_ID" '<verify_commands_json_array>'
Where <verify_commands_json_array> is the verify_commands array from the decomposer's draft for this story. Each entry has {"dd_id": "dd-N", "dd_text": "<DD text>", "command": "<executable command>"}.
This writes a structured VERIFY_COMMANDS event on the ticket, consumed by pre-verifier-execute.sh during sprint Phase F to produce execution traces for the completion verifier.
If the decomposer's output does not include verify_commands for a story (backward compatibility with pre-Phase-2 decompositions), skip this step for that story — pre-verifier-execute.sh handles missing verify commands gracefully.
Step 2: Story Structure Requirements (/dso:preplanning)
Each story must contain:
Title
Format: As a [User/Developer/PO], [goal]
Example: "As a compliance officer, I can see which policies apply to a document"
Description
Include:
- What: The feature or change
- Why: How this advances the epic's vision
- Scope: What's explicitly in and out of this story
Do NOT include: specific file paths, technical implementation details, error codes, or testing requirements. Those belong in /dso:implementation-plan.
Done Definitions
Observable outcomes that bridge the epic's vision to task-level acceptance criteria. Each definition must be:
- Observable: Describes what a user sees, does, or what the system does — not internal implementation
- Measurable:
/dso:implementation-plancan decompose it into tasks with specificVerify:commands - Traceable: Links upward to an epic criterion
Format:
Done Definitions:
- When this story is complete, [observable outcome 1]
← Satisfies: sc-N (sc-N: "[quoted epic criterion]")
- When this story is complete, [observable outcome 2]
← Satisfies: sc-N (sc-N: "[quoted epic criterion]")
The sc-N identifier is assigned by the Story Decomposition phase and recorded
in the artifact story-decomposition-<epic-id>.json. Downstream machine parsing
(SC contradiction check, completion verifier) keys off sc-N; the parenthetical
quoted text is for human readers only.
For done definitions that link to a ## Closure Checks item rather than an epic Success Criterion, use the alternate form:
← Validates Closure Check: "<quoted closure check text>"
← Satisfies: points to ## Success Criteria items in the epic; ← Validates Closure Check: points to ## Closure Checks items. Both forms are accepted traceability annotations — use whichever matches the upstream artifact the DD is satisfying.
Example:
Done Definitions:
- When this story is complete, a user can view all extracted rules
for a document, mark individual rules as approved or rejected,
and see a summary count of pending reviews
← Satisfies: sc-3 (sc-3: "Users can review extracted rules before export")
- When this story is complete, reviewed rules persist across sessions
and are visible when the user returns to the same document
← Satisfies: sc-4 (sc-4: "Review state is preserved")
Example with Closure Check traceability:
Done Definitions:
- When this story is complete, the export summary always lists
which closure checks were verified during preplanning
← Validates Closure Check: "Each epic's closure checks are traceable to a done definition"
Good done definitions (observable outcomes):
- "A user can upload a document and see its classification within 30 seconds"
- "The system processes documents up to 100 pages without timeout"
- "Reviewed rules appear in the exported Rego output"
Bad done definitions (implementation details):
- "The upload endpoint returns a 202 with a job ID"
- "Classification results are stored in the job_results JSON column"
- "The ReviewService calls the ExportService with the approved rule IDs"
SC Contradiction Check
After drafting all done definitions, cross-check each DD against the epic SC it claims to satisfy (← Satisfies:). A done definition contradicts its SC when the DD's observable outcome, if fully achieved, would leave the SC unsatisfied. Common contradiction patterns:
- Bypass annotation: DD plans to "annotate" or "exclude" items from the SC's measurement rather than resolving them (e.g., SC says "zero matches" but DD plans to annotate exceptions)
- Partial coverage: DD addresses only a subset of the SC's scope without noting the remainder
- Scope narrowing: DD redefines the SC's scope to be narrower than what the SC specifies
If a contradiction is found:
- Revise the DD to align with the SC — the SC is authoritative (it was approved by the user in brainstorm)
- If the SC itself is wrong or too strict, flag it for the user:
"SC '<criterion>' may be too strict for this decomposition — <reason>. Revise the SC, or confirm the current SC should be met as written?" - Do NOT proceed with stories whose DDs contradict their SCs — this produces plans that are structurally guaranteed to fail the completion verifier.
Note: A done definition annotated with ← Validates Closure Check: "<text>" is a valid traceability link to the ## Closure Checks section — it is NOT an orphaned annotation and must NOT be flagged as a contradiction. The ← Validates Closure Check: form is an accepted traceability link form alongside ← Satisfies:; both are structurally correct and the SC Contradiction Check applies only to DDs using ← Satisfies:.
TDD Done-of-Done Requirement
Code-change stories (stories that produce or modify source code) must include 'unit tests written and passing for all new or modified logic' as a Done Definition. This is a unit test DoD requirement applied at the story level.
Documentation, research, and other non-code stories are exempt from this requirement — their Done Definitions focus on observable outcomes rather than test coverage.
DD Lexicon Check
Before finalizing each story, check each done definition for precision vocabulary and compound requirements:
for _DD in "${_DONE_DEFINITIONS[@]}"; do
_LEXICON_RESULT=$(echo "$_DD" | bash "${CLAUDE_PLUGIN_ROOT}/scripts/check-dd-lexicon.sh") # shim-exempt: internal orchestration script
_LEXICON_EXIT=$?
if [[ $_LEXICON_EXIT -ne 0 ]]; then
echo "DD LEXICON VIOLATION: $_LEXICON_RESULT"
echo "Split or revise: '$_DD'"
# halt — do not finalize this story
exit 1
fi
done
A compound violation (type compound) means the DD contains AND joining two requirements — split into two separate done definitions. A vague violation (type vague) means the DD uses imprecise language (e.g., properly, correctly, should) — replace with observable, measurable language.
Spec-Fidelity Check
Before finalizing each story, verify PIL criteria against written done definitions:
# Write PIL criteria and story DDs to temp files
_PIL_TMP=$(mktemp /tmp/pil-criteria.XXXXXX)
_DD_TMP=$(mktemp /tmp/story-dds.XXXXXX)
# Populate _PIL_TMP with {"criteria": [...]} JSON from the PIL for this story
# Pop
*Truncated - read the full file at https://github.com/navapbc/digital-service-orchestra/blob/da801cb4330be0aec19dc771bd87d2630cc68902/plugins/dso/skills/preplanning/SKILL.md.*