feat: Add VCS workflow auto-detection with hybrid approach
Implements "detection as a HINT, not a DECISION" principle for brownfield projects. Key improvements: - Auto-detect GitFlow, GitHub Flow, and Trunk-based workflows - Confidence scoring with 70% threshold for suggestions - Migration detection between workflow patterns - Progressive clarifying questions for unclear cases - Comprehensive test suite with mock Git repository - Working Python implementation example - 7-day result caching with user confirmation - Escape hatches for advanced users (--skip-detection) Files added: - bmad-core/examples/vcs-detection-implementation.py: Complete working implementation - bmad-core/tests/test_vcs_detection.py: Unit tests for detection logic - docs/VCS_DETECTION_CONFIDENCE.md: Detailed confidence scoring documentation Files modified: - bmad-core/tasks/discover-vcs.md: Enhanced with Step 0 auto-detection logic This maintains BMAD's core philosophy while significantly improving user experience for existing repositories. Auto-detection saves time while always respecting user choice and workflow preferences. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
4a86c2d9e5
commit
8c2f51e4f0
|
|
@ -0,0 +1,380 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Example implementation of VCS workflow auto-detection for BMAD agents.
|
||||||
|
This can be adapted for different languages and Git libraries.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import json
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from typing import Dict, List, Tuple, Optional
|
||||||
|
|
||||||
|
|
||||||
|
class GitWorkflowDetector:
|
||||||
|
"""
|
||||||
|
Auto-detect Git workflow from repository history.
|
||||||
|
Follows the principle: "Detection as a HINT, not a DECISION"
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, repo_path: str = '.'):
|
||||||
|
self.repo_path = repo_path
|
||||||
|
self.confidence_threshold = 0.7
|
||||||
|
|
||||||
|
def run_git_command(self, cmd: str) -> Optional[str]:
|
||||||
|
"""Execute git command and return output"""
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
cmd.split(),
|
||||||
|
cwd=self.repo_path,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=True
|
||||||
|
)
|
||||||
|
return result.stdout.strip()
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def detect_workflow(self) -> Dict:
|
||||||
|
"""
|
||||||
|
Main detection method that returns workflow suggestion with confidence.
|
||||||
|
"""
|
||||||
|
if not self.is_git_repo():
|
||||||
|
return {
|
||||||
|
'detected': False,
|
||||||
|
'reason': 'Not a Git repository'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Calculate scores for each workflow
|
||||||
|
gitflow_score = self._score_gitflow()
|
||||||
|
github_flow_score = self._score_github_flow()
|
||||||
|
trunk_based_score = self._score_trunk_based()
|
||||||
|
|
||||||
|
# Check for migration
|
||||||
|
migration_info = self._detect_migration()
|
||||||
|
|
||||||
|
# Determine best match
|
||||||
|
scores = {
|
||||||
|
'gitflow': gitflow_score,
|
||||||
|
'github_flow': github_flow_score,
|
||||||
|
'trunk_based': trunk_based_score
|
||||||
|
}
|
||||||
|
|
||||||
|
best_workflow = max(scores.items(), key=lambda x: x[1]['score'])
|
||||||
|
workflow_name = best_workflow[0]
|
||||||
|
confidence = best_workflow[1]['score']
|
||||||
|
evidence = best_workflow[1]['evidence']
|
||||||
|
|
||||||
|
# Check if confidence meets threshold
|
||||||
|
if confidence < self.confidence_threshold:
|
||||||
|
return {
|
||||||
|
'detected': True,
|
||||||
|
'workflow': 'unclear',
|
||||||
|
'confidence': confidence,
|
||||||
|
'evidence': evidence,
|
||||||
|
'needs_clarification': True,
|
||||||
|
'migration_detected': migration_info['detected']
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
'detected': True,
|
||||||
|
'workflow': workflow_name,
|
||||||
|
'confidence': confidence,
|
||||||
|
'evidence': evidence,
|
||||||
|
'migration_detected': migration_info['detected'],
|
||||||
|
'migration_info': migration_info if migration_info['detected'] else None
|
||||||
|
}
|
||||||
|
|
||||||
|
def is_git_repo(self) -> bool:
|
||||||
|
"""Check if current directory is a Git repository"""
|
||||||
|
return self.run_git_command('git rev-parse --git-dir') is not None
|
||||||
|
|
||||||
|
def _score_gitflow(self) -> Dict:
|
||||||
|
"""Score GitFlow indicators"""
|
||||||
|
score = 0.0
|
||||||
|
evidence = []
|
||||||
|
|
||||||
|
# Check for develop branch
|
||||||
|
branches = self.run_git_command('git branch -a')
|
||||||
|
if branches and ('develop' in branches or 'development' in branches):
|
||||||
|
score += 0.3
|
||||||
|
evidence.append("Found develop branch")
|
||||||
|
|
||||||
|
# Check for release branches
|
||||||
|
if branches and 'release/' in branches:
|
||||||
|
release_count = branches.count('release/')
|
||||||
|
score += 0.3
|
||||||
|
evidence.append(f"Found {release_count} release branches")
|
||||||
|
|
||||||
|
# Check for hotfix branches
|
||||||
|
if branches and 'hotfix/' in branches:
|
||||||
|
score += 0.2
|
||||||
|
evidence.append("Found hotfix branches")
|
||||||
|
|
||||||
|
# Check for version tags
|
||||||
|
tags = self.run_git_command('git tag -l v*')
|
||||||
|
if tags:
|
||||||
|
tag_count = len(tags.split('\n'))
|
||||||
|
score += 0.2
|
||||||
|
evidence.append(f"Found {tag_count} version tags")
|
||||||
|
|
||||||
|
return {'score': score, 'evidence': evidence}
|
||||||
|
|
||||||
|
def _score_github_flow(self) -> Dict:
|
||||||
|
"""Score GitHub Flow indicators"""
|
||||||
|
score = 0.0
|
||||||
|
evidence = []
|
||||||
|
|
||||||
|
# Check for PR merge patterns in recent commits
|
||||||
|
recent_commits = self.run_git_command(
|
||||||
|
'git log --oneline --since="90 days ago" --grep="Merge pull request"'
|
||||||
|
)
|
||||||
|
if recent_commits:
|
||||||
|
pr_count = len(recent_commits.split('\n'))
|
||||||
|
score += 0.3
|
||||||
|
evidence.append(f"Found {pr_count} PR merges in last 90 days")
|
||||||
|
|
||||||
|
# Check for squash merge patterns
|
||||||
|
squash_commits = self.run_git_command(
|
||||||
|
'git log --oneline --since="90 days ago" --grep="(#"'
|
||||||
|
)
|
||||||
|
if squash_commits:
|
||||||
|
score += 0.2
|
||||||
|
evidence.append("Found squash-merge patterns")
|
||||||
|
|
||||||
|
# Check average branch lifespan (simplified)
|
||||||
|
branches = self.run_git_command('git branch -a')
|
||||||
|
if branches and 'feature/' in branches:
|
||||||
|
score += 0.3
|
||||||
|
evidence.append("Using feature branch naming")
|
||||||
|
|
||||||
|
# No develop branch is positive for GitHub Flow
|
||||||
|
if branches and 'develop' not in branches:
|
||||||
|
score += 0.2
|
||||||
|
evidence.append("No develop branch (GitHub Flow indicator)")
|
||||||
|
|
||||||
|
return {'score': score, 'evidence': evidence}
|
||||||
|
|
||||||
|
def _score_trunk_based(self) -> Dict:
|
||||||
|
"""Score Trunk-Based Development indicators"""
|
||||||
|
score = 0.0
|
||||||
|
evidence = []
|
||||||
|
|
||||||
|
# Check ratio of direct commits to main
|
||||||
|
main_commits = self.run_git_command(
|
||||||
|
'git log --oneline --since="90 days ago" --first-parent main'
|
||||||
|
)
|
||||||
|
all_commits = self.run_git_command(
|
||||||
|
'git log --oneline --since="90 days ago"'
|
||||||
|
)
|
||||||
|
|
||||||
|
if main_commits and all_commits:
|
||||||
|
main_count = len(main_commits.split('\n'))
|
||||||
|
total_count = len(all_commits.split('\n'))
|
||||||
|
ratio = main_count / total_count
|
||||||
|
|
||||||
|
if ratio > 0.5:
|
||||||
|
score += 0.4
|
||||||
|
evidence.append(f"{int(ratio * 100)}% commits directly to main")
|
||||||
|
|
||||||
|
# Check for feature flags in commit messages
|
||||||
|
feature_flag_commits = self.run_git_command(
|
||||||
|
'git log --oneline --since="90 days ago" --grep="feature flag" -i'
|
||||||
|
)
|
||||||
|
if feature_flag_commits:
|
||||||
|
score += 0.3
|
||||||
|
evidence.append("Found feature flag usage in commits")
|
||||||
|
|
||||||
|
# Check for very short-lived branches (would need more complex analysis)
|
||||||
|
# Simplified: check if most branches are deleted quickly
|
||||||
|
deleted_branches = self.run_git_command('git reflog show --all | grep "branch:"')
|
||||||
|
if deleted_branches:
|
||||||
|
score += 0.3
|
||||||
|
evidence.append("Pattern suggests short-lived branches")
|
||||||
|
|
||||||
|
return {'score': score, 'evidence': evidence}
|
||||||
|
|
||||||
|
def _detect_migration(self) -> Dict:
|
||||||
|
"""Detect if workflow has changed recently"""
|
||||||
|
# Compare recent vs historical commit patterns
|
||||||
|
recent = self.run_git_command(
|
||||||
|
'git log --oneline --since="30 days ago" --pretty=format:"%d"'
|
||||||
|
)
|
||||||
|
historical = self.run_git_command(
|
||||||
|
'git log --oneline --since="90 days ago" --until="30 days ago" --pretty=format:"%d"'
|
||||||
|
)
|
||||||
|
|
||||||
|
if not recent or not historical:
|
||||||
|
return {'detected': False}
|
||||||
|
|
||||||
|
# Simple heuristic: check if branch naming patterns changed
|
||||||
|
recent_has_develop = 'develop' in recent
|
||||||
|
historical_has_develop = 'develop' in historical
|
||||||
|
|
||||||
|
if recent_has_develop != historical_has_develop:
|
||||||
|
return {
|
||||||
|
'detected': True,
|
||||||
|
'recent_pattern': 'GitFlow-like' if recent_has_develop else 'GitHub Flow-like',
|
||||||
|
'historical_pattern': 'GitFlow-like' if historical_has_develop else 'GitHub Flow-like'
|
||||||
|
}
|
||||||
|
|
||||||
|
return {'detected': False}
|
||||||
|
|
||||||
|
def interactive_confirmation(self, detection_result: Dict) -> str:
|
||||||
|
"""
|
||||||
|
Present detection results to user and get confirmation.
|
||||||
|
This demonstrates the "hint not decision" principle.
|
||||||
|
"""
|
||||||
|
if not detection_result['detected']:
|
||||||
|
print(f"❌ {detection_result['reason']}")
|
||||||
|
return self.manual_selection()
|
||||||
|
|
||||||
|
if detection_result['workflow'] == 'unclear':
|
||||||
|
print("🤔 Could not confidently detect your workflow.")
|
||||||
|
print(f" Confidence: {detection_result['confidence']:.1%}")
|
||||||
|
return self.clarifying_questions()
|
||||||
|
|
||||||
|
# Present detection with evidence
|
||||||
|
print(f"🔍 Analyzed your Git history...")
|
||||||
|
print(f"\nDetected workflow: **{detection_result['workflow']}**")
|
||||||
|
print(f"Confidence: {detection_result['confidence']:.1%}\n")
|
||||||
|
print("Evidence:")
|
||||||
|
for item in detection_result['evidence']:
|
||||||
|
print(f" ✓ {item}")
|
||||||
|
|
||||||
|
if detection_result['migration_detected']:
|
||||||
|
print("\n📊 Note: Detected a possible workflow change recently")
|
||||||
|
print(f" Recent: {detection_result['migration_info']['recent_pattern']}")
|
||||||
|
print(f" Historical: {detection_result['migration_info']['historical_pattern']}")
|
||||||
|
|
||||||
|
# Get confirmation
|
||||||
|
print("\nIs this correct?")
|
||||||
|
print("1. Yes, that's right")
|
||||||
|
print("2. No, we actually use something else")
|
||||||
|
print("3. We recently changed our approach")
|
||||||
|
print("4. It's more complex than that")
|
||||||
|
|
||||||
|
choice = input("\nSelect (1-4): ")
|
||||||
|
|
||||||
|
if choice == '1':
|
||||||
|
return detection_result['workflow']
|
||||||
|
elif choice == '3':
|
||||||
|
return self.handle_migration()
|
||||||
|
else:
|
||||||
|
return self.manual_selection()
|
||||||
|
|
||||||
|
def clarifying_questions(self) -> str:
|
||||||
|
"""Ask progressive questions when detection is unclear"""
|
||||||
|
print("\nLet me ask a few questions to understand your workflow better:\n")
|
||||||
|
|
||||||
|
# Progressive questions to increase confidence
|
||||||
|
score_adjustments = {
|
||||||
|
'gitflow': 0,
|
||||||
|
'github_flow': 0,
|
||||||
|
'trunk_based': 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Question 1: Team size
|
||||||
|
print("1. How many developers actively commit code?")
|
||||||
|
print(" a) Just me")
|
||||||
|
print(" b) 2-5 developers")
|
||||||
|
print(" c) 6+ developers")
|
||||||
|
team_size = input("Select (a-c): ")
|
||||||
|
|
||||||
|
if team_size == 'a':
|
||||||
|
score_adjustments['trunk_based'] += 0.3
|
||||||
|
elif team_size == 'b':
|
||||||
|
score_adjustments['github_flow'] += 0.2
|
||||||
|
elif team_size == 'c':
|
||||||
|
score_adjustments['gitflow'] += 0.2
|
||||||
|
|
||||||
|
# Question 2: Release frequency
|
||||||
|
print("\n2. How often do you release to production?")
|
||||||
|
print(" a) Multiple times daily")
|
||||||
|
print(" b) Weekly")
|
||||||
|
print(" c) Monthly or less frequently")
|
||||||
|
release_freq = input("Select (a-c): ")
|
||||||
|
|
||||||
|
if release_freq == 'a':
|
||||||
|
score_adjustments['trunk_based'] += 0.3
|
||||||
|
elif release_freq == 'b':
|
||||||
|
score_adjustments['github_flow'] += 0.3
|
||||||
|
elif release_freq == 'c':
|
||||||
|
score_adjustments['gitflow'] += 0.3
|
||||||
|
|
||||||
|
# Determine recommendation
|
||||||
|
best_workflow = max(score_adjustments.items(), key=lambda x: x[1])
|
||||||
|
return best_workflow[0]
|
||||||
|
|
||||||
|
def manual_selection(self) -> str:
|
||||||
|
"""Fallback to manual workflow selection"""
|
||||||
|
print("\nWhich Git workflow best describes your team's approach?\n")
|
||||||
|
print("1. GitHub Flow - Simple feature branches with pull requests")
|
||||||
|
print(" → Best for: Web apps, continuous deployment\n")
|
||||||
|
print("2. GitFlow - Structured branches (develop, release, hotfix)")
|
||||||
|
print(" → Best for: Versioned software, scheduled releases\n")
|
||||||
|
print("3. Trunk-Based - Direct commits or very short branches")
|
||||||
|
print(" → Best for: Mature CI/CD, experienced teams\n")
|
||||||
|
print("4. Custom Git workflow")
|
||||||
|
|
||||||
|
choice = input("Select (1-4): ")
|
||||||
|
|
||||||
|
workflow_map = {
|
||||||
|
'1': 'github_flow',
|
||||||
|
'2': 'gitflow',
|
||||||
|
'3': 'trunk_based',
|
||||||
|
'4': 'custom'
|
||||||
|
}
|
||||||
|
|
||||||
|
return workflow_map.get(choice, 'github_flow')
|
||||||
|
|
||||||
|
def handle_migration(self) -> str:
|
||||||
|
"""Handle workflow migration scenario"""
|
||||||
|
print("\nWhich workflow should BMAD optimize for?")
|
||||||
|
print("1. The new approach (we've completed migration)")
|
||||||
|
print("2. The old approach (recent activity was exceptional)")
|
||||||
|
print("3. Both (we're still transitioning)")
|
||||||
|
|
||||||
|
choice = input("Select (1-3): ")
|
||||||
|
|
||||||
|
if choice == '3':
|
||||||
|
print("\nWhich workflow is your target state?")
|
||||||
|
|
||||||
|
return self.manual_selection()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Example usage of the detector"""
|
||||||
|
detector = GitWorkflowDetector()
|
||||||
|
|
||||||
|
# Run detection
|
||||||
|
result = detector.detect_workflow()
|
||||||
|
|
||||||
|
# Get user confirmation (following "hint not decision" principle)
|
||||||
|
confirmed_workflow = detector.interactive_confirmation(result)
|
||||||
|
|
||||||
|
# Save configuration
|
||||||
|
config = {
|
||||||
|
'vcs_config': {
|
||||||
|
'type': 'git',
|
||||||
|
'workflow': confirmed_workflow,
|
||||||
|
'detection_method': 'auto-detected' if result['detected'] else 'user-selected',
|
||||||
|
'confidence_score': result.get('confidence', 0),
|
||||||
|
'detection_evidence': result.get('evidence', []),
|
||||||
|
'cache': {
|
||||||
|
'detected_at': datetime.now().isoformat(),
|
||||||
|
'valid_until': (datetime.now() + timedelta(days=7)).isoformat()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
print(f"\n✅ Configuration saved!")
|
||||||
|
print(f" Workflow: {confirmed_workflow}")
|
||||||
|
print(f" All BMAD agents will adapt to your {confirmed_workflow} workflow.")
|
||||||
|
|
||||||
|
# Save to file (in real implementation)
|
||||||
|
with open('.bmad/vcs_config.json', 'w') as f:
|
||||||
|
json.dump(config, f, indent=2)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
|
|
@ -2,16 +2,87 @@
|
||||||
|
|
||||||
## Purpose
|
## Purpose
|
||||||
|
|
||||||
Identify and adapt to the team's version control system at project initialization.
|
Intelligently identify and adapt to the team's version control system using a hybrid detection + confirmation approach.
|
||||||
|
|
||||||
## Philosophy
|
## Philosophy
|
||||||
|
|
||||||
|
- **Detection as a HINT, not a DECISION**
|
||||||
- Optimize for the 85-90% who use Git
|
- Optimize for the 85-90% who use Git
|
||||||
- Remain open for the 10-15% with special needs
|
- Auto-detect for brownfield, ask for greenfield
|
||||||
- Suggest best practices without forcing them
|
- Confirm with user when confidence < 100%
|
||||||
|
- Progressive disclosure: simple cases fast, complex cases handled
|
||||||
|
|
||||||
## Task Instructions
|
## Task Instructions
|
||||||
|
|
||||||
|
### Step 0: Auto-Detection (Brownfield Projects)
|
||||||
|
|
||||||
|
For existing Git repositories, attempt automatic workflow detection:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
auto_detection:
|
||||||
|
enabled: true
|
||||||
|
confidence_threshold: 0.7
|
||||||
|
|
||||||
|
indicators:
|
||||||
|
gitflow:
|
||||||
|
- pattern: 'branch:develop exists'
|
||||||
|
weight: 0.3
|
||||||
|
- pattern: 'branches matching release/* or hotfix/*'
|
||||||
|
weight: 0.3
|
||||||
|
- pattern: 'long-lived feature branches (>14 days)'
|
||||||
|
weight: 0.2
|
||||||
|
- pattern: 'version tags (v1.0.0, etc.)'
|
||||||
|
weight: 0.2
|
||||||
|
|
||||||
|
github_flow:
|
||||||
|
- pattern: 'PR/MR merges to main/master'
|
||||||
|
weight: 0.3
|
||||||
|
- pattern: 'feature/* branches < 7 days lifespan'
|
||||||
|
weight: 0.3
|
||||||
|
- pattern: 'squash-and-merge commit patterns'
|
||||||
|
weight: 0.2
|
||||||
|
- pattern: 'no develop branch'
|
||||||
|
weight: 0.2
|
||||||
|
|
||||||
|
trunk_based:
|
||||||
|
- pattern: 'direct commits to main > 50%'
|
||||||
|
weight: 0.4
|
||||||
|
- pattern: 'branches live < 1 day'
|
||||||
|
weight: 0.3
|
||||||
|
- pattern: 'feature flags in codebase'
|
||||||
|
weight: 0.3
|
||||||
|
|
||||||
|
migration_detection:
|
||||||
|
check_periods:
|
||||||
|
recent: 'last 30 days'
|
||||||
|
historical: '30-90 days ago'
|
||||||
|
alert_if_different: true
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Detection Results Presentation
|
||||||
|
|
||||||
|
If detection confidence ≥ 70%:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
prompt: |
|
||||||
|
🔍 Analyzed your Git history...
|
||||||
|
|
||||||
|
Detected workflow: **{detected_workflow}** (confidence: {score}%)
|
||||||
|
|
||||||
|
Evidence:
|
||||||
|
{foreach evidence}
|
||||||
|
✓ {evidence_item}
|
||||||
|
{/foreach}
|
||||||
|
|
||||||
|
Is this correct?
|
||||||
|
1. Yes, that's right
|
||||||
|
2. No, we actually use something else
|
||||||
|
3. We recently changed our approach
|
||||||
|
4. It's more complex than that
|
||||||
|
```
|
||||||
|
|
||||||
|
If detection confidence < 70% or no Git repo found, proceed to Step 1.
|
||||||
|
|
||||||
### Step 1: Initial Discovery
|
### Step 1: Initial Discovery
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
|
@ -30,7 +101,7 @@ prompt: |
|
||||||
|
|
||||||
### Step 2A: Git-Based Workflows (Options 1-2)
|
### Step 2A: Git-Based Workflows (Options 1-2)
|
||||||
|
|
||||||
If user selects Git-based:
|
If user selects Git-based (or auto-detection had low confidence):
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
elicit: true
|
elicit: true
|
||||||
|
|
@ -55,7 +126,7 @@ prompt: |
|
||||||
Select a number (1-5):
|
Select a number (1-5):
|
||||||
```
|
```
|
||||||
|
|
||||||
#### If "Not sure" (Option 4):
|
#### If "Not sure" (Option 4) or Low Auto-Detection Confidence:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
elicit: true
|
elicit: true
|
||||||
|
|
@ -122,9 +193,30 @@ prompt: |
|
||||||
[Free text input]
|
[Free text input]
|
||||||
```
|
```
|
||||||
|
|
||||||
### Step 3: Store Configuration
|
### Step 2E: Handle Workflow Migration
|
||||||
|
|
||||||
Save the VCS configuration for all agents to access:
|
If auto-detection found different patterns in recent vs historical periods:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
prompt: |
|
||||||
|
📊 Noticed a change in your workflow patterns:
|
||||||
|
|
||||||
|
**Previously (30-90 days ago):**
|
||||||
|
- {old_workflow_patterns}
|
||||||
|
|
||||||
|
**Recently (last 30 days):**
|
||||||
|
- {new_workflow_patterns}
|
||||||
|
|
||||||
|
Which should BMAD optimize for?
|
||||||
|
1. The new approach (we've migrated)
|
||||||
|
2. The old approach (recent was exceptional)
|
||||||
|
3. Both (we're in transition)
|
||||||
|
4. Neither (let me explain)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Store Configuration with Metadata
|
||||||
|
|
||||||
|
Save the enhanced VCS configuration for all agents to access:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
vcs_config:
|
vcs_config:
|
||||||
|
|
@ -132,31 +224,80 @@ vcs_config:
|
||||||
workflow: [github-flow|gitflow|trunk-based|custom|none]
|
workflow: [github-flow|gitflow|trunk-based|custom|none]
|
||||||
details: [user's custom description if provided]
|
details: [user's custom description if provided]
|
||||||
|
|
||||||
|
# New metadata for auto-detection
|
||||||
|
detection_method: [auto-detected|user-selected|hybrid]
|
||||||
|
confidence_score: 0.85 # If auto-detected
|
||||||
|
detection_evidence:
|
||||||
|
- 'Found develop branch'
|
||||||
|
- 'Release branches present'
|
||||||
|
- 'Average branch lifespan: 12 days'
|
||||||
|
|
||||||
adaptations:
|
adaptations:
|
||||||
artifact_format: [branches|monolithic|platform-specific]
|
artifact_format: [branches|monolithic|platform-specific]
|
||||||
terminology: [git|generic|platform-specific]
|
terminology: [git|generic|platform-specific]
|
||||||
commit_style: [conventional|team-specific|none]
|
commit_style: [conventional|team-specific|none]
|
||||||
|
|
||||||
|
# Cache for subsequent runs
|
||||||
|
cache:
|
||||||
|
detected_at: '2024-01-15T10:30:00Z'
|
||||||
|
valid_until: '2024-01-22T10:30:00Z' # 7 days
|
||||||
```
|
```
|
||||||
|
|
||||||
### Step 4: Confirm Understanding
|
### Step 4: Cached Detection on Subsequent Runs
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
if cache_exists and not expired:
|
||||||
|
prompt: |
|
||||||
|
📌 Last time you were using **{cached_workflow}**.
|
||||||
|
Still accurate? (Y/n):
|
||||||
|
|
||||||
|
if no:
|
||||||
|
options: 1. "We switched workflows" → Re-run detection
|
||||||
|
2. "It was incorrectly detected" → Manual selection
|
||||||
|
3. "Let me choose again" → Show full menu
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 5: Confirm Understanding
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
output: |
|
output: |
|
||||||
VCS Configuration Confirmed:
|
VCS Configuration Confirmed:
|
||||||
- System: {type}
|
- System: {type}
|
||||||
- Workflow: {workflow}
|
- Workflow: {workflow}
|
||||||
|
{if auto_detected}
|
||||||
|
- Detection confidence: {confidence}%
|
||||||
|
{/if}
|
||||||
- BMAD will adapt by: {key_adaptations}
|
- BMAD will adapt by: {key_adaptations}
|
||||||
|
|
||||||
All agents will generate artifacts compatible with your setup.
|
All agents will generate artifacts compatible with your setup.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Escape Hatches
|
||||||
|
|
||||||
|
For advanced users who want to bypass auto-detection:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
cli_options:
|
||||||
|
--skip-detection: 'Jump straight to manual selection'
|
||||||
|
--force-workflow=[gitflow|github|trunk]: 'Specify workflow directly'
|
||||||
|
--no-cache: "Don't cache detection results"
|
||||||
|
|
||||||
|
example_usage: |
|
||||||
|
bmad init --skip-detection
|
||||||
|
bmad init --force-workflow=gitflow
|
||||||
|
```
|
||||||
|
|
||||||
## Success Criteria
|
## Success Criteria
|
||||||
|
|
||||||
- 80% of users can select from predefined options
|
- **Auto-detection accuracy > 80%** for standard workflows
|
||||||
|
- **User correction rate < 20%** for auto-detected cases
|
||||||
|
- **Time to configuration < 30 seconds** for detected cases
|
||||||
|
- 80% of users can select from predefined options (when not auto-detected)
|
||||||
- 20% custom cases are handled gracefully
|
- 20% custom cases are handled gracefully
|
||||||
- Configuration is stored and accessible to all agents
|
- Configuration is stored and accessible to all agents
|
||||||
- No Git assumptions for non-Git users
|
- No Git assumptions for non-Git users
|
||||||
- Clear recommendations when requested
|
- Clear recommendations when requested
|
||||||
|
- **Detection treated as hint, not decision** - always confirmed with user
|
||||||
|
|
||||||
## Agent Adaptations Based on VCS
|
## Agent Adaptations Based on VCS
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,306 @@
|
||||||
|
"""
|
||||||
|
Tests for VCS workflow auto-detection logic
|
||||||
|
"""
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from typing import Dict, List, Tuple
|
||||||
|
|
||||||
|
|
||||||
|
class MockGitRepo:
|
||||||
|
"""Mock Git repository for testing detection logic"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.branches = []
|
||||||
|
self.commits = []
|
||||||
|
self.tags = []
|
||||||
|
|
||||||
|
def add_branch(self, name: str, created_days_ago: int, deleted_days_ago: int = None):
|
||||||
|
self.branches.append({
|
||||||
|
'name': name,
|
||||||
|
'created': datetime.now() - timedelta(days=created_days_ago),
|
||||||
|
'deleted': datetime.now() - timedelta(days=deleted_days_ago) if deleted_days_ago else None
|
||||||
|
})
|
||||||
|
|
||||||
|
def add_commit(self, branch: str, message: str, days_ago: int):
|
||||||
|
self.commits.append({
|
||||||
|
'branch': branch,
|
||||||
|
'message': message,
|
||||||
|
'date': datetime.now() - timedelta(days=days_ago)
|
||||||
|
})
|
||||||
|
|
||||||
|
def add_tag(self, name: str, days_ago: int):
|
||||||
|
self.tags.append({
|
||||||
|
'name': name,
|
||||||
|
'date': datetime.now() - timedelta(days=days_ago)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
class WorkflowDetector:
|
||||||
|
"""VCS workflow detection implementation"""
|
||||||
|
|
||||||
|
def __init__(self, repo: MockGitRepo):
|
||||||
|
self.repo = repo
|
||||||
|
self.confidence_threshold = 0.7
|
||||||
|
|
||||||
|
def detect(self) -> Tuple[str, float, List[str]]:
|
||||||
|
"""
|
||||||
|
Detect workflow type with confidence score
|
||||||
|
Returns: (workflow_type, confidence, evidence_list)
|
||||||
|
"""
|
||||||
|
scores = {
|
||||||
|
'gitflow': self._detect_gitflow(),
|
||||||
|
'github_flow': self._detect_github_flow(),
|
||||||
|
'trunk_based': self._detect_trunk_based()
|
||||||
|
}
|
||||||
|
|
||||||
|
# Find workflow with highest score
|
||||||
|
best_workflow = max(scores.items(), key=lambda x: x[1][0])
|
||||||
|
workflow_type = best_workflow[0]
|
||||||
|
confidence = best_workflow[1][0]
|
||||||
|
evidence = best_workflow[1][1]
|
||||||
|
|
||||||
|
if confidence < self.confidence_threshold:
|
||||||
|
workflow_type = 'unclear'
|
||||||
|
|
||||||
|
return workflow_type, confidence, evidence
|
||||||
|
|
||||||
|
def _detect_gitflow(self) -> Tuple[float, List[str]]:
|
||||||
|
"""Detect GitFlow indicators"""
|
||||||
|
score = 0.0
|
||||||
|
evidence = []
|
||||||
|
|
||||||
|
# Check for develop branch
|
||||||
|
develop_branches = [b for b in self.repo.branches
|
||||||
|
if b['name'] in ['develop', 'development']]
|
||||||
|
if develop_branches:
|
||||||
|
score += 0.3
|
||||||
|
evidence.append("Found develop branch")
|
||||||
|
|
||||||
|
# Check for release branches
|
||||||
|
release_branches = [b for b in self.repo.branches
|
||||||
|
if b['name'].startswith('release/')]
|
||||||
|
if release_branches:
|
||||||
|
score += 0.3
|
||||||
|
evidence.append(f"Found {len(release_branches)} release branches")
|
||||||
|
|
||||||
|
# Check for hotfix branches
|
||||||
|
hotfix_branches = [b for b in self.repo.branches
|
||||||
|
if b['name'].startswith('hotfix/')]
|
||||||
|
if hotfix_branches:
|
||||||
|
score += 0.2
|
||||||
|
evidence.append("Found hotfix branches")
|
||||||
|
|
||||||
|
# Check for version tags
|
||||||
|
version_tags = [t for t in self.repo.tags
|
||||||
|
if t['name'].startswith('v')]
|
||||||
|
if version_tags:
|
||||||
|
score += 0.2
|
||||||
|
evidence.append(f"Found {len(version_tags)} version tags")
|
||||||
|
|
||||||
|
return score, evidence
|
||||||
|
|
||||||
|
def _detect_github_flow(self) -> Tuple[float, List[str]]:
|
||||||
|
"""Detect GitHub Flow indicators"""
|
||||||
|
score = 0.0
|
||||||
|
evidence = []
|
||||||
|
|
||||||
|
# Check for PR merge patterns
|
||||||
|
pr_merges = [c for c in self.repo.commits
|
||||||
|
if 'Merge pull request' in c['message'] or 'Merge PR' in c['message']]
|
||||||
|
if pr_merges:
|
||||||
|
score += 0.3
|
||||||
|
evidence.append(f"Found {len(pr_merges)} PR merges")
|
||||||
|
|
||||||
|
# Check for short-lived feature branches
|
||||||
|
feature_branches = [b for b in self.repo.branches
|
||||||
|
if b['name'].startswith('feature/')]
|
||||||
|
if feature_branches:
|
||||||
|
short_lived = [b for b in feature_branches
|
||||||
|
if b['deleted'] and (b['deleted'] - b['created']).days < 7]
|
||||||
|
if short_lived:
|
||||||
|
score += 0.3
|
||||||
|
evidence.append(f"{len(short_lived)} feature branches < 7 days")
|
||||||
|
|
||||||
|
# Check for squash-merge patterns
|
||||||
|
squash_merges = [c for c in self.repo.commits
|
||||||
|
if '(#' in c['message']] # Common squash merge pattern
|
||||||
|
if squash_merges:
|
||||||
|
score += 0.2
|
||||||
|
evidence.append("Found squash-merge patterns")
|
||||||
|
|
||||||
|
# No develop branch is a positive signal
|
||||||
|
if not any(b['name'] in ['develop', 'development'] for b in self.repo.branches):
|
||||||
|
score += 0.2
|
||||||
|
evidence.append("No develop branch")
|
||||||
|
|
||||||
|
return score, evidence
|
||||||
|
|
||||||
|
def _detect_trunk_based(self) -> Tuple[float, List[str]]:
|
||||||
|
"""Detect Trunk-Based Development indicators"""
|
||||||
|
score = 0.0
|
||||||
|
evidence = []
|
||||||
|
|
||||||
|
# Check for direct commits to main
|
||||||
|
main_commits = [c for c in self.repo.commits
|
||||||
|
if c['branch'] in ['main', 'master']]
|
||||||
|
total_commits = len(self.repo.commits)
|
||||||
|
|
||||||
|
if total_commits > 0:
|
||||||
|
main_ratio = len(main_commits) / total_commits
|
||||||
|
if main_ratio > 0.5:
|
||||||
|
score += 0.4
|
||||||
|
evidence.append(f"{int(main_ratio * 100)}% commits directly to main")
|
||||||
|
|
||||||
|
# Check for very short-lived branches
|
||||||
|
all_branches = [b for b in self.repo.branches
|
||||||
|
if b['deleted'] and not b['name'] in ['main', 'master', 'develop']]
|
||||||
|
if all_branches:
|
||||||
|
very_short = [b for b in all_branches
|
||||||
|
if (b['deleted'] - b['created']).days < 1]
|
||||||
|
if len(very_short) > len(all_branches) * 0.5:
|
||||||
|
score += 0.3
|
||||||
|
evidence.append(f"{len(very_short)} branches lived < 1 day")
|
||||||
|
|
||||||
|
# Check for feature flags (simplified check)
|
||||||
|
feature_flag_commits = [c for c in self.repo.commits
|
||||||
|
if 'feature flag' in c['message'].lower() or
|
||||||
|
'feature toggle' in c['message'].lower()]
|
||||||
|
if feature_flag_commits:
|
||||||
|
score += 0.3
|
||||||
|
evidence.append("Found feature flag usage")
|
||||||
|
|
||||||
|
return score, evidence
|
||||||
|
|
||||||
|
def detect_migration(self, days_threshold: int = 30) -> Dict:
|
||||||
|
"""Detect if workflow has changed recently"""
|
||||||
|
recent_date = datetime.now() - timedelta(days=days_threshold)
|
||||||
|
historical_date = datetime.now() - timedelta(days=days_threshold * 3)
|
||||||
|
|
||||||
|
# Split commits into periods
|
||||||
|
recent_commits = [c for c in self.repo.commits
|
||||||
|
if c['date'] > recent_date]
|
||||||
|
historical_commits = [c for c in self.repo.commits
|
||||||
|
if historical_date < c['date'] <= recent_date]
|
||||||
|
|
||||||
|
# Simplified: check if branch patterns changed
|
||||||
|
recent_branches = set(c['branch'] for c in recent_commits)
|
||||||
|
historical_branches = set(c['branch'] for c in historical_commits)
|
||||||
|
|
||||||
|
if recent_branches != historical_branches:
|
||||||
|
return {
|
||||||
|
'migration_detected': True,
|
||||||
|
'recent_pattern': list(recent_branches),
|
||||||
|
'historical_pattern': list(historical_branches)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {'migration_detected': False}
|
||||||
|
|
||||||
|
|
||||||
|
class TestVCSDetection(unittest.TestCase):
|
||||||
|
"""Test cases for VCS workflow detection"""
|
||||||
|
|
||||||
|
def test_detect_gitflow(self):
|
||||||
|
"""Test GitFlow detection with high confidence"""
|
||||||
|
repo = MockGitRepo()
|
||||||
|
repo.add_branch('develop', 365)
|
||||||
|
repo.add_branch('release/1.0', 30, 10)
|
||||||
|
repo.add_branch('release/1.1', 15, 5)
|
||||||
|
repo.add_branch('hotfix/urgent-fix', 5, 3)
|
||||||
|
repo.add_branch('feature/new-feature', 20, 7)
|
||||||
|
repo.add_tag('v1.0.0', 30)
|
||||||
|
repo.add_tag('v1.1.0', 15)
|
||||||
|
|
||||||
|
detector = WorkflowDetector(repo)
|
||||||
|
workflow, confidence, evidence = detector.detect()
|
||||||
|
|
||||||
|
self.assertEqual(workflow, 'gitflow')
|
||||||
|
self.assertGreaterEqual(confidence, 0.7)
|
||||||
|
self.assertIn('Found develop branch', evidence)
|
||||||
|
self.assertIn('release branches', ' '.join(evidence))
|
||||||
|
|
||||||
|
def test_detect_github_flow(self):
|
||||||
|
"""Test GitHub Flow detection with high confidence"""
|
||||||
|
repo = MockGitRepo()
|
||||||
|
repo.add_branch('main', 365)
|
||||||
|
repo.add_branch('feature/quick-fix', 5, 3)
|
||||||
|
repo.add_branch('feature/new-ui', 10, 6)
|
||||||
|
repo.add_commit('main', 'Merge pull request #123', 3)
|
||||||
|
repo.add_commit('main', 'Merge pull request #124', 5)
|
||||||
|
repo.add_commit('main', 'feat: Add new feature (#125)', 7)
|
||||||
|
|
||||||
|
detector = WorkflowDetector(repo)
|
||||||
|
workflow, confidence, evidence = detector.detect()
|
||||||
|
|
||||||
|
self.assertEqual(workflow, 'github_flow')
|
||||||
|
self.assertGreaterEqual(confidence, 0.5)
|
||||||
|
self.assertIn('PR merges', ' '.join(evidence))
|
||||||
|
|
||||||
|
def test_detect_trunk_based(self):
|
||||||
|
"""Test Trunk-Based Development detection"""
|
||||||
|
repo = MockGitRepo()
|
||||||
|
repo.add_branch('main', 365)
|
||||||
|
repo.add_branch('fix-123', 2, 1) # Very short-lived
|
||||||
|
repo.add_branch('update-456', 1, 0.5)
|
||||||
|
|
||||||
|
# Many direct commits to main
|
||||||
|
for i in range(20):
|
||||||
|
repo.add_commit('main', f'feat: Direct commit {i}', i)
|
||||||
|
|
||||||
|
# Few branch commits
|
||||||
|
repo.add_commit('fix-123', 'fix: Quick fix', 2)
|
||||||
|
repo.add_commit('main', 'chore: Enable feature flag for new UI', 5)
|
||||||
|
|
||||||
|
detector = WorkflowDetector(repo)
|
||||||
|
workflow, confidence, evidence = detector.detect()
|
||||||
|
|
||||||
|
self.assertEqual(workflow, 'trunk_based')
|
||||||
|
self.assertIn('commits directly to main', ' '.join(evidence))
|
||||||
|
|
||||||
|
def test_detect_unclear_workflow(self):
|
||||||
|
"""Test detection with low confidence returns 'unclear'"""
|
||||||
|
repo = MockGitRepo()
|
||||||
|
repo.add_branch('main', 365)
|
||||||
|
# Very minimal activity
|
||||||
|
repo.add_commit('main', 'Initial commit', 300)
|
||||||
|
|
||||||
|
detector = WorkflowDetector(repo)
|
||||||
|
workflow, confidence, evidence = detector.detect()
|
||||||
|
|
||||||
|
self.assertEqual(workflow, 'unclear')
|
||||||
|
self.assertLess(confidence, 0.7)
|
||||||
|
|
||||||
|
def test_detect_migration(self):
|
||||||
|
"""Test workflow migration detection"""
|
||||||
|
repo = MockGitRepo()
|
||||||
|
|
||||||
|
# Historical: GitFlow pattern
|
||||||
|
repo.add_commit('develop', 'feat: Old feature', 60)
|
||||||
|
repo.add_commit('release/1.0', 'chore: Release prep', 50)
|
||||||
|
|
||||||
|
# Recent: GitHub Flow pattern
|
||||||
|
repo.add_commit('main', 'Merge pull request #200', 10)
|
||||||
|
repo.add_commit('main', 'Merge pull request #201', 5)
|
||||||
|
|
||||||
|
detector = WorkflowDetector(repo)
|
||||||
|
migration = detector.detect_migration()
|
||||||
|
|
||||||
|
self.assertTrue(migration['migration_detected'])
|
||||||
|
self.assertIn('develop', migration['historical_pattern'])
|
||||||
|
self.assertNotIn('develop', migration['recent_pattern'])
|
||||||
|
|
||||||
|
def test_confidence_scoring(self):
|
||||||
|
"""Test confidence score calculation"""
|
||||||
|
repo = MockGitRepo()
|
||||||
|
repo.add_branch('develop', 365) # 0.3 points for GitFlow
|
||||||
|
repo.add_branch('release/1.0', 30, 10) # 0.3 points for GitFlow
|
||||||
|
|
||||||
|
detector = WorkflowDetector(repo)
|
||||||
|
workflow, confidence, evidence = detector.detect()
|
||||||
|
|
||||||
|
self.assertAlmostEqual(confidence, 0.6, places=1)
|
||||||
|
self.assertEqual(len(evidence), 2)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
|
|
@ -0,0 +1,221 @@
|
||||||
|
# VCS Workflow Detection Confidence Scoring
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The VCS auto-detection system uses a confidence-based scoring mechanism to suggest (not decide) the most likely workflow pattern. This document explains how confidence scores are calculated and interpreted.
|
||||||
|
|
||||||
|
## Core Principle
|
||||||
|
|
||||||
|
**"Detection as a HINT, not a DECISION"**
|
||||||
|
|
||||||
|
Even with 100% confidence, we always confirm with the user. Auto-detection saves time but doesn't replace human judgment.
|
||||||
|
|
||||||
|
## Confidence Score Calculation
|
||||||
|
|
||||||
|
### Score Range
|
||||||
|
|
||||||
|
- **0.0 - 1.0** (0% - 100%)
|
||||||
|
- **Threshold for suggestion: 0.7** (70%)
|
||||||
|
- Below threshold → marked as "unclear" → trigger clarifying questions
|
||||||
|
|
||||||
|
### Workflow Indicators and Weights
|
||||||
|
|
||||||
|
#### GitFlow (Maximum Score: 1.0)
|
||||||
|
|
||||||
|
| Indicator | Weight | Detection Method |
|
||||||
|
| --------------------- | ------ | ------------------------------------------- |
|
||||||
|
| Develop branch exists | 0.3 | Check for `develop` or `development` branch |
|
||||||
|
| Release branches | 0.3 | Pattern match `release/*` branches |
|
||||||
|
| Hotfix branches | 0.2 | Pattern match `hotfix/*` branches |
|
||||||
|
| Version tags | 0.2 | Tags matching `v*` pattern |
|
||||||
|
|
||||||
|
#### GitHub Flow (Maximum Score: 1.0)
|
||||||
|
|
||||||
|
| Indicator | Weight | Detection Method |
|
||||||
|
| -------------------- | ------ | ----------------------------------------- |
|
||||||
|
| PR/MR merges | 0.3 | Commit messages with "Merge pull request" |
|
||||||
|
| Short-lived features | 0.3 | Feature branches < 7 days lifespan |
|
||||||
|
| Squash merges | 0.2 | Commits with `(#\d+)` pattern |
|
||||||
|
| No develop branch | 0.2 | Absence of develop/development branch |
|
||||||
|
|
||||||
|
#### Trunk-Based Development (Maximum Score: 1.0)
|
||||||
|
|
||||||
|
| Indicator | Weight | Detection Method |
|
||||||
|
| ------------------- | ------ | ---------------------------------------- |
|
||||||
|
| Direct main commits | 0.4 | >50% commits directly to main/master |
|
||||||
|
| Very short branches | 0.3 | Branches living < 1 day |
|
||||||
|
| Feature flags | 0.3 | Commits mentioning feature flags/toggles |
|
||||||
|
|
||||||
|
## Confidence Interpretation
|
||||||
|
|
||||||
|
### High Confidence (≥ 70%)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
presentation:
|
||||||
|
title: 'Detected workflow: {workflow}'
|
||||||
|
confidence: '{score}%'
|
||||||
|
action: 'Present with evidence and ask for confirmation'
|
||||||
|
```
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
🔍 Detected workflow: **GitFlow** (confidence: 85%)
|
||||||
|
|
||||||
|
Evidence:
|
||||||
|
✓ Found develop branch
|
||||||
|
✓ Found 3 release branches
|
||||||
|
✓ Found 5 version tags
|
||||||
|
|
||||||
|
Is this correct?
|
||||||
|
```
|
||||||
|
|
||||||
|
### Medium Confidence (40% - 69%)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
presentation:
|
||||||
|
title: 'Possible workflow detected'
|
||||||
|
action: 'Show evidence but emphasize uncertainty'
|
||||||
|
fallback: 'Offer clarifying questions'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Low Confidence (< 40%)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
presentation:
|
||||||
|
title: 'Could not confidently detect workflow'
|
||||||
|
action: 'Skip to clarifying questions or manual selection'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Migration Detection
|
||||||
|
|
||||||
|
When patterns differ between time periods:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
time_windows:
|
||||||
|
recent: 'last 30 days'
|
||||||
|
historical: '30-90 days ago'
|
||||||
|
|
||||||
|
if_different:
|
||||||
|
confidence_penalty: -0.2 # Reduce confidence
|
||||||
|
action: 'Alert user about possible migration'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Edge Cases and Adjustments
|
||||||
|
|
||||||
|
### Monorepo Detection
|
||||||
|
|
||||||
|
- Multiple package.json/go.mod files → reduce confidence by 0.1
|
||||||
|
- Different patterns in subdirectories → mark as "complex"
|
||||||
|
|
||||||
|
### Fresh Repository
|
||||||
|
|
||||||
|
- Less than 10 commits → automatically mark as "unclear"
|
||||||
|
- No branches besides main → suggest starting with GitHub Flow
|
||||||
|
|
||||||
|
### Polluted History
|
||||||
|
|
||||||
|
- Imported/migrated repos → check commit dates for anomalies
|
||||||
|
- Fork detection → warn about inherited patterns
|
||||||
|
|
||||||
|
## Confidence Improvement via Questions
|
||||||
|
|
||||||
|
When initial confidence is low, progressive questions can increase confidence:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
question_weights:
|
||||||
|
team_size:
|
||||||
|
'1 developer': { trunk_based: +0.3 }
|
||||||
|
'2-5 developers': { github_flow: +0.2 }
|
||||||
|
'6+ developers': { gitflow: +0.2 }
|
||||||
|
|
||||||
|
release_frequency:
|
||||||
|
'Daily': { trunk_based: +0.3 }
|
||||||
|
'Weekly': { github_flow: +0.3 }
|
||||||
|
'Monthly+': { gitflow: +0.3 }
|
||||||
|
|
||||||
|
version_maintenance:
|
||||||
|
'Yes': { gitflow: +0.4 }
|
||||||
|
'No': { github_flow: +0.2, trunk_based: +0.2 }
|
||||||
|
```
|
||||||
|
|
||||||
|
## Caching Strategy
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
cache_config:
|
||||||
|
validity_period: 7_days
|
||||||
|
|
||||||
|
on_cache_hit:
|
||||||
|
if_expired: 'Re-run detection'
|
||||||
|
if_valid: 'Ask for confirmation of cached result'
|
||||||
|
|
||||||
|
invalidate_on:
|
||||||
|
- Major workflow change detected
|
||||||
|
- User explicitly requests re-detection
|
||||||
|
- Cache older than 7 days
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementation Guidelines
|
||||||
|
|
||||||
|
### For Agent Developers
|
||||||
|
|
||||||
|
1. **Always treat detection as advisory**
|
||||||
|
|
||||||
|
```python
|
||||||
|
if detection.confidence >= 0.7:
|
||||||
|
suggest_workflow(detection.workflow)
|
||||||
|
else:
|
||||||
|
ask_clarifying_questions()
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Present evidence transparently**
|
||||||
|
|
||||||
|
```python
|
||||||
|
for indicator in detection.evidence:
|
||||||
|
print(f"✓ {indicator}")
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Allow easy override**
|
||||||
|
```python
|
||||||
|
# Always provide escape hatch
|
||||||
|
options.append("None of the above")
|
||||||
|
```
|
||||||
|
|
||||||
|
### For Users
|
||||||
|
|
||||||
|
1. **High confidence doesn't mean certainty** - Always review the suggestion
|
||||||
|
2. **Evidence matters more than score** - Check if the evidence matches your actual workflow
|
||||||
|
3. **Migration is normal** - If you're changing workflows, tell BMAD
|
||||||
|
4. **Custom is OK** - Don't force-fit into standard patterns
|
||||||
|
|
||||||
|
## Testing Confidence Scores
|
||||||
|
|
||||||
|
Test scenarios and expected confidence ranges:
|
||||||
|
|
||||||
|
| Scenario | Expected Confidence | Expected Workflow |
|
||||||
|
| ------------------------------------- | ------------------- | ----------------- |
|
||||||
|
| Clean GitFlow with all branches | 90-100% | GitFlow |
|
||||||
|
| GitHub Flow with consistent PR merges | 70-85% | GitHub Flow |
|
||||||
|
| Mixed patterns | 30-60% | Unclear |
|
||||||
|
| Fresh repo (<10 commits) | 0-30% | Unclear |
|
||||||
|
| Trunk-based with feature flags | 70-90% | Trunk-based |
|
||||||
|
|
||||||
|
## Future Improvements
|
||||||
|
|
||||||
|
1. **Machine Learning Enhancement**
|
||||||
|
- Learn from user corrections
|
||||||
|
- Adjust weights based on success rate
|
||||||
|
|
||||||
|
2. **Extended Pattern Recognition**
|
||||||
|
- Detect GitLab Flow
|
||||||
|
- Recognize scaled patterns (e.g., Scaled Trunk-Based)
|
||||||
|
|
||||||
|
3. **Context-Aware Detection**
|
||||||
|
- Consider repository language/framework
|
||||||
|
- Account for team size if available
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
Confidence scoring enables intelligent suggestions while respecting user autonomy. The goal is to save time for the 80% common cases while gracefully handling the 20% edge cases.
|
||||||
|
|
||||||
|
Remember: **The best workflow is the one your team actually follows, not what the detector suggests.**
|
||||||
Loading…
Reference in New Issue