Agent Skills Framework Extension
Submodule Orchestration Patterns Skill
When to Use This Skill
Use this skill when implementing submodule orchestration patterns patterns in your codebase.
How to Use This Skill
- Review the patterns and examples below
- Apply the relevant patterns to your implementation
- Follow the best practices outlined in this skill
Production git submodule management with automated sync, health checks, and orchestration.
Core Capabilities
- Lifecycle Management - Init, update, sync, cleanup
- Bottom-Up Sync - Automatic submodule → master synchronization
- Health Monitoring - Detached HEAD detection, status tracking
- Dependency Resolution - Cross-submodule dependency analysis
- Automation - CI/CD integration, pre-push hooks
Bottom-Up Synchronization
# scripts/sync_submodules.py
"""
Bottom-up submodule synchronization with conflict detection
"""
import subprocess
import json
from dataclasses import dataclass
from typing import List, Dict, Optional
from pathlib import Path
@dataclass
class SubmoduleStatus:
path: str
current_commit: str
remote_commit: str
branch: str
is_detached: bool
has_changes: bool
needs_sync: bool
class SubmoduleOrchestrator:
"""Manage submodule synchronization"""
def __init__(self, root_path: str):
self.root = Path(root_path)
def discover_submodules(self) -> List[str]:
"""Find all git submodules"""
result = subprocess.run(
['git', 'submodule', 'status'],
cwd=self.root,
capture_output=True,
text=True
)
submodules = []
for line in result.stdout.splitlines():
# Parse: " abc123 path/to/submodule (branch)"
parts = line.strip().split()
if len(parts) >= 2:
submodules.append(parts[1])
return submodules
def check_status(self, submodule_path: str) -> SubmoduleStatus:
"""Check submodule status"""
full_path = self.root / submodule_path
# Get current commit
current = subprocess.run(
['git', 'rev-parse', 'HEAD'],
cwd=full_path,
capture_output=True,
text=True
).stdout.strip()
# Get remote commit
subprocess.run(['git', 'fetch'], cwd=full_path)
remote = subprocess.run(
['git', 'rev-parse', 'origin/main'],
cwd=full_path,
capture_output=True,
text=True
).stdout.strip()
# Check if detached HEAD
branch_result = subprocess.run(
['git', 'branch', '--show-current'],
cwd=full_path,
capture_output=True,
text=True
)
branch = branch_result.stdout.strip()
is_detached = branch == ''
# Check for local changes
status = subprocess.run(
['git', 'status', '--porcelain'],
cwd=full_path,
capture_output=True,
text=True
)
has_changes = status.stdout.strip() != ''
return SubmoduleStatus(
path=submodule_path,
current_commit=current,
remote_commit=remote,
branch=branch or 'HEAD',
is_detached=is_detached,
has_changes=has_changes,
needs_sync=current != remote
)
def sync_submodule(self, submodule_path: str) -> bool:
"""Sync individual submodule"""
full_path = self.root / submodule_path
status = self.check_status(submodule_path)
if status.is_detached:
# Checkout main branch
subprocess.run(['git', 'checkout', 'main'], cwd=full_path)
if status.has_changes:
print(f"Submodule {submodule_path} has uncommitted changes")
return False
# Pull latest changes
result = subprocess.run(
['git', 'pull', 'origin', 'main'],
cwd=full_path,
capture_output=True,
text=True
)
if result.returncode != 0:
print(f"Failed to pull {submodule_path}: {result.stderr}")
return False
return True
def update_master_pointer(self, submodule_path: str) -> bool:
"""Update submodule pointer in master repo"""
# Add submodule changes
subprocess.run(['git', 'add', submodule_path], cwd=self.root)
# Check if anything changed
status = subprocess.run(
['git', 'diff', '--cached', '--quiet'],
cwd=self.root
)
if status.returncode == 0:
print(f"No changes in {submodule_path}")
return False
# Commit pointer update
commit_msg = f"chore: Update {submodule_path} submodule pointer"
subprocess.run(
['git', 'commit', '-m', commit_msg],
cwd=self.root
)
return True
def sync_all(self) -> Dict[str, bool]:
"""Sync all submodules bottom-up"""
results = {}
for submodule in self.discover_submodules():
print(f"Syncing {submodule}...")
# Sync submodule
if self.sync_submodule(submodule):
# Update pointer in master
results[submodule] = self.update_master_pointer(submodule)
else:
results[submodule] = False
return results
Health Monitoring
# scripts/health_check.py
"""
Submodule health monitoring and validation
"""
from dataclasses import dataclass
from typing import List
import subprocess
@dataclass
class HealthIssue:
severity: str # 'warning', 'error', 'critical'
submodule: str
description: str
fix_suggestion: str
class SubmoduleHealthChecker:
"""Monitor submodule health"""
def __init__(self, root_path: str):
self.root = root_path
self.issues: List[HealthIssue] = []
def check_detached_heads(self) -> List[HealthIssue]:
"""Find submodules in detached HEAD state"""
result = subprocess.run(
['git', 'submodule', 'foreach', 'git', 'branch', '--show-current'],
cwd=self.root,
capture_output=True,
text=True
)
issues = []
lines = result.stdout.splitlines()
for i in range(0, len(lines), 2):
if i + 1 < len(lines):
submodule_line = lines[i]
branch_line = lines[i + 1]
if branch_line.strip() == '':
submodule_path = submodule_line.split("'")[1]
issues.append(HealthIssue(
severity='warning',
submodule=submodule_path,
description='Detached HEAD state',
fix_suggestion=f'cd {submodule_path} && git checkout main'
))
return issues
def check_uncommitted_changes(self) -> List[HealthIssue]:
"""Find submodules with uncommitted changes"""
result = subprocess.run(
['git', 'submodule', 'foreach', 'git', 'status', '--porcelain'],
cwd=self.root,
capture_output=True,
text=True
)
issues = []
current_submodule = None
for line in result.stdout.splitlines():
if line.startswith("Entering"):
current_submodule = line.split("'")[1]
elif line.strip() and current_submodule:
issues.append(HealthIssue(
severity='error',
submodule=current_submodule,
description='Uncommitted changes detected',
fix_suggestion=f'cd {current_submodule} && git add . && git commit'
))
current_submodule = None
return issues
def check_sync_status(self) -> List[HealthIssue]:
"""Check if submodules are in sync with remote"""
issues = []
result = subprocess.run(
['git', 'submodule', 'foreach', 'git', 'fetch'],
cwd=self.root
)
status = subprocess.run(
['git', 'submodule', 'foreach', 'git', 'status', '-sb'],
cwd=self.root,
capture_output=True,
text=True
)
current_submodule = None
for line in status.stdout.splitlines():
if line.startswith("Entering"):
current_submodule = line.split("'")[1]
elif 'behind' in line and current_submodule:
issues.append(HealthIssue(
severity='warning',
submodule=current_submodule,
description='Behind remote branch',
fix_suggestion=f'cd {current_submodule} && git pull'
))
return issues
def run_full_check(self) -> List[HealthIssue]:
"""Run all health checks"""
self.issues = []
self.issues.extend(self.check_detached_heads())
self.issues.extend(self.check_uncommitted_changes())
self.issues.extend(self.check_sync_status())
return self.issues
def report(self):
"""Generate health report"""
if not self.issues:
print("✓ All submodules healthy")
return
print(f"Found {len(self.issues)} issues:\n")
for issue in self.issues:
icon = {'warning': '⚠️', 'error': '❌', 'critical': '🔥'}.get(issue.severity, '•')
print(f"{icon} [{issue.severity.upper()}] {issue.submodule}")
print(f" {issue.description}")
print(f" Fix: {issue.fix_suggestion}\n")
Pre-Push Hook
#!/bin/bash
# .git/hooks/pre-push
# Prevent pushing master with out-of-sync submodules
set -e
echo "Checking submodule status before push..."
# Check for submodules ahead of master
git submodule foreach '
if [ "$(git rev-parse HEAD)" != "$(git rev-parse @{u} 2>/dev/null || echo HEAD)" ]; then
echo "ERROR: Submodule $name has unpushed commits"
exit 1
fi
'
# Check if master repo points to latest submodule commits
git submodule update --remote --merge
if ! git diff --quiet; then
echo "ERROR: Submodule pointers out of sync"
echo "Run: git add . && git commit -m 'Update submodule pointers'"
exit 1
fi
echo "✓ All submodules in sync"
exit 0
Usage Examples
Sync All Submodules
Apply submodule-orchestration-patterns skill to implement bottom-up submodule synchronization
Health Check
Apply submodule-orchestration-patterns skill to create health monitoring for detached HEADs and uncommitted changes
Pre-Push Hook
Apply submodule-orchestration-patterns skill to add pre-push hook preventing out-of-sync commits
Success Output
When this skill is successfully applied, output:
✅ SKILL COMPLETE: submodule-orchestration-patterns
Completed:
- [x] Bottom-up sync implemented and tested
- [x] Health monitoring configured (detached HEAD, uncommitted changes)
- [x] Pre-push hooks installed to prevent out-of-sync commits
- [x] Automation integrated (CI/CD or git hooks)
Outputs:
- scripts/sync_submodules.py - Orchestration script
- scripts/health_check.py - Health monitoring
- .git/hooks/pre-push - Sync validation hook
- Documentation of sync workflow
Completion Checklist
Before marking this skill as complete, verify:
- SubmoduleOrchestrator class implemented with discover/sync methods
- Health checks detect detached HEADs and uncommitted changes
- Bottom-up sync updates submodule pointers in master repo
- Pre-push hook prevents pushing out-of-sync state
- All submodules can be synced without conflicts
- Health report shows all submodules healthy
- CI/CD integration configured (if applicable)
- Team trained on sync workflow
Failure Indicators
This skill has FAILED if:
- ❌ Submodules remain in detached HEAD state after sync
- ❌ Master repo pushed with outdated submodule pointers
- ❌ Health checks don't detect uncommitted changes
- ❌ Sync script fails on merge conflicts
- ❌ Pre-push hook allows out-of-sync commits through
- ❌ No clear documentation of sync workflow
- ❌ Team members bypassing sync process
When NOT to Use
Do NOT use this skill when:
- Single repository with no submodules - no orchestration needed
- Using monorepo architecture - prefer monorepo tools instead
- Submodules are read-only vendored dependencies - simpler update strategy sufficient
- Using Git subtree instead of submodules - different tooling required
- No coordination needed between repos - independent versioning is fine
- Small team with manual sync acceptable - automation overhead not justified
Use alternatives instead:
- Monorepo → Use Nx, Turborepo, or Bazel
- Read-only deps → Use package managers (npm, pip, etc.)
- Git subtree → Use subtree merge strategies
- Manual workflow → Document manual sync process
Anti-Patterns (Avoid)
| Anti-Pattern | Problem | Solution |
|---|---|---|
| Pushing master before syncing submodules | Submodule pointers become stale | Always run pre-push hook validation |
| Ignoring detached HEAD warnings | Commits lost when switching branches | Auto-checkout main branch in sync script |
| Force-updating submodules | Loses local changes | Check for uncommitted changes first |
| No health monitoring | Issues discovered too late | Run health check before every sync |
| Skipping pre-push hook | Out-of-sync state gets committed | Make hook mandatory, enforce in CI |
| Manual submodule updates | Human error, inconsistency | Automate with scripts and hooks |
| No documentation | Team confusion, process breakdown | Document workflow with examples |
Principles
This skill embodies:
- #1 Recycle → Extend → Re-Use → Create - Reuse existing git submodule infrastructure
- #2 First Principles Thinking - Understand git submodule mechanics before automating
- #3 Keep It Simple (KISS) - Bottom-up sync is simpler than complex merge strategies
- #4 Separation of Concerns - Separate submodule sync from master repo changes
- #7 Automation - Automate repetitive sync tasks to prevent human error
- #8 No Assumptions - Always check health status before syncing
Full Standard: CODITECT-STANDARD-AUTOMATION.md
Integration Points
- cicd-automation-patterns - Automated sync in CI/CD
- cloud-infrastructure-patterns - Multi-repo deployments
- deployment-strategy-patterns - Coordinated releases