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"
# Story is inlined (small, bounded, and the planner needs it in full)
local story_contents=$(cat "$story_file")
# Load architecture file if available
local arch_contents=""
# Locate the architecture file but pass it by PATH, not embedded 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
if [ -f "$search_path" ]; then
arch_contents=$(cat "$search_path")
arch_file="$search_path"
break
fi
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=""
if type get_decision_log_context >/dev/null 2>&1; then
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
local design_prompt="You are a senior developer planning the implementation of a story.
@ -74,9 +83,7 @@ $story_contents
## Architecture Reference
<architecture>
$arch_contents
</architecture>
**Read the architecture document at:** ${arch_file:-"(none found)"}
## Previous Decisions in This Epic
@ -84,15 +91,12 @@ $arch_contents
$decision_context
</decision-context>
## Exploration Commands
## Exploration
First, explore the codebase to understand existing patterns:
\`\`\`bash
# Find similar implementations
find . -type f -name \"*.ts\" -o -name \"*.js\" | head -20
# Check project structure
ls -la src/ 2>/dev/null || ls -la
\`\`\`
First, explore the codebase to understand existing patterns and conventions
before planning. Use the repository's own structure and language to guide you
(e.g. inspect the relevant source directories, find files similar to what this
story touches, and follow existing patterns rather than introducing new ones).
## Required Output
@ -137,6 +141,9 @@ Be specific and concrete. This plan will guide the implementation phase.
After outputting the design block, output exactly:
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
echo "[DRY RUN] Would execute design phase for $story_id"
return 0
@ -151,6 +158,10 @@ DESIGN COMPLETE: $story_id"
LAST_DESIGN=$(echo "$result" | sed -n '/DESIGN START/,/DESIGN END/p')
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
if type append_to_decision_log >/dev/null 2>&1; then
append_to_decision_log "DESIGN" "$story_id" "$LAST_DESIGN"
@ -164,6 +175,27 @@ DESIGN COMPLETE: $story_id"
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
# Returns the design output or empty string if not available
get_last_design() {
@ -175,7 +207,17 @@ get_last_design() {
build_design_context_for_dev() {
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 ""
return
fi
@ -186,7 +228,7 @@ build_design_context_for_dev() {
The following design was created in the planning phase. Follow this plan:
<design-plan>
$LAST_DESIGN
$design
</design-plan>
### Implementation Guidelines Based on Design

View File

@ -139,6 +139,8 @@ SPRINTS_DIR="$PROJECT_ROOT/docs/sprints"
EPICS_DIR="$PROJECT_ROOT/docs/epics"
UAT_DIR="$PROJECT_ROOT/docs/uat"
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
LOG_FILE="/tmp/bmad-epic-execute-$$.log"