Merge pull request #298 from davesienkowski/feature/branching-strategy
Thanks @davesienkowski! Merging as-is, will follow up with squash merge option.
This commit is contained in:
@@ -34,6 +34,7 @@ Parse current values (default to `true` if not present):
|
||||
- `workflow.plan_check` — spawn plan checker during plan-phase
|
||||
- `workflow.verifier` — spawn verifier during execute-phase
|
||||
- `model_profile` — which model each agent uses (default: `balanced`)
|
||||
- `git.branching_strategy` — branching approach (default: `"none"`)
|
||||
|
||||
## 3. Present Settings
|
||||
|
||||
@@ -77,6 +78,16 @@ AskUserQuestion([
|
||||
{ label: "Yes", description: "Verify must-haves after execution" },
|
||||
{ label: "No", description: "Skip post-execution verification" }
|
||||
]
|
||||
},
|
||||
{
|
||||
question: "Git branching strategy?",
|
||||
header: "Branching",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "None (Recommended)", description: "Commit directly to current branch" },
|
||||
{ label: "Per Phase", description: "Create branch for each phase (gsd/phase-{N}-{name})" },
|
||||
{ label: "Per Milestone", description: "Create branch for entire milestone (gsd/{version}-{name})" }
|
||||
]
|
||||
}
|
||||
])
|
||||
```
|
||||
@@ -95,6 +106,9 @@ Merge new settings into existing config.json:
|
||||
"research": true/false,
|
||||
"plan_check": true/false,
|
||||
"verifier": true/false
|
||||
},
|
||||
"git": {
|
||||
"branching_strategy": "none" | "phase" | "milestone"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -116,6 +130,7 @@ Display:
|
||||
| Plan Researcher | {On/Off} |
|
||||
| Plan Checker | {On/Off} |
|
||||
| Execution Verifier | {On/Off} |
|
||||
| Git Branching | {None/Per Phase/Per Milestone} |
|
||||
|
||||
These settings apply to future /gsd:plan-phase and /gsd:execute-phase runs.
|
||||
|
||||
@@ -130,7 +145,7 @@ Quick commands:
|
||||
|
||||
<success_criteria>
|
||||
- [ ] Current config read
|
||||
- [ ] User presented with 4 settings (profile + 3 toggles)
|
||||
- [ ] Config updated with model_profile and workflow section
|
||||
- [ ] User presented with 5 settings (profile + 3 workflow toggles + git branching)
|
||||
- [ ] Config updated with model_profile, workflow, and git sections
|
||||
- [ ] Changes confirmed to user
|
||||
</success_criteria>
|
||||
|
||||
@@ -7,6 +7,11 @@ Configuration options for `.planning/` directory behavior.
|
||||
"planning": {
|
||||
"commit_docs": true,
|
||||
"search_gitignored": false
|
||||
},
|
||||
"git": {
|
||||
"branching_strategy": "none",
|
||||
"phase_branch_template": "gsd/phase-{phase}-{slug}",
|
||||
"milestone_branch_template": "gsd/{milestone}-{slug}"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -14,6 +19,9 @@ Configuration options for `.planning/` directory behavior.
|
||||
|--------|---------|-------------|
|
||||
| `commit_docs` | `true` | Whether to commit planning artifacts to git |
|
||||
| `search_gitignored` | `false` | Add `--no-ignore` to broad rg searches |
|
||||
| `git.branching_strategy` | `"none"` | Git branching approach: `"none"`, `"phase"`, or `"milestone"` |
|
||||
| `git.phase_branch_template` | `"gsd/phase-{phase}-{slug}"` | Branch template for phase strategy |
|
||||
| `git.milestone_branch_template` | `"gsd/{milestone}-{slug}"` | Branch template for milestone strategy |
|
||||
</config_schema>
|
||||
|
||||
<commit_docs_behavior>
|
||||
@@ -91,4 +99,80 @@ To use uncommitted mode:
|
||||
|
||||
</setup_uncommitted_mode>
|
||||
|
||||
<branching_strategy_behavior>
|
||||
|
||||
**Branching Strategies:**
|
||||
|
||||
| Strategy | When branch created | Branch scope | Merge point |
|
||||
|----------|---------------------|--------------|-------------|
|
||||
| `none` | Never | N/A | N/A |
|
||||
| `phase` | At `execute-phase` start | Single phase | User merges after phase |
|
||||
| `milestone` | At first `execute-phase` of milestone | Entire milestone | At `complete-milestone` |
|
||||
|
||||
**When `git.branching_strategy: "none"` (default):**
|
||||
- All work commits to current branch
|
||||
- Standard GSD behavior
|
||||
|
||||
**When `git.branching_strategy: "phase"`:**
|
||||
- `execute-phase` creates/switches to a branch before execution
|
||||
- Branch name from `phase_branch_template` (e.g., `gsd/phase-03-authentication`)
|
||||
- All plan commits go to that branch
|
||||
- User merges branches manually after phase completion
|
||||
- `complete-milestone` offers to merge all phase branches
|
||||
|
||||
**When `git.branching_strategy: "milestone"`:**
|
||||
- First `execute-phase` of milestone creates the milestone branch
|
||||
- Branch name from `milestone_branch_template` (e.g., `gsd/v1.0-mvp`)
|
||||
- All phases in milestone commit to same branch
|
||||
- `complete-milestone` offers to merge milestone branch to main
|
||||
|
||||
**Template variables:**
|
||||
|
||||
| Variable | Available in | Description |
|
||||
|----------|--------------|-------------|
|
||||
| `{phase}` | phase_branch_template | Zero-padded phase number (e.g., "03") |
|
||||
| `{slug}` | Both | Lowercase, hyphenated name |
|
||||
| `{milestone}` | milestone_branch_template | Milestone version (e.g., "v1.0") |
|
||||
|
||||
**Checking the config:**
|
||||
|
||||
```bash
|
||||
# Get branching strategy (default: none)
|
||||
BRANCHING_STRATEGY=$(cat .planning/config.json 2>/dev/null | grep -o '"branching_strategy"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*:.*"\([^"]*\)"/\1/' || echo "none")
|
||||
|
||||
# Get phase branch template
|
||||
PHASE_BRANCH_TEMPLATE=$(cat .planning/config.json 2>/dev/null | grep -o '"phase_branch_template"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*:.*"\([^"]*\)"/\1/' || echo "gsd/phase-{phase}-{slug}")
|
||||
|
||||
# Get milestone branch template
|
||||
MILESTONE_BRANCH_TEMPLATE=$(cat .planning/config.json 2>/dev/null | grep -o '"milestone_branch_template"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*:.*"\([^"]*\)"/\1/' || echo "gsd/{milestone}-{slug}")
|
||||
```
|
||||
|
||||
**Branch creation:**
|
||||
|
||||
```bash
|
||||
# For phase strategy
|
||||
if [ "$BRANCHING_STRATEGY" = "phase" ]; then
|
||||
PHASE_SLUG=$(echo "$PHASE_NAME" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//;s/-$//')
|
||||
BRANCH_NAME=$(echo "$PHASE_BRANCH_TEMPLATE" | sed "s/{phase}/$PADDED_PHASE/g" | sed "s/{slug}/$PHASE_SLUG/g")
|
||||
git checkout -b "$BRANCH_NAME" 2>/dev/null || git checkout "$BRANCH_NAME"
|
||||
fi
|
||||
|
||||
# For milestone strategy
|
||||
if [ "$BRANCHING_STRATEGY" = "milestone" ]; then
|
||||
MILESTONE_SLUG=$(echo "$MILESTONE_NAME" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//;s/-$//')
|
||||
BRANCH_NAME=$(echo "$MILESTONE_BRANCH_TEMPLATE" | sed "s/{milestone}/$MILESTONE_VERSION/g" | sed "s/{slug}/$MILESTONE_SLUG/g")
|
||||
git checkout -b "$BRANCH_NAME" 2>/dev/null || git checkout "$BRANCH_NAME"
|
||||
fi
|
||||
```
|
||||
|
||||
**Use cases:**
|
||||
|
||||
| Strategy | Best for |
|
||||
|----------|----------|
|
||||
| `none` | Solo development, simple projects |
|
||||
| `phase` | Code review per phase, granular rollback, team collaboration |
|
||||
| `milestone` | Release branches, staging environments, PR per version |
|
||||
|
||||
</branching_strategy_behavior>
|
||||
|
||||
</planning_config>
|
||||
|
||||
@@ -579,6 +579,125 @@ Progress: [updated progress bar]
|
||||
|
||||
</step>
|
||||
|
||||
<step name="handle_branches">
|
||||
|
||||
Check if branching was used and offer merge options.
|
||||
|
||||
**Check branching strategy:**
|
||||
|
||||
```bash
|
||||
# Get branching strategy from config
|
||||
BRANCHING_STRATEGY=$(cat .planning/config.json 2>/dev/null | grep -o '"branching_strategy"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*:.*"\([^"]*\)"/\1/' || echo "none")
|
||||
```
|
||||
|
||||
**If strategy is "none":** Skip to git_tag step.
|
||||
|
||||
**For "phase" strategy — find phase branches:**
|
||||
|
||||
```bash
|
||||
PHASE_BRANCH_TEMPLATE=$(cat .planning/config.json 2>/dev/null | grep -o '"phase_branch_template"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*:.*"\([^"]*\)"/\1/' || echo "gsd/phase-{phase}-{slug}")
|
||||
|
||||
# Extract prefix from template (before first variable)
|
||||
BRANCH_PREFIX=$(echo "$PHASE_BRANCH_TEMPLATE" | sed 's/{.*//')
|
||||
|
||||
# Find all phase branches for this milestone
|
||||
PHASE_BRANCHES=$(git branch --list "${BRANCH_PREFIX}*" 2>/dev/null | sed 's/^\*//' | tr -d ' ')
|
||||
```
|
||||
|
||||
**For "milestone" strategy — find milestone branch:**
|
||||
|
||||
```bash
|
||||
MILESTONE_BRANCH_TEMPLATE=$(cat .planning/config.json 2>/dev/null | grep -o '"milestone_branch_template"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*:.*"\([^"]*\)"/\1/' || echo "gsd/{milestone}-{slug}")
|
||||
|
||||
# Extract prefix from template
|
||||
BRANCH_PREFIX=$(echo "$MILESTONE_BRANCH_TEMPLATE" | sed 's/{.*//')
|
||||
|
||||
# Find milestone branch
|
||||
MILESTONE_BRANCH=$(git branch --list "${BRANCH_PREFIX}*" 2>/dev/null | sed 's/^\*//' | tr -d ' ' | head -1)
|
||||
```
|
||||
|
||||
**If no branches found:** Skip to git_tag step.
|
||||
|
||||
**If branches exist — present merge options:**
|
||||
|
||||
```
|
||||
## Git Branches Detected
|
||||
|
||||
Branching strategy: {phase/milestone}
|
||||
|
||||
Branches found:
|
||||
{list of branches}
|
||||
|
||||
Options:
|
||||
1. **Merge to main** — Merge branch(es) to main
|
||||
2. **Delete without merging** — Branches already merged or not needed
|
||||
3. **Keep branches** — Leave for manual handling
|
||||
```
|
||||
|
||||
Use AskUserQuestion:
|
||||
|
||||
```
|
||||
AskUserQuestion([
|
||||
{
|
||||
question: "How should branches be handled?",
|
||||
header: "Branches",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Merge to main", description: "Merge all branches to main sequentially" },
|
||||
{ label: "Delete without merging", description: "Branches already merged or not needed" },
|
||||
{ label: "Keep branches", description: "Leave branches for manual handling later" }
|
||||
]
|
||||
}
|
||||
])
|
||||
```
|
||||
|
||||
**If "Merge to main":**
|
||||
|
||||
```bash
|
||||
CURRENT_BRANCH=$(git branch --show-current)
|
||||
git checkout main
|
||||
|
||||
# For phase strategy - merge each phase branch
|
||||
if [ "$BRANCHING_STRATEGY" = "phase" ]; then
|
||||
for branch in $PHASE_BRANCHES; do
|
||||
echo "Merging $branch..."
|
||||
git merge --no-ff "$branch" -m "Merge branch '$branch' for v[X.Y]"
|
||||
done
|
||||
fi
|
||||
|
||||
# For milestone strategy - merge milestone branch
|
||||
if [ "$BRANCHING_STRATEGY" = "milestone" ]; then
|
||||
echo "Merging $MILESTONE_BRANCH..."
|
||||
git merge --no-ff "$MILESTONE_BRANCH" -m "Merge branch '$MILESTONE_BRANCH' for v[X.Y]"
|
||||
fi
|
||||
|
||||
git checkout "$CURRENT_BRANCH"
|
||||
```
|
||||
|
||||
Report: "Merged branches to main"
|
||||
|
||||
**If "Delete without merging":**
|
||||
|
||||
```bash
|
||||
if [ "$BRANCHING_STRATEGY" = "phase" ]; then
|
||||
for branch in $PHASE_BRANCHES; do
|
||||
git branch -d "$branch" 2>/dev/null || git branch -D "$branch"
|
||||
done
|
||||
fi
|
||||
|
||||
if [ "$BRANCHING_STRATEGY" = "milestone" ]; then
|
||||
git branch -d "$MILESTONE_BRANCH" 2>/dev/null || git branch -D "$MILESTONE_BRANCH"
|
||||
fi
|
||||
```
|
||||
|
||||
Report: "Deleted branches"
|
||||
|
||||
**If "Keep branches":**
|
||||
|
||||
Report: "Branches preserved for manual handling"
|
||||
|
||||
</step>
|
||||
|
||||
<step name="git_tag">
|
||||
|
||||
Create git tag for milestone:
|
||||
|
||||
@@ -65,6 +65,81 @@ git check-ignore -q .planning 2>/dev/null && COMMIT_PLANNING_DOCS=false
|
||||
```
|
||||
|
||||
Store `COMMIT_PLANNING_DOCS` for use in git operations.
|
||||
|
||||
**Load git branching config:**
|
||||
|
||||
```bash
|
||||
# Get branching strategy (default: none)
|
||||
BRANCHING_STRATEGY=$(cat .planning/config.json 2>/dev/null | grep -o '"branching_strategy"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*:.*"\([^"]*\)"/\1/' || echo "none")
|
||||
|
||||
# Get templates
|
||||
PHASE_BRANCH_TEMPLATE=$(cat .planning/config.json 2>/dev/null | grep -o '"phase_branch_template"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*:.*"\([^"]*\)"/\1/' || echo "gsd/phase-{phase}-{slug}")
|
||||
MILESTONE_BRANCH_TEMPLATE=$(cat .planning/config.json 2>/dev/null | grep -o '"milestone_branch_template"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*:.*"\([^"]*\)"/\1/' || echo "gsd/{milestone}-{slug}")
|
||||
```
|
||||
|
||||
Store `BRANCHING_STRATEGY` and templates for use in branch creation step.
|
||||
</step>
|
||||
|
||||
<step name="handle_branching">
|
||||
Create or switch to appropriate branch based on branching strategy.
|
||||
|
||||
**Skip if strategy is "none":**
|
||||
|
||||
```bash
|
||||
if [ "$BRANCHING_STRATEGY" = "none" ]; then
|
||||
# No branching, continue on current branch
|
||||
exit 0
|
||||
fi
|
||||
```
|
||||
|
||||
**For "phase" strategy — create phase branch:**
|
||||
|
||||
```bash
|
||||
if [ "$BRANCHING_STRATEGY" = "phase" ]; then
|
||||
# Get phase name from directory (e.g., "03-authentication" → "authentication")
|
||||
PHASE_NAME=$(basename "$PHASE_DIR" | sed 's/^[0-9]*-//')
|
||||
|
||||
# Create slug from phase name
|
||||
PHASE_SLUG=$(echo "$PHASE_NAME" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//;s/-$//')
|
||||
|
||||
# Apply template
|
||||
BRANCH_NAME=$(echo "$PHASE_BRANCH_TEMPLATE" | sed "s/{phase}/$PADDED_PHASE/g" | sed "s/{slug}/$PHASE_SLUG/g")
|
||||
|
||||
# Create or switch to branch
|
||||
git checkout -b "$BRANCH_NAME" 2>/dev/null || git checkout "$BRANCH_NAME"
|
||||
|
||||
echo "Branch: $BRANCH_NAME (phase branching)"
|
||||
fi
|
||||
```
|
||||
|
||||
**For "milestone" strategy — create/switch to milestone branch:**
|
||||
|
||||
```bash
|
||||
if [ "$BRANCHING_STRATEGY" = "milestone" ]; then
|
||||
# Get current milestone info from ROADMAP.md
|
||||
MILESTONE_VERSION=$(grep -oE 'v[0-9]+\.[0-9]+' .planning/ROADMAP.md | head -1 || echo "v1.0")
|
||||
MILESTONE_NAME=$(grep -A1 "## .*$MILESTONE_VERSION" .planning/ROADMAP.md | tail -1 | sed 's/.*- //' | cut -d'(' -f1 | tr -d ' ' || echo "milestone")
|
||||
|
||||
# Create slug
|
||||
MILESTONE_SLUG=$(echo "$MILESTONE_NAME" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//;s/-$//')
|
||||
|
||||
# Apply template
|
||||
BRANCH_NAME=$(echo "$MILESTONE_BRANCH_TEMPLATE" | sed "s/{milestone}/$MILESTONE_VERSION/g" | sed "s/{slug}/$MILESTONE_SLUG/g")
|
||||
|
||||
# Create or switch to branch (same branch for all phases in milestone)
|
||||
git checkout -b "$BRANCH_NAME" 2>/dev/null || git checkout "$BRANCH_NAME"
|
||||
|
||||
echo "Branch: $BRANCH_NAME (milestone branching)"
|
||||
fi
|
||||
```
|
||||
|
||||
**Report branch status:**
|
||||
|
||||
```
|
||||
Branching: {strategy} → {branch_name}
|
||||
```
|
||||
|
||||
**Note:** All subsequent plan commits go to this branch. User handles merging based on their workflow.
|
||||
</step>
|
||||
|
||||
<step name="validate_phase">
|
||||
|
||||
Reference in New Issue
Block a user