diff --git a/bmad-core/examples/vcs-detection-implementation.py b/bmad-core/examples/vcs-detection-implementation.py new file mode 100644 index 00000000..65c693b0 --- /dev/null +++ b/bmad-core/examples/vcs-detection-implementation.py @@ -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() \ No newline at end of file diff --git a/bmad-core/tasks/discover-vcs.md b/bmad-core/tasks/discover-vcs.md index 861159eb..eccd3ce6 100644 --- a/bmad-core/tasks/discover-vcs.md +++ b/bmad-core/tasks/discover-vcs.md @@ -2,16 +2,87 @@ ## 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 +- **Detection as a HINT, not a DECISION** - Optimize for the 85-90% who use Git -- Remain open for the 10-15% with special needs -- Suggest best practices without forcing them +- Auto-detect for brownfield, ask for greenfield +- Confirm with user when confidence < 100% +- Progressive disclosure: simple cases fast, complex cases handled ## 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 ```yaml @@ -30,7 +101,7 @@ prompt: | ### 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 elicit: true @@ -55,7 +126,7 @@ prompt: | Select a number (1-5): ``` -#### If "Not sure" (Option 4): +#### If "Not sure" (Option 4) or Low Auto-Detection Confidence: ```yaml elicit: true @@ -122,9 +193,30 @@ prompt: | [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 vcs_config: @@ -132,31 +224,80 @@ vcs_config: workflow: [github-flow|gitflow|trunk-based|custom|none] 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: artifact_format: [branches|monolithic|platform-specific] terminology: [git|generic|platform-specific] 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 output: | VCS Configuration Confirmed: - System: {type} - Workflow: {workflow} + {if auto_detected} + - Detection confidence: {confidence}% + {/if} - BMAD will adapt by: {key_adaptations} 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 -- 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 - Configuration is stored and accessible to all agents - No Git assumptions for non-Git users - Clear recommendations when requested +- **Detection treated as hint, not decision** - always confirmed with user ## Agent Adaptations Based on VCS diff --git a/bmad-core/tests/test_vcs_detection.py b/bmad-core/tests/test_vcs_detection.py new file mode 100644 index 00000000..2d2a16c8 --- /dev/null +++ b/bmad-core/tests/test_vcs_detection.py @@ -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() \ No newline at end of file diff --git a/docs/VCS_DETECTION_CONFIDENCE.md b/docs/VCS_DETECTION_CONFIDENCE.md new file mode 100644 index 00000000..76fbb498 --- /dev/null +++ b/docs/VCS_DETECTION_CONFIDENCE.md @@ -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.**