Claude Code Multi-Repository Project Management

Table of Contents

  1. Cross-Repository Refactoring Techniques
  2. Maintaining Consistency Across Microservices
  3. Dependency Update Coordination
  4. Shared Configuration Management
  5. Monorepo vs Polyrepo Best Practices
  6. Git Submodules and Subtrees Usage
  7. Cross-Repo Search and Replace Patterns
  8. Repository Synchronization Strategies

Cross-Repository Refactoring Techniques

Modern Tools and Approaches

1. Composite Builds with Gradle

The Gradle Composite Builds feature enables treating separate multi-projects as a single cohesive build:

// settings.gradle
includeBuild('../common-library')
includeBuild('../service-a')
includeBuild('../service-b')

This approach provides:

  • Cross-repository IDE experience with IntelliJ IDEA integration
  • Convenient code navigation across repositories
  • Cross-multiproduct refactoring capabilities

2. Moderne Platform for Large-Scale Refactoring

Moderne leverages lossless semantic tree (LST) representations and OpenRewrite recipes:

# Example OpenRewrite recipe
type: specs.openrewrite.org/v1beta/recipe
name: com.example.MigrateToJava17
recipeList:
  - org.openrewrite.java.migrate.Java8toJava11
  - org.openrewrite.java.migrate.Java11toJava17

Common use cases:

  • Language version migrations (Java 8 to Java 17)
  • Framework version migrations (Spring Boot, JUnit)
  • Technology platform migrations (Cloud Foundry to Kubernetes)

3. AI-Assisted Cross-Repository Navigation

Tools like Sourcegraph Cody provide:

  • Understanding of cross-repository dependencies
  • Inline suggestions for modularization
  • Context-aware refactoring recommendations

Best Practices

  1. Start with Documentation

    • Understand why code exists before refactoring
    • Document the history and rationale
    • Map out dependencies across repositories
  2. Temporary Repository Consolidation

    # Create temporary consolidated repo
    git init temp-refactor
    cd temp-refactor
     
    # Add all repositories as subdirectories
    git subtree add --prefix=service-a https://github.com/org/service-a main
    git subtree add --prefix=service-b https://github.com/org/service-b main
     
    # Perform refactoring
    # Split back when complete
  3. Incremental Changes

    • Break down refactoring into bite-sized modifications
    • Maintain working state between changes
    • Use feature flags for gradual rollout

Maintaining Consistency Across Microservices

Key Consistency Patterns

1. Saga Pattern

Manages distributed transactions across microservices:

// Orchestration-based Saga example
class OrderSaga {
  async execute(order) {
    try {
      await paymentService.charge(order);
      await inventoryService.reserve(order);
      await shippingService.schedule(order);
    } catch (error) {
      // Compensating transactions
      await this.rollback(order, error);
    }
  }
}

Implementation frameworks:

  • Camunda
  • Apache Camel
  • Axon Framework

2. Event Sourcing

Store data changes as a series of events:

// Event sourcing example
class OrderAggregate {
  applyEvent(event) {
    switch(event.type) {
      case 'OrderCreated':
        this.status = 'pending';
        break;
      case 'PaymentReceived':
        this.paid = true;
        break;
    }
  }
}

3. CQRS (Command Query Responsibility Segregation)

Separate read and write models:

// Command model
interface CreateOrderCommand {
  customerId: string;
  items: OrderItem[];
}
 
// Query model
interface OrderReadModel {
  orderId: string;
  customerName: string;
  totalAmount: number;
  status: string;
}

Best Practices for 2024

  1. Idempotency

    // Idempotent operation example
    async processPayment(paymentId, amount) {
      const existing = await this.findPayment(paymentId);
      if (existing) return existing;
      
      return await this.createPayment(paymentId, amount);
    }
  2. Event Monitoring

    • Use distributed tracing (Jaeger, Zipkin)
    • Implement event stores with audit logs
    • Set up alerting for failed compensations
  3. Avoid Synchronous Calls

    • Prefer async messaging (RabbitMQ, Kafka)
    • Use circuit breakers for necessary sync calls
    • Implement timeouts and retries

Dependency Update Coordination

Major Tools

1. Renovate Bot

// renovate.json configuration
{
  "extends": ["config:base"],
  "packageRules": [
    {
      "matchRepositories": ["org/service-*"],
      "groupName": "microservices",
      "schedule": ["after 10pm on sunday"]
    }
  ],
  "prConcurrentLimit": 3,
  "automerge": true,
  "automergeType": "pr"
}

Features:

  • Supports 90+ package managers
  • Highly configurable
  • Platform agnostic (runs as Docker container)

2. Dependabot

# .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "weekly"
    groups:
      production-dependencies:
        dependency-type: "production"
      development-dependencies:
        dependency-type: "development"

Features:

  • Native GitHub integration
  • Grouped updates support
  • Security vulnerability detection

3. Custom Coordination Scripts

#!/bin/bash
# update-dependencies.sh
REPOS=("service-a" "service-b" "service-c")
 
for repo in "${REPOS[@]}"; do
  cd "$repo"
  npm update shared-library@latest
  git add package.json package-lock.json
  git commit -m "chore: update shared-library"
  git push origin update-shared-library
  gh pr create --title "Update shared-library" --body "Automated update"
  cd ..
done

Best Practices

  1. Grouped Updates

    • Group related dependencies
    • Coordinate updates across repositories
    • Use consistent versioning strategies
  2. Review Process

    • Establish clear ownership for reviews
    • Automate testing for dependency updates
    • Use staging environments for validation
  3. Timing Strategy

    • Schedule updates during low-traffic periods
    • Batch updates to reduce noise
    • Implement gradual rollout strategies

Shared Configuration Management

Strategies

1. Separate Configuration Repository

# config-repo/base/database.yml
production:
  host: ${DB_HOST}
  port: 5432
  pool: 20
 
# service-a/config/database.yml
imports:
  - git::https://github.com/org/config-repo//base/database.yml

2. GitOps Approach

# platform-config/namespaces/production/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
 
resources:
  - ../../base
  
patches:
  - target:
      kind: ConfigMap
      name: app-config
    patch: |-
      - op: replace
        path: /data/environment
        value: production

3. Centralized Configuration Service

// config-service/index.js
class ConfigService {
  async getConfig(service, environment) {
    const baseConfig = await this.loadBase(service);
    const envConfig = await this.loadEnvironment(service, environment);
    return merge(baseConfig, envConfig);
  }
}

Best Practices

  1. Version Control Everything

    • Treat configuration as code
    • Use semantic versioning for configs
    • Maintain change history
  2. Environment Separation

    config-repo/
    ├── base/
    │   ├── app.yml
    │   └── database.yml
    ├── environments/
    │   ├── dev/
    │   ├── staging/
    │   └── production/
    
  3. Access Control

    • Use CODEOWNERS files
    • Implement RBAC for sensitive configs
    • Audit configuration changes

Monorepo vs Polyrepo Best Practices

Comparison Matrix

AspectMonorepoPolyrepo
Code SharingTrivialRequires packages
Atomic ChangesEasyComplex coordination
CI/CDComplex setupSimple per-repo
Access ControlChallengingGranular
ToolingSpecialized requiredStandard tools work
ScalingEventually problematicNo limits

Monorepo Best Practices

  1. Use Modern Tooling

    // nx.json
    {
      "tasksRunnerOptions": {
        "default": {
          "runner": "@nrwl/nx-cloud",
          "options": {
            "cacheableOperations": ["build", "test", "lint"],
            "parallel": true
          }
        }
      }
    }
  2. Implement Efficient CI/CD

    # Only build affected projects
    - name: Build affected
      run: nx affected:build --base=origin/main
  3. Structure for Scale

    monorepo/
    ├── apps/
    │   ├── web-app/
    │   └── mobile-app/
    ├── libs/
    │   ├── shared-ui/
    │   └── common-utils/
    └── tools/
        └── workspace-scripts/
    

Polyrepo Best Practices

  1. Standardize Repository Structure

    service-template/
    ├── .github/
    │   └── workflows/
    ├── src/
    ├── tests/
    ├── Dockerfile
    └── README.md
    
  2. Implement Shared Libraries

    // package.json
    {
      "dependencies": {
        "@company/shared-lib": "^2.1.0",
        "@company/auth-lib": "^1.5.0"
      }
    }
  3. Coordinate Releases

    # release-coordinator.sh
    ./scripts/update-version.sh 2.0.0
    ./scripts/create-releases.sh
    ./scripts/update-dependencies.sh

Git Submodules and Subtrees Usage

Git Submodules

When to Use

  • External dependencies with specific versions
  • Separate repository access control needed
  • Component-based development

Example Usage

# Add submodule
git submodule add https://github.com/org/shared-lib libs/shared
git commit -m "Add shared library submodule"
 
# Update submodule
git submodule update --remote libs/shared
git add libs/shared
git commit -m "Update shared library"
 
# Clone with submodules
git clone --recurse-submodules https://github.com/org/main-project

Git Subtrees

When to Use

  • Need full history in main repository
  • Simpler workflow for team members
  • System-based development

Example Usage

# Add subtree
git subtree add --prefix=vendor/shared \
  https://github.com/org/shared-lib main --squash
 
# Pull updates
git subtree pull --prefix=vendor/shared \
  https://github.com/org/shared-lib main --squash
 
# Push changes back
git subtree push --prefix=vendor/shared \
  https://github.com/org/shared-lib feature-branch

Comparison

FeatureSubmodulesSubtrees
Repository SizeSmaller (links)Larger (full copy)
Update ProcessManualIntegrated
Learning CurveSteeperMinimal
Push ChangesEasyMore complex
Metadata.gitmodulesNone

Cross-Repo Search and Replace Patterns

Tools and Techniques

1. Moderne Platform with OpenRewrite

# rewrite.yml
type: specs.openrewrite.org/v1beta/recipe
name: com.example.UpdateApiEndpoints
recipeList:
  - org.openrewrite.java.ChangeMethodName:
      methodPattern: "com.example.api.* getUser(..)"
      newMethodName: "fetchUser"

2. IDE-Based Solutions

IntelliJ IDEA Structural Search and Replace:

// Search template
$Instance$.$MethodCall$($Arguments$)
 
// Replace template
$Instance$.newMethodName($Arguments$)

3. Script-Based Approach

#!/bin/bash
# cross-repo-replace.sh
PATTERN="oldFunction"
REPLACEMENT="newFunction"
REPOS=("service-a" "service-b" "service-c")
 
for repo in "${REPOS[@]}"; do
  cd "$repo"
  # Use ripgrep for finding
  rg -l "$PATTERN" | while read file; do
    sed -i "s/$PATTERN/$REPLACEMENT/g" "$file"
  done
  
  # Create PR
  git checkout -b "refactor-$PATTERN"
  git add -A
  git commit -m "refactor: replace $PATTERN with $REPLACEMENT"
  git push origin "refactor-$PATTERN"
  gh pr create --title "Refactor: $PATTERN$REPLACEMENT"
  cd ..
done

Best Practices

  1. Test Thoroughly

    # Dry run first
    rg "$PATTERN" --stats
  2. Use AST-Based Tools

    • More accurate than text replacement
    • Handles edge cases better
    • Preserves code structure
  3. Coordinate Changes

    • Create tracking issue
    • Use feature branches
    • Deploy incrementally

Repository Synchronization Strategies

Strategy 1: Multiple Remotes

# Setup
git remote add github https://github.com/org/project.git
git remote add gitlab https://gitlab.com/org/project.git
 
# Sync workflow
git fetch github
git fetch gitlab
git checkout -b sync-branch github/main
git push gitlab sync-branch

Strategy 2: Mirror Repository

# Create mirror
git clone --mirror https://github.com/org/source.git
cd source.git
 
# Setup push mirror
git remote set-url --push origin https://gitlab.com/org/mirror.git
 
# Sync
git fetch -p origin
git push --mirror

Strategy 3: Automated Synchronization

Using git-sync Tool

# git-sync configuration
sync:
  - source:
      url: https://github.com/org/source.git
      branch: main
    target:
      url: https://gitlab.com/org/target.git
      branch: main
    conflict_resolution: source_wins

Using GitHub Actions

# .github/workflows/sync.yml
name: Sync Repositories
on:
  push:
    branches: [main]
 
jobs:
  sync:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0
      
      - name: Push to GitLab
        run: |
          git remote add gitlab https://oauth2:${{ secrets.GITLAB_TOKEN }}@gitlab.com/org/mirror.git
          git push gitlab main --force

Best Practices

  1. Conflict Resolution Strategy

    • Define clear ownership rules
    • Use prefix-based branching
    • Automate conflict detection
  2. Security Considerations

    • Use deploy keys or tokens
    • Avoid storing credentials in code
    • Implement audit logging
  3. Monitoring and Alerts

    # monitoring/sync-health.yml
    checks:
      - name: sync_lag
        query: "time_since_last_sync > 3600"
        alert: true
      - name: sync_conflicts
        query: "unresolved_conflicts > 0"
        alert: true

Practical Examples and Scenarios

Scenario 1: Microservices API Update

# 1. Create tracking issue
gh issue create --title "API v2 Migration" --body "Migrate all services to API v2"
 
# 2. Update shared library
cd shared-api-lib
npm version major
git push --tags
 
# 3. Update services using Renovate grouping
# renovate.json in each service
{
  "packageRules": [{
    "matchPackageNames": ["@company/shared-api-lib"],
    "groupName": "api-v2-migration"
  }]
}
 
# 4. Coordinate deployment
kubectl rollout status deployment/service-a
kubectl rollout status deployment/service-b

Scenario 2: Security Patch Across Repositories

// security-patch.js
const { Octokit } = require("@octokit/rest");
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
 
async function applySecurityPatch() {
  const repos = await getAffectedRepos();
  
  for (const repo of repos) {
    await createBranch(repo, 'security-patch');
    await updateDependency(repo, 'vulnerable-lib', '2.0.1');
    await createPR(repo, {
      title: 'Security: Update vulnerable-lib',
      body: 'Fixes CVE-2024-12345',
      labels: ['security', 'priority-high']
    });
  }
}

Scenario 3: Monorepo to Polyrepo Migration

# 1. Identify boundaries
nx graph # Visualize dependencies
 
# 2. Extract service
git subtree split --prefix=apps/service-a -b service-a-branch
 
# 3. Create new repository
gh repo create org/service-a
git remote add service-a https://github.com/org/service-a.git
git push service-a service-a-branch:main
 
# 4. Update references
find . -name "*.json" -exec sed -i 's|apps/service-a|@org/service-a|g' {} \;
 
# 5. Setup CI/CD
cp .github/workflows/template.yml ../service-a/.github/workflows/ci.yml

Conclusion

Managing multiple repositories effectively requires a combination of:

  • Right tooling (Renovate, Moderne, Git features)
  • Clear strategies (consistency patterns, synchronization approaches)
  • Best practices (incremental changes, automation, monitoring)
  • Team coordination (review processes, documentation, communication)

The choice between monorepo and polyrepo, the selection of synchronization strategies, and the implementation of cross-repository operations should align with your organization’s scale, team structure, and technical requirements.

Key takeaways:

  1. Automate repetitive tasks with tools like Renovate and custom scripts
  2. Use event-driven architectures for microservices consistency
  3. Implement proper CI/CD strategies for both monorepo and polyrepo setups
  4. Choose between submodules and subtrees based on your workflow needs
  5. Leverage modern tools for cross-repository refactoring
  6. Maintain clear documentation and communication channels

Success in multi-repository management comes from finding the right balance between automation and control, standardization and flexibility, and independence and coordination.