Skip to content

Commit eb9f69b

Browse files
jwaldripclaude
andcommitted
feat(plugin): discovery scratchpad, design subagents, hybrid change strategy
Three process improvements based on production feedback: 1. Discovery scratchpad (discovery.md): Persist elaboration findings to .ai-dlc/{slug}/discovery.md during Phase 2.5 domain discovery. Offloads detailed findings from context to disk, keeping the context window lean. Injected into subagent context (section headers) and session start (section count indicator). Copied into intent worktree in Phase 6. 2. Design analysis via subagents: Delegate Figma/design analysis to general-purpose subagents during elaboration to avoid flooding the main context window with design data. One subagent per design file, spawned in parallel with codebase exploration. 3. Hybrid per-unit change strategy: A foundational unit can use "unit" strategy (own PR to main) while remaining units use "intent" strategy (merge to intent branch). Adds parse_unit_change_strategy() to dag.sh, updates advance/construct merge logic, and adds hybrid-aware integrator skip checks. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 8359fa0 commit eb9f69b

File tree

7 files changed

+298
-14
lines changed

7 files changed

+298
-14
lines changed

plugin/hooks/inject-context.sh

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,17 @@ if [ -n "$INTENT_DIR" ] && [ -f "${INTENT_DIR}/completion-criteria.md" ]; then
381381
echo ""
382382
fi
383383

384+
# Show discovery.md availability indicator
385+
if [ -n "$INTENT_DIR" ] && [ -f "${INTENT_DIR}/discovery.md" ]; then
386+
DISCOVERY_COUNT=$(grep -cE '^## ' "${INTENT_DIR}/discovery.md" 2>/dev/null || echo "0")
387+
if [ "$DISCOVERY_COUNT" -gt 0 ]; then
388+
echo "### Discovery Log"
389+
echo ""
390+
echo "**${DISCOVERY_COUNT} sections** of elaboration findings available in \`.ai-dlc/${INTENT_SLUG}/discovery.md\`"
391+
echo ""
392+
fi
393+
fi
394+
384395
# Load and display current plan (from cached values)
385396
PLAN="${KEEP_VALUES[current-plan.md]}"
386397
if [ -n "$PLAN" ]; then

plugin/hooks/subagent-context.sh

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,20 @@ if [ -f "${INTENT_DIR}/completion-criteria.md" ]; then
116116
echo ""
117117
fi
118118

119+
# Inject discovery.md section headers (keep subagent context lean)
120+
if [ -f "${INTENT_DIR}/discovery.md" ]; then
121+
DISCOVERY_HEADERS=$(grep -E '^## ' "${INTENT_DIR}/discovery.md" 2>/dev/null || true)
122+
if [ -n "$DISCOVERY_HEADERS" ]; then
123+
echo "### Discovery Log"
124+
echo ""
125+
echo "Elaboration findings available in \`.ai-dlc/${INTENT_SLUG}/discovery.md\`:"
126+
echo ""
127+
echo "$DISCOVERY_HEADERS"
128+
echo ""
129+
echo "*Read the full file for detailed findings.*"
130+
echo ""
131+
fi
132+
fi
119133

120134
# Source DAG library if available
121135
DAG_LIB="${CLAUDE_PLUGIN_ROOT}/lib/dag.sh"

plugin/lib/dag.sh

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,35 @@ parse_unit_branch() {
109109
_yaml_get_simple "branch" "" < "$unit_file"
110110
}
111111

112+
# Parse per-unit change strategy from nested git: { change_strategy: ... } frontmatter
113+
# Usage: parse_unit_change_strategy <unit_file>
114+
# Returns the strategy value if set, empty string if not
115+
parse_unit_change_strategy() {
116+
local unit_file="$1"
117+
[ ! -f "$unit_file" ] && { echo ""; return; }
118+
local in_frontmatter=false in_git=false value=""
119+
while IFS= read -r line; do
120+
[[ "$line" == "---" ]] && { $in_frontmatter && break || in_frontmatter=true; continue; }
121+
$in_frontmatter || continue
122+
if [[ "$line" =~ ^git:\ *$ ]]; then
123+
in_git=true
124+
continue
125+
fi
126+
if $in_git; then
127+
if [[ "$line" =~ ^[[:space:]]+change_strategy:\ *(.*)$ ]]; then
128+
value="${BASH_REMATCH[1]}"
129+
value="${value#\"}"
130+
value="${value%\"}"
131+
value="${value#\'}"
132+
value="${value%\'}"
133+
break
134+
fi
135+
[[ ! "$line" =~ ^[[:space:]] ]] && in_git=false
136+
fi
137+
done < "$unit_file"
138+
echo "$value"
139+
}
140+
112141
# Check if all dependencies of a unit are completed
113142
# Returns 0 (true) if all deps completed, 1 (false) otherwise
114143
# Usage: are_deps_completed <intent_dir> <unit_file>

plugin/skills/advance/SKILL.md

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,16 @@ INTENT_DIR=".ai-dlc/${INTENT_SLUG}"
108108
CONFIG=$(get_ai_dlc_config "$INTENT_DIR")
109109
AUTO_MERGE=$(echo "$CONFIG" | jq -r '.auto_merge // "true"')
110110
AUTO_SQUASH=$(echo "$CONFIG" | jq -r '.auto_squash // "false"')
111-
CHANGE_STRATEGY=$(echo "$CONFIG" | jq -r '.change_strategy // "unit"')
112111
DEFAULT_BRANCH=$(echo "$CONFIG" | jq -r '.default_branch')
113112

113+
# Resolve effective change strategy: per-unit override takes priority over intent-level
114+
source "${CLAUDE_PLUGIN_ROOT}/lib/dag.sh"
115+
UNIT_CHANGE_STRATEGY=""
116+
if [ -n "$CURRENT_UNIT" ] && [ -f "$INTENT_DIR/${CURRENT_UNIT}.md" ]; then
117+
UNIT_CHANGE_STRATEGY=$(parse_unit_change_strategy "$INTENT_DIR/${CURRENT_UNIT}.md")
118+
fi
119+
CHANGE_STRATEGY="${UNIT_CHANGE_STRATEGY:-$(echo "$CONFIG" | jq -r '.change_strategy // "unit"')}"
120+
114121
UNIT_SLUG="${CURRENT_UNIT#unit-}"
115122
UNIT_BRANCH="ai-dlc/${INTENT_SLUG}/${UNIT_SLUG}"
116123

@@ -176,10 +183,26 @@ if (dagSummary.allComplete) {
176183
// ALL UNITS COMPLETE - Check if integrator should run
177184
// Skip integrator for:
178185
// - Single-unit intents (reviewer already validated it)
179-
// - change_strategy "unit" (each unit reviewed individually via per-unit MR)
180-
const unitCount = dagSummary.totalCount;
181-
const changeStrategy = config.change_strategy || "unit";
182-
const skipIntegrator = unitCount <= 1 || changeStrategy === "unit";
186+
// - ALL units effectively use "unit" strategy (each reviewed individually via per-unit MR)
187+
// Hybrid check: iterate all units to see if any use non-unit strategy
188+
```
189+
190+
```bash
191+
source "${CLAUDE_PLUGIN_ROOT}/lib/dag.sh"
192+
ALL_UNIT_STRATEGY=true
193+
for unit_file in "$INTENT_DIR"/unit-*.md; do
194+
[ -f "$unit_file" ] || continue
195+
UNIT_CS=$(parse_unit_change_strategy "$unit_file")
196+
EFFECTIVE_CS="${UNIT_CS:-$(echo "$CONFIG" | jq -r '.change_strategy // "unit"')}"
197+
[ "$EFFECTIVE_CS" != "unit" ] && { ALL_UNIT_STRATEGY=false; break; }
198+
done
199+
UNIT_COUNT=$(ls -1 "$INTENT_DIR"/unit-*.md 2>/dev/null | wc -l)
200+
SKIP_INTEGRATOR=false
201+
[ "$UNIT_COUNT" -le 1 ] && SKIP_INTEGRATOR=true
202+
[ "$ALL_UNIT_STRATEGY" = "true" ] && SKIP_INTEGRATOR=true
203+
```
204+
205+
```javascript
183206
if (!skipIntegrator && !state.integratorComplete) {
184207
// Spawn integrator on the intent branch
185208
// See Step 2e below

plugin/skills/construct/SKILL.md

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,10 @@ han keep save iteration.json "$STATE"
351351
AGENT_TEAMS_ENABLED="${CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS:-}"
352352
CHANGE_STRATEGY=$(han parse yaml "git.change_strategy" -r --default "unit" < "$INTENT_DIR/intent.md")
353353

354-
# Unit strategy always uses Sequential path (subagent delegation, no team orchestration)
354+
# Pure unit strategy always uses Sequential path (subagent delegation, no team orchestration).
355+
# Hybrid strategy (intent-level "intent" + some units overriding to "unit") keeps Teams enabled —
356+
# the intent-level strategy drives orchestration. Per-unit overrides are resolved at merge time
357+
# by /advance, not at spawn time.
355358
if [ "$CHANGE_STRATEGY" = "unit" ]; then
356359
AGENT_TEAMS_ENABLED=""
357360
fi
@@ -625,18 +628,50 @@ Task({
625628

626629
a. Mark unit as completed: `update_unit_status "$UNIT_FILE" "completed"`
627630
b. Remove unit from `unitStates`
628-
c. Merge unit branch into intent branch:
631+
c. Merge or PR based on effective change strategy:
629632

630633
```bash
631-
# Merge unit branch into intent branch
634+
# Determine merge behavior based on per-unit or intent-level change strategy
632635
source "${CLAUDE_PLUGIN_ROOT}/lib/config.sh"
636+
source "${CLAUDE_PLUGIN_ROOT}/lib/dag.sh"
633637
INTENT_DIR=".ai-dlc/${INTENT_SLUG}"
634638
CONFIG=$(get_ai_dlc_config "$INTENT_DIR")
635639
AUTO_MERGE=$(echo "$CONFIG" | jq -r '.auto_merge // "true"')
636640
AUTO_SQUASH=$(echo "$CONFIG" | jq -r '.auto_squash // "false"')
641+
DEFAULT_BRANCH=$(echo "$CONFIG" | jq -r '.default_branch')
642+
643+
# Check per-unit change strategy override
644+
UNIT_FILE="$INTENT_DIR/${UNIT_NAME}.md"
645+
UNIT_CS=$(parse_unit_change_strategy "$UNIT_FILE")
646+
EFFECTIVE_CS="${UNIT_CS:-$(echo "$CONFIG" | jq -r '.change_strategy // "unit"')}"
647+
UNIT_BRANCH="ai-dlc/${INTENT_SLUG}/${UNIT_SLUG}"
648+
649+
if [ "$EFFECTIVE_CS" = "unit" ]; then
650+
# Per-unit strategy: create PR to default branch (same as advance/SKILL.md unit path)
651+
git push -u origin "$UNIT_BRANCH" 2>/dev/null || true
652+
653+
UNIT_TICKET=$(han parse yaml ticket -r --default "" < "$UNIT_FILE" 2>/dev/null || echo "")
654+
TICKET_LINE=""
655+
[ -n "$UNIT_TICKET" ] && TICKET_LINE="Closes ${UNIT_TICKET}"
656+
657+
gh pr create \
658+
--base "$DEFAULT_BRANCH" \
659+
--head "$UNIT_BRANCH" \
660+
--title "unit: ${UNIT_NAME}" \
661+
--body "## Unit: ${UNIT_NAME}
662+
663+
Part of intent: ${INTENT_SLUG}
664+
665+
${TICKET_LINE}
666+
667+
---
668+
*Built with [AI-DLC](https://ai-dlc.dev)*" 2>/dev/null || echo "PR may already exist for $UNIT_BRANCH"
637669

638-
if [ "$AUTO_MERGE" = "true" ]; then
639-
UNIT_BRANCH="ai-dlc/${INTENT_SLUG}/${UNIT_SLUG}"
670+
WORKTREE_PATH="${PROJECT_ROOT}/.ai-dlc/worktrees/${INTENT_SLUG}-${UNIT_SLUG}"
671+
[ -d "$WORKTREE_PATH" ] && git worktree remove "$WORKTREE_PATH"
672+
673+
elif [ "$AUTO_MERGE" = "true" ]; then
674+
# Intent strategy: merge unit branch into intent branch (existing behavior)
640675
git checkout "ai-dlc/${INTENT_SLUG}/main"
641676

642677
if [ "$AUTO_SQUASH" = "true" ]; then
@@ -698,15 +733,27 @@ UNIT_COUNT=$(ls -1 "$INTENT_DIR"/unit-*.md 2>/dev/null | wc -l)
698733

699734
Skip the integrator if:
700735
- Only one unit (the reviewer already validated it)
701-
- `change_strategy` is `unit` (each unit reviewed individually via per-unit MR)
736+
- ALL units effectively use `unit` strategy (each unit reviewed individually via per-unit MR)
737+
738+
Note: In hybrid mode (intent-level `intent` + some units overriding to `unit`), the integrator still runs because non-unit units merge into the intent branch and need integration verification.
702739

703740
```bash
704741
source "${CLAUDE_PLUGIN_ROOT}/lib/config.sh"
742+
source "${CLAUDE_PLUGIN_ROOT}/lib/dag.sh"
705743
CONFIG=$(get_ai_dlc_config "$INTENT_DIR")
706-
CHANGE_STRATEGY=$(echo "$CONFIG" | jq -r '.change_strategy // "unit"')
744+
745+
# Hybrid-aware check: iterate all units to determine if ALL effectively use "unit" strategy
746+
ALL_UNIT_STRATEGY=true
747+
for unit_file in "$INTENT_DIR"/unit-*.md; do
748+
[ -f "$unit_file" ] || continue
749+
UNIT_CS=$(parse_unit_change_strategy "$unit_file")
750+
EFFECTIVE_CS="${UNIT_CS:-$(echo "$CONFIG" | jq -r '.change_strategy // "unit"')}"
751+
[ "$EFFECTIVE_CS" != "unit" ] && { ALL_UNIT_STRATEGY=false; break; }
752+
done
753+
707754
SKIP_INTEGRATOR=false
708755
[ "$UNIT_COUNT" -le 1 ] && SKIP_INTEGRATOR=true
709-
[ "$CHANGE_STRATEGY" = "unit" ] && SKIP_INTEGRATOR=true
756+
[ "$ALL_UNIT_STRATEGY" = "true" ] && SKIP_INTEGRATOR=true
710757
```
711758

712759
If `SKIP_INTEGRATOR` is false and `integratorComplete` is not `true`:

0 commit comments

Comments
 (0)