Skip to main content

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

  1. Review the patterns and examples below
  2. Apply the relevant patterns to your implementation
  3. Follow the best practices outlined in this skill

Production git submodule management with automated sync, health checks, and orchestration.

Core Capabilities

  1. Lifecycle Management - Init, update, sync, cleanup
  2. Bottom-Up Sync - Automatic submodule → master synchronization
  3. Health Monitoring - Detached HEAD detection, status tracking
  4. Dependency Resolution - Cross-submodule dependency analysis
  5. 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-PatternProblemSolution
Pushing master before syncing submodulesSubmodule pointers become staleAlways run pre-push hook validation
Ignoring detached HEAD warningsCommits lost when switching branchesAuto-checkout main branch in sync script
Force-updating submodulesLoses local changesCheck for uncommitted changes first
No health monitoringIssues discovered too lateRun health check before every sync
Skipping pre-push hookOut-of-sync state gets committedMake hook mandatory, enforce in CI
Manual submodule updatesHuman error, inconsistencyAutomate with scripts and hooks
No documentationTeam confusion, process breakdownDocument 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