Skip to content

Commit 9d32003

Browse files
jwaldripclaude
andcommitted
feat: unit targeting, enriched change strategies, remove bolt strategy
- Remove `bolt` change strategy from schema, config, setup, elaborate, construct, and advance (iteration terminology retained) - Enrich `unit` strategy: per-unit MRs to default branch, skip integrator, force sequential path, support `/construct <unit-name>` targeting - Enrich `intent` strategy: autonomous DAG-driven, single intent MR with all ticket references collected from units - Add `get_unit_dep_status()` to dag.sh for formatted dep precheck reports - Add unit targeting to /construct with arg disambiguation, dep validation, completion check, and targetUnit state persistence - Update /advance with targeted unit early exit, strategy-aware merge (per-unit PR for unit strategy, intent branch merge for intent strategy) - Preserve targetUnit in enforce-iteration.sh continuation command - Elaborate: remove decomposition question (agent decides), enforce unit boundaries (no spanning deps or domains), require criteria display before confirmation, handle "Other" free-text as conversation pause Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 00259b7 commit 9d32003

File tree

9 files changed

+360
-61
lines changed

9 files changed

+360
-61
lines changed

plugin/hooks/enforce-iteration.sh

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,12 @@ elif [ "$READY_COUNT" -gt 0 ] || [ "$IN_PROGRESS_COUNT" -gt 0 ]; then
141141
echo ""
142142
echo "### ACTION REQUIRED"
143143
echo ""
144-
echo "Call \`/construct\` to continue the autonomous loop."
144+
TARGET_UNIT=$(echo "$ITERATION_JSON" | han parse json targetUnit -r --default "" 2>/dev/null || echo "")
145+
if [ -n "$TARGET_UNIT" ]; then
146+
echo "Call \`/construct ${INTENT_SLUG} ${TARGET_UNIT}\` to continue targeted construction."
147+
else
148+
echo "Call \`/construct\` to continue the autonomous loop."
149+
fi
145150
echo ""
146151
echo "**Note:** Subagents have clean context. No \`/clear\` needed."
147152
echo ""

plugin/lib/config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@ import { dirname, join } from "node:path"
1414
/**
1515
* Change strategy determines how git branches are organized
1616
*/
17-
export type ChangeStrategy = "trunk" | "unit" | "bolt" | "intent"
17+
export type ChangeStrategy = "trunk" | "unit" | "intent"
1818

1919
/**
2020
* VCS configuration for git or jj
2121
*/
2222
export interface VcsConfig {
23-
/** How changes are organized: trunk, unit, bolt, or intent */
23+
/** How changes are organized: trunk, unit, or intent */
2424
change_strategy: ChangeStrategy
2525
/** Whether to create PR for elaborated intent review before planning */
2626
elaboration_review: boolean

plugin/lib/dag.sh

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,52 @@ are_deps_completed() {
137137
return 0
138138
}
139139

140+
# Check dependency status for a specific unit and output formatted report
141+
# Returns 0 if all deps met, 1 if blocked (prints blocking report to stdout)
142+
# Usage: get_unit_dep_status <intent_dir> <unit_name>
143+
get_unit_dep_status() {
144+
local intent_dir="$1"
145+
local unit_name="$2"
146+
local unit_file="$intent_dir/$unit_name.md"
147+
148+
if [ ! -f "$unit_file" ]; then
149+
echo "Error: Unit file not found: $unit_file" >&2
150+
return 1
151+
fi
152+
153+
local deps
154+
deps=$(parse_unit_deps "$unit_file")
155+
156+
# No dependencies means all deps met
157+
[ -z "$deps" ] && return 0
158+
159+
local has_blocking=false
160+
local table_rows=""
161+
162+
for dep in $deps; do
163+
[ -z "$dep" ] && continue
164+
local dep_file="$intent_dir/$dep.md"
165+
local dep_status
166+
dep_status=$(parse_unit_status "$dep_file")
167+
168+
if [ "$dep_status" != "completed" ]; then
169+
has_blocking=true
170+
table_rows="${table_rows}| $dep | $dep_status (blocking) |\n"
171+
else
172+
table_rows="${table_rows}| $dep | $dep_status |\n"
173+
fi
174+
done
175+
176+
if [ "$has_blocking" = "true" ]; then
177+
echo "| Dependency | Status |"
178+
echo "|------------|--------|"
179+
printf "%b" "$table_rows"
180+
return 1
181+
fi
182+
183+
return 0
184+
}
185+
140186
# Find ready units (pending + all deps completed)
141187
# Returns unit names (without .md) one per line
142188
# Usage: find_ready_units <intent_dir>

plugin/schemas/settings.schema.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,9 +137,9 @@
137137
"properties": {
138138
"change_strategy": {
139139
"type": "string",
140-
"enum": ["trunk", "unit", "bolt", "intent"],
140+
"enum": ["trunk", "unit", "intent"],
141141
"default": "unit",
142-
"description": "How changes are organized:\n- trunk: All work on main branch, no feature branches\n- unit: One branch per unit of work (default)\n- bolt: One branch per intent with squashed commits\n- intent: One long-lived branch per intent with linear history"
142+
"description": "How changes are organized:\n- trunk: All work on main branch, no feature branches\n- unit: One branch per unit, each gets its own MR reviewed individually (default)\n- intent: One long-lived branch per intent, agents build autonomously via DAG ordering"
143143
},
144144
"elaboration_review": {
145145
"type": "boolean",
@@ -153,7 +153,7 @@
153153
},
154154
"auto_merge": {
155155
"type": "boolean",
156-
"description": "Whether to automatically merge completed unit branches. Only applies to 'unit' and 'bolt' strategies."
156+
"description": "Whether to automatically merge completed unit branches. Only applies to 'unit' and 'intent' strategies."
157157
},
158158
"auto_squash": {
159159
"type": "boolean",

plugin/skills/advance/SKILL.md

Lines changed: 80 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,32 @@ if [ -n "$CURRENT_UNIT" ] && [ -f "$INTENT_DIR/${CURRENT_UNIT}.md" ]; then
6969
fi
7070
```
7171

72-
### Step 2c: Merge Unit Branch on Completion
72+
### Step 2c: Handle Targeted Unit Completion
7373

74-
After marking a unit as completed, merge the unit branch into the intent branch:
74+
When `targetUnit` is set in state and matches the just-completed unit, handle early exit:
75+
76+
```bash
77+
TARGET_UNIT=$(echo "$STATE" | han parse json targetUnit -r --default "")
78+
if [ -n "$TARGET_UNIT" ] && [ "$TARGET_UNIT" = "$CURRENT_UNIT" ]; then
79+
# Clear targetUnit from state
80+
STATE=$(echo "$STATE" | han parse json --set "targetUnit=")
81+
han keep save iteration.json "$STATE"
82+
83+
echo "## Targeted Unit Complete: ${CURRENT_UNIT}"
84+
echo ""
85+
echo "The targeted unit has finished its workflow."
86+
echo ""
87+
echo "**Next steps:**"
88+
echo "- Run \`/construct\` to continue with the next ready unit"
89+
echo "- Run \`/construct <unit-name>\` to target another specific unit"
90+
echo "- Run \`/advance\` if all units are complete"
91+
exit 0
92+
fi
93+
```
94+
95+
### Step 2d: Merge Unit Branch on Completion
96+
97+
After marking a unit as completed, merge behavior depends on `change_strategy`:
7598

7699
```bash
77100
# Load config for merge settings
@@ -80,11 +103,45 @@ INTENT_DIR=".ai-dlc/${INTENT_SLUG}"
80103
CONFIG=$(get_ai_dlc_config "$INTENT_DIR")
81104
AUTO_MERGE=$(echo "$CONFIG" | jq -r '.auto_merge // "true"')
82105
AUTO_SQUASH=$(echo "$CONFIG" | jq -r '.auto_squash // "false"')
106+
CHANGE_STRATEGY=$(echo "$CONFIG" | jq -r '.change_strategy // "unit"')
107+
DEFAULT_BRANCH=$(echo "$CONFIG" | jq -r '.default_branch')
108+
109+
UNIT_SLUG="${CURRENT_UNIT#unit-}"
110+
UNIT_BRANCH="ai-dlc/${INTENT_SLUG}/${UNIT_SLUG}"
111+
112+
if [ "$CHANGE_STRATEGY" = "unit" ]; then
113+
# Unit strategy: open a PR/MR for the unit branch directly to the default branch
114+
git push -u origin "$UNIT_BRANCH" 2>/dev/null || true
115+
116+
# Get this unit's ticket reference (if any) for the PR body
117+
UNIT_TICKET=$(han parse yaml ticket -r --default "" < "$INTENT_DIR/${CURRENT_UNIT}.md" 2>/dev/null || echo "")
118+
TICKET_LINE=""
119+
if [ -n "$UNIT_TICKET" ]; then
120+
TICKET_LINE="Closes ${UNIT_TICKET}"
121+
fi
122+
123+
gh pr create \
124+
--base "$DEFAULT_BRANCH" \
125+
--head "$UNIT_BRANCH" \
126+
--title "unit: ${CURRENT_UNIT}" \
127+
--body "$(cat <<EOF
128+
## Unit: ${CURRENT_UNIT}
83129
84-
if [ "$AUTO_MERGE" = "true" ]; then
85-
UNIT_SLUG="${CURRENT_UNIT#unit-}"
86-
UNIT_BRANCH="ai-dlc/${INTENT_SLUG}/${UNIT_SLUG}"
130+
Part of intent: ${INTENT_SLUG}
87131
132+
${TICKET_LINE}
133+
134+
---
135+
*Built with [AI-DLC](https://ai-dlc.dev)*
136+
EOF
137+
)" 2>/dev/null || echo "PR may already exist for $UNIT_BRANCH"
138+
139+
# Clean up unit worktree
140+
WORKTREE_PATH="/tmp/ai-dlc-${INTENT_SLUG}-${UNIT_SLUG}"
141+
[ -d "$WORKTREE_PATH" ] && git worktree remove "$WORKTREE_PATH"
142+
143+
elif [ "$AUTO_MERGE" = "true" ]; then
144+
# Intent/trunk strategy: merge unit branch into intent branch
88145
# Ensure we're on the intent branch
89146
git checkout "ai-dlc/${INTENT_SLUG}/main"
90147

@@ -114,10 +171,10 @@ if (dagSummary.allComplete) {
114171
// ALL UNITS COMPLETE - Check if integrator should run
115172
// Skip integrator for:
116173
// - Single-unit intents (reviewer already validated it)
117-
// - change_strategy "bolt" (single squashed branch, no multi-unit merge)
174+
// - change_strategy "unit" (each unit reviewed individually via per-unit MR)
118175
const unitCount = dagSummary.totalCount;
119176
const changeStrategy = config.change_strategy || "unit";
120-
const skipIntegrator = unitCount <= 1 || changeStrategy === "bolt";
177+
const skipIntegrator = unitCount <= 1 || changeStrategy === "unit";
121178
if (!skipIntegrator && !state.integratorComplete) {
122179
// Spawn integrator on the intent branch
123180
// See Step 2e below
@@ -389,7 +446,20 @@ INTENT_BRANCH="ai-dlc/${INTENT_SLUG}/main"
389446
git push -u origin "$INTENT_BRANCH" 2>/dev/null || true
390447
```
391448

392-
2. Create PR/MR:
449+
2. Collect ticket references from all units:
450+
451+
```bash
452+
TICKET_REFS=""
453+
for unit_file in "$INTENT_DIR"/unit-*.md; do
454+
[ -f "$unit_file" ] || continue
455+
TICKET=$(han parse yaml ticket -r --default "" < "$unit_file" 2>/dev/null || echo "")
456+
if [ -n "$TICKET" ]; then
457+
TICKET_REFS="${TICKET_REFS}\nCloses ${TICKET}"
458+
fi
459+
done
460+
```
461+
462+
3. Create PR/MR:
393463

394464
```bash
395465
gh pr create \
@@ -408,6 +478,8 @@ ${SUCCESS_CRITERIA_AS_CHECKLIST}
408478
## Changes
409479
${COMPLETED_UNITS_AS_CHANGE_LIST}
410480
481+
$(printf "%b" "${TICKET_REFS}")
482+
411483
---
412484
*Built with [AI-DLC](https://ai-dlc.dev)*
413485
EOF

0 commit comments

Comments
 (0)