Planner Hard Skill
Hard-mode wrapper that delegates plan creation to planner-hard-agent subagent.
Extends skill-planner with H8 plan requirements passed in delegation context.
Relationship to base skill: Structurally identical to skill-planner except it
dispatches to planner-hard-agent and passes H8 plan requirements.
Maintenance note: changes to skill-planner postflight should be mirrored here.
Context References
Reference (do not load eagerly):
- Path:
.claude/context/formats/return-metadata-file.md- Metadata file schema - Path:
.claude/context/contracts/reference-grounding.md- H3 contract (loaded by agent) - Path:
.claude/context/patterns/postflight-control.md- Marker file protocol - Path:
.claude/context/patterns/jq-escaping-workarounds.md- jq escaping patterns
Trigger Conditions
This skill activates when:
/plan N --hardis invoked and no extension hard variant exists- Routed here by
command-route-skill.shwitheffort_flag="hard"
Execution Flow
Stage 1: Input Validation
task_data=$(jq -r --argjson num "$task_number" \
'.active_projects[] | select(.project_number == $num)' \
specs/state.json)
if [ -z "$task_data" ]; then
return error "Task $task_number not found"
fi
task_type=$(echo "$task_data" | jq -r '.task_type // "general"')
status=$(echo "$task_data" | jq -r '.status')
project_name=$(echo "$task_data" | jq -r '.project_name')
description=$(echo "$task_data" | jq -r '.description // ""')
if [ "$status" = "completed" ] || [ "$status" = "abandoned" ] || [ "$status" = "expanded" ]; then
return error "Task is in terminal state [$status]"
fi
Stage 1.5: Hard-Mode Cost Note
session_flag_file="/tmp/.hard-mode-notified-${SESSION_ID:-$$}"
if [ ! -f "$session_flag_file" ]; then
echo "[hard-mode] Hard mode active. Cost: ~3-5x standard. Use --hard for deflection-prone or formally complex tasks." >&2
touch "$session_flag_file"
fi
Stage 2 + Stage 3: Preflight Status Update and Postflight Marker
Source skill-base.sh once, then follow @.claude/context/patterns/skill-preflight-flow.md in
full for Stage 2 (preflight status update) and Stage 3 (marker creation):
source .claude/scripts/skill-base.sh
padded_num=$(printf "%03d" "$task_number")
skill_name="skill-planner-hard"
operation="plan"
Marker unification note: this skill's marker previously dropped created and
stop_hook_active (Shape C) — a drift, not a hard-mode design decision. Routing through
skill_create_postflight_marker restores both fields as part of this conversion, matching every
other importer's Shape A schema.
Stage 3a: Read Artifact Number
artifact_number=$(jq -r --argjson num "$task_number" \
'.active_projects[] | select(.project_number == $num) | .next_artifact_number // 1' \
specs/state.json)
if [ "$artifact_number" = "null" ] || [ -z "$artifact_number" ]; then
artifact_number=1
fi
# Plans use (current - 1) to stay in the same round as research
plan_artifact_number=$(( artifact_number - 1 ))
[ "$plan_artifact_number" -lt 1 ] && plan_artifact_number=1
plan_padded=$(printf "%02d" "$plan_artifact_number")
Stage 3b: Find Research Report and Prior Plan
padded_num=$(printf "%03d" "$task_number")
task_dir="specs/${padded_num}_${project_name}"
# Find latest research report
research_path=$(ls "${task_dir}/reports/"*.md 2>/dev/null | sort | tail -1)
# Find latest prior plan
prior_plan_path=$(ls "${task_dir}/plans/"*.md 2>/dev/null | sort | tail -1)
Stage 4a: Memory Retrieval (Auto)
if [ "$clean_flag" != "true" ]; then
memory_context=$(bash .claude/scripts/memory-retrieve.sh "$description" "$task_type" "" 2>/dev/null) || memory_context=""
fi
Literature Detection and Injection (Stage 4a, shared block)
Follow @.claude/context/patterns/lit-stage4a-flow.md in full to resolve --lit and set
lit_context: call literature-lit-flag-resolve.sh, branch on all six directives
(LIT_DISABLED, SUBINDEX_PRESENT, GLOBAL_MISSING, PROMPT_NEEDED, AUTONOMOUS_GLOBAL,
SPARSE_PROMPT_NEEDED), issue the real four-option AskUserQuestion for the two interactive
directives (including the "Search online to ingest" option wired to the STABLE-CONTRACT
literature-ingest-online.sh bridge), apply the two-checkpoint sparse re-prompt after "Use
global corpus now", and take the deterministic [lit:auto] autonomous fallback when
orchestrator_mode == "true" (never calling AskUserQuestion in that case). This skill
supplies the shared block's preconditions: lit_flag, description, and orchestrator_mode
(read from the delegation context; default "false" when unset).
Note: lit_flag is independent of clean_flag. Using --clean --lit suppresses memory retrieval but still injects literature briefing. Literature briefing is gated solely on lit_flag == "true".
Stage 4: Prepare Delegation Context
Pass H8 plan requirements explicitly in the delegation context:
{
"session_id": "{session_id}",
"delegation_depth": 1,
"delegation_path": ["orchestrator", "plan", "skill-planner-hard"],
"timeout": 3600,
"task_context": {
"task_number": N,
"task_name": "{project_name}",
"description": "{description}",
"task_type": "{task_type}"
},
"artifact_number": "{plan_artifact_number}",
"research_path": "{research_path or null}",
"prior_plan_path": "{prior_plan_path or null}",
"effort_flag": "hard",
"model_flag": "{model_flag from command, null if not set}",
"roadmap_path": "specs/ROADMAP.md",
"roadmap_flag": "{roadmap_flag from command}",
"metadata_file_path": "specs/{NNN}_{SLUG}/.return-meta.json",
"hard_mode_requirements": {
"phase_sizing_constraint": "Each phase must be completable in one agent run (~100-500 lines output)",
"postmortem_constraints_required": true,
"preserved_assets_accounting": "Required when prior plan exists",
"source_to_implementation_mapping": "Required for Tier 1/2 reference tasks",
"wave_map_required": true
}
}
Stage 4b: Read Format Specification
format_content=$(cat .claude/context/formats/plan-format.md)
Stage 5: Invoke Subagent
Tool: Agent
Parameters:
- subagent_type: "planner-hard-agent"
- prompt: [task_context, delegation_context, format specification, memory_context, lit_context]
- description: "Create hard-mode implementation plan for task {N}"
If lit_context is non-empty, inject it as a <literature-briefing> block after the memory context and before the task-specific instructions.
Stage 5b: Self-Execution Fallback
Follow @.claude/context/patterns/skill-self-execution-fallback.md in full. This skill's success
status value for that block's write obligation is "planned".
Postflight (ALWAYS EXECUTE)
Stage 6: Parse Subagent Return
metadata_file="specs/${padded_num}_${project_name}/.return-meta.json"
if [ -f "$metadata_file" ] && jq empty "$metadata_file" 2>/dev/null; then
status=$(jq -r '.status' "$metadata_file")
artifact_path=$(jq -r '.artifacts[0].path // ""' "$metadata_file")
artifact_type=$(jq -r '.artifacts[0].type // ""' "$metadata_file")
artifact_summary=$(jq -r '.artifacts[0].summary // ""' "$metadata_file")
memory_candidates=$(jq -c '.memory_candidates // []' "$metadata_file")
postmortem_rules_count=$(jq -r '.postmortem_rules_count // 0' "$metadata_file")
echo "[hard-mode] Postmortem rules added to plan: $postmortem_rules_count" >&2
else
status="failed"
fi
Stage 6a: Validate Artifact Content (non-blocking)
if [ "$status" = "planned" ] && [ -n "$artifact_path" ] && [ -f "$artifact_path" ]; then
bash .claude/scripts/validate-artifact.sh "$artifact_path" plan --fix || true
fi
Stage 6b: Skeleton Task Allocation (H8 escape valve)
Duplicate-with-modification of skill-spawn/SKILL.md Stages 7-11 (do NOT extract a shared
helper — this is a deliberate lower-risk, smaller-diff first cut; revisit only if a second
consumer appears). Structurally identical, EXCEPT the dependency direction is REVERSED (see
below) and the artifact consumed is .skeleton-return.json rather than .spawn-return.json.
Runs only when planner-hard-agent (Stage 4a of planner-hard-agent.md) declared a skeleton
plan. No-ops cleanly otherwise.
skeleton_file="specs/${padded_num}_${project_name}/.skeleton-return.json"
if [ -f "$skeleton_file" ] && jq empty "$skeleton_file" 2>/dev/null; then
new_tasks=$(jq -r '.new_tasks' "$skeleton_file")
task_count=$(jq '.new_tasks | length' "$skeleton_file")
if [ "$task_count" -gt 0 ]; then
dependency_order=$(jq -r '.dependency_order' "$skeleton_file")
# Stage 6b-i: Get next task numbers (mirrors skill-spawn Stage 8)
next_num=$(jq -r '.next_project_number' specs/state.json)
# Stage 6b-ii: Apply topological sort (mirrors skill-spawn Stage 9)
# dependency_order is already topologically sorted (foundational first).
declare -A task_num_map
order_idx=0
for idx in $(echo "$dependency_order" | jq -r '.[]'); do
task_num_map[$idx]=$((next_num + order_idx))
order_idx=$((order_idx + 1))
done
# Stage 6b-iii: Create new task directories (mirrors skill-spawn Stage 10)
for idx in $(echo "$dependency_order" | jq -r '.[]'); do
new_task_num=${task_num_map[$idx]}
new_padded=$(printf "%03d" "$new_task_num")
task_title=$(jq -r --argjson i "$idx" '.new_tasks[$i].title' "$skeleton_file")
task_slug=$(echo "$task_title" | tr '[:upper:]' '[:lower:]' | tr ' ' '_' | sed 's/[^a-z0-9_]//g')
mkdir -p "specs/${new_padded}_${task_slug}/reports"
done
# Stage 6b-iv: Update state.json with new tasks (mirrors skill-spawn Stage 11), BUT with the
# SETTLED REVERSED dependency direction: each follow-up task's `dependencies` includes the
# SKELETON (current) task number, NOT sibling new_tasks that gate it the spawn-agent way.
# This is the inverse of skill-spawn Stage 13 -- copying that step verbatim here would wire
# dependencies backwards and leave the skeleton task perpetually [BLOCKED].
for idx in $(echo "$dependency_order" | jq -r '.[]'); do
new_task_num=${task_num_map[$idx]}
task_title=$(jq -r --argjson i "$idx" '.new_tasks[$i].title' "$skeleton_file")
task_desc=$(jq -r --argjson i "$idx" '.new_tasks[$i].description' "$skeleton_file")
task_effort=$(jq -r --argjson i "$idx" '.new_tasks[$i].effort' "$skeleton_file")
task_type_new=$(jq -r --argjson i "$idx" '.new_tasks[$i].task_type' "$skeleton_file")
task_slug=$(echo "$task_title" | tr '[:upper:]' '[:lower:]' | tr ' ' '_' | sed 's/[^a-z0-9_]//g')
# REVERSED: dependencies = [skeleton_task_number] (the current task), not the spawn-agent
# sibling-index resolution. The skeleton task's own `dependencies` field is untouched --
# there is no Stage 13-equivalent "update parent task dependencies" step here.
resolved_deps="[$task_number]"
bash .claude/scripts/state-write.sh \
'.active_projects += [{
"project_number": $num,
"project_name": $name,
"status": "not_started",
"task_type": $lang,
"description": $desc,
"effort": $effort,
"parent_task": $parent,
"dependencies": $deps,
"created": $ts,
"last_updated": $ts,
"artifacts": []
}]' \
--session-id "$session_id" \
--arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
--argjson num "$new_task_num" \
--arg name "$task_slug" \
--arg desc "$task_desc" \
--arg effort "$task_effort" \
--arg lang "$task_type_new" \
--argjson deps "$resolved_deps" \
--argjson parent "$task_number"
done
# Update next_project_number
bash .claude/scripts/state-write.sh \
'.next_project_number = $next' \
--session-id "$session_id" \
--argjson next "$((next_num + task_count))"
# Stage 6b-v: Record follow-up task numbers on the skeleton (current) task's plan_metadata
# (skeleton: true, follow_up_tasks: [...]) per plan-format.md's schema (Phase 3).
follow_up_task_nums="[]"
for idx in $(echo "$dependency_order" | jq -r '.[]'); do
follow_up_task_nums=$(echo "$follow_up_task_nums" | jq --argjson n "${task_num_map[$idx]}" '. + [$n]')
done
# Stage 6b-vi: fold --regen-todo -- this write is immediately followed by nothing but the
# TODO.md regen, matching the review.md `.active_goal` precedent.
bash .claude/scripts/state-write.sh \
'(.active_projects[] | select(.project_number == $num) | .plan_metadata) =
((.active_projects[] | select(.project_number == $num) | .plan_metadata) // {} +
{"skeleton": true, "follow_up_tasks": $follow_ups})' \
--session-id "$session_id" \
--argjson num "$task_number" --argjson follow_ups "$follow_up_task_nums" \
--regen-todo
fi
fi
Stage 6c: Placeholder-Token Substitution Pass
Immediately after Stage 6b's allocation. Resolves every {{FOLLOWUP:i}} token written by
planner-hard-agent in the just-written plan file to the concrete allocated task number from
Stage 6b's task_num_map. This step has no skill-spawn equivalent (spawn-agent never emits
forward-reference tokens into a sibling artifact).
Guards to a clean no-op when .skeleton-return.json is absent (non-skeleton plans) — the plan
file is left completely untouched in that case.
if [ -f "$skeleton_file" ] && [ "$task_count" -gt 0 ] && [ -n "$artifact_path" ] && [ -f "$artifact_path" ]; then
# Single text-substitution pass: replace {{FOLLOWUP:i}} with the real allocated task number.
# Covers both the plan overview prose and the `## Planned Strategic Sorries` table's
# `Follow-Up Task` column -- both are plain textual occurrences of the same token.
for idx in $(echo "$dependency_order" | jq -r '.[]'); do
token="{{FOLLOWUP:${idx}}}"
real_num="${task_num_map[$idx]}"
sed -i "s/${token//\//\\/}/${real_num}/g" "$artifact_path"
done
# Verify no unresolved tokens remain (defensive check, non-fatal)
if grep -q '{{FOLLOWUP:' "$artifact_path" 2>/dev/null; then
echo "[hard-mode] Warning: unresolved {{FOLLOWUP:i}} token(s) remain in $artifact_path after substitution pass" >&2
fi
echo "[hard-mode] Skeleton plan: allocated ${task_count} follow-up task(s), substituted placeholder tokens" >&2
fi
Stage 7: Update Task Status (Postflight)
Follow @.claude/context/patterns/skill-postflight-flow.md's Stage 7 (postflight status update):
skill_postflight_update "$task_number" "$operation" "$session_id" "$status"
Stage 7a: Propagate Memory Candidates
Was a Same as skill-planner Stage 7a pattern prose cross-reference pointing at a target that
did not exist until skill-planner gained a real Stage 7a — drifted and functionally broken, not
cosmetic. Now a real import of @.claude/context/patterns/skill-postflight-flow.md's Stage 7a,
resolving to the identical shared block skill-planner itself uses:
skill_propagate_memory_candidates "$task_number" "$memory_candidates" "$session_id"
Stage 8: Link Artifacts
Follow @.claude/context/patterns/skill-postflight-flow.md's Stage 8 (artifact linking):
field_name='**Plan**'
next_field='**Description**'
skill_link_artifacts "$task_number" "$artifact_path" "$artifact_type" "$artifact_summary" \
"$field_name" "$next_field" "$session_id"
Performs the two-step jq pattern internally (Issue #1132-safe) and regenerates TODO.md when
artifact_path is non-empty.
Stage 8a: Lifecycle TTS Notification
Follow @.claude/context/patterns/skill-postflight-flow.md's Stage 8a (TTS notify):
skill_lifecycle_notify "$status"
Stage 9: Cleanup
Follow @.claude/context/patterns/skill-postflight-flow.md's Stage 9 (cleanup):
skill_cleanup "$padded_num" "$project_name"
MUST NOT (Postflight Boundary)
After the agent returns, this skill MUST NOT:
- Edit source files - All planning work is done by agent
- Run build/test commands - Verification is done by agent
- Use research tools - Web/codebase search is for agent use only
- Analyze task requirements - Analysis is agent work
- Write plan files - Artifact creation is agent work
The postflight phase is LIMITED TO:
- Reading agent metadata file
- Updating status via
update-task-status.sh(handles state.json + TODO.md atomically) - Linking artifacts in state.json
- Git commit
- Cleanup of temp/marker files
Reference: @.claude/context/standards/postflight-tool-restrictions.md