feat(epic-execute): bound design-phase context and persist plans

Design phase improvements #1 and #2:

#1 Bounded context:
- Pass architecture.md by path instead of embedding full contents
  (the main unbounded size risk in this prompt)
- Cap decision-log context at last 20KB (matches dev phase)
- Add log_prompt_size guard, consistent with other phases
- Replace hardcoded JS/TS exploration hints with language-agnostic guidance

#2 File persistence:
- Add DESIGN_DIR config and persist each plan to <DESIGN_DIR>/<story>-design.md
- build_design_context_for_dev falls back to the persisted file when the
  in-memory plan is empty, so resumed runs keep their design context

Story file remains inlined (small, bounded, needed in full by the planner).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Caleb 2026-06-03 05:57:58 -05:00
parent cf03b6f50d
commit 8a41477ae0
2 changed files with 61 additions and 17 deletions

View File

@ -32,21 +32,30 @@ execute_design_phase() {
log ">>> DESIGN PHASE: $story_id" log ">>> DESIGN PHASE: $story_id"
# Story is inlined (small, bounded, and the planner needs it in full)
local story_contents=$(cat "$story_file") local story_contents=$(cat "$story_file")
# Load architecture file if available # Locate the architecture file but pass it by PATH, not embedded contents.
local arch_contents="" # architecture.md is the main unbounded size risk in this prompt.
local arch_file=""
for search_path in "$PROJECT_ROOT/docs/architecture.md" "$PROJECT_ROOT/docs/architecture/architecture.md" "$PROJECT_ROOT/architecture.md"; do for search_path in "$PROJECT_ROOT/docs/architecture.md" "$PROJECT_ROOT/docs/architecture/architecture.md" "$PROJECT_ROOT/architecture.md"; do
if [ -f "$search_path" ]; then if [ -f "$search_path" ]; then
arch_contents=$(cat "$search_path") arch_file="$search_path"
break break
fi fi
done done
# Load previous decisions for context # Load previous decisions for context, bounded to the last 20KB
# (matches the dev phase; the decision log grows across the epic).
local decision_context="" local decision_context=""
if type get_decision_log_context >/dev/null 2>&1; then if type get_decision_log_context >/dev/null 2>&1; then
decision_context=$(get_decision_log_context) decision_context=$(get_decision_log_context)
local dec_size
dec_size=$(get_byte_size "$decision_context")
if [ "$dec_size" -gt 20000 ]; then
decision_context=$(printf '%s' "$decision_context" | tail -c 20000)
[ "$VERBOSE" = true ] && log_warn "Design: decision log truncated to last 20KB"
fi
fi fi
local design_prompt="You are a senior developer planning the implementation of a story. local design_prompt="You are a senior developer planning the implementation of a story.
@ -74,9 +83,7 @@ $story_contents
## Architecture Reference ## Architecture Reference
<architecture> **Read the architecture document at:** ${arch_file:-"(none found)"}
$arch_contents
</architecture>
## Previous Decisions in This Epic ## Previous Decisions in This Epic
@ -84,15 +91,12 @@ $arch_contents
$decision_context $decision_context
</decision-context> </decision-context>
## Exploration Commands ## Exploration
First, explore the codebase to understand existing patterns: First, explore the codebase to understand existing patterns and conventions
\`\`\`bash before planning. Use the repository's own structure and language to guide you
# Find similar implementations (e.g. inspect the relevant source directories, find files similar to what this
find . -type f -name \"*.ts\" -o -name \"*.js\" | head -20 story touches, and follow existing patterns rather than introducing new ones).
# Check project structure
ls -la src/ 2>/dev/null || ls -la
\`\`\`
## Required Output ## Required Output
@ -137,6 +141,9 @@ Be specific and concrete. This plan will guide the implementation phase.
After outputting the design block, output exactly: After outputting the design block, output exactly:
DESIGN COMPLETE: $story_id" DESIGN COMPLETE: $story_id"
# Log prompt size in verbose mode (consistent with other phases)
log_prompt_size "$design_prompt" "design-phase"
if [ "$DRY_RUN" = true ]; then if [ "$DRY_RUN" = true ]; then
echo "[DRY RUN] Would execute design phase for $story_id" echo "[DRY RUN] Would execute design phase for $story_id"
return 0 return 0
@ -151,6 +158,10 @@ DESIGN COMPLETE: $story_id"
LAST_DESIGN=$(echo "$result" | sed -n '/DESIGN START/,/DESIGN END/p') LAST_DESIGN=$(echo "$result" | sed -n '/DESIGN START/,/DESIGN END/p')
if [ -n "$LAST_DESIGN" ]; then if [ -n "$LAST_DESIGN" ]; then
# Persist the plan to a per-story file so the dev phase can read it
# even after a resume (when the in-memory LAST_DESIGN is empty).
persist_design "$story_id" "$LAST_DESIGN"
# Save to decision log # Save to decision log
if type append_to_decision_log >/dev/null 2>&1; then if type append_to_decision_log >/dev/null 2>&1; then
append_to_decision_log "DESIGN" "$story_id" "$LAST_DESIGN" append_to_decision_log "DESIGN" "$story_id" "$LAST_DESIGN"
@ -164,6 +175,27 @@ DESIGN COMPLETE: $story_id"
fi fi
} }
# Persist a design plan to a per-story file under DESIGN_DIR.
# Arguments:
# $1 - story_id
# $2 - design content
persist_design() {
local story_id="$1"
local content="$2"
if [ -z "${DESIGN_DIR:-}" ]; then
return 0
fi
mkdir -p "$DESIGN_DIR" 2>/dev/null || true
local design_file="$DESIGN_DIR/${story_id}-design.md"
if printf '%s\n' "$content" > "$design_file" 2>/dev/null; then
[ "$VERBOSE" = true ] && log "Design plan saved: $design_file"
else
log_warn "Failed to persist design plan: $design_file"
fi
}
# Get the last design for inclusion in dev phase prompt # Get the last design for inclusion in dev phase prompt
# Returns the design output or empty string if not available # Returns the design output or empty string if not available
get_last_design() { get_last_design() {
@ -175,7 +207,17 @@ get_last_design() {
build_design_context_for_dev() { build_design_context_for_dev() {
local story_id="$1" local story_id="$1"
if [ -z "$LAST_DESIGN" ]; then # Prefer the in-memory plan; fall back to the persisted file so a resumed
# run (where LAST_DESIGN is empty) still gets the design context.
local design="$LAST_DESIGN"
if [ -z "$design" ] && [ -n "${DESIGN_DIR:-}" ]; then
local design_file="$DESIGN_DIR/${story_id}-design.md"
if [ -f "$design_file" ]; then
design=$(cat "$design_file")
fi
fi
if [ -z "$design" ]; then
echo "" echo ""
return return
fi fi
@ -186,7 +228,7 @@ build_design_context_for_dev() {
The following design was created in the planning phase. Follow this plan: The following design was created in the planning phase. Follow this plan:
<design-plan> <design-plan>
$LAST_DESIGN $design
</design-plan> </design-plan>
### Implementation Guidelines Based on Design ### Implementation Guidelines Based on Design

View File

@ -139,6 +139,8 @@ SPRINTS_DIR="$PROJECT_ROOT/docs/sprints"
EPICS_DIR="$PROJECT_ROOT/docs/epics" EPICS_DIR="$PROJECT_ROOT/docs/epics"
UAT_DIR="$PROJECT_ROOT/docs/uat" UAT_DIR="$PROJECT_ROOT/docs/uat"
LOGS_DIR="$SPRINT_ARTIFACTS_DIR/logs" LOGS_DIR="$SPRINT_ARTIFACTS_DIR/logs"
# Per-story design plans (persisted so dev phase can read them after resume)
DESIGN_DIR="$SPRINT_ARTIFACTS_DIR/design"
# Temporary log file during execution - will be copied to LOGS_DIR on completion # Temporary log file during execution - will be copied to LOGS_DIR on completion
LOG_FILE="/tmp/bmad-epic-execute-$$.log" LOG_FILE="/tmp/bmad-epic-execute-$$.log"